]> Zhao Yanbai Git Server - minix.git/commitdiff
Import NetBSD's make
authorArun Thomas <arun@minix3.org>
Thu, 4 Feb 2010 16:52:54 +0000 (16:52 +0000)
committerArun Thomas <arun@minix3.org>
Thu, 4 Feb 2010 16:52:54 +0000 (16:52 +0000)
90 files changed:
commands/bmake/Makefile [new file with mode: 0644]
commands/bmake/Makefile.boot [new file with mode: 0644]
commands/bmake/PSD.doc/Makefile [new file with mode: 0644]
commands/bmake/PSD.doc/tutorial.ms [new file with mode: 0644]
commands/bmake/arch.c [new file with mode: 0644]
commands/bmake/buf.c [new file with mode: 0644]
commands/bmake/buf.h [new file with mode: 0644]
commands/bmake/build.sh [new file with mode: 0755]
commands/bmake/compat.c [new file with mode: 0644]
commands/bmake/cond.c [new file with mode: 0644]
commands/bmake/config.h [new file with mode: 0644]
commands/bmake/dir.c [new file with mode: 0644]
commands/bmake/dir.h [new file with mode: 0644]
commands/bmake/for.c [new file with mode: 0644]
commands/bmake/hash.c [new file with mode: 0644]
commands/bmake/hash.h [new file with mode: 0644]
commands/bmake/job.c [new file with mode: 0644]
commands/bmake/job.h [new file with mode: 0644]
commands/bmake/lst.h [new file with mode: 0644]
commands/bmake/lst.lib/Makefile [new file with mode: 0644]
commands/bmake/lst.lib/lstAppend.c [new file with mode: 0644]
commands/bmake/lst.lib/lstAtEnd.c [new file with mode: 0644]
commands/bmake/lst.lib/lstAtFront.c [new file with mode: 0644]
commands/bmake/lst.lib/lstClose.c [new file with mode: 0644]
commands/bmake/lst.lib/lstConcat.c [new file with mode: 0644]
commands/bmake/lst.lib/lstDatum.c [new file with mode: 0644]
commands/bmake/lst.lib/lstDeQueue.c [new file with mode: 0644]
commands/bmake/lst.lib/lstDestroy.c [new file with mode: 0644]
commands/bmake/lst.lib/lstDupl.c [new file with mode: 0644]
commands/bmake/lst.lib/lstEnQueue.c [new file with mode: 0644]
commands/bmake/lst.lib/lstFind.c [new file with mode: 0644]
commands/bmake/lst.lib/lstFindFrom.c [new file with mode: 0644]
commands/bmake/lst.lib/lstFirst.c [new file with mode: 0644]
commands/bmake/lst.lib/lstForEach.c [new file with mode: 0644]
commands/bmake/lst.lib/lstForEachFrom.c [new file with mode: 0644]
commands/bmake/lst.lib/lstInit.c [new file with mode: 0644]
commands/bmake/lst.lib/lstInsert.c [new file with mode: 0644]
commands/bmake/lst.lib/lstInt.h [new file with mode: 0644]
commands/bmake/lst.lib/lstIsAtEnd.c [new file with mode: 0644]
commands/bmake/lst.lib/lstIsEmpty.c [new file with mode: 0644]
commands/bmake/lst.lib/lstLast.c [new file with mode: 0644]
commands/bmake/lst.lib/lstMember.c [new file with mode: 0644]
commands/bmake/lst.lib/lstNext.c [new file with mode: 0644]
commands/bmake/lst.lib/lstOpen.c [new file with mode: 0644]
commands/bmake/lst.lib/lstPrev.c [new file with mode: 0644]
commands/bmake/lst.lib/lstRemove.c [new file with mode: 0644]
commands/bmake/lst.lib/lstReplace.c [new file with mode: 0644]
commands/bmake/lst.lib/lstSucc.c [new file with mode: 0644]
commands/bmake/main.c [new file with mode: 0644]
commands/bmake/make.1 [new file with mode: 0644]
commands/bmake/make.c [new file with mode: 0644]
commands/bmake/make.h [new file with mode: 0644]
commands/bmake/make_malloc.c [new file with mode: 0644]
commands/bmake/make_malloc.h [new file with mode: 0644]
commands/bmake/nonints.h [new file with mode: 0644]
commands/bmake/parse.c [new file with mode: 0644]
commands/bmake/pathnames.h [new file with mode: 0644]
commands/bmake/sprite.h [new file with mode: 0644]
commands/bmake/str.c [new file with mode: 0644]
commands/bmake/strlist.c [new file with mode: 0644]
commands/bmake/strlist.h [new file with mode: 0644]
commands/bmake/suff.c [new file with mode: 0644]
commands/bmake/targ.c [new file with mode: 0644]
commands/bmake/trace.c [new file with mode: 0644]
commands/bmake/trace.h [new file with mode: 0644]
commands/bmake/unit-tests/Makefile [new file with mode: 0644]
commands/bmake/unit-tests/comment [new file with mode: 0644]
commands/bmake/unit-tests/cond1 [new file with mode: 0644]
commands/bmake/unit-tests/dotwait [new file with mode: 0644]
commands/bmake/unit-tests/export [new file with mode: 0644]
commands/bmake/unit-tests/export-all [new file with mode: 0644]
commands/bmake/unit-tests/forsubst [new file with mode: 0644]
commands/bmake/unit-tests/moderrs [new file with mode: 0644]
commands/bmake/unit-tests/modmatch [new file with mode: 0644]
commands/bmake/unit-tests/modmisc [new file with mode: 0644]
commands/bmake/unit-tests/modorder [new file with mode: 0644]
commands/bmake/unit-tests/modts [new file with mode: 0644]
commands/bmake/unit-tests/modword [new file with mode: 0644]
commands/bmake/unit-tests/posix [new file with mode: 0644]
commands/bmake/unit-tests/qequals [new file with mode: 0644]
commands/bmake/unit-tests/ternary [new file with mode: 0644]
commands/bmake/unit-tests/test.exp [new file with mode: 0644]
commands/bmake/unit-tests/unexport [new file with mode: 0644]
commands/bmake/unit-tests/unexport-env [new file with mode: 0644]
commands/bmake/unit-tests/varcmd [new file with mode: 0644]
commands/bmake/util.c [new file with mode: 0644]
commands/bmake/var.c [new file with mode: 0644]
etc/mk/sys.mk [new file with mode: 0644]
include/ar.h [new file with mode: 0644]
include/sys/param.h

diff --git a/commands/bmake/Makefile b/commands/bmake/Makefile
new file mode 100644 (file)
index 0000000..51cbda3
--- /dev/null
@@ -0,0 +1,40 @@
+#      $NetBSD: Makefile,v 1.49 2009/04/14 22:15:23 lukem Exp $
+#      @(#)Makefile    5.2 (Berkeley) 12/28/90
+
+PROG=  make
+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 trace.c var.c util.c 
+SRCS+=  strlist.c
+SRCS+=  make_malloc.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
+SRCS += lstPrev.c
+
+.PATH: ${.CURDIR}/lst.lib
+.if make(install)
+SUBDIR=        PSD.doc
+.endif
+.if make(obj) || make(clean)
+SUBDIR+= unit-tests
+.endif
+
+.include <bsd.prog.mk>
+.include <bsd.subdir.mk>
+
+CPPFLAGS+= -DMAKE_NATIVE
+COPTS.var.c+= -Wno-cast-qual
+
+.ifdef TOOLDIR
+# this is a native netbsd build, 
+# use libutil rather than the local emalloc etc.
+CPPFLAGS+= -DUSE_EMALLOC
+LDADD+=-lutil
+DPADD+=${LIBUTIL}
+.endif
+
+# A simple unit-test driver to help catch regressions
+accept test:
+       cd ${.CURDIR}/unit-tests && ${.MAKE:S,^./,${.CURDIR}/,} TEST_MAKE=${TEST_MAKE:U${.OBJDIR}/${PROG:T}} ${.TARGET}
diff --git a/commands/bmake/Makefile.boot b/commands/bmake/Makefile.boot
new file mode 100644 (file)
index 0000000..f6c2bb0
--- /dev/null
@@ -0,0 +1,47 @@
+#      $NetBSD: Makefile.boot,v 1.19 2009/01/24 11:59:39 dsl Exp $
+#
+# a very simple makefile...
+#
+# You only want to use this if you aren't running NetBSD.
+#
+# modify MACHINE and MACHINE_ARCH as appropriate for your target architecture
+#
+#CC=gcc -O -g
+CC=cc
+CFLAGS=-g -Wall -DHAVE_SETENV -DHAVE_STRERROR -DHAVE_STRDUP -DHAVE_STRFTIME -DHAVE_VSNPRINTF -DUSE_SELECT -D_POSIX_SOURCE
+
+.c.o:
+       ${CC} ${CFLAGS} -c $< -o $@
+
+MACHINE=i386
+MACHINE_ARCH=i386
+# tested on HP-UX 10.20
+#MAKE_MACHINE=hp700
+#MAKE_MACHINE_ARCH=hppa
+CFLAGS+= -DTARGET_MACHINE=\"${MACHINE}\" \
+       -DTARGET_MACHINE_ARCH=\"${MACHINE_ARCH}\" \
+       -DMAKE_MACHINE=\"${MACHINE}\"
+LIBS=
+
+OBJ=arch.o buf.o compat.o cond.o dir.o for.o hash.o job.o main.o make.o \
+    make_malloc.o parse.o str.o strlist.o suff.o targ.o trace.o var.o util.o
+
+LIBOBJ= lst.lib/lstAppend.o lst.lib/lstAtEnd.o lst.lib/lstAtFront.o \
+       lst.lib/lstClose.o lst.lib/lstConcat.o lst.lib/lstDatum.o \
+       lst.lib/lstDeQueue.o lst.lib/lstDestroy.o lst.lib/lstDupl.o \
+       lst.lib/lstEnQueue.o lst.lib/lstFind.o lst.lib/lstFindFrom.o \
+       lst.lib/lstFirst.o lst.lib/lstForEach.o lst.lib/lstForEachFrom.o \
+       lst.lib/lstInit.o lst.lib/lstInsert.o lst.lib/lstIsAtEnd.o \
+       lst.lib/lstIsEmpty.o lst.lib/lstLast.o lst.lib/lstMember.o \
+       lst.lib/lstNext.o lst.lib/lstOpen.o lst.lib/lstRemove.o \
+       lst.lib/lstReplace.o lst.lib/lstSucc.o lst.lib/lstPrev.o
+
+bmake: ${OBJ} ${LIBOBJ}
+#      @echo 'make of make and make.0 started.'
+       ${CC} ${CFLAGS} ${OBJ} ${LIBOBJ} -o bmake ${LIBS}
+       @ls -l $@
+#      nroff -h -man make.1 > make.0
+#      @echo 'make of make and make.0 completed.'
+
+clean:
+       rm -f ${OBJ} ${LIBOBJ} ${PORTOBJ} bmake
diff --git a/commands/bmake/PSD.doc/Makefile b/commands/bmake/PSD.doc/Makefile
new file mode 100644 (file)
index 0000000..8e1f1fa
--- /dev/null
@@ -0,0 +1,8 @@
+#      $NetBSD: Makefile,v 1.2 1995/06/14 15:20:23 christos Exp $
+#      @(#)Makefile    8.1 (Berkeley) 8/14/93
+
+DIR=   psd/12.make
+SRCS=  tutorial.ms
+MACROS=        -ms
+
+.include <bsd.doc.mk>
diff --git a/commands/bmake/PSD.doc/tutorial.ms b/commands/bmake/PSD.doc/tutorial.ms
new file mode 100644 (file)
index 0000000..9802e4b
--- /dev/null
@@ -0,0 +1,3772 @@
+.\"    $NetBSD: tutorial.ms,v 1.10 2004/06/27 19:12:33 uwe Exp $
+.\" 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. 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.
+.\"
+.\" Copyright (c) 1988, 1989 by Adam de Boor
+.\" Copyright (c) 1989 by Berkeley Softworks
+.\"
+.\" 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
+.\"
+.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
+..
+.\" 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 C
+.el \&\\$3\fC\\$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
+.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\a-\w\a\\*(g9\au/2u\a\&\\*(g9
+.sp |\\n(g8u
+.sp 166u
+\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 makefile system
+.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 makefile system
+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(%%
diff --git a/commands/bmake/arch.c b/commands/bmake/arch.c
new file mode 100644 (file)
index 0000000..263a447
--- /dev/null
@@ -0,0 +1,1354 @@
+/*     $NetBSD: arch.c,v 1.59 2009/01/23 21:58:27 dsl Exp $    */
+
+/*
+ * 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: arch.c,v 1.59 2009/01/23 21:58:27 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)arch.c     8.2 (Berkeley) 1/2/94";
+#else
+__RCSID("$NetBSD: arch.c,v 1.59 2009/01/23 21:58:27 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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    <ar.h>
+#include    <ctype.h>
+#include    <fcntl.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <utime.h>
+
+#include    "make.h"
+#include    "hash.h"
+#include    "dir.h"
+#include    "config.h"
+
+#ifdef TARGET_MACHINE
+#undef MAKE_MACHINE
+#define MAKE_MACHINE TARGET_MACHINE
+#endif
+#ifdef TARGET_MACHINE_ARCH
+#undef MAKE_MACHINE_ARCH
+#define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH
+#endif
+
+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(const void *, const void *);
+#ifdef CLEANUP
+static void ArchFree(void *);
+#endif
+static struct ar_hdr *ArchStatMember(char *, char *, Boolean);
+static FILE *ArchFindMember(char *, char *, struct ar_hdr *, const char *);
+#if defined(__svr4__) || defined(__SVR4) || defined(__ELF__)
+#define SVR4ARCHIVES
+static int ArchSVR4Entry(Arch *, char *, size_t, FILE *);
+#endif
+
+#ifdef CLEANUP
+/*-
+ *-----------------------------------------------------------------------
+ * 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);
+    if (a->fnametab)
+       free(a->fnametab);
+    Hash_DeleteTable(&a->members);
+    free(a);
+}
+#endif
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     linePtr         Pointer to start of specification
+ *     nodeLst         Lst on which to place the nodes
+ *     ctxt            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;
+           void        *freeIt;
+           char        *result;
+
+           result = Var_Parse(cp, ctxt, TRUE, &length, &freeIt);
+           if (freeIt)
+               free(freeIt);
+           if (result == var_Error) {
+               return(FAILURE);
+           } else {
+               subLibName = TRUE;
+           }
+
+           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;
+               void    *freeIt;
+               char    *result;
+
+               result = Var_Parse(cp, ctxt, TRUE, &length, &freeIt);
+               if (freeIt)
+                   free(freeIt);
+               if (result == var_Error) {
+                   return(FAILURE);
+               } else {
+                   doSubst = TRUE;
+               }
+
+               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 = bmake_malloc(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, 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, nsz;
+           nameBuf = bmake_malloc(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 = bmake_realloc(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, gn);
+               }
+           }
+           Lst_Destroy(members, NULL);
+           free(nameBuf);
+       } else {
+           size_t      sz = strlen(libName) + strlen(memName) + 3;
+           nameBuf = bmake_malloc(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, 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.
+ *
+ * Input:
+ *     ar              Current list element
+ *     archName        Name we want
+ *
+ * Results:
+ *     0 if it is, non-zero if it isn't.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+ArchFindArchive(const void *ar, const void *archName)
+{
+    return (strcmp(archName, ((const Arch *)ar)->name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ArchStatMember --
+ *     Locate a member of an archive, given the path of the archive and
+ *     the path of the desired member.
+ *
+ * Input:
+ *     archive         Path to the archive
+ *     member          Name of member. If it is a path, only the last
+ *                     component is used.
+ *     hash            TRUE if archive should be hashed if not already so.
+ *
+ * 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) {
+       member = cp + 1;
+    }
+
+    ln = Lst_Find(archives, 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 = bmake_malloc(sizeof(Arch));
+    ar->name = bmake_strdup(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((unsigned char)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);
+               if (DEBUG(ARCH) || DEBUG(MAKE)) {
+                   fprintf(debug_file, "ArchStat: Extended format entry for %s\n", memName);
+               }
+           }
+#endif
+
+           he = Hash_CreateEntry(&ar->members, memName, NULL);
+           Hash_SetValue(he, bmake_malloc(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, 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);
+    if (ar->fnametab)
+       free(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) {
+           if (DEBUG(ARCH)) {
+               fprintf(debug_file, "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 = bmake_malloc(size);
+       ar->fnamesize = size;
+
+       if (fread(ar->fnametab, size, 1, arch) != 1) {
+           if (DEBUG(ARCH)) {
+               fprintf(debug_file, "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;
+           }
+       if (DEBUG(ARCH)) {
+           fprintf(debug_file, "Found svr4 archive name table with %lu entries\n",
+                   (u_long)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]) {
+       if (DEBUG(ARCH)) {
+           fprintf(debug_file, "Could not parse SVR4 name %s\n", name);
+       }
+       return 2;
+    }
+    if (entry >= ar->fnamesize) {
+       if (DEBUG(ARCH)) {
+           fprintf(debug_file, "SVR4 entry offset %s is greater than %lu\n",
+                  name, (u_long)ar->fnamesize);
+       }
+       return 2;
+    }
+
+    if (DEBUG(ARCH)) {
+       fprintf(debug_file, "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".
+ *
+ * Input:
+ *     archive         Path to the archive
+ *     member          Name of member. If it is a path, only the last
+ *                     component is used.
+ *     arhPtr          Pointer to header structure to be filled in
+ *     mode            The mode for opening the stream
+ *
+ * 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,
+    const 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) {
+       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((unsigned char)arhPtr->ar_name[sizeof(AR_EFMT1) - 1])) {
+
+               unsigned int elen = atoi(&arhPtr->ar_name[sizeof(AR_EFMT1)-1]);
+               char ename[MAXPATHLEN + 1];
+
+               if (elen > MAXPATHLEN) {
+                       fclose(arch);
+                       return NULL;
+               }
+               if (fread(ename, elen, 1, arch) != 1) {
+                       fclose(arch);
+                       return NULL;
+               }
+               ename[elen] = '\0';
+               if (DEBUG(ARCH) || DEBUG(MAKE)) {
+                   fprintf(debug_file, "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.
+ *
+ * Input:
+ *     gn              Node of member to touch
+ *
+ * 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(MEMBER, gn, &p2),
+                         &arh, "r+");
+    if (p1)
+       free(p1);
+    if (p2)
+       free(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.
+ *
+ * Input:
+ *     gn              The node of the library to touch
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     Both the modification time of the library and of the RANLIBMAG
+ *     member are set to 'now'.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+#if !defined(RANLIBMAG)
+Arch_TouchLib(GNode *gn __unused)
+#else
+Arch_TouchLib(GNode *gn)
+#endif
+{
+#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, UNCONST(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.
+ *
+ * Input:
+ *     gn              Node describing archive member
+ *
+ * Results:
+ *     The modification time(seconds).
+ *
+ * Side Effects:
+ *     The mtime field of the given node is filled in with the value
+ *     returned by the function.
+ *
+ *-----------------------------------------------------------------------
+ */
+time_t
+Arch_MTime(GNode *gn)
+{
+    struct ar_hdr *arhPtr;    /* Header of desired member */
+    time_t       modTime;    /* Modification time as an integer */
+    char *p1, *p2;
+
+    arhPtr = ArchStatMember(Var_Value(ARCHIVE, gn, &p1),
+                            Var_Value(MEMBER, gn, &p2),
+                            TRUE);
+    if (p1)
+       free(p1);
+    if (p2)
+       free(p2);
+
+    if (arhPtr != NULL) {
+       modTime = (time_t)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.
+ *
+ *-----------------------------------------------------------------------
+ */
+time_t
+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->flags & REMAKE) &&
+               strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) {
+                                    gn->mtime = Arch_MTime(pgn);
+           }
+       } else if (pgn->flags & REMAKE) {
+           /*
+            * 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 library along the given search path.
+ *
+ * Input:
+ *     gn              Node of library to find
+ *     path            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 = strlen(gn->name) + 6 - 2;
+
+    libName = bmake_malloc(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, 0);
+#else
+    Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn, 0);
+#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.
+ *
+ *     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.
+ *
+ * Input:
+ *     gn              The library's graph node
+ *
+ * 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 (gn->type & OP_PHONY) {
+       oodate = TRUE;
+    } else if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) {
+       oodate = FALSE;
+    } else if ((!Lst_IsEmpty(gn->children) && gn->cmtime == 0) ||
+              (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, UNCONST(RANLIBMAG), FALSE);
+
+       if (arhPtr != NULL) {
+           modTimeTOC = (int)strtol(arhPtr->ar_date, NULL, 10);
+
+           if (DEBUG(ARCH) || DEBUG(MAKE)) {
+               fprintf(debug_file, "%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)) {
+               fprintf(debug_file, "No t.o.c....");
+           }
+           oodate = TRUE;
+       }
+#else
+       oodate = FALSE;
+#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)
+{
+#ifdef CLEANUP
+    Lst_Destroy(archives, ArchFree);
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Arch_IsLib --
+ *     Check if the node is a library
+ *
+ * Results:
+ *     True or False.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Arch_IsLib(GNode *gn)
+{
+    static const char armag[] = "!<arch>\n";
+    char buf[sizeof(armag)-1];
+    int fd;
+
+    if ((fd = open(gn->path, O_RDONLY)) == -1)
+       return FALSE;
+
+    if (read(fd, buf, sizeof(buf)) != sizeof(buf)) {
+       (void)close(fd);
+       return FALSE;
+    }
+
+    (void)close(fd);
+
+    return memcmp(buf, armag, sizeof(buf)) == 0;
+}
diff --git a/commands/bmake/buf.c b/commands/bmake/buf.c
new file mode 100644 (file)
index 0000000..1b9f5b4
--- /dev/null
@@ -0,0 +1,250 @@
+/*     $NetBSD: buf.c,v 1.24 2009/01/17 13:29:37 dsl Exp $     */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: buf.c,v 1.24 2009/01/17 13:29:37 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)buf.c      8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: buf.c,v 1.24 2009/01/17 13:29:37 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * buf.c --
+ *     Functions for automatically-expanded buffers.
+ */
+
+#include    "make.h"
+#include    "buf.h"
+
+#ifndef max
+#define max(a,b)  ((a) > (b) ? (a) : (b))
+#endif
+
+#define BUF_DEF_SIZE   256     /* Default buffer size */
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Expand_1 --
+ *     Extend buffer for single byte add.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Expand_1(Buffer *bp)
+{
+    bp->size += max(bp->size, 16);
+    bp->buffer = bmake_realloc(bp->buffer, bp->size);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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)
+{
+    int count = bp->count;
+    Byte *ptr;
+
+    if (__predict_false(count + numBytes >= bp->size)) {
+       bp->size += max(bp->size, numBytes + 16);
+       bp->buffer = bmake_realloc(bp->buffer, bp->size);
+    }
+
+    ptr = bp->buffer + count;
+    bp->count = count + numBytes;
+    ptr[numBytes] = 0;
+    memcpy(ptr, bytesPtr, 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 != NULL)
+       *numBytesPtr = bp->count;
+
+    return (bp->buffer);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Empty --
+ *     Throw away bytes in a buffer.
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     The bytes are discarded.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Empty(Buffer *bp)
+{
+
+    bp->count = 0;
+    *bp->buffer = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Init --
+ *     Initialize a buffer. If no initial size is given, a reasonable
+ *     default is used.
+ *
+ * Input:
+ *     size            Initial size for the buffer
+ *
+ * Results:
+ *     A buffer to be given to other functions in this library.
+ *
+ * Side Effects:
+ *     The buffer is created, the space allocated and pointers
+ *     initialized.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Buf_Init(Buffer *bp, int size)
+{
+    if (size <= 0) {
+       size = BUF_DEF_SIZE;
+    }
+    bp->size = size;
+    bp->count = 0;
+    bp->buffer = bmake_malloc(size);
+    *bp->buffer = 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Buf_Destroy --
+ *     Nuke a buffer and all its resources.
+ *
+ * Input:
+ *     buf             Buffer to destroy
+ *     freeData        TRUE if the data should be destroyed
+ *
+ * Results:
+ *     Data buffer, NULL if freed
+ *
+ * Side Effects:
+ *     The buffer is freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+Byte *
+Buf_Destroy(Buffer *buf, Boolean freeData)
+{
+    Byte *data;
+
+    data = buf->buffer;
+    if (freeData) {
+       free(data);
+       data = NULL;
+    }
+
+    buf->size = 0;
+    buf->count = 0;
+    buf->buffer = NULL;
+
+    return data;
+}
diff --git a/commands/bmake/buf.h b/commands/bmake/buf.h
new file mode 100644 (file)
index 0000000..82e897a
--- /dev/null
@@ -0,0 +1,118 @@
+/*     $NetBSD: buf.h,v 1.16 2009/01/17 13:55:42 dsl Exp $     */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * 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. 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: @(#)buf.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * 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.
+ *
+ *     from: @(#)buf.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * buf.h --
+ *     Header for users of the buf library.
+ */
+
+#ifndef _BUF_H
+#define _BUF_H
+
+typedef char Byte;
+
+typedef struct Buffer {
+    int            size;       /* Current size of the buffer */
+    int     count;     /* Number of bytes in buffer */
+    Byte    *buffer;   /* The buffer itself (zero terminated) */
+} Buffer;
+
+/* If we aren't on netbsd, __predict_false() might not be defined. */
+#ifndef __predict_false
+#define __predict_false(x) (x)
+#endif
+
+/* Buf_AddByte adds a single byte to a buffer. */
+#define        Buf_AddByte(bp, byte) do { \
+       int _count = ++(bp)->count; \
+       char *_ptr; \
+       if (__predict_false(_count >= (bp)->size)) \
+               Buf_Expand_1(bp); \
+       _ptr = (bp)->buffer + _count; \
+       _ptr[-1] = (byte); \
+       _ptr[0] = 0; \
+    } while (0)
+
+#define BUF_ERROR 256
+
+#define Buf_Size(bp) ((bp)->count)
+
+void Buf_Expand_1(Buffer *);
+void Buf_AddBytes(Buffer *, int, const Byte *);
+Byte *Buf_GetAll(Buffer *, int *);
+void Buf_Empty(Buffer *);
+void Buf_Init(Buffer *, int);
+Byte *Buf_Destroy(Buffer *, Boolean);
+
+#endif /* _BUF_H */
diff --git a/commands/bmake/build.sh b/commands/bmake/build.sh
new file mode 100755 (executable)
index 0000000..4b80e12
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+export PATH=$PATH:/usr/gnu/bin
+make -f Makefile.boot clean
+make -f Makefile.boot CFLAGS="-g -Wall -DHAVE_SETENV -DHAVE_STRERROR -DHAVE_STRDUP -DHAVE_STRFTIME -DHAVE_VSNPRINTF -D_GNU_SOURCE -DUSE_SELECT -DSYSV -D_POSIX_SOURCE"
+#make -f Makefile.boot
diff --git a/commands/bmake/compat.c b/commands/bmake/compat.c
new file mode 100644 (file)
index 0000000..944ed36
--- /dev/null
@@ -0,0 +1,717 @@
+/*     $NetBSD: compat.c,v 1.76 2009/02/22 07:33:00 dholland Exp $     */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: compat.c,v 1.76 2009/02/22 07:33:00 dholland Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)compat.c   8.2 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: compat.c,v 1.76 2009/02/22 07:33:00 dholland Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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    <sys/types.h>
+#include    <sys/stat.h>
+#include    <sys/wait.h>
+
+#include    <ctype.h>
+#include    <errno.h>
+#include    <signal.h>
+#include    <stdio.h>
+
+#include    "make.h"
+#include    "hash.h"
+#include    "dir.h"
+#include    "job.h"
+#include    "pathnames.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 void
+Compat_Init(void)
+{
+    const char *cp;
+
+    Shell_Init();              /* setup default shell */
+    
+    for (cp = "#=|^(){};&<>*?[]:$`\\\n"; *cp != '\0'; cp++) {
+       meta[(unsigned char) *cp] = 1;
+    }
+    /*
+     * The null character serves as a sentinel in the string.
+     */
+    meta[0] = 1;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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) {
+           Error("*** %s removed", file);
+       }
+       if (p1)
+           free(p1);
+
+       /*
+        * Run .INTERRUPT only if hit with interrupt signal
+        */
+       if (signo == SIGINT) {
+           gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
+           if (gn != NULL) {
+               Compat_Make(gn, gn);
+           }
+       }
+
+    }
+    exit(signo);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     cmdp            Command to execute
+ *     gnp             Node from which the command came
+ *
+ * Results:
+ *     0 if the command succeeded, 1 if an error occurred.
+ *
+ * Side Effects:
+ *     The node's 'made' field may be set to ERROR.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+CompatRunCommand(void *cmdp, void *gnp)
+{
+    char         *cmdStart;    /* Start of expanded command */
+    char         *cp, *bp;
+    Boolean      silent,       /* Don't print command */
+                 doIt;         /* Execute even if -n */
+    volatile Boolean errCheck;         /* Check errors */
+    int          reason;       /* Reason for child's death */
+    int                  status;       /* Description of child's death */
+    pid_t        cpid;         /* Child actually found */
+    pid_t        retstat;      /* Result of wait */
+    LstNode      cmdNode;      /* Node where current command is located */
+    const char  ** volatile av;        /* Argument vector for thing to exec */
+    char       ** volatile mav;/* Copy of the argument vector for freeing */
+    int                  argc;         /* Number of arguments in av or 0 if not
+                                * dynamically allocated */
+    Boolean      local;        /* TRUE if command should be executed
+                                * locally */
+    Boolean      useShell;     /* TRUE if command should be executed
+                                * using a shell */
+    char         * volatile cmd = (char *)cmdp;
+    GNode        *gn = (GNode *)gnp;
+
+    silent = gn->type & OP_SILENT;
+    errCheck = !(gn->type & OP_IGNORE);
+    doIt = FALSE;
+    
+    cmdNode = Lst_Member(gn->commands, 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);
+    }
+    cmd = cmdStart;
+    Lst_Replace(cmdNode, cmdStart);
+
+    if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) {
+       (void)Lst_AtEnd(ENDNode->commands, cmdStart);
+       return(0);
+    }
+    if (strcmp(cmdStart, "...") == 0) {
+       gn->type |= OP_SAVE_CMDS;
+       return(0);
+    }
+
+    while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) {
+       switch (*cmd) {
+       case '@':
+           silent = DEBUG(LOUD) ? FALSE : TRUE;
+           break;
+       case '-':
+           errCheck = FALSE;
+           break;
+       case '+':
+           doIt = TRUE;
+           if (!meta[0])               /* we came here from jobs */
+               Compat_Init();
+           break;
+       }
+       cmd++;
+    }
+
+    while (isspace((unsigned char)*cmd))
+       cmd++;
+
+#if !defined(MAKE_NATIVE)
+    /*
+     * In a non-native build, the host environment might be weird enough
+     * that it's necessary to go through a shell to get the correct
+     * behaviour.  Or perhaps the shell has been replaced with something
+     * that does extra logging, and that should not be bypassed.
+     */
+    useShell = TRUE;
+#else
+    /*
+     * 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;
+    }
+    useShell = (*cp != '\0');
+#endif
+
+    /*
+     * 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(gn)) {
+       printf("%s\n", cmd);
+       fflush(stdout);
+    }
+
+    /*
+     * If we're not supposed to execute any commands, this is as far as
+     * we go...
+     */
+    if (!doIt && NoExecute(gn)) {
+       return (0);
+    }
+    if (DEBUG(JOB))
+       fprintf(debug_file, "Execute: '%s'\n", cmd);
+
+again:
+    if (useShell) {
+       /*
+        * We need to pass the command off to the shell, typically
+        * because the command contains a "meta" character.
+        */
+       static const char *shargv[4];
+
+       shargv[0] = shellPath;
+       /*
+        * The following work for any of the builtin shell specs.
+        */
+       if (DEBUG(SHELL))
+               shargv[1] = "-xc";
+       else
+               shargv[1] = "-c";
+       shargv[2] = cmd;
+       shargv[3] = NULL;
+       av = shargv;
+       argc = 0;
+       bp = NULL;
+       mav = NULL;
+    } else {
+       /*
+        * No meta-characters, so no need to exec a shell. Break the command
+        * into words to form an argument vector we can execute.
+        */
+       mav = brk_string(cmd, &argc, TRUE, &bp);
+       if (mav == NULL) {
+               useShell = 1;
+               goto again;
+       }
+       av = (const char **)mav;
+    }
+
+    local = TRUE;
+
+    /*
+     * Fork and execute the single command. If the fork fails, we abort.
+     */
+#if defined(__minix)
+    cpid = fork();
+#else
+    cpid = vfork();
+#endif
+    if (cpid < 0) {
+       Fatal("Could not fork");
+    }
+    if (cpid == 0) {
+       Check_Cwd(av);
+       Var_ExportVars();
+       if (local)
+           (void)execvp(av[0], (char *const *)UNCONST(av));
+       else
+           (void)execv(av[0], (char *const *)UNCONST(av));
+       execError("exec", av[0]);
+       _exit(1);
+    }
+    if (mav)
+       free(mav);
+    if (bp)
+       free(bp);
+    Lst_Replace(cmdNode, NULL);
+
+    /*
+     * The child is off and running. Now all we can do is wait...
+     */
+    while (1) {
+
+       while ((retstat = wait(&reason)) != cpid) {
+           if (retstat == -1 && errno != EINTR) {
+               break;
+           }
+       }
+
+       if (retstat > -1) {
+           if (WIFSTOPPED(reason)) {
+               status = WSTOPSIG(reason);              /* stopped */
+           } else if (WIFEXITED(reason)) {
+               status = WEXITSTATUS(reason);           /* exited */
+               if (status != 0) {
+                   if (DEBUG(ERROR)) {
+                       fprintf(debug_file, "\n*** Failed target:  %s\n*** Failed command: ",
+                           gn->name);
+                       for (cp = cmd; *cp; ) {
+                           if (isspace((unsigned char)*cp)) {
+                               fprintf(debug_file, " ");
+                               while (isspace((unsigned char)*cp))
+                                   cp++;
+                           } else {
+                               fprintf(debug_file, "%c", *cp);
+                               cp++;
+                           }
+                       }
+                       fprintf(debug_file, "\n");
+                   }
+                   fprintf(debug_file, "*** 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: %s", retstat, strerror(errno));
+           /*NOTREACHED*/
+       }
+    }
+    free(cmdStart);
+
+    return (status);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * Compat_Make --
+ *     Make a target.
+ *
+ * Input:
+ *     gnp             The node to make
+ *     pgnp            Parent to abort if necessary
+ *
+ * Results:
+ *     0
+ *
+ * Side Effects:
+ *     If an error is detected and not being ignored, the process exits.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Compat_Make(void *gnp, void *pgnp)
+{
+    GNode *gn = (GNode *)gnp;
+    GNode *pgn = (GNode *)pgnp;
+
+    if (!meta[0])              /* we came here from jobs */
+       Compat_Init();
+    if (gn->made == UNMADE && (gn == pgn || (pgn->type & OP_MADE) == 0)) {
+       /*
+        * 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->flags |= REMAKE;
+       gn->made = BEINGMADE;
+       if ((gn->type & OP_MADE) == 0)
+           Suff_FindDeps(gn);
+       Lst_ForEach(gn->children, Compat_Make, gn);
+       if ((gn->flags & REMAKE) == 0) {
+           gn->made = ABORTED;
+           pgn->flags &= ~REMAKE;
+           goto cohorts;
+       }
+
+       if (Lst_Member(gn->iParents, pgn) != NULL) {
+           char *p1;
+           Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0);
+           if (p1)
+               free(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.
+        */
+       if (DEBUG(MAKE)) {
+           fprintf(debug_file, "Examining %s...", gn->name);
+       }
+       if (! Make_OODate(gn)) {
+           gn->made = UPTODATE;
+           if (DEBUG(MAKE)) {
+               fprintf(debug_file, "up-to-date.\n");
+           }
+           goto cohorts;
+       } else if (DEBUG(MAKE)) {
+           fprintf(debug_file, "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 || (gn->type & OP_MAKE)) {
+               curTarg = gn;
+               Lst_ForEach(gn->commands, CompatRunCommand, 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;
+           pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0;
+           if (!(gn->type & OP_EXEC)) {
+               pgn->flags |= CHILDMADE;
+               Make_TimeStamp(pgn, gn);
+           }
+       } else if (keepgoing) {
+           pgn->flags &= ~REMAKE;
+       } else {
+           PrintOnError("\n\nStop.");
+           exit(1);
+       }
+    } else if (gn->made == ERROR) {
+       /*
+        * Already had an error when making this beastie. Tell the parent
+        * to abort.
+        */
+       pgn->flags &= ~REMAKE;
+    } else {
+       if (Lst_Member(gn->iParents, pgn) != NULL) {
+           char *p1;
+           Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0);
+           if (p1)
+               free(p1);
+       }
+       switch(gn->made) {
+           case BEINGMADE:
+               Error("Graph cycles through %s", gn->name);
+               gn->made = ERROR;
+               pgn->flags &= ~REMAKE;
+               break;
+           case MADE:
+               if ((gn->type & OP_EXEC) == 0) {
+                   pgn->flags |= CHILDMADE;
+                   Make_TimeStamp(pgn, gn);
+               }
+               break;
+           case UPTODATE:
+               if ((gn->type & OP_EXEC) == 0) {
+                   Make_TimeStamp(pgn, gn);
+               }
+               break;
+           default:
+               break;
+       }
+    }
+
+cohorts:
+    Lst_ForEach(gn->cohorts, Compat_Make, pgnp);
+    return (0);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * Compat_Run --
+ *     Initialize this mode and start making.
+ *
+ * Input:
+ *     targs           List of target nodes to re-create
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     Guess what?
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Compat_Run(Lst targs)
+{
+    GNode        *gn = NULL;/* Current root target */
+    int                  errors;   /* Number of targets not remade due to errors */
+
+    Compat_Init();
+
+    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);
+    }
+
+    ENDNode = Targ_FindNode(".END", TARG_CREATE);
+    ENDNode->type = OP_SPECIAL;
+    /*
+     * 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) {
+           Compat_Make(gn, gn);
+            if (gn->made == ERROR) {
+                PrintOnError("\n\nStop.");
+                exit(1);
+            }
+       }
+    }
+
+    /*
+     * Expand .USE nodes right now, because they can modify the structure
+     * of the tree.
+     */
+    Make_ExpandUse(targs);
+
+    /*
+     * For each entry in the list of targets to create, call Compat_Make on
+     * it to create the thing. Compat_Make 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);
+       Compat_Make(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) {
+       Compat_Make(ENDNode, ENDNode);
+       if (gn->made == ERROR) {
+           PrintOnError("\n\nStop.");
+           exit(1);
+       }
+    }
+}
diff --git a/commands/bmake/cond.c b/commands/bmake/cond.c
new file mode 100644 (file)
index 0000000..3292f19
--- /dev/null
@@ -0,0 +1,1410 @@
+/*     $NetBSD: cond.c,v 1.60 2009/11/06 19:44:06 dsl Exp $    */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: cond.c,v 1.60 2009/11/06 19:44:06 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cond.c     8.2 (Berkeley) 1/2/94";
+#else
+__RCSID("$NetBSD: cond.c,v 1.60 2009/11/06 19:44:06 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * cond.c --
+ *     Functions to handle conditionals in a makefile.
+ *
+ * Interface:
+ *     Cond_Eval       Evaluate the conditional in the passed line.
+ *
+ */
+
+#include    <ctype.h>
+#include    <errno.h>    /* For strtoul() error checking */
+
+#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 -> commands(name)
+ *     T -> symbol
+ *     T -> $(varspec) op value
+ *     T -> $(varspec) == "string"
+ *     T -> $(varspec) != "string"
+ *     T -> "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 TOK_AND for '&' and '&&', TOK_OR for '|' and '||',
+ * TOK_NOT for '!', TOK_LPAREN for '(', TOK_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 TOK_TRUE
+ * or TOK_FALSE.
+ *
+ * TOK_FALSE is 0 and TOK_TRUE 1 so we can directly assign C comparisons.
+ *
+ * All Non-Terminal functions (CondE, CondF and CondT) return TOK_ERROR on
+ * error.
+ */
+typedef enum {
+    TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT,
+    TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR
+} 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 **, const char *);
+static Boolean CondDoDefined(int, const char *);
+static int CondStrMatch(const void *, const void *);
+static Boolean CondDoMake(int, const char *);
+static Boolean CondDoExists(int, const char *);
+static Boolean CondDoTarget(int, const char *);
+static Boolean CondDoCommands(int, const char *);
+static Boolean CondCvtArg(char *, double *);
+static Token CondToken(Boolean);
+static Token CondT(Boolean);
+static Token CondF(Boolean);
+static Token CondE(Boolean);
+static int do_Cond_EvalExpression(Boolean *);
+
+static const struct If {
+    const char *form;        /* Form of if */
+    int                formlen;      /* Length of form */
+    Boolean    doNot;        /* TRUE if default function should be negated */
+    Boolean    (*defProc)(int, const char *); /* Default function to apply */
+} ifs[] = {
+    { "def",     3,      FALSE,  CondDoDefined },
+    { "ndef",    4,      TRUE,   CondDoDefined },
+    { "make",    4,      FALSE,  CondDoMake },
+    { "nmake",   5,      TRUE,   CondDoMake },
+    { "",        0,      FALSE,  CondDoDefined },
+    { NULL,      0,      FALSE,  NULL }
+};
+
+static const struct If *if_info;        /* Info for current statement */
+static char      *condExpr;            /* The expression to parse */
+static Token     condPushBack=TOK_NONE;        /* Single push-back token used in
+                                        * parsing */
+
+static unsigned int    cond_depth = 0;         /* current .if nesting level */
+static unsigned int    cond_min_depth = 0;     /* depth at makefile open */
+
+static int
+istoken(const char *str, const char *tok, size_t len)
+{
+       return strncmp(str, tok, len) == 0 && !isalpha((unsigned char)str[len]);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondPushBack --
+ *     Push back the most recent token read. We only need one level of
+ *     this, so the thing is just stored in 'condPushback'.
+ *
+ * Input:
+ *     t               Token to push back into the "stream"
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     condPushback is overwritten.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+CondPushBack(Token t)
+{
+    condPushBack = t;
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * CondGetArg --
+ *     Find the argument of a built-in function.
+ *
+ * Input:
+ *     parens          TRUE if arg should be 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, const char *func)
+{
+    char         *cp;
+    int                  argLen;
+    Buffer       buf;
+    int           paren_depth;
+    char          ch;
+
+    cp = *linePtr;
+    if (func != NULL)
+       /* Skip opening '(' - verfied by caller */
+       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 = NULL;
+       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_Init(&buf, 16);
+
+    paren_depth = 0;
+    for (;;) {
+       ch = *cp;
+       if (ch == 0 || ch == ' ' || ch == '\t')
+           break;
+       if ((ch == '&' || ch == '|') && paren_depth == 0)
+           break;
+       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;
+           void        *freeIt;
+
+           cp2 = Var_Parse(cp, VAR_CMD, TRUE, &len, &freeIt);
+           Buf_AddBytes(&buf, strlen(cp2), cp2);
+           if (freeIt)
+               free(freeIt);
+           cp += len;
+           continue;
+       }
+       if (ch == '(')
+           paren_depth++;
+       else
+           if (ch == ')' && --paren_depth < 0)
+               break;
+       Buf_AddByte(&buf, *cp);
+       cp++;
+    }
+
+    *argPtr = Buf_GetAll(&buf, &argLen);
+    Buf_Destroy(&buf, FALSE);
+
+    while (*cp == ' ' || *cp == '\t') {
+       cp++;
+    }
+
+    if (func != NULL && *cp++ != ')') {
+       Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()",
+                    func);
+       return (0);
+    }
+
+    *linePtr = cp;
+    return (argLen);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoDefined --
+ *     Handle the 'defined' function for conditionals.
+ *
+ * Results:
+ *     TRUE if the given variable is defined.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoDefined(int argLen, const char *arg)
+{
+    char    *p1;
+    Boolean result;
+
+    if (Var_Value(arg, VAR_CMD, &p1) != NULL) {
+       result = TRUE;
+    } else {
+       result = FALSE;
+    }
+    if (p1)
+       free(p1);
+    return (result);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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(const void *string, const void *pattern)
+{
+    return(!Str_Match(string, pattern));
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoMake --
+ *     Handle the 'make' function for conditionals.
+ *
+ * Results:
+ *     TRUE if the given target is being made.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoMake(int argLen, const char *arg)
+{
+    return Lst_Find(create, arg, CondStrMatch) != NULL;
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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, const char *arg)
+{
+    Boolean result;
+    char    *path;
+
+    path = Dir_FindFile(arg, dirSearchPath);
+    if (path != NULL) {
+       result = TRUE;
+       free(path);
+    } else {
+       result = FALSE;
+    }
+    if (DEBUG(COND)) {
+       fprintf(debug_file, "exists(%s) result is \"%s\"\n",
+              arg, path ? path : "");
+    }    
+    return (result);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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, const char *arg)
+{
+    GNode   *gn;
+
+    gn = Targ_FindNode(arg, TARG_NOCREATE);
+    return (gn != NULL) && !OP_NOP(gn->type);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondDoCommands --
+ *     See if the given node exists and is an actual target with commands
+ *     associated with it.
+ *
+ * Results:
+ *     TRUE if the node exists as a target and has commands associated with
+ *     it and FALSE if it does not.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondDoCommands(int argLen, const char *arg)
+{
+    GNode   *gn;
+
+    gn = Targ_FindNode(arg, TARG_NOCREATE);
+    return (gn != NULL) && !OP_NOP(gn->type) && !Lst_IsEmpty(gn->commands);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * CondCvtArg --
+ *     Convert the given number into a double.
+ *     We try a base 10 or 16 integer conversion first, if that fails
+ *     then we try a floating point conversion instead.
+ *
+ * Results:
+ *     Sets 'value' to double value of string.
+ *     Returns 'true' if the convertion suceeded
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+CondCvtArg(char *str, double *value)
+{
+    char *eptr, ech;
+    unsigned long l_val;
+    double d_val;
+
+    errno = 0;
+    l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10);
+    ech = *eptr;
+    if (ech == 0 && errno != ERANGE) {
+       d_val = str[0] == '-' ? -(double)-l_val : (double)l_val;
+    } else {
+       if (ech != 0 && ech != '.' && ech != 'e' && ech != 'E')
+           return FALSE;
+       d_val = strtod(str, &eptr);
+       if (*eptr)
+           return FALSE;
+    }
+
+    *value = d_val;
+    return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondGetString --
+ *     Get a string from a variable reference or an optionally quoted
+ *     string.  This is called for the lhs and rhs of string compares.
+ *
+ * Results:
+ *     Sets freeIt if needed,
+ *     Sets quoted if string was quoted,
+ *     Returns NULL on error,
+ *     else returns string - absent any quotes.
+ *
+ * Side Effects:
+ *     Moves condExpr to end of this token.
+ *
+ *
+ *-----------------------------------------------------------------------
+ */
+/* coverity:[+alloc : arg-*2] */
+static char *
+CondGetString(Boolean doEval, Boolean *quoted, void **freeIt)
+{
+    Buffer buf;
+    char *cp;
+    char *str;
+    int        len;
+    int qt;
+    char *start;
+
+    Buf_Init(&buf, 0);
+    str = NULL;
+    *freeIt = NULL;
+    *quoted = qt = *condExpr == '"' ? 1 : 0;
+    if (qt)
+       condExpr++;
+    for (start = condExpr; *condExpr && str == NULL; condExpr++) {
+       switch (*condExpr) {
+       case '\\':
+           if (condExpr[1] != '\0') {
+               condExpr++;
+               Buf_AddByte(&buf, *condExpr);
+           }
+           break;
+       case '"':
+           if (qt) {
+               condExpr++;             /* we don't want the quotes */
+               goto got_str;
+           } else
+               Buf_AddByte(&buf, *condExpr); /* likely? */
+           break;
+       case ')':
+       case '!':
+       case '=':
+       case '>':
+       case '<':
+       case ' ':
+       case '\t':
+           if (!qt)
+               goto got_str;
+           else
+               Buf_AddByte(&buf, *condExpr);
+           break;
+       case '$':
+           /* if we are in quotes, then an undefined variable is ok */
+           str = Var_Parse(condExpr, VAR_CMD, (qt ? 0 : doEval),
+                           &len, freeIt);
+           if (str == var_Error) {
+               if (*freeIt) {
+                   free(*freeIt);
+                   *freeIt = NULL;
+               }
+               /*
+                * Even if !doEval, we still report syntax errors, which
+                * is what getting var_Error back with !doEval means.
+                */
+               str = NULL;
+               goto cleanup;
+           }
+           condExpr += len;
+           /*
+            * If the '$' was first char (no quotes), and we are
+            * followed by space, the operator or end of expression,
+            * we are done.
+            */
+           if ((condExpr == start + len) &&
+               (*condExpr == '\0' ||
+                isspace((unsigned char) *condExpr) ||
+                strchr("!=><)", *condExpr))) {
+               goto cleanup;
+           }
+           /*
+            * Nope, we better copy str to buf
+            */
+           for (cp = str; *cp; cp++) {
+               Buf_AddByte(&buf, *cp);
+           }
+           if (*freeIt) {
+               free(*freeIt);
+               *freeIt = NULL;
+           }
+           str = NULL;                 /* not finished yet */
+           condExpr--;                 /* don't skip over next char */
+           break;
+       default:
+           Buf_AddByte(&buf, *condExpr);
+           break;
+       }
+    }
+ got_str:
+    str = Buf_GetAll(&buf, NULL);
+    *freeIt = str;
+ cleanup:
+    Buf_Destroy(&buf, FALSE);
+    return str;
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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 TOK_NONE if it is used.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+compare_expression(Boolean doEval)
+{
+    Token      t;
+    char       *lhs;
+    char       *rhs;
+    char       *op;
+    void       *lhsFree;
+    void       *rhsFree;
+    Boolean lhsQuoted;
+    Boolean rhsQuoted;
+    double     left, right;
+
+    t = TOK_ERROR;
+    rhs = NULL;
+    lhsFree = rhsFree = FALSE;
+    lhsQuoted = rhsQuoted = FALSE;
+    
+    /*
+     * Parse the variable spec and skip over it, saving its
+     * value in lhs.
+     */
+    lhs = CondGetString(doEval, &lhsQuoted, &lhsFree);
+    if (!lhs)
+       goto done;
+
+    /*
+     * 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:
+           if (!doEval) {
+               t = TOK_FALSE;
+               goto done;
+           }
+           /* For .ifxxx "..." check for non-empty string. */
+           if (lhsQuoted) {
+               t = lhs[0] != 0;
+               goto done;
+           }
+           /* For .ifxxx <number> compare against zero */
+           if (CondCvtArg(lhs, &left)) { 
+               t = left != 0.0;
+               goto done;
+           }
+           /* For .if ${...} check for non-empty string (defProc is ifdef). */
+           if (if_info->form[0] == 0) {
+               t = lhs[0] != 0;
+               goto done;
+           }
+           /* Otherwise action default test ... */
+           t = if_info->defProc(strlen(lhs), lhs) != if_info->doNot;
+           goto done;
+    }
+
+    while (isspace((unsigned char)*condExpr))
+       condExpr++;
+
+    if (*condExpr == '\0') {
+       Parse_Error(PARSE_WARNING,
+                   "Missing right-hand-side of operator");
+       goto done;
+    }
+
+    rhs = CondGetString(doEval, &rhsQuoted, &rhsFree);
+    if (!rhs)
+       goto done;
+
+    if (rhsQuoted || lhsQuoted) {
+do_string_compare:
+       if (((*op != '!') && (*op != '=')) || (op[1] != '=')) {
+           Parse_Error(PARSE_WARNING,
+    "String comparison operator should be either == or !=");
+           goto done;
+       }
+
+       if (DEBUG(COND)) {
+           fprintf(debug_file, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n",
+                  lhs, rhs, op);
+       }
+       /*
+        * Null-terminate rhs and perform the comparison.
+        * t is set to the result.
+        */
+       if (*op == '=') {
+           t = strcmp(lhs, rhs) == 0;
+       } else {
+           t = strcmp(lhs, rhs) != 0;
+       }
+    } else {
+       /*
+        * rhs is either a float or an integer. Convert both the
+        * lhs and the rhs to a double and compare the two.
+        */
+    
+       if (!CondCvtArg(lhs, &left) || !CondCvtArg(rhs, &right))
+           goto do_string_compare;
+
+       if (DEBUG(COND)) {
+           fprintf(debug_file, "left = %f, right = %f, op = %.2s\n", left,
+                  right, op);
+       }
+       switch(op[0]) {
+       case '!':
+           if (op[1] != '=') {
+               Parse_Error(PARSE_WARNING,
+                           "Unknown operator");
+               goto done;
+           }
+           t = (left != right);
+           break;
+       case '=':
+           if (op[1] != '=') {
+               Parse_Error(PARSE_WARNING,
+                           "Unknown operator");
+               goto done;
+           }
+           t = (left == right);
+           break;
+       case '<':
+           if (op[1] == '=') {
+               t = (left <= right);
+           } else {
+               t = (left < right);
+           }
+           break;
+       case '>':
+           if (op[1] == '=') {
+               t = (left >= right);
+           } else {
+               t = (left > right);
+           }
+           break;
+       }
+    }
+
+done:
+    if (lhsFree)
+       free(lhsFree);
+    if (rhsFree)
+       free(rhsFree);
+    return t;
+}
+
+static int
+get_mpt_arg(char **linePtr, char **argPtr, const char *func)
+{
+    /*
+     * Use Var_Parse to parse the spec in parens and return
+     * TOK_TRUE if the resulting string is empty.
+     */
+    int            length;
+    void    *freeIt;
+    char    *val;
+    char    *cp = *linePtr;
+
+    /* We do all the work here and return the result as the length */
+    *argPtr = NULL;
+
+    val = Var_Parse(cp - 1, VAR_CMD, FALSE, &length, &freeIt);
+    /*
+     * Advance *linePtr to beyond the closing ). Note that
+     * we subtract one because 'length' is calculated from 'cp - 1'.
+     */
+    *linePtr = cp - 1 + length;
+
+    if (val == var_Error) {
+       free(freeIt);
+       return -1;
+    }
+
+    /* A variable is empty when it just contains spaces... 4/15/92, christos */
+    while (isspace(*(unsigned char *)val))
+       val++;
+
+    /*
+     * For consistency with the other functions we can't generate the
+     * true/false here.
+     */
+    length = *val ? 2 : 1;
+    if (freeIt)
+       free(freeIt);
+    return length;
+}
+
+static Boolean
+CondDoEmpty(int arglen, const char *arg)
+{
+    return arglen == 1;
+}
+
+static Token
+compare_function(Boolean doEval)
+{
+    static const struct fn_def {
+       const char  *fn_name;
+       int         fn_name_len;
+        int         (*fn_getarg)(char **, char **, const char *);
+       Boolean     (*fn_proc)(int, const char *);
+    } fn_defs[] = {
+       { "defined",   7, CondGetArg, CondDoDefined },
+       { "make",      4, CondGetArg, CondDoMake },
+       { "exists",    6, CondGetArg, CondDoExists },
+       { "empty",     5, get_mpt_arg, CondDoEmpty },
+       { "target",    6, CondGetArg, CondDoTarget },
+       { "commands",  8, CondGetArg, CondDoCommands },
+       { NULL,        0, NULL, NULL },
+    };
+    const struct fn_def *fn_def;
+    Token      t;
+    char       *arg = NULL;
+    int        arglen;
+    char *cp = condExpr;
+    char *cp1;
+
+    for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) {
+       if (!istoken(cp, fn_def->fn_name, fn_def->fn_name_len))
+           continue;
+       cp += fn_def->fn_name_len;
+       /* There can only be whitespace before the '(' */
+       while (isspace(*(unsigned char *)cp))
+           cp++;
+       if (*cp != '(')
+           break;
+
+       arglen = fn_def->fn_getarg(&cp, &arg, fn_def->fn_name);
+       if (arglen <= 0) {
+           condExpr = cp;
+           return arglen < 0 ? TOK_ERROR : TOK_FALSE;
+       }
+       /* Evaluate the argument using the required function. */
+       t = !doEval || fn_def->fn_proc(arglen, arg);
+       if (arg)
+           free(arg);
+       condExpr = cp;
+       return t;
+    }
+
+    /* Push anything numeric through the compare expression */
+    cp = condExpr;
+    if (isdigit((unsigned char)cp[0]) || strchr("+-", cp[0]))
+       return compare_expression(doEval);
+
+    /*
+     * Most likely we have a naked token to apply the default function to.
+     * However ".if a == b" gets here when the "a" is unquoted and doesn't
+     * start with a '$'. This surprises people.
+     * If what follows the function argument is a '=' or '!' then the syntax
+     * would be invalid if we did "defined(a)" - so instead treat as an
+     * expression.
+     */
+    arglen = CondGetArg(&cp, &arg, NULL);
+    for (cp1 = cp; isspace(*(unsigned char *)cp1); cp1++)
+       continue;
+    if (*cp1 == '=' || *cp1 == '!')
+       return compare_expression(doEval);
+    condExpr = cp;
+
+    /*
+     * Evaluate the argument using the default function.
+     * This path always treats .if as .ifdef. To get here the character
+     * after .if must have been taken literally, so the argument cannot
+     * be empty - even if it contained a variable expansion.
+     */
+    t = !doEval || if_info->defProc(arglen, arg) != if_info->doNot;
+    if (arg)
+       free(arg);
+    return t;
+}
+
+static Token
+CondToken(Boolean doEval)
+{
+    Token t;
+
+    t = condPushBack;
+    if (t != TOK_NONE) {
+       condPushBack = TOK_NONE;
+       return t;
+    }
+
+    while (*condExpr == ' ' || *condExpr == '\t') {
+       condExpr++;
+    }
+
+    switch (*condExpr) {
+
+    case '(':
+       condExpr++;
+       return TOK_LPAREN;
+
+    case ')':
+       condExpr++;
+       return TOK_RPAREN;
+
+    case '|':
+       if (condExpr[1] == '|') {
+           condExpr++;
+       }
+       condExpr++;
+       return TOK_OR;
+
+    case '&':
+       if (condExpr[1] == '&') {
+           condExpr++;
+       }
+       condExpr++;
+       return TOK_AND;
+
+    case '!':
+       condExpr++;
+       return TOK_NOT;
+
+    case '#':
+    case '\n':
+    case '\0':
+       return TOK_EOF;
+
+    case '"':
+    case '$':
+       return compare_expression(doEval);
+
+    default:
+       return compare_function(doEval);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * CondT --
+ *     Parse a single term in the expression. This consists of a terminal
+ *     symbol or TOK_NOT and a terminal symbol (not including the binary
+ *     operators):
+ *         T -> defined(variable) | make(target) | exists(file) | symbol
+ *         T -> ! T | ( E )
+ *
+ * Results:
+ *     TOK_TRUE, TOK_FALSE or TOK_ERROR.
+ *
+ * Side Effects:
+ *     Tokens are consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondT(Boolean doEval)
+{
+    Token   t;
+
+    t = CondToken(doEval);
+
+    if (t == TOK_EOF) {
+       /*
+        * If we reached the end of the expression, the expression
+        * is malformed...
+        */
+       t = TOK_ERROR;
+    } else if (t == TOK_LPAREN) {
+       /*
+        * T -> ( E )
+        */
+       t = CondE(doEval);
+       if (t != TOK_ERROR) {
+           if (CondToken(doEval) != TOK_RPAREN) {
+               t = TOK_ERROR;
+           }
+       }
+    } else if (t == TOK_NOT) {
+       t = CondT(doEval);
+       if (t == TOK_TRUE) {
+           t = TOK_FALSE;
+       } else if (t == TOK_FALSE) {
+           t = TOK_TRUE;
+       }
+    }
+    return (t);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * CondF --
+ *     Parse a conjunctive factor (nice name, wot?)
+ *         F -> T && F | T
+ *
+ * Results:
+ *     TOK_TRUE, TOK_FALSE or TOK_ERROR
+ *
+ * Side Effects:
+ *     Tokens are consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondF(Boolean doEval)
+{
+    Token   l, o;
+
+    l = CondT(doEval);
+    if (l != TOK_ERROR) {
+       o = CondToken(doEval);
+
+       if (o == TOK_AND) {
+           /*
+            * F -> T && F
+            *
+            * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we have to
+            * parse the r.h.s. anyway (to throw it away).
+            * If T is TOK_TRUE, the result is the r.h.s., be it an TOK_ERROR or no.
+            */
+           if (l == TOK_TRUE) {
+               l = CondF(doEval);
+           } else {
+               (void)CondF(FALSE);
+           }
+       } else {
+           /*
+            * F -> T
+            */
+           CondPushBack(o);
+       }
+    }
+    return (l);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * CondE --
+ *     Main expression production.
+ *         E -> F || E | F
+ *
+ * Results:
+ *     TOK_TRUE, TOK_FALSE or TOK_ERROR.
+ *
+ * Side Effects:
+ *     Tokens are, of course, consumed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Token
+CondE(Boolean doEval)
+{
+    Token   l, o;
+
+    l = CondF(doEval);
+    if (l != TOK_ERROR) {
+       o = CondToken(doEval);
+
+       if (o == TOK_OR) {
+           /*
+            * E -> F || E
+            *
+            * A similar thing occurs for ||, except that here we make sure
+            * the l.h.s. is TOK_FALSE before we bother to evaluate the r.h.s.
+            * Once again, if l is TOK_FALSE, the result is the r.h.s. and once
+            * again if l is TOK_TRUE, we parse the r.h.s. to throw it away.
+            */
+           if (l == TOK_FALSE) {
+               l = CondE(doEval);
+           } else {
+               (void)CondE(FALSE);
+           }
+       } else {
+           /*
+            * E -> F
+            */
+           CondPushBack(o);
+       }
+    }
+    return (l);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Cond_EvalExpression --
+ *     Evaluate an expression in the passed line. The expression
+ *     consists of &&, ||, !, make(target), defined(variable)
+ *     and parenthetical groupings thereof.
+ *
+ * Results:
+ *     COND_PARSE      if the condition was valid grammatically
+ *     COND_INVALID    if not a valid conditional.
+ *
+ *     (*value) is set to the boolean value of the condition
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Cond_EvalExpression(const struct If *info, char *line, Boolean *value, int eprint)
+{
+    static const struct If *dflt_info;
+    const struct If *sv_if_info = if_info;
+    char *sv_condExpr = condExpr;
+    Token sv_condPushBack = condPushBack;
+    int rval;
+
+    while (*line == ' ' || *line == '\t')
+       line++;
+
+    if (info == NULL && (info = dflt_info) == NULL) {
+       /* Scan for the entry for .if - it can't be first */
+       for (info = ifs; ; info++)
+           if (info->form[0] == 0)
+               break;
+       dflt_info = info;
+    }
+
+    if_info = info != NULL ? info : ifs + 4;
+    condExpr = line;
+    condPushBack = TOK_NONE;
+
+    rval = do_Cond_EvalExpression(value);
+
+    if (rval == COND_INVALID && eprint)
+       Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", line);
+
+    if_info = sv_if_info;
+    condExpr = sv_condExpr;
+    condPushBack = sv_condPushBack;
+
+    return rval;
+}
+
+static int
+do_Cond_EvalExpression(Boolean *value)
+{
+
+    switch (CondE(TRUE)) {
+    case TOK_TRUE:
+       if (CondToken(TRUE) == TOK_EOF) {
+           *value = TRUE;
+           return COND_PARSE;
+       }
+       break;
+    case TOK_FALSE:
+       if (CondToken(TRUE) == TOK_EOF) {
+           *value = FALSE;
+           return COND_PARSE;
+       }
+       break;
+    default:
+    case TOK_ERROR:
+       break;
+    }
+
+    return COND_INVALID;
+}
+
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     line            Line to parse
+ *
+ * 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.
+ *
+ * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order
+ * to detect splurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF)
+ * otherwise .else could be treated as '.elif 1'.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Cond_Eval(char *line)
+{
+    #define        MAXIF       64      /* maximum depth of .if'ing */
+    enum if_states {
+       IF_ACTIVE,              /* .if or .elif part active */
+       ELSE_ACTIVE,            /* .else part active */
+       SEARCH_FOR_ELIF,        /* searching for .elif/else to execute */
+       SKIP_TO_ELSE,           /* has been true, but not seen '.else' */
+       SKIP_TO_ENDIF           /* nothing else to execute */
+    };
+    static enum if_states cond_state[MAXIF + 1] = { IF_ACTIVE };
+
+    const struct If *ifp;
+    Boolean        isElif;
+    Boolean        value;
+    int                    level;      /* Level at which to report errors. */
+    enum if_states  state;
+
+    level = PARSE_FATAL;
+
+    /* skip leading character (the '.') and any whitespace */
+    for (line++; *line == ' ' || *line == '\t'; line++)
+       continue;
+
+    /* Find what type of if we're dealing with.  */
+    if (line[0] == 'e') {
+       if (line[1] != 'l') {
+           if (!istoken(line + 1, "ndif", 4))
+               return COND_INVALID;
+           /* End of conditional section */
+           if (cond_depth == cond_min_depth) {
+               Parse_Error(level, "if-less endif");
+               return COND_PARSE;
+           }
+           /* Return state for previous conditional */
+           cond_depth--;
+           if (cond_depth > MAXIF)
+               return COND_SKIP;
+           return cond_state[cond_depth] <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
+       }
+
+       /* Quite likely this is 'else' or 'elif' */
+       line += 2;
+       if (istoken(line, "se", 2)) {
+           /* It is else... */
+           if (cond_depth == cond_min_depth) {
+               Parse_Error(level, "if-less else");
+               return COND_PARSE;
+           }
+
+           if (cond_depth > MAXIF)
+               return COND_SKIP;
+           state = cond_state[cond_depth];
+           switch (state) {
+           case SEARCH_FOR_ELIF:
+               state = ELSE_ACTIVE;
+               break;
+           case ELSE_ACTIVE:
+           case SKIP_TO_ENDIF:
+               Parse_Error(PARSE_WARNING, "extra else");
+               /* FALLTHROUGH */
+           default:
+           case IF_ACTIVE:
+           case SKIP_TO_ELSE:
+               state = SKIP_TO_ENDIF;
+               break;
+           }
+           cond_state[cond_depth] = state;
+           return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP;
+       }
+       /* Assume for now it is an elif */
+       isElif = TRUE;
+    } else
+       isElif = FALSE;
+
+    if (line[0] != 'i' || line[1] != 'f')
+       /* Not an ifxxx or elifxxx line */
+       return COND_INVALID;
+
+    /*
+     * Figure out what sort of conditional it is -- what its default
+     * function is, etc. -- by looking in the table of valid "ifs"
+     */
+    line += 2;
+    for (ifp = ifs; ; ifp++) {
+       if (ifp->form == NULL)
+           return COND_INVALID;
+       if (istoken(ifp->form, line, ifp->formlen)) {
+           line += ifp->formlen;
+           break;
+       }
+    }
+
+    /* Now we know what sort of 'if' it is... */
+
+    if (isElif) {
+       if (cond_depth == cond_min_depth) {
+           Parse_Error(level, "if-less elif");
+           return COND_PARSE;
+       }
+       if (cond_depth > MAXIF)
+           /* Error reported when we saw the .if ... */
+           return COND_SKIP;
+       state = cond_state[cond_depth];
+       if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) {
+           Parse_Error(PARSE_WARNING, "extra elif");
+           cond_state[cond_depth] = SKIP_TO_ENDIF;
+           return COND_SKIP;
+       }
+       if (state != SEARCH_FOR_ELIF) {
+           /* Either just finished the 'true' block, or already SKIP_TO_ELSE */
+           cond_state[cond_depth] = SKIP_TO_ELSE;
+           return COND_SKIP;
+       }
+    } else {
+       /* Normal .if */
+       if (cond_depth >= MAXIF) {
+           cond_depth++;
+           Parse_Error(PARSE_FATAL, "Too many nested if's. %d max.", MAXIF);
+           return COND_SKIP;
+       }
+       state = cond_state[cond_depth];
+       cond_depth++;
+       if (state > ELSE_ACTIVE) {
+           /* If we aren't parsing the data, treat as always false */
+           cond_state[cond_depth] = SKIP_TO_ELSE;
+           return COND_SKIP;
+       }
+    }
+
+    /* And evaluate the conditional expresssion */
+    if (Cond_EvalExpression(ifp, line, &value, 1) == COND_INVALID) {
+       /* Syntax error in conditional, error message already output. */
+       /* Skip everything to matching .endif */
+       cond_state[cond_depth] = SKIP_TO_ELSE;
+       return COND_SKIP;
+    }
+
+    if (!value) {
+       cond_state[cond_depth] = SEARCH_FOR_ELIF;
+       return COND_SKIP;
+    }
+    cond_state[cond_depth] = IF_ACTIVE;
+    return COND_PARSE;
+}
+
+
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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_restore_depth(unsigned int saved_depth)
+{
+    int open_conds = cond_depth - cond_min_depth;
+
+    if (open_conds != 0 || saved_depth > cond_depth) {
+       Parse_Error(PARSE_FATAL, "%d open conditional%s", open_conds,
+                   open_conds == 1 ? "" : "s");
+       cond_depth = cond_min_depth;
+    }
+
+    cond_min_depth = saved_depth;
+}
+
+unsigned int
+Cond_save_depth(void)
+{
+    int depth = cond_min_depth;
+
+    cond_min_depth = cond_depth;
+    return depth;
+}
diff --git a/commands/bmake/config.h b/commands/bmake/config.h
new file mode 100644 (file)
index 0000000..7ff9995
--- /dev/null
@@ -0,0 +1,154 @@
+/*     $NetBSD: config.h,v 1.20 2007/10/14 20:22:53 apb Exp $  */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * 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. 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: @(#)config.h      8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * 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.
+ *
+ *     from: @(#)config.h      8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * 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
+
+/*
+ * USE_IOVEC
+ *     We have writev(2)
+ */
+#define USE_IOVEC
+
+#if defined(MAKE_NATIVE) && !defined(__ELF__)
+# ifndef RANLIBMAG
+#  define RANLIBMAG "__.SYMDEF"
+# endif
+#endif
diff --git a/commands/bmake/dir.c b/commands/bmake/dir.c
new file mode 100644 (file)
index 0000000..7863d49
--- /dev/null
@@ -0,0 +1,1766 @@
+/*     $NetBSD: dir.c,v 1.61 2009/01/24 10:59:09 dsl Exp $     */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: dir.c,v 1.61 2009/01/24 10:59:09 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)dir.c      8.2 (Berkeley) 1/2/94";
+#else
+__RCSID("$NetBSD: dir.c,v 1.61 2009/01/24 10:59:09 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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_InitCur         Set the cur Path.
+ *
+ *     Dir_InitDot         Set the dot Path.
+ *
+ *     Dir_End             Cleanup the module.
+ *
+ *     Dir_SetPATH         Set ${.PATH} to reflect state of dirSearchPath.
+ *
+ *     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_FindHereOrAbove Search for a path in the current directory and
+ *                         then all the directories above it in turn until
+ *                         the path is found or we reach the root ("/").
+ * 
+ *     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 <sys/types.h>
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.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 Path              *cur;     /* contents of current directory, if not dot */
+static Path      *dotLast; /* a fake path entry indicating we need to
+                            * look for . last */
+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(const void *, const void *);
+static int DirMatchFiles(const char *, Path *, Lst);
+static void DirExpandCurly(const char *, const char *, Lst, Lst);
+static void DirExpandInt(const char *, Lst, Lst);
+static int DirPrintWord(void *, void *);
+static int DirPrintDir(void *, void *);
+static char *DirLookup(Path *, const char *, const char *, Boolean);
+static char *DirLookupSubdir(Path *, const char *);
+static char *DirFindDot(Boolean, const char *, const char *);
+static char *DirLookupAbs(Path *, const char *, const char *);
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Init --
+ *     initialize things for this module
+ *
+ * Results:
+ *     none
+ *
+ * Side Effects:
+ *     some directories may be opened.
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Init(const char *cdname)
+{
+    dirSearchPath = Lst_Init(FALSE);
+    openDirectories = Lst_Init(FALSE);
+    Hash_InitTable(&mtimes, 0);
+
+    Dir_InitCur(cdname);
+
+    dotLast = bmake_malloc(sizeof(Path));
+    dotLast->refCount = 1;
+    dotLast->hits = 0;
+    dotLast->name = bmake_strdup(".DOTLAST");
+    Hash_InitTable(&dotLast->files, -1);
+}
+
+/*
+ * Called by Dir_Init() and whenever .CURDIR is assigned to.
+ */
+void
+Dir_InitCur(const char *cdname)
+{
+    Path *p;
+    
+    if (cdname != NULL) {
+       /*
+        * Our build directory is not the same as our source directory.
+        * Keep this one around too.
+        */
+       if ((p = Dir_AddDir(NULL, cdname))) {
+           p->refCount += 1;
+           if (cur && cur != p) {
+               /*
+                * We've been here before, cleanup.
+                */
+               cur->refCount -= 1;
+               Dir_Destroy(cur);
+           }
+           cur = p;
+       }
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_InitDot --
+ *     (re)initialize "dot" (current/object directory) path hash
+ *
+ * Results:
+ *     none
+ *
+ * Side Effects:
+ *     some directories may be opened.
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_InitDot(void)
+{
+    if (dot != NULL) {
+       LstNode ln;
+
+       /* Remove old entry from openDirectories, but do not destroy. */
+       ln = Lst_Member(openDirectories, dot);
+       (void)Lst_Remove(openDirectories, ln);
+    }
+
+    dot = Dir_AddDir(NULL, ".");
+
+    if (dot == NULL) {
+       Error("Cannot open `.' (%s)", strerror(errno));
+       exit(1);
+    }
+
+    /*
+     * We always need to have dot around, so we increment its reference count
+     * to make sure it's not destroyed.
+     */
+    dot->refCount += 1;
+    Dir_SetPATH();                     /* initialize */
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_End --
+ *     cleanup things for this module
+ *
+ * Results:
+ *     none
+ *
+ * Side Effects:
+ *     none
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_End(void)
+{
+#ifdef CLEANUP
+    if (cur) {
+       cur->refCount -= 1;
+       Dir_Destroy(cur);
+    }
+    dot->refCount -= 1;
+    dotLast->refCount -= 1;
+    Dir_Destroy(dotLast);
+    Dir_Destroy(dot);
+    Dir_ClearPath(dirSearchPath);
+    Lst_Destroy(dirSearchPath, NULL);
+    Dir_ClearPath(openDirectories);
+    Lst_Destroy(openDirectories, NULL);
+    Hash_DeleteTable(&mtimes);
+#endif
+}
+
+/*
+ * We want ${.PATH} to indicate the order in which we will actually
+ * search, so we rebuild it after any .PATH: target.
+ * This is the simplest way to deal with the effect of .DOTLAST.
+ */
+void
+Dir_SetPATH(void)
+{
+    LstNode       ln;          /* a list element */
+    Path *p;
+    Boolean      hasLastDot = FALSE;   /* true we should search dot last */
+
+    Var_Delete(".PATH", VAR_GLOBAL);
+    
+    if (Lst_Open(dirSearchPath) == SUCCESS) {
+       if ((ln = Lst_First(dirSearchPath)) != NULL) {
+           p = (Path *)Lst_Datum(ln);
+           if (p == dotLast) {
+               hasLastDot = TRUE;
+               Var_Append(".PATH", dotLast->name, VAR_GLOBAL);
+           }
+       }
+
+       if (!hasLastDot) {
+           if (dot)
+               Var_Append(".PATH", dot->name, VAR_GLOBAL);
+           if (cur)
+               Var_Append(".PATH", cur->name, VAR_GLOBAL);
+       }
+
+       while ((ln = Lst_Next(dirSearchPath)) != NULL) {
+           p = (Path *)Lst_Datum(ln);
+           if (p == dotLast)
+               continue;
+           if (p == dot && hasLastDot)
+               continue;
+           Var_Append(".PATH", p->name, VAR_GLOBAL);
+       }
+
+       if (hasLastDot) {
+           if (dot)
+               Var_Append(".PATH", dot->name, VAR_GLOBAL);
+           if (cur)
+               Var_Append(".PATH", cur->name, VAR_GLOBAL);
+       }
+       Lst_Close(dirSearchPath);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     p               Current name
+ *     dname           Desired name
+ *
+ * Results:
+ *     0 if it is the same. Non-zero otherwise
+ *
+ * Side Effects:
+ *     None
+ *-----------------------------------------------------------------------
+ */
+static int
+DirFindName(const void *p, const void *dname)
+{
+    return (strcmp(((const Path *)p)->name, dname));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_HasWildcards  --
+ *     see if the given name has any wildcard characters in it
+ *     be careful not to expand unmatching brackets or braces.
+ *     XXX: This code is not 100% correct. ([^]] fails etc.) 
+ *     I really don't think that make(1) should be expanding
+ *     patterns, because then you have to set a mechanism for
+ *     escaping the expansion!
+ *
+ * Input:
+ *     name            name to check
+ *
+ * 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.
+ *
+ * Input:
+ *     pattern         Pattern to look for
+ *     p               Directory to search
+ *     expansion       Place to store the results
+ *
+ * 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(const 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 != 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 ? bmake_strdup(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.
+ *
+ * Input:
+ *     word            Entire word to expand
+ *     brace           First curly brace in it
+ *     path            Search path to use
+ *     expansions      Place to store the expansions
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     The given list is filled with the expansions...
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+DirExpandCurly(const char *word, const char *brace, Lst path, Lst expansions)
+{
+    const char   *end;         /* Character after the closing brace */
+    const char   *cp;          /* Current position in brace clause */
+    const 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 = bmake_malloc(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;
+           }
+       }
+       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...
+ *
+ * Input:
+ *     word            Word to expand
+ *     path            Path on which to look
+ *     expansions      Place to store the result
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     Things are added to the expansions list.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+DirExpandInt(const 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)
+{
+    fprintf(debug_file, "%s ", (char *)word);
+
+    return(dummy ? 0 : 0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Expand  --
+ *     Expand the given word into a list of words by globbing it looking
+ *     in the directories on the given search path.
+ *
+ * Input:
+ *     word            the word to expand
+ *     path            the list of directories in which to find the
+ *                     resulting files
+ *     expansions      the list on which to place the results
+ *
+ * Results:
+ *     A list of words consisting of the files which exist along the search
+ *     path matching the given pattern.
+ *
+ * Side Effects:
+ *     Directories may be opened. Who knows?
+ *-----------------------------------------------------------------------
+ */
+void
+Dir_Expand(const char *word, Lst path, Lst expansions)
+{
+    const char           *cp;
+
+    if (DEBUG(DIR)) {
+       fprintf(debug_file, "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];
+                   ((char *)UNCONST(cp))[1] = '\0';
+                   dirpath = Dir_FindFile(word, path);
+                   ((char *)UNCONST(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 != NULL) {
+                       char *dp = &dirpath[strlen(dirpath) - 1];
+                       if (*dp == '/')
+                           *dp = '\0';
+                       path = Lst_Init(FALSE);
+                       (void)Dir_AddDir(path, dirpath);
+                       DirExpandInt(cp+1, path, expansions);
+                       Lst_Destroy(path, NULL);
+                   }
+               } 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, NULL);
+       fprintf(debug_file, "\n");
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirLookup  --
+ *     Find if the file with the given name exists in the given 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:
+ *     None.
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirLookup(Path *p, const char *name __unused, const char *cp, 
+          Boolean hasSlash __unused)
+{
+    char *file;                /* the current filename to check */
+
+    if (DEBUG(DIR)) {
+       fprintf(debug_file, "   %s ...\n", p->name);
+    }
+
+    if (Hash_FindEntry(&p->files, cp) == NULL)
+       return NULL;
+
+    file = str_concat(p->name, cp, STR_ADDSLASH);
+    if (DEBUG(DIR)) {
+       fprintf(debug_file, "   returning %s\n", file);
+    }
+    p->hits += 1;
+    hits += 1;
+    return file;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirLookupSubdir  --
+ *     Find if the file with the given name exists in the given 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, it is added in the modification times hash
+ *     table.
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirLookupSubdir(Path *p, const char *name)
+{
+    struct stat          stb;          /* Buffer for stat, if necessary */
+    Hash_Entry  *entry;        /* Entry for mtimes table */
+    char        *file;         /* the current filename to check */
+
+    if (p != dot) {
+       file = str_concat(p->name, name, STR_ADDSLASH);
+    } else {
+       /*
+        * Checking in dot -- DON'T put a leading ./ on the thing.
+        */
+       file = bmake_strdup(name);
+    }
+
+    if (DEBUG(DIR)) {
+       fprintf(debug_file, "checking %s ...\n", file);
+    }
+
+    if (stat(file, &stb) == 0) {
+       if (stb.st_mtime == 0)
+               stb.st_mtime = 1;
+       /*
+        * Save the modification time so if it's needed, we don't have
+        * to fetch it again.
+        */
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "   Caching %s for %s\n", Targ_FmtTime(stb.st_mtime),
+                   file);
+       }
+       entry = Hash_CreateEntry(&mtimes, file, NULL);
+       Hash_SetTimeValue(entry, stb.st_mtime);
+       nearmisses += 1;
+       return (file);
+    }
+    free(file);
+    return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirLookupAbs  --
+ *     Find if the file with the given name exists in the given path.
+ *
+ * Results:
+ *     The path to the file, the empty string or NULL. If the file is
+ *     the empty string, the search should be terminated.
+ *     This path is guaranteed to be in a different part of memory
+ *     than name and so may be safely free'd.
+ *
+ * Side Effects:
+ *     None.
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirLookupAbs(Path *p, const char *name, const char *cp)
+{
+       char *p1;               /* pointer into p->name */
+       const char *p2;         /* pointer into name */
+
+       if (DEBUG(DIR)) {
+               fprintf(debug_file, "   %s ...\n", p->name);
+       }
+
+       /*
+        * If the file has a leading path component and that component
+        * exactly matches the entire name of the current search
+        * directory, we can attempt another cache lookup. And if we don't
+        * have a hit, we can safely assume the file does not exist at all.
+        */
+       for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) {
+               continue;
+       }
+       if (*p1 != '\0' || p2 != cp - 1) {
+               return NULL;
+       }
+
+       if (Hash_FindEntry(&p->files, cp) == NULL) {
+               if (DEBUG(DIR)) {
+                       fprintf(debug_file, "   must be here but isn't -- returning\n");
+               }
+               /* Return empty string: terminates search */
+               return bmake_strdup("");
+       }
+
+       p->hits += 1;
+       hits += 1;
+       if (DEBUG(DIR)) {
+               fprintf(debug_file, "   returning %s\n", name);
+       }
+       return (bmake_strdup(name));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * DirFindDot  --
+ *     Find the file given on "." or curdir
+ *
+ * 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:
+ *     Hit counts change
+ *-----------------------------------------------------------------------
+ */
+static char *
+DirFindDot(Boolean hasSlash __unused, const char *name, const char *cp)
+{
+
+       if (Hash_FindEntry(&dot->files, cp) != NULL) {
+           if (DEBUG(DIR)) {
+               fprintf(debug_file, "   in '.'\n");
+           }
+           hits += 1;
+           dot->hits += 1;
+           return (bmake_strdup(name));
+       }
+       if (cur &&
+           Hash_FindEntry(&cur->files, cp) != NULL) {
+           if (DEBUG(DIR)) {
+               fprintf(debug_file, "   in ${.CURDIR} = %s\n", cur->name);
+           }
+           hits += 1;
+           cur->hits += 1;
+           return str_concat(cur->name, cp, STR_ADDSLASH);
+       }
+
+       return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_FindFile  --
+ *     Find the file with the given name along the given search path.
+ *
+ * Input:
+ *     name            the file to find
+ *     path            the Lst of directories to search
+ *
+ * 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(const char *name, Lst path)
+{
+    LstNode       ln;                  /* a list element */
+    char         *file;                /* the current filename to check */
+    Path         *p;                   /* current path member */
+    const char   *cp;                  /* Terminal name of file */
+    Boolean      hasLastDot = FALSE;   /* true we should search dot last */
+    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;
+    }
+
+    if (DEBUG(DIR)) {
+       fprintf(debug_file, "Searching for %s ...", name);
+    }
+
+    if (Lst_Open(path) == FAILURE) {
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "couldn't open path, file not found\n");
+       }
+       misses += 1;
+       return NULL;
+    }
+
+    if ((ln = Lst_First(path)) != NULL) {
+       p = (Path *)Lst_Datum(ln);
+       if (p == dotLast) {
+           hasLastDot = TRUE;
+            if (DEBUG(DIR))
+               fprintf(debug_file, "[dot last]...");
+       }
+    }
+    if (DEBUG(DIR)) {
+       fprintf(debug_file, "\n");
+    }
+
+    /*
+     * If there's no leading directory components or if the leading
+     * directory component is exactly `./', consult the cached contents
+     * of each of the directories on the search path.
+     */
+    if (!hasSlash || (cp - name == 2 && *name == '.')) {
+           /*
+            * We look through all the directories on the path seeking one which
+            * contains the final component of the given name.  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...
+            * 
+            * No matter what, we always look for the file in the current
+            * directory before anywhere else (unless we found the magic
+            * DOTLAST path, in which case we search it last) 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 (!hasLastDot &&
+                       (file = DirFindDot(hasSlash, name, cp)) != NULL) {
+                   Lst_Close(path);
+                   return file;
+           }
+
+           while ((ln = Lst_Next(path)) != NULL) {
+               p = (Path *)Lst_Datum(ln);
+               if (p == dotLast)
+                   continue;
+               if ((file = DirLookup(p, name, cp, hasSlash)) != NULL) {
+                   Lst_Close(path);
+                   return file;
+               }
+           }
+
+           if (hasLastDot &&
+                       (file = DirFindDot(hasSlash, name, cp)) != NULL) {
+                   Lst_Close(path);
+                   return file;
+           }
+    }
+    Lst_Close(path);
+
+    /*
+     * We didn't find the file on any directory in the search path.
+     * 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).
+     * [ This no longer applies: 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) {
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "   failed.\n");
+       }
+       misses += 1;
+       return NULL;
+    }
+
+    if (name[0] != '/') {
+       Boolean checkedDot = FALSE;
+
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "   Trying subdirectories...\n");
+       }
+
+       if (!hasLastDot) {
+               if (dot) {
+                       checkedDot = TRUE;
+                       if ((file = DirLookupSubdir(dot, name)) != NULL)
+                               return file;
+               }
+               if (cur && (file = DirLookupSubdir(cur, name)) != NULL)
+                       return file;
+       }
+
+       (void)Lst_Open(path);
+       while ((ln = Lst_Next(path)) != NULL) {
+           p = (Path *)Lst_Datum(ln);
+           if (p == dotLast)
+               continue;
+           if (p == dot) {
+                   if (checkedDot)
+                           continue;
+               checkedDot = TRUE;
+           }
+           if ((file = DirLookupSubdir(p, name)) != NULL) {
+               Lst_Close(path);
+               return file;
+           }
+       }
+       Lst_Close(path);
+
+       if (hasLastDot) {
+               if (dot && !checkedDot) {
+                       checkedDot = TRUE;
+                       if ((file = DirLookupSubdir(dot, name)) != NULL)
+                               return file;
+               }
+               if (cur && (file = DirLookupSubdir(cur, name)) != NULL)
+                       return file;
+       }
+
+       if (checkedDot) {
+           /*
+            * Already checked by the given name, since . was in the path,
+            * so no point in proceeding...
+            */
+           if (DEBUG(DIR)) {
+               fprintf(debug_file, "   Checked . already, returning NULL\n");
+           }
+           return NULL;
+       }
+
+    } else { /* name[0] == '/' */
+
+       /*
+        * For absolute names, compare directory path prefix against the
+        * the directory path of each member on the search path for an exact
+        * match. If we have an exact match on any member of the search path,
+        * use the cached contents of that member to lookup the final file
+        * component. If that lookup fails we can safely assume that the
+        * file does not exist at all.  This is signified by DirLookupAbs()
+        * returning an empty string.
+        */
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "   Trying exact path matches...\n");
+       }
+
+       if (!hasLastDot && cur && (file = DirLookupAbs(cur, name, cp)) != NULL)
+           return *file?file:NULL;
+
+       (void)Lst_Open(path);
+       while ((ln = Lst_Next(path)) != NULL) {
+           p = (Path *)Lst_Datum(ln);
+           if (p == dotLast)
+               continue;
+           if ((file = DirLookupAbs(p, name, cp)) != NULL) {
+               Lst_Close(path);
+               return *file?file:NULL;
+           }
+       }
+       Lst_Close(path);
+
+       if (hasLastDot && cur && (file = DirLookupAbs(cur, name, cp)) != NULL)
+           return *file?file: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';
+    (void)Dir_AddDir(path, name);
+    cp[-1] = '/';
+
+    bigmisses += 1;
+    ln = Lst_Last(path);
+    if (ln == NULL) {
+       return NULL;
+    } else {
+       p = (Path *)Lst_Datum(ln);
+    }
+
+    if (Hash_FindEntry(&p->files, cp) != NULL) {
+       return (bmake_strdup(name));
+    } else {
+       return NULL;
+    }
+#else /* !notdef */
+    if (DEBUG(DIR)) {
+       fprintf(debug_file, "   Looking for \"%s\" ...\n", name);
+    }
+
+    bigmisses += 1;
+    entry = Hash_FindEntry(&mtimes, name);
+    if (entry != NULL) {
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "   got it (in mtime cache)\n");
+       }
+       return(bmake_strdup(name));
+    } else if (stat(name, &stb) == 0) {
+       if (stb.st_mtime == 0)
+               stb.st_mtime = 1;
+       entry = Hash_CreateEntry(&mtimes, name, NULL);
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "   Caching %s for %s\n", Targ_FmtTime(stb.st_mtime),
+                   name);
+       }
+       Hash_SetTimeValue(entry, stb.st_mtime);
+       return (bmake_strdup(name));
+    } else {
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "   failed. Returning NULL\n");
+       }
+       return NULL;
+    }
+#endif /* notdef */
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_FindHereOrAbove  --
+ *     search for a path starting at a given directory and then working 
+ *     our way up towards the root.
+ *
+ * Input:
+ *     here            starting directory
+ *     search_path     the path we are looking for
+ *     result          the result of a successful search is placed here
+ *     rlen            the length of the result buffer 
+ *                     (typically MAXPATHLEN + 1)
+ *
+ * Results:
+ *     0 on failure, 1 on success [in which case the found path is put
+ *     in the result buffer].
+ *
+ * Side Effects:
+ *-----------------------------------------------------------------------
+ */
+int 
+Dir_FindHereOrAbove(char *here, char *search_path, char *result, int rlen) {
+
+       struct stat st;
+       char dirbase[MAXPATHLEN + 1], *db_end;
+        char try[MAXPATHLEN + 1], *try_end;
+
+       /* copy out our starting point */
+       snprintf(dirbase, sizeof(dirbase), "%s", here);
+       db_end = dirbase + strlen(dirbase);
+
+       /* loop until we determine a result */
+       while (1) {
+
+               /* try and stat(2) it ... */
+               snprintf(try, sizeof(try), "%s/%s", dirbase, search_path);
+               if (stat(try, &st) != -1) {
+                       /*
+                        * success!  if we found a file, chop off
+                        * the filename so we return a directory.
+                        */
+                       if ((st.st_mode & S_IFMT) != S_IFDIR) {
+                               try_end = try + strlen(try);
+                               while (try_end > try && *try_end != '/')
+                                       try_end--;
+                               if (try_end > try) 
+                                       *try_end = 0;   /* chop! */
+                       }
+
+                       /*
+                        * done!
+                        */
+                       snprintf(result, rlen, "%s", try);
+                       return(1);
+               }
+
+               /* 
+                * nope, we didn't find it.  if we used up dirbase we've
+                * reached the root and failed.
+                */
+               if (db_end == dirbase)
+                       break;          /* failed! */
+
+               /*
+                * truncate dirbase from the end to move up a dir
+                */
+               while (db_end > dirbase && *db_end != '/')
+                       db_end--;
+               *db_end = 0;            /* chop! */
+
+       } /* while (1) */
+
+       /*
+        * we failed... 
+        */
+       return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_MTime  --
+ *     Find the modification time of the file described by gn along the
+ *     search path dirSearchPath.
+ *
+ * Input:
+ *     gn              the file whose modification time is desired
+ *
+ * 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->type & OP_PHONY) {
+       gn->mtime = 0;
+       return 0;
+    } else if (gn->path == NULL) {
+       if (gn->type & OP_NOPATH)
+           fullName = NULL;
+       else {
+           fullName = Dir_FindFile(gn->name, Suff_FindPath(gn));
+           if (DEBUG(DIR))
+               fprintf(debug_file, "Found '%s' as '%s'\n",
+                       gn->name, fullName ? fullName : "(not found)" );
+       }
+    } else {
+       fullName = gn->path;
+    }
+
+    if (fullName == NULL) {
+       fullName = bmake_strdup(gn->name);
+    }
+
+    entry = Hash_FindEntry(&mtimes, fullName);
+    if (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 file system.
+        */
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "Using cached time %s for %s\n",
+                   Targ_FmtTime(Hash_GetTimeValue(entry)), fullName);
+       }
+       stb.st_mtime = Hash_GetTimeValue(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;
+       }
+    } else if (stb.st_mtime == 0) {
+       /*
+        * 0 handled specially by the code, if the time is really 0, return
+        * something else instead
+        */
+       stb.st_mtime = 1;
+    }
+       
+    if (fullName && gn->path == 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...
+ *
+ * Input:
+ *     path            the path to which the directory should be
+ *                     added
+ *     name            the name of the directory to add
+ *
+ * Results:
+ *     none
+ *
+ * Side Effects:
+ *     A structure is added to the list and the directory is
+ *     read and hashed.
+ *-----------------------------------------------------------------------
+ */
+Path *
+Dir_AddDir(Lst path, const char *name)
+{
+    LstNode       ln = NULL; /* node in case Path structure is found */
+    Path         *p = NULL;  /* pointer to new Path structure */
+    DIR          *d;         /* for reading directory */
+    struct dirent *dp;       /* entry in directory */
+
+    if (strcmp(name, ".DOTLAST") == 0) {
+       ln = Lst_Find(path, name, DirFindName);
+       if (ln != NULL)
+           return (Path *)Lst_Datum(ln);
+       else {
+           dotLast->refCount += 1;
+           (void)Lst_AtFront(path, dotLast);
+       }
+    }
+
+    if (path)
+       ln = Lst_Find(openDirectories, name, DirFindName);
+    if (ln != NULL) {
+       p = (Path *)Lst_Datum(ln);
+       if (path && Lst_Member(path, p) == NULL) {
+           p->refCount += 1;
+           (void)Lst_AtEnd(path, p);
+       }
+    } else {
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "Caching %s ...", name);
+       }
+
+       if ((d = opendir(name)) != NULL) {
+           p = bmake_malloc(sizeof(Path));
+           p->name = bmake_strdup(name);
+           p->hits = 0;
+           p->refCount = 1;
+           Hash_InitTable(&p->files, -1);
+
+           while ((dp = readdir(d)) != 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 */
+               (void)Hash_CreateEntry(&p->files, dp->d_name, NULL);
+           }
+           (void)closedir(d);
+           (void)Lst_AtEnd(openDirectories, p);
+           if (path != NULL)
+               (void)Lst_AtEnd(path, p);
+       }
+       if (DEBUG(DIR)) {
+           fprintf(debug_file, "done\n");
+       }
+    }
+    return p;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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 (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.
+ *
+ * Input:
+ *     flag            flag which should precede each directory
+ *     path            list of directories
+ *
+ * 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(const char *flag, Lst path)
+{
+    char         *str;   /* the string which will be returned */
+    char         *s1, *s2;/* the current directory preceded by 'flag' */
+    LstNode      ln;     /* the node of the current directory */
+    Path         *p;     /* the structure describing the current directory */
+
+    str = bmake_strdup("");
+
+    if (Lst_Open(path) == SUCCESS) {
+       while ((ln = Lst_Next(path)) != NULL) {
+           p = (Path *)Lst_Datum(ln);
+           s2 = str_concat(flag, p->name, 0);
+           str = str_concat(s1 = str, s2, STR_ADDSPACE);
+           free(s1);
+           free(s2);
+       }
+       Lst_Close(path);
+    }
+
+    return (str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Destroy --
+ *     Nuke a directory descriptor, if possible. Callback procedure
+ *     for the suffixes module when destroying a search path.
+ *
+ * Input:
+ *     pp              The directory descriptor to nuke
+ *
+ * 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, 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.
+ *
+ * Input:
+ *     path            Path to clear
+ *
+ * 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(p);
+    }
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Dir_Concat --
+ *     Concatenate two paths, adding the second to the end of the first.
+ *     Makes sure to avoid duplicates.
+ *
+ * Input:
+ *     path1           Dest
+ *     path2           Source
+ *
+ * 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, p) == NULL) {
+           p->refCount += 1;
+           (void)Lst_AtEnd(path1, p);
+       }
+    }
+}
+
+/********** DEBUG INFO **********/
+void
+Dir_PrintDirectories(void)
+{
+    LstNode    ln;
+    Path       *p;
+
+    fprintf(debug_file, "#*** Directory Cache:\n");
+    fprintf(debug_file, "# 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));
+    fprintf(debug_file, "# %-20s referenced\thits\n", "directory");
+    if (Lst_Open(openDirectories) == SUCCESS) {
+       while ((ln = Lst_Next(openDirectories)) != NULL) {
+           p = (Path *)Lst_Datum(ln);
+           fprintf(debug_file, "# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits);
+       }
+       Lst_Close(openDirectories);
+    }
+}
+
+static int
+DirPrintDir(void *p, void *dummy)
+{
+    fprintf(debug_file, "%s ", ((Path *)p)->name);
+    return (dummy ? 0 : 0);
+}
+
+void
+Dir_PrintPath(Lst path)
+{
+    Lst_ForEach(path, DirPrintDir, NULL);
+}
diff --git a/commands/bmake/dir.h b/commands/bmake/dir.h
new file mode 100644 (file)
index 0000000..d758371
--- /dev/null
@@ -0,0 +1,108 @@
+/*     $NetBSD: dir.h,v 1.14 2009/01/23 21:26:30 dsl Exp $     */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * 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. 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: @(#)dir.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * 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.
+ *
+ *     from: @(#)dir.h 8.1 (Berkeley) 6/6/93
+ */
+
+/* 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(const char *);
+void Dir_InitCur(const char *);
+void Dir_InitDot(void);
+void Dir_End(void);
+void Dir_SetPATH(void);
+Boolean Dir_HasWildcards(char *);
+void Dir_Expand(const char *, Lst, Lst);
+char *Dir_FindFile(const char *, Lst);
+int Dir_FindHereOrAbove(char *, char *, char *, int);
+int Dir_MTime(GNode *);
+Path *Dir_AddDir(Lst, const char *);
+char *Dir_MakeFlags(const 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 */
diff --git a/commands/bmake/for.c b/commands/bmake/for.c
new file mode 100644 (file)
index 0000000..62f483d
--- /dev/null
@@ -0,0 +1,471 @@
+/*     $NetBSD: for.c,v 1.46 2009/01/17 13:29:37 dsl Exp $     */
+
+/*
+ * Copyright (c) 1992, 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: for.c,v 1.46 2009/01/17 13:29:37 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)for.c      8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: for.c,v 1.46 2009/01/17 13:29:37 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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    <assert.h>
+#include    <ctype.h>
+
+#include    "make.h"
+#include    "hash.h"
+#include    "dir.h"
+#include    "buf.h"
+#include    "strlist.h"
+
+#define FOR_SUB_ESCAPE_CHAR  1
+#define FOR_SUB_ESCAPE_BRACE 2
+#define FOR_SUB_ESCAPE_PAREN 4
+
+/*
+ * 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.
+ *
+ * Note that any nested fors are just passed through; they get handled
+ * recursively in For_Eval when we're expanding the enclosing for in
+ * For_Run.
+ */
+
+static int       forLevel = 0;         /* Nesting level        */
+
+/*
+ * State of a for loop.
+ */
+typedef struct _For {
+    Buffer       buf;                  /* Body of loop         */
+    strlist_t     vars;                        /* Iteration variables  */
+    strlist_t     items;               /* Substitution items */
+    char          *parse_buf;
+    int           short_var;
+    int           sub_next;
+} For;
+
+static For        *accumFor;            /* Loop being accumulated */
+
+\f
+
+static char *
+make_str(const char *ptr, int len)
+{
+       char *new_ptr;
+
+       new_ptr = bmake_malloc(len + 1);
+       memcpy(new_ptr, ptr, len);
+       new_ptr[len] = 0;
+       return new_ptr;
+}
+
+static void
+For_Free(For *arg)
+{
+    Buf_Destroy(&arg->buf, TRUE);
+    strlist_clean(&arg->vars);
+    strlist_clean(&arg->items);
+    free(arg->parse_buf);
+
+    free(arg);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * For_Eval --
+ *     Evaluate the for loop in the passed line. The line
+ *     looks like this:
+ *         .for <variable> in <varlist>
+ *
+ * Input:
+ *     line            Line to parse
+ *
+ * Results:
+ *      0: Not a .for statement, parse the line
+ *     1: We found a for loop
+ *     -1: A .for statement with a bad syntax error, discard.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+For_Eval(char *line)
+{
+    For *new_for;
+    char *ptr = line, *sub;
+    int len;
+    int escapes;
+    unsigned char ch;
+
+    /* Skip the '.' and any following whitespace */
+    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])) {
+       if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) {
+           Parse_Error(PARSE_FATAL, "for-less endfor");
+           return -1;
+       }
+       return 0;
+    }
+    ptr += 3;
+
+    /*
+     * we found a for loop, and now we are going to parse it.
+     */
+
+    new_for = bmake_malloc(sizeof *new_for);
+    memset(new_for, 0, sizeof *new_for);
+
+    /* Grab the variables. Terminate on "in". */
+    for (;; ptr += len) {
+       while (*ptr && isspace((unsigned char) *ptr))
+           ptr++;
+       if (*ptr == '\0') {
+           Parse_Error(PARSE_FATAL, "missing `in' in for");
+           For_Free(new_for);
+           return -1;
+       }
+       for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++)
+           continue;
+       if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') {
+           ptr += 2;
+           break;
+       }
+       if (len == 1)
+           new_for->short_var = 1;
+       strlist_add_str(&new_for->vars, make_str(ptr, len), len);
+    }
+
+    if (strlist_num(&new_for->vars) == 0) {
+       Parse_Error(PARSE_FATAL, "no iteration variables in for");
+       For_Free(new_for);
+       return -1;
+    }
+
+    while (*ptr && isspace((unsigned char) *ptr))
+       ptr++;
+
+    /*
+     * Make a list with the remaining words
+     * The values are substituted as ${:U<value>...} so we must \ escape
+     * characters that break that syntax.
+     * Variables are fully expanded - so it is safe for escape $.
+     * We can't do the escapes here - because we don't know whether
+     * we are substuting into ${...} or $(...).
+     */
+    sub = Var_Subst(NULL, ptr, VAR_GLOBAL, FALSE);
+
+    for (ptr = sub;; ptr += len) {
+       while (*ptr && isspace((unsigned char)*ptr))
+           ptr++;
+       if (*ptr == 0)
+           break;
+       escapes = 0;
+       for (len = 0; (ch = ptr[len]) != 0 && !isspace(ch); len++) {
+           if (ch == ':' || ch == '$' || ch == '\\')
+               escapes |= FOR_SUB_ESCAPE_CHAR;
+           else if (ch == ')')
+               escapes |= FOR_SUB_ESCAPE_PAREN;
+           else if (ch == /*{*/ '}')
+               escapes |= FOR_SUB_ESCAPE_BRACE;
+       }
+       strlist_add_str(&new_for->items, make_str(ptr, len), escapes);
+    }
+
+    free(sub);
+
+    if (strlist_num(&new_for->items) % strlist_num(&new_for->vars)) {
+       Parse_Error(PARSE_FATAL,
+               "Wrong number of words in .for substitution list %d %d",
+               strlist_num(&new_for->items), strlist_num(&new_for->vars));
+       /*
+        * Return 'success' so that the body of the .for loop is accumulated.
+        * Remove all items so that the loop doesn't iterate.
+        */
+       strlist_clean(&new_for->items);
+    }
+
+    Buf_Init(&new_for->buf, 0);
+    accumFor = new_for;
+    forLevel = 1;
+    return 1;
+}
+
+/*
+ * Add another line to a .for loop.
+ * Returns 0 when the matching .endfor is reached.
+ */
+
+int
+For_Accum(char *line)
+{
+    char *ptr = line;
+
+    if (*ptr == '.') {
+
+       for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++)
+           continue;
+
+       if (strncmp(ptr, "endfor", 6) == 0 &&
+               (isspace((unsigned char) ptr[6]) || !ptr[6])) {
+           if (DEBUG(FOR))
+               (void)fprintf(debug_file, "For: end for %d\n", forLevel);
+           if (--forLevel <= 0)
+               return 0;
+       } else if (strncmp(ptr, "for", 3) == 0 &&
+                isspace((unsigned char) ptr[3])) {
+           forLevel++;
+           if (DEBUG(FOR))
+               (void)fprintf(debug_file, "For: new loop %d\n", forLevel);
+       }
+    }
+
+    Buf_AddBytes(&accumFor->buf, strlen(line), line);
+    Buf_AddByte(&accumFor->buf, '\n');
+    return 1;
+}
+
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * For_Run --
+ *     Run the for loop, imitating the actions of an include file
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static int
+for_var_len(const char *var)
+{
+    char ch, var_start, var_end;
+    int depth;
+    int len;
+
+    var_start = *var;
+    if (var_start == 0)
+       /* just escape the $ */
+       return 0;
+
+    if (var_start == '(')
+       var_end = ')';
+    else if (var_start == '{')
+       var_end = '}';
+    else
+       /* Single char variable */
+       return 1;
+
+    depth = 1;
+    for (len = 1; (ch = var[len++]) != 0;) {
+       if (ch == var_start)
+           depth++;
+       else if (ch == var_end && --depth == 0)
+           return len;
+    }
+
+    /* Variable end not found, escape the $ */
+    return 0;
+}
+
+static void
+for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech)
+{
+    const char *item = strlist_str(items, item_no);
+    int len;
+    char ch;
+
+    /* If there were no escapes, or the only escape is the other variable
+     * terminator, then just substitute the full string */
+    if (!(strlist_info(items, item_no) &
+           (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) {
+       Buf_AddBytes(cmds, strlen(item), item);
+       return;
+    }
+
+    /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */
+    while ((ch = *item++) != 0) {
+       if (ch == '$') {
+           len = for_var_len(item);
+           if (len != 0) {
+               Buf_AddBytes(cmds, len + 1, item - 1);
+               item += len;
+               continue;
+           }
+           Buf_AddByte(cmds, '\\');
+       } else if (ch == ':' || ch == '\\' || ch == ech)
+           Buf_AddByte(cmds, '\\');
+       Buf_AddByte(cmds, ch);
+    }
+}
+
+static char *
+For_Iterate(void *v_arg)
+{
+    For *arg = v_arg;
+    int i, len;
+    char *var;
+    char *cp;
+    char *cmd_cp;
+    char *body_end;
+    char ch;
+    Buffer cmds;
+
+    if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) {
+       /* No more iterations */
+       For_Free(arg);
+       return NULL;
+    }
+
+    free(arg->parse_buf);
+    arg->parse_buf = NULL;
+
+    /*
+     * Scan the for loop body and replace references to the loop variables
+     * with variable references that expand to the required text.
+     * Using variable expansions ensures that the .for loop can't generate
+     * syntax, and that the later parsing will still see a variable.
+     * We assume that the null variable will never be defined.
+     *
+     * The detection of substitions of the loop control variable is naive.
+     * Many of the modifiers use \ to escape $ (not $) so it is possible
+     * to contrive a makefile where an unwanted substitution happens.
+     */
+
+    cmd_cp = Buf_GetAll(&arg->buf, &len);
+    body_end = cmd_cp + len;
+    Buf_Init(&cmds, len + 256);
+    for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) {
+       char ech;
+       ch = *++cp;
+       if ((ch == '(' && (ech = ')')) || (ch == '{' && (ech = '}'))) {
+           cp++;
+           /* Check variable name against the .for loop variables */
+           STRLIST_FOREACH(var, &arg->vars, i) {
+               len = strlist_info(&arg->vars, i);
+               if (memcmp(cp, var, len) != 0)
+                   continue;
+               if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\')
+                   continue;
+               /* Found a variable match. Replace with :U<value> */
+               Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp);
+               Buf_AddBytes(&cmds, 2, ":U");
+               cp += len;
+               cmd_cp = cp;
+               for_substitute(&cmds, &arg->items, arg->sub_next + i, ech);
+               break;
+           }
+           continue;
+       }
+       if (ch == 0)
+           break;
+       /* Probably a single character name, ignore $$ and stupid ones. {*/
+       if (!arg->short_var || strchr("}):$", ch) != NULL) {
+           cp++;
+           continue;
+       }
+       STRLIST_FOREACH(var, &arg->vars, i) {
+           if (var[0] != ch || var[1] != 0)
+               continue;
+           /* Found a variable match. Replace with ${:U<value>} */
+           Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp);
+           Buf_AddBytes(&cmds, 3, "{:U");
+           cmd_cp = ++cp;
+           for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}');
+           Buf_AddBytes(&cmds, 1, "}");
+           break;
+       }
+    }
+    Buf_AddBytes(&cmds, body_end - cmd_cp, cmd_cp);
+
+    cp = Buf_Destroy(&cmds, FALSE);
+    if (DEBUG(FOR))
+       (void)fprintf(debug_file, "For: loop body:\n%s", cp);
+
+    arg->sub_next += strlist_num(&arg->vars);
+
+    arg->parse_buf = cp;
+    return cp;
+}
+
+void
+For_Run(int lineno)
+{ 
+    For *arg;
+  
+    arg = accumFor;
+    accumFor = NULL;
+
+    if (strlist_num(&arg->items) == 0) {
+        /* Nothing to expand - possibly due to an earlier syntax error. */
+        For_Free(arg);
+        return;
+    }
+    Parse_SetInput(NULL, lineno, -1, For_Iterate, arg);
+}
diff --git a/commands/bmake/hash.c b/commands/bmake/hash.c
new file mode 100644 (file)
index 0000000..a22e2f2
--- /dev/null
@@ -0,0 +1,463 @@
+/*     $NetBSD: hash.c,v 1.19 2009/01/24 10:59:09 dsl Exp $    */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: hash.c,v 1.19 2009/01/24 10:59:09 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)hash.c     8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: hash.c,v 1.19 2009/01/24 10:59:09 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/* 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 "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 3
+
+/*
+ *---------------------------------------------------------
+ *
+ * Hash_InitTable --
+ *
+ *     This routine just sets up the hash table.
+ *
+ * Input:
+ *     t               Structure to to hold table.
+ *     numBuckets      How many buckets to create for starters. This
+ *                     number is rounded up to a power of two.   If
+ *                     <= 0, a reasonable default is chosen. The
+ *                     table will grow in size later 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 = bmake_malloc(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(h);
+               }
+       }
+       free(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.
+ *
+ * Input:
+ *     t               Hash table to search.
+ *     key             A hash 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, const char *key)
+{
+       Hash_Entry *e;
+       unsigned h;
+       const 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.
+ *
+ * Input:
+ *     t               Hash table to search.
+ *     key             A hash key.
+ *     newPtr          Filled in with TRUE if new entry created,
+ *                     FALSE otherwise.
+ *
+ * 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, const char *key, Boolean *newPtr)
+{
+       Hash_Entry *e;
+       unsigned h;
+       const 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 = bmake_malloc(sizeof(*e) + keylen);
+       hp = &t->bucketPtr[h & t->mask];
+       e->next = *hp;
+       *hp = e;
+       Hash_SetValue(e, 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(p);
+                       t->numEntries--;
+                       return;
+               }
+       }
+       (void)write(2, "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.
+ *
+ * Input:
+ *     t               Table to be searched.
+ *     searchPtr       Area in which to keep state about search.
+ *
+ * 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.
+ *
+ * Input:
+ *     searchPtr       Area used to keep state about search.
+ *
+ * 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 nil if we are starting up.  If not nil, 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 = bmake_malloc(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(oldhp);
+}
diff --git a/commands/bmake/hash.h b/commands/bmake/hash.h
new file mode 100644 (file)
index 0000000..31d2ff1
--- /dev/null
@@ -0,0 +1,154 @@
+/*     $NetBSD: hash.h,v 1.10 2009/01/24 10:59:09 dsl Exp $    */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 The Regents of the University of California.
+ *
+ * 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. 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: @(#)hash.h        8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * 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.
+ *
+ *     from: @(#)hash.h        8.1 (Berkeley) 6/6/93
+ */
+
+/* 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. */
+    union {
+       void          *clientPtr;       /* Arbitrary pointer */
+       time_t        clientTime;       /* Arbitrary Time */
+    } clientInfo;
+    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)->clientInfo.clientPtr)
+#define Hash_GetTimeValue(h) ((h)->clientInfo.clientTime)
+
+/*
+ * Hash_SetValue(h, val);
+ *     Hash_Entry *h;
+ *     char *val;
+ */
+
+#define Hash_SetValue(h, val) ((h)->clientInfo.clientPtr = (val))
+#define Hash_SetTimeValue(h, val) ((h)->clientInfo.clientTime = (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 *, const char *);
+Hash_Entry *Hash_CreateEntry(Hash_Table *, const 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 */
diff --git a/commands/bmake/job.c b/commands/bmake/job.c
new file mode 100644 (file)
index 0000000..a8a9443
--- /dev/null
@@ -0,0 +1,2953 @@
+/*     $NetBSD: job.c,v 1.146 2009/06/26 01:26:32 sjg Exp $    */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: job.c,v 1.146 2009/06/26 01:26:32 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)job.c      8.2 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: job.c,v 1.146 2009/06/26 01:26:32 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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.
+ *
+ *     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_End                 Cleanup any memory used.
+ *
+ *     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/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#ifndef USE_SELECT
+#include <poll.h>
+#endif
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <utime.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+#include "trace.h"
+# define STATIC static
+
+/*
+ * 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 */
+#define JOB_TOKENS     "+EI+"      /* Token to requeue for each abort state */
+
+/*
+ * this tracks the number of tokens currently "out" to build jobs.
+ */
+int jobTokensRunning = 0;
+int not_parallel = 0;              /* set if .NOT_PARALLEL */
+
+/*
+ * 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 = NULL;
+                                   /* 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 */
+
+/*
+ * Descriptions for various shells.
+ *
+ * The build environment may set DEFSHELL_INDEX to one of
+ * DEFSHELL_INDEX_SH, DEFSHELL_INDEX_KSH, or DEFSHELL_INDEX_CSH, to
+ * select one of the prefedined shells as the default shell.
+ *
+ * Alternatively, the build environment may set DEFSHELL_CUSTOM to the
+ * name or the full path of a sh-compatible shell, which will be used as
+ * the default shell.
+ *
+ * ".SHELL" lines in Makefiles can choose the default shell from the
+ # set defined here, or add additional shells.
+ */
+
+#ifdef DEFSHELL_CUSTOM
+#define DEFSHELL_INDEX_CUSTOM 0
+#define DEFSHELL_INDEX_SH     1
+#define DEFSHELL_INDEX_KSH    2
+#define DEFSHELL_INDEX_CSH    3
+#else /* !DEFSHELL_CUSTOM */
+#define DEFSHELL_INDEX_SH     0
+#define DEFSHELL_INDEX_KSH    1
+#define DEFSHELL_INDEX_CSH    2
+#endif /* !DEFSHELL_CUSTOM */
+
+#ifndef DEFSHELL_INDEX
+#define DEFSHELL_INDEX 0       /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */
+#endif /* !DEFSHELL_INDEX */
+
+static Shell    shells[] = {
+#ifdef DEFSHELL_CUSTOM
+    /*
+     * An sh-compatible shell with a non-standard name.
+     *
+     * Keep this in sync with the "sh" description below, but avoid
+     * non-portable features that might not be supplied by all
+     * sh-compatible shells.
+     */
+{
+    DEFSHELL_CUSTOM,
+    FALSE, "", "", "", 0,
+    FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
+    "",
+    "",
+},
+#endif /* DEFSHELL_CUSTOM */
+    /*
+     * SH description. Echo control is also possible and, under
+     * sun UNIX anyway, one can even control error checking.
+     */
+{
+    "sh",
+    FALSE, "", "", "", 0,
+    FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
+#if defined(MAKE_NATIVE) && defined(__NetBSD__)
+    "q",
+#else
+    "",
+#endif
+    "",
+},
+    /*
+     * KSH description. 
+     */
+{
+    "ksh",
+    TRUE, "set +v", "set -v", "set +v", 6,
+    FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#',
+    "v",
+    "",
+},
+    /*
+     * 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\"\n", "", "'\\\n'", '#',
+    "v", "e",
+},
+    /*
+     * UNKNOWN.
+     */
+{
+    NULL,
+    FALSE, NULL, NULL, NULL, 0,
+    FALSE, NULL, NULL, NULL, NULL, 0,
+    NULL, NULL,
+}
+};
+static Shell *commandShell = &shells[DEFSHELL_INDEX]; /* this is the shell to
+                                                  * which we pass all
+                                                  * commands in the Makefile.
+                                                  * It is set by the
+                                                  * Job_ParseShell function */
+const char *shellPath = NULL,                    /* full pathname of
+                                                  * executable image */
+           *shellName = NULL;                    /* last component of shell */
+static const char *shellArgv = NULL;             /* Custom shell args */
+
+
+STATIC Job     *job_table;     /* The structures that describe them */
+STATIC Job     *job_table_end; /* job_table + maxJobs */
+static int     wantToken;      /* we want a token */
+static int lurking_children = 0;
+static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */
+
+/*
+ * Set of descriptors of pipes connected to
+ * the output channels of children
+ */
+static struct pollfd *fds = NULL;
+static Job **jobfds = NULL;
+static int nfds = 0;
+static void watchfd(Job *);
+static void clearfd(Job *);
+static int readyfd(Job *);
+
+STATIC GNode           *lastNode;      /* The node for which output was most recently
+                                * produced. */
+STATIC const char *targFmt;    /* Format string to use to head output from a
+                                * job when it's not the most-recent job heard
+                                * from */
+static char *targPrefix = NULL; /* What we print at the start of targFmt */
+static Job tokenWaitJob;       /* token wait pseudo-job */
+
+static Job childExitJob;       /* child exit pseudo-job */
+#define        CHILD_EXIT      "."
+#define        DO_JOB_RESUME   "R"
+
+#define TARG_FMT  "%s %s ---\n" /* Default format */
+#define MESSAGE(fp, gn) \
+       (void)fprintf(fp, targFmt, targPrefix, gn->name)
+
+static sigset_t caught_signals;        /* Set of signals we handle */
+#if defined(SYSV) || defined(__minix)
+#define KILLPG(pid, sig)       kill(-(pid), (sig))
+#else
+#define KILLPG(pid, sig)       killpg((pid), (sig))
+#endif
+
+static char *tmpdir;           /* directory name, always ending with "/" */
+
+static void JobChildSig(int);
+static void JobContinueSig(int);
+static Job *JobFindPid(int, int);
+static int JobPrintCommand(void *, void *);
+static int JobSaveCommand(void *, void *);
+static void JobClose(Job *);
+static void JobExec(Job *, char **);
+static void JobMakeArgv(Job *, char **);
+static int JobStart(GNode *, int);
+static char *JobOutput(Job *, char *, char *, int);
+static void JobDoOutput(Job *, Boolean);
+static Shell *JobMatchShell(const char *);
+static void JobInterrupt(int, int);
+static void JobRestartJobs(void);
+static void JobTokenAdd(void);
+static void JobSigLock(sigset_t *);
+static void JobSigUnlock(sigset_t *);
+static void JobSigReset(void);
+
+const char *malloc_options="A";
+
+static void
+job_table_dump(const char *where)
+{
+    Job *job;
+
+    fprintf(debug_file, "job table @ %s\n", where);
+    for (job = job_table; job < job_table_end; job++) {
+       fprintf(debug_file, "job %d, status %d, flags %d, pid %d\n",
+           (int)(job - job_table), job->job_state, job->flags, job->pid);
+    }
+}
+
+/*
+ * JobSigLock/JobSigUnlock
+ *
+ * Signal lock routines to get exclusive access. Currently used to
+ * protect `jobs' and `stoppedJobs' list manipulations.
+ */
+static void JobSigLock(sigset_t *omaskp)
+{
+       if (sigprocmask(SIG_BLOCK, &caught_signals, omaskp) != 0) {
+               Punt("JobSigLock: sigprocmask: %s", strerror(errno));
+               sigemptyset(omaskp);
+       }
+}
+
+static void JobSigUnlock(sigset_t *omaskp)
+{
+       (void)sigprocmask(SIG_SETMASK, omaskp, NULL);
+}
+
+static void
+JobCreatePipe(Job *job, int minfd)
+{
+    int i, fd;
+
+    if (pipe(job->jobPipe) == -1)
+       Punt("Cannot create pipe: %s", strerror(errno));
+
+    /* Set close-on-exec flag for both */
+    (void)fcntl(job->jobPipe[0], F_SETFD, 1);
+    (void)fcntl(job->jobPipe[1], F_SETFD, 1);
+
+    /*
+     * We mark the input side of the pipe non-blocking; we poll(2) the
+     * pipe when we're waiting for a job token, but we might lose the
+     * race for the token when a new one becomes available, so the read 
+     * from the pipe should not block.
+     */
+    fcntl(job->jobPipe[0], F_SETFL, 
+       fcntl(job->jobPipe[0], F_GETFL, 0) | O_NONBLOCK);
+
+    for (i = 0; i < 2; i++) {
+       /* Avoid using low numbered fds */
+       fd = fcntl(job->jobPipe[i], F_DUPFD, minfd);
+       if (fd != -1) {
+          close(job->jobPipe[i]);
+          job->jobPipe[i] = fd;
+       }
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobCondPassSig --
+ *     Pass a signal to a job
+ *
+ * Input:
+ *     signop          Signal to send it
+ *
+ * Side Effects:
+ *     None, except the job may bite it.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobCondPassSig(int signo)
+{
+    Job *job;
+
+    if (DEBUG(JOB)) {
+       (void)fprintf(debug_file, "JobCondPassSig(%d) called.\n", signo);
+    }
+
+    for (job = job_table; job < job_table_end; job++) {
+       if (job->job_state != JOB_ST_RUNNING)
+           continue;
+       if (DEBUG(JOB)) {
+           (void)fprintf(debug_file,
+                          "JobCondPassSig passing signal %d to child %d.\n",
+                          signo, job->pid);
+       }
+       KILLPG(job->pid, signo);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobChldSig --
+ *     SIGCHLD handler.
+ *
+ * Input:
+ *     signo           The signal number we've received
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     Sends a token on the child exit pipe to wake us up from
+ *     select()/poll().
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobChildSig(int signo __unused)
+{
+    write(childExitJob.outPipe, CHILD_EXIT, 1);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobContinueSig --
+ *     Resume all stopped jobs.
+ *
+ * Input:
+ *     signo           The signal number we've received
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     Jobs start running again.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobContinueSig(int signo __unused)
+{
+    /*
+     * Defer sending to SIGCONT to our stopped children until we return
+     * from the signal handler.
+     */
+    write(childExitJob.outPipe, DO_JOB_RESUME, 1);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobPassSig --
+ *     Pass a signal on to all jobs, then resend to ourselves.
+ *
+ * Input:
+ *     signo           The signal number we've received
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     We die by the same signal.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobPassSig_int(int signo)
+{
+    /* Run .INTERRUPT target then exit */
+    JobInterrupt(TRUE, signo);
+}
+
+static void
+JobPassSig_term(int signo)
+{
+    /* Dont run .INTERRUPT target then exit */
+    JobInterrupt(FALSE, signo);
+}
+
+static void
+JobPassSig_suspend(int signo)
+{
+    sigset_t nmask, omask;
+    struct sigaction act;
+
+    /* Suppress job started/continued messages */
+    make_suspended = 1;
+
+    /* Pass the signal onto every job */
+    JobCondPassSig(signo);
+
+    /*
+     * 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.
+     */
+    sigfillset(&nmask);
+    sigdelset(&nmask, signo);
+    (void)sigprocmask(SIG_SETMASK, &nmask, &omask);
+
+    act.sa_handler = SIG_DFL;
+    sigemptyset(&act.sa_mask);
+    act.sa_flags = 0;
+    (void)sigaction(signo, &act, NULL);
+
+    if (DEBUG(JOB)) {
+       (void)fprintf(debug_file,
+                      "JobPassSig passing signal %d to self.\n", signo);
+    }
+
+    (void)kill(getpid(), signo);
+
+    /*
+     * We've been continued.
+     *
+     * A whole host of signals continue to happen!
+     * SIGCHLD for any processes that actually suspended themselves.
+     * SIGCHLD for any processes that exited while we were alseep.
+     * The SIGCONT that actually caused us to wakeup.
+     *
+     * Since we defer passing the SIGCONT on to our children until
+     * the main processing loop, we can be sure that all the SIGCHLD
+     * events will have happened by then - and that the waitpid() will
+     * collect the child 'suspended' events.
+     * For correct sequencing we just need to ensure we process the
+     * waitpid() before passign on the SIGCONT.
+     *
+     * In any case nothing else is needed here.
+     */
+
+    /* Restore handler and signal mask */
+    act.sa_handler = JobPassSig_suspend;
+    (void)sigaction(signo, &act, NULL);
+    (void)sigprocmask(SIG_SETMASK, &omask, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobFindPid  --
+ *     Compare the pid of the job with the given pid and return 0 if they
+ *     are equal. This function is called from Job_CatchChildren
+ *     to find the job descriptor of the finished job.
+ *
+ * Input:
+ *     job             job to examine
+ *     pid             process id desired
+ *
+ * Results:
+ *     Job with matching pid
+ *
+ * Side Effects:
+ *     None
+ *-----------------------------------------------------------------------
+ */
+static Job *
+JobFindPid(int pid, int status)
+{
+    Job *job;
+
+    for (job = job_table; job < job_table_end; job++) {
+       if ((job->job_state == status) && job->pid == pid)
+           return job;
+    }
+    if (DEBUG(JOB))
+       job_table_dump("no pid");
+    return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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_End when all things are done.
+ *     This function is called from JobStart via Lst_ForEach.
+ *
+ * Input:
+ *     cmdp            command string to print
+ *     jobp            job for which to print it
+ *
+ * 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 */
+    const char    *cmdTemplate;            /* Template to use when printing the
+                                    * command */
+    char         *cmdStart;        /* Start of expanded command */
+    char         *escCmd = NULL;    /* Command with quotes/backticks escaped */
+    char         *cmd = (char *)cmdp;
+    Job           *job = (Job *)jobp;
+    char         *cp, *tmp;
+    int           i, j;
+
+    noSpecials = NoExecute(job->node);
+
+    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,
+                                               cmd));
+           return 1;
+       }
+       return 0;
+    }
+
+#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) {   \
+       (void)fprintf(debug_file, fmt, arg);    \
+    }                                          \
+   (void)fprintf(job->cmdFILE, fmt, arg);      \
+   (void)fflush(job->cmdFILE);
+
+    numCommands += 1;
+
+    cmdStart = cmd = Var_Subst(NULL, cmd, job->node, FALSE);
+
+    cmdTemplate = "%s\n";
+
+    /*
+     * Check for leading @' and -'s to control echoing and error checking.
+     */
+    while (*cmd == '@' || *cmd == '-' || (*cmd == '+')) {
+       switch (*cmd) {
+       case '@':
+           shutUp = DEBUG(LOUD) ? FALSE : TRUE;
+           break;
+       case '-':
+           errOff = TRUE;
+           break;
+       case '+':
+           if (noSpecials) {
+               /*
+                * We're not actually executing anything...
+                * but this one needs to be - use compat mode just for it.
+                */
+               CompatRunCommand(cmdp, job->node);
+               return 0;
+           }
+           break;
+       }
+       cmd++;
+    }
+
+    while (isspace((unsigned char) *cmd))
+       cmd++;
+
+    /*
+     * If the shell doesn't have error control the alternate echo'ing will
+     * be done (to avoid showing additional error checking code) 
+     * and this will need the characters '$ ` \ "' escaped
+     */
+
+    if (!commandShell->hasErrCtl) {
+       /* Worst that could happen is every char needs escaping. */
+       escCmd = bmake_malloc((strlen(cmd) * 2) + 1);
+       for (i = 0, j= 0; cmd[i] != '\0'; i++, j++) {
+               if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || 
+                       cmd[i] == '"')
+                       escCmd[j++] = '\\';
+               escCmd[j] = cmd[i];     
+       }
+       escCmd[j] = 0;
+    }
+
+    if (shutUp) {
+       if (!(job->flags & JOB_SILENT) && !noSpecials &&
+           commandShell->hasEchoCtl) {
+               DBPRINTF("%s\n", commandShell->echoOff);
+       } else {
+           if (commandShell->hasErrCtl)
+               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) {
+                       if (commandShell->hasEchoCtl) {
+                               DBPRINTF("%s\n", commandShell->echoOff);
+                       }
+                       DBPRINTF(commandShell->errCheck, escCmd);
+                       shutUp = TRUE;
+               } else {
+                       if (!shutUp) {
+                               DBPRINTF(commandShell->errCheck, escCmd);
+                       }
+               }
+               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;
+       }
+    } else {
+
+       /* 
+        * If errors are being checked and the shell doesn't have error control
+        * but does supply an errOut template, then setup commands to run
+        * through it.
+        */
+
+       if (!commandShell->hasErrCtl && commandShell->errOut && 
+           (*commandShell->errOut != '\0')) {
+               if (!(job->flags & JOB_SILENT) && !shutUp) {
+                       if (commandShell->hasEchoCtl) {
+                               DBPRINTF("%s\n", commandShell->echoOff);
+                       }
+                       DBPRINTF(commandShell->errCheck, escCmd);
+                       shutUp = TRUE;
+               }
+               /* If it's a comment line or blank, treat as an ignored error */
+               if ((escCmd[0] == commandShell->commentChar) ||
+                   (escCmd[0] == 0))
+                       cmdTemplate = commandShell->ignErr;
+               else
+                       cmdTemplate = commandShell->errOut;
+               errOff = FALSE;
+       }
+    }
+
+    if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 &&
+       (job->flags & JOB_TRACED) == 0) {
+           DBPRINTF("set -%s\n", "x");
+           job->flags |= JOB_TRACED;
+    }
+    
+    if ((cp = Check_Cwd_Cmd(cmd)) != NULL) {
+           DBPRINTF("test -d %s && ", cp);
+           DBPRINTF("cd %s\n", cp);
+    }
+
+    DBPRINTF(cmdTemplate, cmd);
+    free(cmdStart);
+    if (escCmd)
+        free(escCmd);
+    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 && commandShell->hasEchoCtl) {
+       DBPRINTF("%s\n", commandShell->echoOn);
+    }
+    if (cp != NULL) {
+           DBPRINTF("test -d %s && ", cp);
+           DBPRINTF("cd %s\n", Var_Value(".OBJDIR", VAR_GLOBAL, &tmp));
+    }
+    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 = 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)
+{
+    clearfd(job);
+    (void)close(job->outPipe);
+    job->outPipe = -1;
+
+    JobDoOutput(job, TRUE);
+    (void)close(job->inPipe);
+    job->inPipe = -1;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     job             job to finish
+ *     status          sub-why job went away
+ *
+ * Results:
+ *     None
+ *
+ * Side Effects:
+ *     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, return_job_token;
+
+    if (DEBUG(JOB)) {
+       fprintf(debug_file, "Jobfinish: %d [%s], status %d\n",
+                               job->pid, job->node->name, status);
+    }
+
+    if ((WIFEXITED(status) &&
+        (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) ||
+       WIFSIGNALED(status))
+    {
+       /*
+        * 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...
+        */
+       JobClose(job);
+       if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+          (void)fclose(job->cmdFILE);
+          job->cmdFILE = NULL;
+       }
+       done = TRUE;
+    } 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);
+    } else {
+       /*
+        * No need to close things down or anything.
+        */
+       done = FALSE;
+    }
+
+    if (done) {
+       if (WIFEXITED(status)) {
+           if (DEBUG(JOB)) {
+               (void)fprintf(debug_file, "Process %d [%s] exited.\n",
+                               job->pid, job->node->name);
+           }
+           if (WEXITSTATUS(status) != 0) {
+               if (job->node != lastNode) {
+                   MESSAGE(stdout, job->node);
+                   lastNode = job->node;
+               }
+               (void)printf("*** [%s] Error code %d%s\n",
+                               job->node->name,
+                              WEXITSTATUS(status),
+                              (job->flags & JOB_IGNERR) ? "(ignored)" : "");
+               if (job->flags & JOB_IGNERR)
+                   status = 0;
+           } else if (DEBUG(JOB)) {
+               if (job->node != lastNode) {
+                   MESSAGE(stdout, job->node);
+                   lastNode = job->node;
+               }
+               (void)printf("*** [%s] Completed successfully\n",
+                               job->node->name);
+           }
+       } else {
+           if (job->node != lastNode) {
+               MESSAGE(stdout, job->node);
+               lastNode = job->node;
+           }
+           (void)printf("*** [%s] Signal %d\n",
+                       job->node->name, WTERMSIG(status));
+       }
+       (void)fflush(stdout);
+    }
+
+    return_job_token = FALSE;
+
+    Trace_Log(JOBEND, job);
+    if (!(job->flags & JOB_SPECIAL)) {
+       if ((status != 0) ||
+               (aborting == ABORT_ERROR) ||
+               (aborting == ABORT_INTERRUPT))
+           return_job_token = TRUE;
+    }
+
+    if ((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,
+                           job->node);
+       }
+       job->node->made = MADE;
+       if (!(job->flags & JOB_SPECIAL))
+           return_job_token = TRUE;
+       Make_Update(job->node);
+       job->job_state = JOB_ST_FREE;
+    } else if (status != 0) {
+       errors += 1;
+       job->job_state = JOB_ST_FREE;
+    }
+
+    /*
+     * 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 (return_job_token)
+       Job_TokenReturn();
+
+    if (aborting == ABORT_ERROR && jobTokensRunning == 0) {
+       /*
+        * 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
+ *
+ * Input:
+ *     gn              the node of the file to touch
+ *     silent          TRUE if should not print message
+ *
+ * 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_USEBEFORE|OP_EXEC|OP_OPTIONAL|OP_PHONY)) {
+       /*
+        * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets
+        * and, as such, shouldn't really be created.
+        */
+       return;
+    }
+
+    if (!silent || NoExecute(gn)) {
+       (void)fprintf(stdout, "touch %s\n", gn->name);
+       (void)fflush(stdout);
+    }
+
+    if (NoExecute(gn)) {
+       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.
+ *
+ * Input:
+ *     gn              The target whose commands need verifying
+ *     abortProc       Function to abort with message
+ *
+ * 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 || Lst_IsEmpty(gn->children))) {
+       /*
+        * No commands. Look for .DEFAULT rule from which we might infer
+        * commands
+        */
+       if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) &&
+               (gn->type & OP_SPECIAL) == 0) {
+           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, 0);
+           if (p1)
+               free(p1);
+       } else if (Dir_MTime(gn) == 0 && (gn->type & OP_SPECIAL) == 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[] = ": don't know how to make";
+
+           if (gn->flags & FROM_DEPEND) {
+               fprintf(stdout, "%s: ignoring stale .depend for %s\n",
+                       progname, gn->name);
+               return TRUE;
+           }
+
+           if (gn->type & OP_OPTIONAL) {
+               (void)fprintf(stdout, "%s%s %s(ignored)\n", progname,
+                   msg, gn->name);
+               (void)fflush(stdout);
+           } else if (keepgoing) {
+               (void)fprintf(stdout, "%s%s %s(continuing)\n", progname,
+                   msg, gn->name);
+               (void)fflush(stdout);
+               return FALSE;
+           } else {
+               (*abortProc)("%s%s %s. Stop", progname, msg, gn->name);
+               return FALSE;
+           }
+       }
+    }
+    return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobExec --
+ *     Execute the shell for the given job. Called from JobStart
+ *
+ * Input:
+ *     job             Job to execute
+ *
+ * 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 */
+    sigset_t     mask;
+
+    job->flags &= ~JOB_TRACED;
+
+    if (DEBUG(JOB)) {
+       int       i;
+
+       (void)fprintf(debug_file, "Running %s %sly\n", job->node->name, "local");
+       (void)fprintf(debug_file, "\tCommand: ");
+       for (i = 0; argv[i] != NULL; i++) {
+           (void)fprintf(debug_file, "%s ", argv[i]);
+       }
+       (void)fprintf(debug_file, "\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_SILENT)) {
+       MESSAGE(stdout, job->node);
+       lastNode = job->node;
+    }
+
+    /* No interruptions until this job is on the `jobs' list */
+    JobSigLock(&mask);
+
+    /* Pre-emptively mark job running, pid still zero though */
+    job->job_state = JOB_ST_RUNNING;
+
+#if defined(__minix)
+    cpid = fork();
+#else
+    cpid = vfork();
+#endif
+    if (cpid == -1)
+       Punt("Cannot vfork: %s", strerror(errno));
+
+    if (cpid == 0) {
+       /* Child */
+       sigset_t tmask;
+
+       /*
+        * Reset all signal handlers; this is necessary because we also
+        * need to unblock signals before we exec(2).
+        */
+       JobSigReset();
+
+       /* Now unblock signals */
+       sigemptyset(&tmask);
+       JobSigUnlock(&tmask);
+
+       /*
+        * 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) {
+           execError("dup2", "job->cmdFILE");
+           _exit(1);
+       }
+       (void)fcntl(0, F_SETFD, 0);
+       (void)lseek(0, (off_t)0, SEEK_SET);
+
+       if (job->node->type & OP_MAKE) {
+               /*
+                * Pass job token pipe to submakes.
+                */
+               fcntl(tokenWaitJob.inPipe, F_SETFD, 0);
+               fcntl(tokenWaitJob.outPipe, F_SETFD, 0);                
+       }
+       
+       /*
+        * Set up the child's output to be routed through the pipe
+        * we've created for it.
+        */
+       if (dup2(job->outPipe, 1) == -1) {
+           execError("dup2", "job->outPipe");
+           _exit(1);
+       }
+       /*
+        * 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) {
+           execError("dup2", "1, 2");
+           _exit(1);
+       }
+
+       /*
+        * 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) || defined(__minix)
+       /* XXX: dsl - I'm sure this should be setpgrp()... */
+       (void)setsid();
+#else
+       (void)setpgid(0, getpid());
+#endif
+
+       Var_ExportVars();
+
+       (void)execv(shellPath, argv);
+       execError("exec", shellPath);
+       _exit(1);
+    }
+
+    /* Parent, continuing after the child exec */
+    job->pid = cpid;
+
+    Trace_Log(JOBSTART, job);
+
+    /*
+     * Set the current position in the buffer to the beginning
+     * and mark another stream to watch in the outputs mask
+     */
+    job->curPos = 0;
+
+    watchfd(job);
+
+    if (job->cmdFILE != NULL && job->cmdFILE != stdout) {
+       (void)fclose(job->cmdFILE);
+       job->cmdFILE = NULL;
+    }
+
+    /*
+     * Now the job is actually running, add it to the table.
+     */
+    if (DEBUG(JOB)) {
+       fprintf(debug_file, "JobExec(%s): pid %d added to jobs table\n",
+               job->node->name, job->pid);
+       job_table_dump("job started");
+    }
+    JobSigUnlock(&mask);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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] = UNCONST(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)snprintf(args, sizeof(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] = UNCONST(commandShell->exit);
+           argc++;
+       }
+       if (!(job->flags & JOB_SILENT) && commandShell->echo) {
+           argv[argc] = UNCONST(commandShell->echo);
+           argc++;
+       }
+    }
+    argv[argc] = NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobStart  --
+ *     Start a target-creation process going for the target described
+ *     by the graph node gn.
+ *
+ * Input:
+ *     gn              target to create
+ *     flags           flags for the job to override normal ones.
+ *                     e.g. JOB_SPECIAL or JOB_IGNDOTS
+ *     previous        The previous Job structure for this node, if any.
+ *
+ * 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.
+ *
+ * NB: I'm fairly sure that this code is never called with JOB_SPECIAL set
+ *     JOB_IGNDOTS is never set (dsl)
+ *     Also the return value is ignored by everyone.
+ *-----------------------------------------------------------------------
+ */
+static int
+JobStart(GNode *gn, int flags)
+{
+    Job                  *job;       /* new job descriptor */
+    char         *argv[10];  /* Argument vector to shell */
+    Boolean      cmdsOK;     /* true if the nodes commands were all right */
+    Boolean      noExec;     /* Set true if we decide not to run the job */
+    int                  tfd;        /* File descriptor to the temp file */
+
+    for (job = job_table; job < job_table_end; job++) {
+       if (job->job_state == JOB_ST_FREE)
+           break;
+    }
+    if (job >= job_table_end)
+       Punt("JobStart no job slots vacant");
+
+    memset(job, 0, sizeof *job);
+    job->job_state = JOB_ST_SETUP;
+    if (gn->type & OP_SPECIAL)
+       flags |= JOB_SPECIAL;
+
+    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
+     */
+    cmdsOK = Job_CheckCommands(gn, Error);
+
+    job->inPollfd = NULL;
+    /*
+     * 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) && !(noRecursiveExecute)) ||
+           (!noExecute && !touchFlag)) {
+       /*
+        * tfile is the name of a file into which all shell commands are
+        * put. It is removed before the child shell is executed, unless
+        * DEBUG(SCRIPT) is set.
+        */
+       char *tfile;
+       sigset_t mask;
+       /*
+        * We're serious here, but if the commands were bogus, we're
+        * also dead...
+        */
+       if (!cmdsOK) {
+           DieHorribly();
+       }
+
+       JobSigLock(&mask);
+       tfile = bmake_malloc(strlen(tmpdir) + sizeof(TMPPAT));
+       strcpy(tfile, tmpdir);
+       strcat(tfile, TMPPAT);
+       if ((tfd = mkstemp(tfile)) == -1)
+           Punt("Could not create temporary file %s", strerror(errno));
+       if (!DEBUG(SCRIPT))
+               (void)eunlink(tfile);
+       JobSigUnlock(&mask);
+
+       job->cmdFILE = fdopen(tfd, "w+");
+       if (job->cmdFILE == NULL) {
+           Punt("Could not fdopen %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;
+
+       /*
+        * We can do all the commands at once. hooray for sanity
+        */
+       numCommands = 0;
+       Lst_ForEach(gn->commands, JobPrintCommand, 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;
+       }
+
+       free(tfile);
+    } else if (NoExecute(gn)) {
+       /*
+        * 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, 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;
+    }
+    /* Just in case it isn't already... */
+    (void)fflush(job->cmdFILE);
+
+    /*
+     * If we're not supposed to execute a shell, don't.
+     */
+    if (noExec) {
+       if (!(job->flags & JOB_SPECIAL))
+           Job_TokenReturn();
+       /*
+        * Unlink and close the command file if we opened one
+        */
+       if (job->cmdFILE != stdout) {
+           if (job->cmdFILE != NULL) {
+               (void)fclose(job->cmdFILE);
+               job->cmdFILE = NULL;
+           }
+       }
+
+       /*
+        * 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 && aborting == 0) {
+           if (job->tailCmds != NULL) {
+               Lst_ForEachFrom(job->node->commands, job->tailCmds,
+                               JobSaveCommand,
+                              job->node);
+           }
+           job->node->made = MADE;
+           Make_Update(job->node);
+       }
+       job->job_state = JOB_ST_FREE;
+       return cmdsOK ? JOB_FINISHED : JOB_ERROR;
+    }
+
+    /*
+     * Set up the control arguments to the shell. This is based on the flags
+     * set earlier for this job.
+     */
+    JobMakeArgv(job, argv);
+
+    /* Create the pipe by which we'll get the shell's output.  */
+    JobCreatePipe(job, 3);
+
+    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 (!beSilent && 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.
+ *
+ * Input:
+ *     job             the job whose output needs printing
+ *     finish          TRUE if this is the last time we'll be called
+ *                     for this job
+ *
+ * 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 */
+
+    /*
+     * 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) {
+       if (errno == EAGAIN)
+           return;
+       if (DEBUG(JOB)) {
+           perror("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 (!beSilent && 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;
+    }
+}
+
+static void
+JobRun(GNode *targ)
+{
+#ifdef notyet
+    /*
+     * Unfortunately it is too complicated to run .BEGIN, .END,
+     * and .INTERRUPT job in the parallel job module. This has
+     * the nice side effect that it avoids a lot of other problems.
+     */
+    Lst lst = Lst_Init(FALSE);
+    Lst_AtEnd(lst, targ);
+    (void)Make_Run(lst);
+    Lst_Destroy(lst, NULL);
+    JobStart(targ, JOB_SPECIAL);
+    while (jobTokensRunning) {
+       Job_CatchOutput();
+    }
+#else
+    Compat_Make(targ, targ);
+    if (targ->made == ERROR) {
+       PrintOnError("\n\nStop.");
+       exit(1);
+    }
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_CatchChildren --
+ *     Handle the exit of a child. Called from Make_Make.
+ *
+ * Input:
+ *     block           TRUE if should block on the wait
+ *
+ * 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.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Job_CatchChildren(void)
+{
+    int          pid;          /* pid of dead child */
+    Job                  *job;         /* job descriptor for dead child */
+    int                  status;       /* Exit/termination status */
+
+    /*
+     * Don't even bother if we know there's no one around.
+     */
+    if (jobTokensRunning == 0)
+       return;
+
+    while ((pid = waitpid((pid_t) -1, &status, WNOHANG | WUNTRACED)) > 0) {
+       if (DEBUG(JOB)) {
+           (void)fprintf(debug_file, "Process %d exited/stopped status %x.\n", pid,
+             status);
+       }
+
+       job = JobFindPid(pid, JOB_ST_RUNNING);
+       if (job == NULL) {
+           if (!lurking_children)
+               Error("Child (%d) status %x not in table?", pid, status);
+           continue;
+       }
+       if (WIFSTOPPED(status)) {
+           if (DEBUG(JOB)) {
+               (void)fprintf(debug_file, "Process %d (%s) stopped.\n",
+                               job->pid, job->node->name);
+           }
+           if (!make_suspended) {
+                   switch (WSTOPSIG(status)) {
+                   case SIGTSTP:
+                       (void)printf("*** [%s] Suspended\n", job->node->name);
+                       break;
+                   case SIGSTOP:
+                       (void)printf("*** [%s] Stopped\n", job->node->name);
+                       break;
+                   default:
+                       (void)printf("*** [%s] Stopped -- signal %d\n",
+                           job->node->name, WSTOPSIG(status));
+                   }
+                   job->job_suspended = 1;
+           }
+           (void)fflush(stdout);
+           continue;
+       }
+
+       job->job_state = JOB_ST_FINISHED;
+       job->exit_status = status;
+
+       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 nready;
+    Job *job;
+    int i;
+
+    (void)fflush(stdout);
+
+    /* The first fd in the list is the job token pipe */
+    nready = poll(fds + 1 - wantToken, nfds - 1 + wantToken, POLL_MSEC);
+
+    if (nready < 0 || readyfd(&childExitJob)) {
+       char token = 0;
+       nready -= 1;
+       (void)read(childExitJob.inPipe, &token, 1);
+       if (token == DO_JOB_RESUME[0])
+           /* Complete relay requested from our SIGCONT handler */
+           JobRestartJobs();
+       Job_CatchChildren();
+    }
+
+    if (nready <= 0)
+       return;
+
+    if (wantToken && readyfd(&tokenWaitJob))
+       nready--;
+
+    for (i = 2; i < nfds; i++) {
+       if (!fds[i].revents)
+           continue;
+       job = jobfds[i];
+       if (job->job_state != JOB_ST_RUNNING)
+           continue;
+       JobDoOutput(job, FALSE);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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);
+}
+
+void
+Shell_Init(void)
+{
+    if (shellPath == NULL) {
+       /*
+        * We are using the default shell, which may be an absolute
+        * path if DEFSHELL_CUSTOM is defined.
+        */
+       shellName = commandShell->name;
+#ifdef DEFSHELL_CUSTOM
+       if (*shellName == '/') {
+           shellPath = shellName;
+           shellName = strrchr(shellPath, '/');
+           shellName++;
+       } else
+#endif
+       shellPath = str_concat(_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH);
+    }
+    if (commandShell->exit == NULL) {
+       commandShell->exit = "";
+    }
+    if (commandShell->echo == NULL) {
+       commandShell->echo = "";
+    }
+}
+
+/*-
+ * Returns the string literal that is used in the current command shell
+ * to produce a newline character.
+ */
+const char *
+Shell_GetNewline(void)
+{
+
+    return commandShell->newline;
+}
+
+void
+Job_SetPrefix(void)
+{
+    
+    if (targPrefix) {
+       free(targPrefix);
+    } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) {
+       Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL, 0);
+    }
+
+    targPrefix = Var_Subst(NULL, "${" MAKE_JOB_PREFIX "}", VAR_GLOBAL, 0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_Init --
+ *     Initialize the process module
+ *
+ * Input:
+ *
+ * Results:
+ *     none
+ *
+ * Side Effects:
+ *     lists and counters are initialized
+ *-----------------------------------------------------------------------
+ */
+void
+Job_Init(void)
+{
+    GNode         *begin;     /* node for commands to do at the very start */
+    const char    *p;
+    size_t        len;
+
+    /* Allocate space for all the job info */
+    job_table = bmake_malloc(maxJobs * sizeof *job_table);
+    memset(job_table, 0, maxJobs * sizeof *job_table);
+    job_table_end = job_table + maxJobs;
+    wantToken =        0;
+
+    aborting =           0;
+    errors =     0;
+
+    lastNode =   NULL;
+
+    /* set tmpdir, and ensure that it ends with "/" */
+    p = getenv("TMPDIR");
+    if (p == NULL || *p == '\0') {
+       p = _PATH_TMP;
+    }
+    len = strlen(p);
+    tmpdir = bmake_malloc(len + 2);
+    strcpy(tmpdir, p);
+    if (tmpdir[len - 1] != '/') {
+       strcat(tmpdir, "/");
+    }
+
+    if (maxJobs == 1) {
+       /*
+        * If only one job can run at a time, there's no need for a banner,
+        * is there?
+        */
+       targFmt = "";
+    } else {
+       targFmt = TARG_FMT;
+    }
+
+    /*
+     * There is a non-zero chance that we already have children.
+     * eg after 'make -f- <<EOF'
+     * Since their termination causes a 'Child (pid) not in table' message,
+     * Collect the status of any that are already dead, and suppress the
+     * error message if there are any undead ones.
+     */
+    for (;;) {
+       int rval, status;
+       rval = waitpid((pid_t) -1, &status, WNOHANG);
+       if (rval > 0)
+           continue;
+       if (rval == 0)
+           lurking_children = 1;
+       break;
+    }
+
+    Shell_Init();
+
+    JobCreatePipe(&childExitJob, 3);
+
+    /* We can only need to wait for tokens, children and output from each job */
+    fds = bmake_malloc(sizeof (*fds) * (2 + maxJobs));
+    jobfds = bmake_malloc(sizeof (*jobfds) * (2 + maxJobs));
+
+    /* These are permanent entries and take slots 0 and 1 */
+    watchfd(&tokenWaitJob);
+    watchfd(&childExitJob);
+
+    sigemptyset(&caught_signals);
+    /*
+     * Install a SIGCHLD handler.
+     */
+    (void)signal(SIGCHLD, JobChildSig);
+    sigaddset(&caught_signals, SIGCHLD);
+
+#define ADDSIG(s,h)                            \
+    if (signal(s, SIG_IGN) != SIG_IGN) {       \
+       sigaddset(&caught_signals, s);          \
+       (void)signal(s, h);                     \
+    }
+
+    /*
+     * Catch the four signals that POSIX specifies if they aren't ignored.
+     * JobPassSig will take care of calling JobInterrupt if appropriate.
+     */
+    ADDSIG(SIGINT, JobPassSig_int)
+    ADDSIG(SIGHUP, JobPassSig_term)
+    ADDSIG(SIGTERM, JobPassSig_term)
+    ADDSIG(SIGQUIT, JobPassSig_term)
+
+    /*
+     * 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)
+     */
+    ADDSIG(SIGTSTP, JobPassSig_suspend)
+    ADDSIG(SIGTTOU, JobPassSig_suspend)
+    ADDSIG(SIGTTIN, JobPassSig_suspend)
+    ADDSIG(SIGWINCH, JobCondPassSig)
+    ADDSIG(SIGCONT, JobContinueSig)
+#undef ADDSIG
+
+    begin = Targ_FindNode(".BEGIN", TARG_NOCREATE);
+
+    if (begin != NULL) {
+       JobRun(begin);
+       if (begin->made == ERROR) {
+           PrintOnError("\n\nStop.");
+           exit(1);
+       }
+    }
+    postCommands = Targ_FindNode(".END", TARG_CREATE);
+}
+
+static void JobSigReset(void)
+{
+#define DELSIG(s)                                      \
+    if (sigismember(&caught_signals, s)) {             \
+       (void)signal(s, SIG_DFL);                       \
+    }
+
+    DELSIG(SIGINT)
+    DELSIG(SIGHUP)
+    DELSIG(SIGQUIT)
+    DELSIG(SIGTERM)
+    DELSIG(SIGTSTP)
+    DELSIG(SIGTTOU)
+    DELSIG(SIGTTIN)
+    DELSIG(SIGWINCH)
+    DELSIG(SIGCONT)
+#undef DELSIG
+    (void)signal(SIGCHLD, SIG_DFL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobMatchShell --
+ *     Find a shell in 'shells' given its name.
+ *
+ * Results:
+ *     A pointer to the Shell structure.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Shell *
+JobMatchShell(const char *name)
+{
+    Shell      *sh;
+
+    for (sh = shells; sh->name != NULL; sh++) {
+       if (strcmp(name, sh->name) == 0)
+               return (sh);
+    }
+    return NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_ParseShell --
+ *     Parse a shell specification and set up commandShell, shellPath
+ *     and shellName appropriately.
+ *
+ * Input:
+ *     line            The shell spec
+ *
+ * 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.
+ *         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
+ *         newline         String literal to represent a newline char
+ *         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;
+    char       **argv;
+    int                argc;
+    char       *path;
+    Shell      newShell;
+    Boolean    fullSpec = FALSE;
+    Shell      *sh;
+
+    while (isspace((unsigned char)*line)) {
+       line++;
+    }
+
+    if (shellArgv)
+       free(UNCONST(shellArgv));
+
+    memset(&newShell, 0, sizeof(newShell));
+
+    /*
+     * Parse the specification by keyword
+     */
+    words = brk_string(line, &argc, TRUE, &path);
+    if (words == NULL) {
+       Error("Unterminated quoted string [%s]", line);
+       return FAILURE;
+    }
+    shellArgv = path;
+
+    for (path = NULL, argv = words; 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, "newline=", 8) == 0) {
+                   newShell.newline = &argv[0][8];
+               } 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 if (strncmp(*argv, "errout=", 7) == 0) {
+                   newShell.errOut = &argv[0][7];
+               } else if (strncmp(*argv, "comment=", 8) == 0) {
+                   newShell.commentChar = argv[0][8];
+               } else {
+                   Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"",
+                               *argv);
+                   free(words);
+                   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");
+           free(words);
+           return(FAILURE);
+       } else {
+           if ((sh = JobMatchShell(newShell.name)) == NULL) {
+                   Parse_Error(PARSE_WARNING, "%s: No matching shell",
+                               newShell.name);
+                   free(words);
+                   return(FAILURE);
+           }
+           commandShell = sh;
+           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 = UNCONST(shellPath);
+       } else {
+           path += 1;
+       }
+       if (newShell.name != NULL) {
+           shellName = newShell.name;
+       } else {
+           shellName = path;
+       }
+       if (!fullSpec) {
+           if ((sh = JobMatchShell(shellName)) == NULL) {
+                   Parse_Error(PARSE_WARNING, "%s: No matching shell",
+                               shellName);
+                   free(words);
+                   return(FAILURE);
+           }
+           commandShell = sh;
+       } else {
+           commandShell = bmake_malloc(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";
+       }
+    }
+
+    /*
+     * Do not free up the words themselves, since they might be in use by the
+     * shell specification.
+     */
+    free(words);
+    return SUCCESS;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobInterrupt --
+ *     Handle the receipt of an interrupt.
+ *
+ * Input:
+ *     runINTERRUPT    Non-zero if commands for the .INTERRUPT target
+ *                     should be executed
+ *     signo           signal received
+ *
+ * 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)
+{
+    Job                *job;           /* job descriptor in that element */
+    GNode      *interrupt;     /* the node describing the .INTERRUPT target */
+    sigset_t   mask;
+    GNode      *gn;
+
+    aborting = ABORT_INTERRUPT;
+
+    JobSigLock(&mask);
+
+    for (job = job_table; job < job_table_end; job++) {
+       if (job->job_state != JOB_ST_RUNNING)
+           continue;
+
+       gn = job->node;
+
+       if ((gn->type & (OP_JOIN|OP_PHONY)) == 0 && !Targ_Precious(gn)) {
+           char *file = (gn->path == NULL ? gn->name : gn->path);
+           if (!noExecute && eunlink(file) != -1) {
+               Error("*** %s removed", file);
+           }
+       }
+       if (job->pid) {
+           if (DEBUG(JOB)) {
+               (void)fprintf(debug_file,
+                          "JobInterrupt passing signal %d to child %d.\n",
+                          signo, job->pid);
+           }
+           KILLPG(job->pid, signo);
+       }
+    }
+
+    JobSigUnlock(&mask);
+
+    if (runINTERRUPT && !touchFlag) {
+       interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE);
+       if (interrupt != NULL) {
+           ignoreErrors = FALSE;
+           JobRun(interrupt);
+       }
+    }
+    Trace_Log(MAKEINTR, 0);
+    exit(signo);
+}
+
+/*
+ *-----------------------------------------------------------------------
+ * Job_Finish --
+ *     Do final processing such as the running of the commands
+ *     attached to the .END target.
+ *
+ * Results:
+ *     Number of errors reported.
+ *
+ * Side Effects:
+ *     None.
+ *-----------------------------------------------------------------------
+ */
+int
+Job_Finish(void)
+{
+    if (postCommands != NULL &&
+       (!Lst_IsEmpty(postCommands->commands) ||
+        !Lst_IsEmpty(postCommands->children))) {
+       if (errors) {
+           Error("Errors reported so .END ignored");
+       } else {
+           JobRun(postCommands);
+       }
+    }
+    return(errors);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_End --
+ *     Cleanup any memory used by the jobs module
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     Memory is freed
+ *-----------------------------------------------------------------------
+ */
+void
+Job_End(void)
+{
+#ifdef CLEANUP
+    if (shellArgv)
+       free(shellArgv);
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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 (jobTokensRunning != 0) {
+       Job_CatchOutput();
+    }
+    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)
+{
+    Job                *job;   /* the job descriptor in that element */
+    int                foo;
+
+    aborting = ABORT_ERROR;
+
+    if (jobTokensRunning) {
+       for (job = job_table; job < job_table_end; job++) {
+           if (job->job_state != JOB_ST_RUNNING)
+               continue;
+           /*
+            * kill the child process with increasingly drastic signals to make
+            * darn sure it's dead.
+            */
+           KILLPG(job->pid, SIGINT);
+           KILLPG(job->pid, SIGKILL);
+       }
+    }
+
+    /*
+     * Catch as many children as want to report in at first, then give up
+     */
+    while (waitpid((pid_t) -1, &foo, WNOHANG) > 0)
+       continue;
+}
+
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * JobRestartJobs --
+ *     Tries to restart stopped jobs if there are slots available.
+ *     Called in process context in response to a SIGCONT.
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     Resumes jobs.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+JobRestartJobs(void)
+{
+    Job *job;
+
+    for (job = job_table; job < job_table_end; job++) {
+       if (job->job_state == JOB_ST_RUNNING &&
+               (make_suspended || job->job_suspended)) {
+           if (DEBUG(JOB)) {
+               (void)fprintf(debug_file, "Restarting stopped job pid %d.\n",
+                       job->pid);
+           }
+           if (job->job_suspended) {
+                   (void)printf("*** [%s] Continued\n", job->node->name);
+                   (void)fflush(stdout);
+           }
+           job->job_suspended = 0;
+           if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) {
+               fprintf(debug_file, "Failed to send SIGCONT to %d\n", job->pid);
+           }
+       }
+       if (job->job_state == JOB_ST_FINISHED)
+           /* Job exit deferred after calling waitpid() in a signal handler */
+           JobFinish(job, job->exit_status);
+    }
+    make_suspended = 0;
+}
+
+static void
+watchfd(Job *job)
+{
+    if (job->inPollfd != NULL)
+       Punt("Watching watched job");
+
+    fds[nfds].fd = job->inPipe;
+    fds[nfds].events = POLLIN;
+    jobfds[nfds] = job;
+    job->inPollfd = &fds[nfds];
+    nfds++;
+}
+
+static void
+clearfd(Job *job)
+{
+    int i;
+    if (job->inPollfd == NULL)
+       Punt("Unwatching unwatched job");
+    i = job->inPollfd - fds;
+    nfds--;
+    /*
+     * Move last job in table into hole made by dead job.
+     */
+    if (nfds != i) {
+       fds[i] = fds[nfds];
+       jobfds[i] = jobfds[nfds];
+       jobfds[i]->inPollfd = &fds[i];
+    }
+    job->inPollfd = NULL;
+}
+
+static int
+readyfd(Job *job)
+{
+    if (job->inPollfd == NULL)
+       Punt("Polling unwatched job");
+    return (job->inPollfd->revents & POLLIN) != 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * JobTokenAdd --
+ *     Put a token into the job pipe so that some make process can start
+ *     another job.
+ *
+ * Side Effects:
+ *     Allows more build jobs to be spawned somewhere.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static void
+JobTokenAdd(void)
+{
+    char tok = JOB_TOKENS[aborting], tok1;
+
+    /* If we are depositing an error token flush everything else */
+    while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1)
+       continue;
+
+    if (DEBUG(JOB))
+       fprintf(debug_file, "(%d) aborting %d, deposit token %c\n",
+           getpid(), aborting, JOB_TOKENS[aborting]);
+    write(tokenWaitJob.outPipe, &tok, 1);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_ServerStartTokenAdd --
+ *     Prep the job token pipe in the root make process.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Job_ServerStart(int max_tokens, int jp_0, int jp_1)
+{
+    int i;
+    char jobarg[64];
+    
+    if (jp_0 >= 0 && jp_1 >= 0) {
+       /* Pipe passed in from parent */
+       tokenWaitJob.inPipe = jp_0;
+       tokenWaitJob.outPipe = jp_1;
+       return;
+    }
+
+    JobCreatePipe(&tokenWaitJob, 15);
+
+    snprintf(jobarg, sizeof(jobarg), "%d,%d",
+           tokenWaitJob.inPipe, tokenWaitJob.outPipe);
+
+    Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
+    Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL);                 
+
+    /*
+     * Preload the job pipe with one token per job, save the one
+     * "extra" token for the primary job.
+     * 
+     * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is
+     * larger than the write buffer size of the pipe, we will
+     * deadlock here.
+     */
+    for (i = 1; i < max_tokens; i++)
+       JobTokenAdd();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_TokenReturn --
+ *     Return a withdrawn token to the pool.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+void
+Job_TokenReturn(void)
+{
+    jobTokensRunning--;
+    if (jobTokensRunning < 0)
+       Punt("token botch");
+    if (jobTokensRunning || JOB_TOKENS[aborting] != '+')
+       JobTokenAdd();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Job_TokenWithdraw --
+ *     Attempt to withdraw a token from the pool.
+ *
+ * Results:
+ *     Returns TRUE if a token was withdrawn, and FALSE if the pool
+ *     is currently empty.
+ *
+ * Side Effects:
+ *     If pool is empty, set wantToken so that we wake up
+ *     when a token is released.
+ *
+ *-----------------------------------------------------------------------
+ */
+
+
+Boolean
+Job_TokenWithdraw(void)
+{
+    char tok, tok1;
+    int count;
+
+    wantToken = 0;
+    if (DEBUG(JOB))
+       fprintf(debug_file, "Job_TokenWithdraw(%d): aborting %d, running %d\n",
+               getpid(), aborting, jobTokensRunning);
+
+    if (aborting || (jobTokensRunning >= maxJobs))
+       return FALSE;
+
+    count = read(tokenWaitJob.inPipe, &tok, 1);
+    if (count == 0)
+       Fatal("eof on job pipe!");
+    if (count < 0 && jobTokensRunning != 0) {
+       if (errno != EAGAIN) {
+           Fatal("job pipe read: %s", strerror(errno));
+       }
+       if (DEBUG(JOB))
+           fprintf(debug_file, "(%d) blocked for token\n", getpid());
+       wantToken = 1;
+       return FALSE;
+    }
+
+    if (count == 1 && tok != '+') {
+       /* make being abvorted - remove any other job tokens */
+       if (DEBUG(JOB))
+           fprintf(debug_file, "(%d) aborted by token %c\n", getpid(), tok);
+       while (read(tokenWaitJob.inPipe, &tok1, 1) == 1)
+           continue;
+       /* And put the stopper back */
+       write(tokenWaitJob.outPipe, &tok, 1);
+       Fatal("A failure has been detected in another branch of the parallel make");
+    }
+
+    if (count == 1 && jobTokensRunning == 0)
+       /* We didn't want the token really */
+       write(tokenWaitJob.outPipe, &tok, 1);
+
+    jobTokensRunning++;
+    if (DEBUG(JOB))
+       fprintf(debug_file, "(%d) withdrew token\n", getpid());
+    return TRUE;
+}
+
+#ifdef USE_SELECT
+int
+emul_poll(struct pollfd *fd, int nfd, int timeout)
+{
+    fd_set rfds, wfds;
+    int i, maxfd, nselect, npoll;
+    struct timeval tv, *tvp;
+    long usecs;
+
+    FD_ZERO(&rfds);
+    FD_ZERO(&wfds);
+
+    maxfd = -1;
+    for (i = 0; i < nfd; i++) {
+       fd[i].revents = 0;
+
+       if (fd[i].events & POLLIN)
+           FD_SET(fd[i].fd, &rfds);
+
+       if (fd[i].events & POLLOUT)
+           FD_SET(fd[i].fd, &wfds);
+
+       if (fd[i].fd > maxfd)
+           maxfd = fd[i].fd;
+    }
+    
+    if (maxfd >= FD_SETSIZE) {
+       Punt("Ran out of fd_set slots; " 
+            "recompile with a larger FD_SETSIZE.");
+    }
+
+    if (timeout < 0) {
+       tvp = NULL;
+    } else {
+       usecs = timeout * 1000;
+       tv.tv_sec = usecs / 1000000;
+       tv.tv_usec = usecs % 1000000;
+        tvp = &tv;
+    }
+
+    nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp);
+
+    if (nselect <= 0)
+       return nselect;
+
+    npoll = 0;
+    for (i = 0; i < nfd; i++) {
+       if (FD_ISSET(fd[i].fd, &rfds))
+           fd[i].revents |= POLLIN;
+
+       if (FD_ISSET(fd[i].fd, &wfds))
+           fd[i].revents |= POLLOUT;
+
+       if (fd[i].revents)
+           npoll++;
+    }
+
+    return npoll;
+}
+#endif /* USE_SELECT */
diff --git a/commands/bmake/job.h b/commands/bmake/job.h
new file mode 100644 (file)
index 0000000..326aa3e
--- /dev/null
@@ -0,0 +1,263 @@
+/*     $NetBSD: job.h,v 1.39 2009/04/11 09:41:18 apb Exp $     */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 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. 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: @(#)job.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * 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.
+ *
+ *     from: @(#)job.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * job.h --
+ *     Definitions pertaining to the running of jobs in parallel mode.
+ */
+#ifndef _JOB_H_
+#define _JOB_H_
+
+#define TMPPAT "makeXXXXXX"            /* relative to tmpdir */
+
+#ifdef USE_SELECT
+/*
+ * Emulate poll() in terms of select().  This is not a complete
+ * emulation but it is sufficient for make's purposes.
+ */
+
+#define poll emul_poll
+#define pollfd emul_pollfd
+
+struct emul_pollfd {
+    int fd;
+    short events;
+    short revents;
+};
+
+#define        POLLIN          0x0001
+#define        POLLOUT         0x0004
+
+int
+emul_poll(struct pollfd *fd, int nfd, int timeout);
+#endif
+
+/*
+ * The POLL_MSEC constant determines the maximum number of milliseconds spent
+ * in poll before coming out to see if a child has finished. 
+ */
+#define POLL_MSEC      5000
+
+\f
+/*-
+ * 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) 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.
+ *     6) A word of flags which determine how the module handles errors,
+ *        echoing, etc. for the job
+ *
+ * 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.
+ */
+struct pollfd;
+
+#define JOB_BUFSIZE    1024
+typedef struct Job {
+    int        pid;        /* The child's process ID */
+    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                exit_status; /* from wait4() in signal handler */
+    char        job_state;  /* status of the job entry */
+#define JOB_ST_FREE    0       /* Job is available */
+#define JOB_ST_SETUP   1       /* Job is allocated but otherwise invalid */
+#define JOB_ST_RUNNING 3       /* Job is running, pid valid */
+#define JOB_ST_FINISHED        4       /* Job is done (ie after SIGCHILD) */
+    char        job_suspended;
+    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_TRACED     0x400   /* we've sent 'set -x' */
+
+    int                 jobPipe[2];    /* Pipe for readind output from job */
+    struct pollfd *inPollfd;   /* pollfd associated with inPipe */
+    char       outBuf[JOB_BUFSIZE + 1];
+                               /* Buffer for storing the output of the
+                                * job, line by line */
+    int        curPos; /* Current position in op_outBuf */
+} Job;
+
+#define inPipe jobPipe[0]
+#define outPipe jobPipe[1]
+
+\f
+/*-
+ * 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. Finally errOut
+ * is a printf template for running the command and causing the shell to 
+ * exit on error. If any of these strings are empty when hasErrCtl is FALSE, 
+ * the command will be executed anyway as is and if it causes an error, so be 
+ * it. Any templates setup to echo the command will escape any '$ ` \ "'i
+ * characters in the command string to avoid common problems with 
+ * echo "%s\n" as a template.
+ */
+typedef struct Shell {
+    const 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 */
+    const char   *echoOff;     /* command to turn off echo */
+    const char   *echoOn;      /* command to turn it back on again */
+    const 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 */
+    const char  *errCheck;     /* string to turn error checking on */
+    const char  *ignErr;       /* string to turn off error checking */
+    const char  *errOut;       /* string to use for testing exit code */
+    const char  *newline;      /* string literal that results in a newline
+                                * character when it appears outside of any
+                                * 'quote' or "quote" characters */
+    char   commentChar;                /* character used by shell for comment lines */
+
+    /*
+     * command-line flags
+     */
+    const char   *echo;                /* echo commands */
+    const char   *exit;                /* exit on error */
+}               Shell;
+
+extern const char *shellPath;
+extern const char *shellName;
+
+extern int     jobTokensRunning; /* tokens currently "out" */
+extern int     maxJobs;        /* Max jobs we can run */
+
+void Shell_Init(void);
+const char *Shell_GetNewline(void);
+void Job_Touch(GNode *, Boolean);
+Boolean Job_CheckCommands(GNode *, void (*abortProc )(const char *, ...));
+#define CATCH_BLOCK    1
+void Job_CatchChildren(void);
+void Job_CatchOutput(void);
+void Job_Make(GNode *);
+void Job_Init(void);
+Boolean Job_Full(void);
+Boolean Job_Empty(void);
+ReturnStatus Job_ParseShell(char *);
+int Job_Finish(void);
+void Job_End(void);
+void Job_Wait(void);
+void Job_AbortAll(void);
+void JobFlagForMigration(int);
+void Job_TokenReturn(void);
+Boolean Job_TokenWithdraw(void);
+void Job_ServerStart(int, int, int);
+void Job_SetPrefix(void);
+
+#endif /* _JOB_H_ */
diff --git a/commands/bmake/lst.h b/commands/bmake/lst.h
new file mode 100644 (file)
index 0000000..e067407
--- /dev/null
@@ -0,0 +1,189 @@
+/*     $NetBSD: lst.h,v 1.18 2009/01/23 21:58:27 dsl Exp $     */
+
+/*
+ * Copyright (c) 1988, 1989, 1990 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. 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: @(#)lst.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * 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.
+ *
+ *     from: @(#)lst.h 8.1 (Berkeley) 6/6/93
+ */
+
+/*-
+ * 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 List     *Lst;
+typedef        struct ListNode *LstNode;
+
+typedef void           *DuplicateProc(void *);
+typedef void           FreeProc(void *);
+
+#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, DuplicateProc *);
+/* Destroy an old one */
+void           Lst_Destroy(Lst, FreeProc *);
+/* True if list is empty */
+Boolean                Lst_IsEmpty(Lst);
+
+/*
+ * Functions to modify a list
+ */
+/* Insert an element before another */
+ReturnStatus   Lst_InsertBefore(Lst, LstNode, void *);
+/* Insert an element after another */
+ReturnStatus   Lst_InsertAfter(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);
+/* Return predecessor to given element */
+LstNode                Lst_Prev(LstNode);
+/* Get datum from LstNode */
+void           *Lst_Datum(LstNode);
+
+/*
+ * Functions for entire lists
+ */
+/* Find an element in a list */
+LstNode                Lst_Find(Lst, const void *, int (*)(const void *, const void *));
+/* Find an element starting from somewhere */
+LstNode                Lst_FindFrom(Lst, LstNode, const void *,
+                            int (*cProc)(const void *, const 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 */
+int            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.
+ */
+int            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_ */
diff --git a/commands/bmake/lst.lib/Makefile b/commands/bmake/lst.lib/Makefile
new file mode 100644 (file)
index 0000000..5b33a50
--- /dev/null
@@ -0,0 +1,10 @@
+#      $NetBSD: Makefile,v 1.6 2006/11/11 21:23:36 dsl Exp $
+
+OBJ=lstAppend.o lstDupl.o lstInit.o lstOpen.o lstAtEnd.o lstEnQueue.o \
+    lstInsert.o lstAtFront.o lstIsAtEnd.o lstClose.o lstFind.o lstIsEmpty.o \
+    lstRemove.o lstConcat.o lstFindFrom.o lstLast.o lstReplace.o lstFirst.o \
+    lstDatum.o lstForEach.o lstMember.o lstSucc.o lstDeQueue.o \
+    lstForEachFrom.o lstDestroy.o lstNext.o lstPrev.o
+
+CPPFLAGS=-I${.CURDIR}/..
+all: ${OBJ}
diff --git a/commands/bmake/lst.lib/lstAppend.c b/commands/bmake/lst.lib/lstAppend.c
new file mode 100644 (file)
index 0000000..4dafe83
--- /dev/null
@@ -0,0 +1,122 @@
+/*     $NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $       */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstAppend.c        8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstAppend.c --
+ *     Add a new node with a new datum after an existing node
+ */
+
+#include       "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_InsertAfter --
+ *     Create a new node and add it to the given list after the given node.
+ *
+ * Input:
+ *     l               affected list
+ *     ln              node after which to append the datum
+ *     d               said datum
+ *
+ * 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_InsertAfter(Lst l, LstNode ln, void *d)
+{
+    List       list;
+    ListNode   lNode;
+    ListNode   nLNode;
+
+    if (LstValid (l) && (ln == NULL && LstIsEmpty (l))) {
+       goto ok;
+    }
+
+    if (!LstValid (l) || LstIsEmpty (l)  || ! LstNodeValid (ln, l)) {
+       return (FAILURE);
+    }
+    ok:
+
+    list = l;
+    lNode = 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);
+}
+
diff --git a/commands/bmake/lst.lib/lstAtEnd.c b/commands/bmake/lst.lib/lstAtEnd.c
new file mode 100644 (file)
index 0000000..10f191a
--- /dev/null
@@ -0,0 +1,79 @@
+/*     $NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $        */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstAtEnd.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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
+ *
+ * Input:
+ *     l               List to which to add the datum
+ *     d               Datum to add
+ *
+ * Results:
+ *     SUCCESS if life is good.
+ *
+ * Side Effects:
+ *     A new ListNode is created and added to the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_AtEnd(Lst l, void *d)
+{
+    LstNode    end;
+
+    end = Lst_Last(l);
+    return (Lst_InsertAfter(l, end, d));
+}
diff --git a/commands/bmake/lst.lib/lstAtFront.c b/commands/bmake/lst.lib/lstAtFront.c
new file mode 100644 (file)
index 0000000..d8be166
--- /dev/null
@@ -0,0 +1,76 @@
+/*     $NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $      */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstAtFront.c       8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(Lst l, void *d)
+{
+    LstNode    front;
+
+    front = Lst_First(l);
+    return (Lst_InsertBefore(l, front, d));
+}
diff --git a/commands/bmake/lst.lib/lstClose.c b/commands/bmake/lst.lib/lstClose.c
new file mode 100644 (file)
index 0000000..06b68c5
--- /dev/null
@@ -0,0 +1,86 @@
+/*     $NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $        */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstClose.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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.
+ *
+ * Input:
+ *     l               The list to close
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     The list is closed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Lst_Close(Lst l)
+{
+    List       list = l;
+
+    if (LstValid(l) == TRUE) {
+       list->isOpen = FALSE;
+       list->atEnd = Unknown;
+    }
+}
+
diff --git a/commands/bmake/lst.lib/lstConcat.c b/commands/bmake/lst.lib/lstConcat.c
new file mode 100644 (file)
index 0000000..534d34e
--- /dev/null
@@ -0,0 +1,185 @@
+/*     $NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $       */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstConcat.c        8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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.
+ *
+ * Input:
+ *     l1              The list to which l2 is to be appended
+ *     l2              The list to append to l1
+ *     flags           LST_CONCNEW if LstNode's should be duplicated
+ *                     LST_CONCLINK if should just be relinked
+ *
+ * Results:
+ *     SUCCESS if all went well. FAILURE otherwise.
+ *
+ * Side Effects:
+ *     New elements are created and appended the first list.
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_Concat(Lst l1, Lst l2, int flags)
+{
+    ListNode   ln;     /* original LstNode */
+    ListNode   nln;    /* new LstNode */
+    ListNode   last;   /* the last element in the list. Keeps
+                                * bookkeeping until the end */
+    List       list1 = l1;
+    List       list2 = 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 NIL 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 NIL space and the first element will be untouched if it
+            * existed before and will also point to NIL 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 nil 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 nil,
+        * 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);
+}
+
diff --git a/commands/bmake/lst.lib/lstDatum.c b/commands/bmake/lst.lib/lstDatum.c
new file mode 100644 (file)
index 0000000..6e2d9ad
--- /dev/null
@@ -0,0 +1,77 @@
+/*     $NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $        */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDatum.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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 NULL if the node is invalid.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Lst_Datum(LstNode ln)
+{
+    if (ln != NULL) {
+       return ((ln)->datum);
+    } else {
+       return NULL;
+    }
+}
+
diff --git a/commands/bmake/lst.lib/lstDeQueue.c b/commands/bmake/lst.lib/lstDeQueue.c
new file mode 100644 (file)
index 0000000..bdb05cc
--- /dev/null
@@ -0,0 +1,87 @@
+/*     $NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $      */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDeQueue.c       8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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 NULL if the list
+ *     is empty.
+ *
+ * Side Effects:
+ *     The head node is removed from the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void *
+Lst_DeQueue(Lst l)
+{
+    void *rd;
+    ListNode   tln;
+
+    tln = Lst_First(l);
+    if (tln == NULL) {
+       return NULL;
+    }
+
+    rd = tln->datum;
+    if (Lst_Remove(l, tln) == FAILURE) {
+       return NULL;
+    } else {
+       return (rd);
+    }
+}
+
diff --git a/commands/bmake/lst.lib/lstDestroy.c b/commands/bmake/lst.lib/lstDestroy.c
new file mode 100644 (file)
index 0000000..92c5b2b
--- /dev/null
@@ -0,0 +1,101 @@
+/*     $NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $      */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDestroy.c       8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(Lst list, FreeProc *freeProc)
+{
+    ListNode   ln;
+    ListNode   tln = NULL;
+
+    if (list == NULL)
+       return;
+
+    /* To ease scanning */
+    if (list->lastPtr != NULL)
+       list->lastPtr->nextPtr = NULL;
+    else {
+       free(list);
+       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(list);
+}
diff --git a/commands/bmake/lst.lib/lstDupl.c b/commands/bmake/lst.lib/lstDupl.c
new file mode 100644 (file)
index 0000000..2174ff7
--- /dev/null
@@ -0,0 +1,107 @@
+/*     $NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $ */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstDupl.c  8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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.
+ *
+ * Input:
+ *     l               the list to duplicate
+ *     copyProc        A function to duplicate each void *
+ *
+ * Results:
+ *     The new Lst structure or NULL if failure.
+ *
+ * Side Effects:
+ *     A new list is created.
+ *-----------------------------------------------------------------------
+ */
+Lst
+Lst_Duplicate(Lst l, DuplicateProc *copyProc)
+{
+    Lst        nl;
+    ListNode   ln;
+    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 != NULL) {
+           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);
+}
diff --git a/commands/bmake/lst.lib/lstEnQueue.c b/commands/bmake/lst.lib/lstEnQueue.c
new file mode 100644 (file)
index 0000000..be386c9
--- /dev/null
@@ -0,0 +1,78 @@
+/*     $NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $      */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstEnQueue.c       8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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_InsertAfter.
+ *
+ * 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(Lst l, void *d)
+{
+    if (LstValid (l) == FALSE) {
+       return (FAILURE);
+    }
+
+    return (Lst_InsertAfter(l, Lst_Last(l), d));
+}
+
diff --git a/commands/bmake/lst.lib/lstFind.c b/commands/bmake/lst.lib/lstFind.c
new file mode 100644 (file)
index 0000000..d07dbe7
--- /dev/null
@@ -0,0 +1,74 @@
+/*     $NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $ */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstFind.c  8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(Lst l, const void *d, int (*cProc)(const void *, const void *))
+{
+    return (Lst_FindFrom(l, Lst_First(l), d, cProc));
+}
+
diff --git a/commands/bmake/lst.lib/lstFindFrom.c b/commands/bmake/lst.lib/lstFindFrom.c
new file mode 100644 (file)
index 0000000..e2beab6
--- /dev/null
@@ -0,0 +1,90 @@
+/*     $NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $     */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstFindFrom.c      8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(Lst l, LstNode ln, const void *d,
+            int (*cProc)(const void *, const void *))
+{
+    ListNode   tln;
+
+    if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) {
+       return NULL;
+    }
+
+    tln = ln;
+
+    do {
+       if ((*cProc)(tln->datum, d) == 0)
+           return (tln);
+       tln = tln->nextPtr;
+    } while (tln != ln && tln != NULL);
+
+    return NULL;
+}
+
diff --git a/commands/bmake/lst.lib/lstFirst.c b/commands/bmake/lst.lib/lstFirst.c
new file mode 100644 (file)
index 0000000..4e8334f
--- /dev/null
@@ -0,0 +1,77 @@
+/*     $NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $        */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstFirst.c 8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(Lst l)
+{
+    if (!LstValid (l) || LstIsEmpty (l)) {
+       return NULL;
+    } else {
+       return (l->firstPtr);
+    }
+}
+
diff --git a/commands/bmake/lst.lib/lstForEach.c b/commands/bmake/lst.lib/lstForEach.c
new file mode 100644 (file)
index 0000000..917e4ea
--- /dev/null
@@ -0,0 +1,76 @@
+/*     $NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $      */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstForEach.c       8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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*/
+int
+Lst_ForEach(Lst l, int (*proc)(void *, void *), void *d)
+{
+    return Lst_ForEachFrom(l, Lst_First(l), proc, d);
+}
+
diff --git a/commands/bmake/lst.lib/lstForEachFrom.c b/commands/bmake/lst.lib/lstForEachFrom.c
new file mode 100644 (file)
index 0000000..c7f44ad
--- /dev/null
@@ -0,0 +1,125 @@
+/*     $NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $  */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstForEachFrom.c   8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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*/
+int
+Lst_ForEachFrom(Lst l, LstNode ln, int (*proc)(void *, void *),
+               void *d)
+{
+    ListNode   tln = ln;
+    List       list = l;
+    ListNode   next;
+    Boolean            done;
+    int                result;
+
+    if (!LstValid (list) || LstIsEmpty (list)) {
+       return 0;
+    }
+
+    do {
+       /*
+        * Take care of having the current element deleted out from under
+        * us.
+        */
+
+       next = tln->nextPtr;
+
+       /*
+        * We're done with the traversal if
+        *  - the next node to examine is the first in the queue or
+        *    doesn't exist and
+        *  - nothing's been added after the current node (check this
+        *    after proc() has been called).
+        */
+       done = (next == NULL || next == list->firstPtr);
+
+       (void) tln->useCount++;
+       result = (*proc) (tln->datum, d);
+       (void) tln->useCount--;
+
+       /*
+        * Now check whether a node has been added.
+        * Note: this doesn't work if this node was deleted before
+        *       the new node was added.
+        */
+       if (next != tln->nextPtr) {
+               next = tln->nextPtr;
+               done = 0;
+       }
+
+       if (tln->flags & LN_DELETED) {
+           free((char *)tln);
+       }
+       tln = next;
+    } while (!result && !LstIsEmpty(list) && !done);
+
+    return result;
+}
+
diff --git a/commands/bmake/lst.lib/lstInit.c b/commands/bmake/lst.lib/lstInit.c
new file mode 100644 (file)
index 0000000..f98ac42
--- /dev/null
@@ -0,0 +1,85 @@
+/*     $NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstInit.c  8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * init.c --
+ *     Initialize a new linked list.
+ */
+
+#include       "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Init --
+ *     Create and initialize a new list.
+ *
+ * Input:
+ *     circ            TRUE if the list should be made circular
+ *
+ * Results:
+ *     The created list.
+ *
+ * Side Effects:
+ *     A list is created, what else?
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Lst_Init(Boolean circ)
+{
+    List       nList;
+
+    PAlloc (nList, List);
+
+    nList->firstPtr = NULL;
+    nList->lastPtr = NULL;
+    nList->isOpen = FALSE;
+    nList->isCirc = circ;
+    nList->atEnd = Unknown;
+
+    return (nList);
+}
diff --git a/commands/bmake/lst.lib/lstInsert.c b/commands/bmake/lst.lib/lstInsert.c
new file mode 100644 (file)
index 0000000..77187bb
--- /dev/null
@@ -0,0 +1,122 @@
+/*     $NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $       */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstInsert.c        8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstInsert.c --
+ *     Insert a new datum before an old one
+ */
+
+#include       "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_InsertBefore --
+ *     Insert a new node with the given piece of data before the given
+ *     node in the given list.
+ *
+ * Input:
+ *     l               list to manipulate
+ *     ln              node before which to insert d
+ *     d               datum to be inserted
+ *
+ * Results:
+ *     SUCCESS or FAILURE.
+ *
+ * Side Effects:
+ *     the firstPtr field will be changed if ln is the first node in the
+ *     list.
+ *
+ *-----------------------------------------------------------------------
+ */
+ReturnStatus
+Lst_InsertBefore(Lst l, LstNode ln, void *d)
+{
+    ListNode   nLNode; /* new lnode for d */
+    ListNode   lNode = ln;
+    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);
+}
+
diff --git a/commands/bmake/lst.lib/lstInt.h b/commands/bmake/lst.lib/lstInt.h
new file mode 100644 (file)
index 0000000..34a2fbd
--- /dev/null
@@ -0,0 +1,105 @@
+/*     $NetBSD: lstInt.h,v 1.20 2009/01/24 14:43:29 dsl Exp $  */
+
+/*
+ * 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. 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
+ */
+
+/*-
+ * lstInt.h --
+ *     Internals for the list library
+ */
+#ifndef _LSTINT_H_
+#define _LSTINT_H_
+
+#include         "../lst.h"
+#include         "../make_malloc.h"
+
+typedef struct ListNode {
+       struct ListNode *prevPtr;   /* previous element in list */
+       struct ListNode *nextPtr;   /* next in list */
+       unsigned 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 List {
+       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) bmake_malloc(sizeof *(var))
+
+/*
+ * LstValid (l) --
+ *     Return TRUE if the list l is valid
+ */
+#define LstValid(l)    ((Lst)(l) != NULL)
+
+/*
+ * LstNodeValid (ln, l) --
+ *     Return TRUE if the LstNode ln is valid with respect to l
+ */
+#define LstNodeValid(ln, l)    ((ln) != NULL)
+
+/*
+ * LstIsEmpty (l) --
+ *     TRUE if the list l is empty.
+ */
+#define LstIsEmpty(l)  (((List)(l))->firstPtr == NULL)
+
+#endif /* _LSTINT_H_ */
diff --git a/commands/bmake/lst.lib/lstIsAtEnd.c b/commands/bmake/lst.lib/lstIsAtEnd.c
new file mode 100644 (file)
index 0000000..70270d2
--- /dev/null
@@ -0,0 +1,87 @@
+/*     $NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $ */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstIsAtEnd.c       8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(Lst l)
+{
+    List list = l;
+
+    return (!LstValid (l) || !list->isOpen ||
+           (list->atEnd == Head) || (list->atEnd == Tail));
+}
+
diff --git a/commands/bmake/lst.lib/lstIsEmpty.c b/commands/bmake/lst.lib/lstIsEmpty.c
new file mode 100644 (file)
index 0000000..8b1d6ed
--- /dev/null
@@ -0,0 +1,75 @@
+/*     $NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $      */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstIsEmpty.c       8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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 NULL).
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Lst_IsEmpty(Lst l)
+{
+    return ( ! LstValid (l) || LstIsEmpty(l));
+}
+
diff --git a/commands/bmake/lst.lib/lstLast.c b/commands/bmake/lst.lib/lstLast.c
new file mode 100644 (file)
index 0000000..096ca24
--- /dev/null
@@ -0,0 +1,77 @@
+/*     $NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstLast.c  8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(Lst l)
+{
+    if (!LstValid(l) || LstIsEmpty (l)) {
+       return NULL;
+    } else {
+       return (l->lastPtr);
+    }
+}
+
diff --git a/commands/bmake/lst.lib/lstMember.c b/commands/bmake/lst.lib/lstMember.c
new file mode 100644 (file)
index 0000000..0ff2ed1
--- /dev/null
@@ -0,0 +1,74 @@
+/*     $NetBSD: lstMember.c,v 1.13 2009/01/23 21:26:30 dsl Exp $       */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstMember.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstMember.c        8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstMember.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * lstMember.c --
+ *     See if a given datum is on a given list.
+ */
+
+#include    "lstInt.h"
+
+LstNode
+Lst_Member(Lst l, void *d)
+{
+    List               list = l;
+    ListNode   lNode;
+
+    lNode = list->firstPtr;
+    if (lNode == NULL) {
+       return NULL;
+    }
+
+    do {
+       if (lNode->datum == d) {
+           return lNode;
+       }
+       lNode = lNode->nextPtr;
+    } while (lNode != NULL && lNode != list->firstPtr);
+
+    return NULL;
+}
diff --git a/commands/bmake/lst.lib/lstNext.c b/commands/bmake/lst.lib/lstNext.c
new file mode 100644 (file)
index 0000000..5c2e0ee
--- /dev/null
@@ -0,0 +1,120 @@
+/*     $NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstNext.c  8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(Lst l)
+{
+    ListNode   tln;
+    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 (tln);
+}
+
diff --git a/commands/bmake/lst.lib/lstOpen.c b/commands/bmake/lst.lib/lstOpen.c
new file mode 100644 (file)
index 0000000..941293e
--- /dev/null
@@ -0,0 +1,87 @@
+/*     $NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstOpen.c  8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(Lst l)
+{
+       if (LstValid (l) == FALSE) {
+               return (FAILURE);
+       }
+       (l)->isOpen = TRUE;
+       (l)->atEnd = LstIsEmpty (l) ? Head : Unknown;
+       (l)->curPtr = NULL;
+
+       return (SUCCESS);
+}
+
diff --git a/commands/bmake/lst.lib/lstPrev.c b/commands/bmake/lst.lib/lstPrev.c
new file mode 100644 (file)
index 0000000..0ec865d
--- /dev/null
@@ -0,0 +1,79 @@
+/*     $NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $  */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstSucc.c  8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstPrev.c --
+ *     return the predecessor to a given node
+ */
+
+#include       "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Prev --
+ *     Return the predecessor to the given node on its list.
+ *
+ * Results:
+ *     The predecessor 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
+ *     predecessor).
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+LstNode
+Lst_Prev(LstNode ln)
+{
+    if (ln == NULL) {
+       return NULL;
+    } else {
+       return (ln->prevPtr);
+    }
+}
+
diff --git a/commands/bmake/lst.lib/lstRemove.c b/commands/bmake/lst.lib/lstRemove.c
new file mode 100644 (file)
index 0000000..54d7b33
--- /dev/null
@@ -0,0 +1,136 @@
+/*     $NetBSD: lstRemove.c,v 1.14 2008/12/13 15:19:29 dsl Exp $       */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstRemove.c,v 1.14 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstRemove.c        8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstRemove.c,v 1.14 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(Lst l, LstNode ln)
+{
+    List       list = l;
+    ListNode   lNode = 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);
+}
+
diff --git a/commands/bmake/lst.lib/lstReplace.c b/commands/bmake/lst.lib/lstReplace.c
new file mode 100644 (file)
index 0000000..090e91a
--- /dev/null
@@ -0,0 +1,78 @@
+/*     $NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $      */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstReplace.c       8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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(LstNode ln, void *d)
+{
+    if (ln == NULL) {
+       return (FAILURE);
+    } else {
+       (ln)->datum = d;
+       return (SUCCESS);
+    }
+}
+
diff --git a/commands/bmake/lst.lib/lstSucc.c b/commands/bmake/lst.lib/lstSucc.c
new file mode 100644 (file)
index 0000000..3f13aa5
--- /dev/null
@@ -0,0 +1,79 @@
+/*     $NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $ */
+
+/*
+ * 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. 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)lstSucc.c  8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * LstSucc.c --
+ *     return the successor to a given node
+ */
+
+#include       "lstInt.h"
+
+/*-
+ *-----------------------------------------------------------------------
+ * Lst_Succ --
+ *     Return the successor 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(LstNode ln)
+{
+    if (ln == NULL) {
+       return NULL;
+    } else {
+       return (ln->nextPtr);
+    }
+}
+
diff --git a/commands/bmake/main.c b/commands/bmake/main.c
new file mode 100644 (file)
index 0000000..54f4e67
--- /dev/null
@@ -0,0 +1,1859 @@
+/*     $NetBSD: main.c,v 1.174 2009/09/09 17:09:49 sjg Exp $   */
+
+/*
+ * 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: main.c,v 1.174 2009/09/09 17:09:49 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\
+ The Regents of the University of California.  All rights reserved.");
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)main.c     8.3 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: main.c,v 1.174 2009/09/09 17:09:49 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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.
+ *
+ *     Error                   Print a tagged error message. The global
+ *                             MAKE variable must have been defined. This
+ *                             takes a format string and two optional
+ *                             arguments for it.
+ *
+ *     Fatal                   Print an error message and exit. Also takes
+ *                             a format string and two arguments.
+ *
+ *     Punt                    Aborts all jobs and exits with a message. Also
+ *                             takes a format string and two arguments.
+ *
+ *     Finish                  Finish things up by printing the number of
+ *                             errors which occurred, as passed to it, and
+ *                             exiting.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#ifdef MAKE_NATIVE
+#include <sys/utsname.h>
+#endif
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "make.h"
+#include "hash.h"
+#include "dir.h"
+#include "job.h"
+#include "pathnames.h"
+#include "trace.h"
+
+#ifdef USE_IOVEC
+#include <sys/uio.h>
+#endif
+
+#ifndef        DEFMAXLOCAL
+#define        DEFMAXLOCAL DEFMAXJOBS
+#endif /* DEFMAXLOCAL */
+
+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         printVars;      /* print value of one or more vars */
+static Lst             variables;      /* list of variables to print */
+int                    maxJobs;        /* -j argument */
+static int             maxJobTokens;   /* -j argument */
+Boolean                        compatMake;     /* -B argument */
+int                    debug;          /* -d argument */
+Boolean                        noExecute;      /* -n flag */
+Boolean                        noRecursiveExecute;     /* -N flag */
+Boolean                        keepgoing;      /* -k flag */
+Boolean                        queryFlag;      /* -q flag */
+Boolean                        touchFlag;      /* -t flag */
+Boolean                        ignoreErrors;   /* -i flag */
+Boolean                        beSilent;       /* -s flag */
+Boolean                        oldVars;        /* variable substitution style */
+Boolean                        checkEnvFirst;  /* -e flag */
+Boolean                        parseWarnFatal; /* -W flag */
+Boolean                        jobServer;      /* -J flag */
+static int jp_0 = -1, jp_1 = -1;       /* ends of parent job pipe */
+Boolean                        varNoExportEnv; /* -X flag */
+Boolean                        doing_depend;   /* Set while reading .depend */
+static Boolean         jobsRunning;    /* TRUE if the jobs might be running */
+static const char *    tracefile;
+static char *          Check_Cwd_av(int, char **, int);
+static void            MainParseArgs(int, char **);
+static int             ReadMakefile(const void *, const void *);
+static void            usage(void);
+
+static Boolean         ignorePWD;      /* if we use -C, PWD is meaningless */
+static char curdir[MAXPATHLEN + 1];    /* startup directory */
+static char objdir[MAXPATHLEN + 1];    /* where we chdir'ed to */
+char *progname;                                /* the program name */
+
+Boolean forceJobs = FALSE;
+
+extern Lst parseIncPath;
+
+static void
+parse_debug_options(const char *argvalue)
+{
+       const char *modules;
+       const char *mode;
+       char *fname;
+       int len;
+
+       for (modules = argvalue; *modules; ++modules) {
+               switch (*modules) {
+               case 'A':
+                       debug = ~0;
+                       break;
+               case 'a':
+                       debug |= DEBUG_ARCH;
+                       break;
+               case 'C':
+                       debug |= DEBUG_CWD;
+                       break;
+               case 'c':
+                       debug |= DEBUG_COND;
+                       break;
+               case 'd':
+                       debug |= DEBUG_DIR;
+                       break;
+               case 'e':
+                       debug |= DEBUG_ERROR;
+                       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;
+                       }
+                       else if (modules[1] == '3') {
+                               debug |= DEBUG_GRAPH3;
+                               ++modules;
+                       }
+                       break;
+               case 'j':
+                       debug |= DEBUG_JOB;
+                       break;
+               case 'l':
+                       debug |= DEBUG_LOUD;
+                       break;
+               case 'm':
+                       debug |= DEBUG_MAKE;
+                       break;
+               case 'n':
+                       debug |= DEBUG_SCRIPT;
+                       break;
+               case 'p':
+                       debug |= DEBUG_PARSE;
+                       break;
+               case 's':
+                       debug |= DEBUG_SUFF;
+                       break;
+               case 't':
+                       debug |= DEBUG_TARG;
+                       break;
+               case 'v':
+                       debug |= DEBUG_VAR;
+                       break;
+               case 'x':
+                       debug |= DEBUG_SHELL;
+                       break;
+               case 'F':
+                       if (debug_file != stdout && debug_file != stderr)
+                               fclose(debug_file);
+                       if (*++modules == '+')
+                               mode = "a";
+                       else
+                               mode = "w";
+                       if (strcmp(modules, "stdout") == 0) {
+                               debug_file = stdout;
+                               goto debug_setbuf;
+                       }
+                       if (strcmp(modules, "stderr") == 0) {
+                               debug_file = stderr;
+                               goto debug_setbuf;
+                       }
+                       len = strlen(modules);
+                       fname = malloc(len + 20);
+                       memcpy(fname, modules, len + 1);
+                       /* Let the filename be modified by the pid */
+                       if (strcmp(fname + len - 3, ".%d") == 0)
+                               snprintf(fname + len - 2, 20, "%d", getpid());
+                       debug_file = fopen(fname, mode);
+                       if (!debug_file) {
+                               fprintf(stderr, "Cannot open debug file %s\n",
+                                   fname);
+                               usage();
+                       }
+                       free(fname);
+                       goto debug_setbuf;
+               default:
+                       (void)fprintf(stderr,
+                           "%s: illegal argument to d option -- %c\n",
+                           progname, *modules);
+                       usage();
+               }
+       }
+debug_setbuf:
+       /*
+        * Make the debug_file unbuffered, and make
+        * stdout line buffered (unless debugfile == stdout).
+        */
+       setvbuf(debug_file, NULL, _IONBF, 0);
+       if (debug_file != stdout) {
+               setvbuf(stdout, NULL, _IOLBF, 0);
+       }
+}
+
+/*-
+ * 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 = '?';
+       int arginc;
+       char *argvalue;
+       const char *getopt_def;
+       char *optscan;
+       Boolean inOption, dashDash = FALSE;
+       char found_path[MAXPATHLEN + 1];        /* for searching for sys.mk */
+
+#define OPTFLAGS "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrst"
+/* Can't actually use getopt(3) because rescanning is not portable */
+
+       getopt_def = OPTFLAGS;
+rearg: 
+       inOption = FALSE;
+       optscan = NULL;
+       while(argc > 1) {
+               char *getopt_spec;
+               if(!inOption)
+                       optscan = argv[1];
+               c = *optscan++;
+               arginc = 0;
+               if(inOption) {
+                       if(c == '\0') {
+                               ++argv;
+                               --argc;
+                               inOption = FALSE;
+                               continue;
+                       }
+               } else {
+                       if (c != '-' || dashDash)
+                               break;
+                       inOption = TRUE;
+                       c = *optscan++;
+               }
+               /* '-' found at some earlier point */
+               getopt_spec = strchr(getopt_def, c);
+               if(c != '\0' && getopt_spec != NULL && getopt_spec[1] == ':') {
+                       /* -<something> found, and <something> should have an arg */
+                       inOption = FALSE;
+                       arginc = 1;
+                       argvalue = optscan;
+                       if(*argvalue == '\0') {
+                               if (argc < 3)
+                                       goto noarg;
+                               argvalue = argv[2];
+                               arginc = 2;
+                       }
+               } else {
+                       argvalue = NULL; 
+               }
+               switch(c) {
+               case '\0':
+                       arginc = 1;
+                       inOption = FALSE;
+                       break;
+               case 'B':
+                       compatMake = TRUE;
+                       Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL);
+                       break;
+               case 'C':
+                       if (chdir(argvalue) == -1) {
+                               (void)fprintf(stderr,
+                                             "%s: chdir %s: %s\n",
+                                             progname, argvalue,
+                                             strerror(errno));
+                               exit(1);
+                       }
+                       ignorePWD = TRUE;
+                       break;
+               case 'D':
+                       if (argvalue == NULL || argvalue[0] == 0) goto noarg;
+                       Var_Set(argvalue, "1", VAR_GLOBAL, 0);
+                       Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL);
+                       Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+                       break;
+               case 'I':
+                       if (argvalue == NULL) goto noarg;
+                       Parse_AddIncludeDir(argvalue);
+                       Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL);
+                       Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+                       break;
+               case 'J':
+                       if (argvalue == NULL) goto noarg;
+                       if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) {
+                           (void)fprintf(stderr,
+                               "%s: internal error -- J option malformed (%s)\n",
+                               progname, argvalue);
+                               usage();
+                       }
+                       if ((fcntl(jp_0, F_GETFD, 0) < 0) ||
+                           (fcntl(jp_1, F_GETFD, 0) < 0)) {
+#if 0
+                           (void)fprintf(stderr,
+                               "%s: ###### warning -- J descriptors were closed!\n",
+                               progname);
+                           exit(2);
+#endif
+                           jp_0 = -1;
+                           jp_1 = -1;
+                           compatMake = TRUE;
+                       } else {
+                           Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL);
+                           Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+                           jobServer = TRUE;
+                       }
+                       break;
+               case 'N':
+                       noExecute = TRUE;
+                       noRecursiveExecute = TRUE;
+                       Var_Append(MAKEFLAGS, "-N", VAR_GLOBAL);
+                       break;
+               case 'S':
+                       keepgoing = FALSE;
+                       Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL);
+                       break;
+               case 'T':
+                       if (argvalue == NULL) goto noarg;
+                       tracefile = bmake_strdup(argvalue);
+                       Var_Append(MAKEFLAGS, "-T", VAR_GLOBAL);
+                       Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+                       break;
+               case 'V':
+                       if (argvalue == NULL) goto noarg;
+                       printVars = TRUE;
+                       (void)Lst_AtEnd(variables, argvalue);
+                       Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL);
+                       Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+                       break;
+               case 'W':
+                       parseWarnFatal = TRUE;
+                       break;
+               case 'X':
+                       varNoExportEnv = TRUE;
+                       Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL);
+                       break;
+               case 'd':
+                       if (argvalue == NULL) goto noarg;
+                       /* If '-d-opts' don't pass to children */
+                       if (argvalue[0] == '-')
+                           argvalue++;
+                       else {
+                           Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL);
+                           Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+                       }
+                       parse_debug_options(argvalue);
+                       break;
+               case 'e':
+                       checkEnvFirst = TRUE;
+                       Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL);
+                       break;
+               case 'f':
+                       if (argvalue == NULL) goto noarg;
+                       (void)Lst_AtEnd(makefiles, argvalue);
+                       break;
+               case 'i':
+                       ignoreErrors = TRUE;
+                       Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL);
+                       break;
+               case 'j':
+                       if (argvalue == NULL) goto noarg;
+                       forceJobs = TRUE;
+                       maxJobs = strtol(argvalue, &p, 0);
+                       if (*p != '\0' || maxJobs < 1) {
+                               (void)fprintf(stderr, "%s: illegal argument to -j -- must be positive integer!\n",
+                                   progname);
+                               exit(1);
+                       }
+                       Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL);
+                       Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL);
+                       maxJobTokens = maxJobs;
+                       break;
+               case 'k':
+                       keepgoing = TRUE;
+                       Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL);
+                       break;
+               case 'm':
+                       if (argvalue == NULL) goto noarg;
+                       /* look for magic parent directory search string */
+                       if (strncmp(".../", argvalue, 4) == 0) {
+                               if (!Dir_FindHereOrAbove(curdir, argvalue+4,
+                                   found_path, sizeof(found_path)))
+                                       break;          /* nothing doing */
+                               (void)Dir_AddDir(sysIncPath, found_path);
+                               
+                       } else {
+                               (void)Dir_AddDir(sysIncPath, argvalue);
+                       }
+                       Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL);
+                       Var_Append(MAKEFLAGS, argvalue, 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 '-':
+                       dashDash = TRUE;
+                       break;
+               default:
+               case '?':
+                       usage();
+               }
+               argv += arginc;
+               argc -= arginc;
+       }
+
+       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 (; argc > 1; ++argv, --argc)
+               if (Parse_IsVar(argv[1])) {
+                       Parse_DoVar(argv[1], VAR_CMD);
+               } else {
+                       if (!*argv[1])
+                               Punt("illegal (null) argument.");
+                       if (*argv[1] == '-' && !dashDash)
+                               goto rearg;
+                       (void)Lst_AtEnd(create, bmake_strdup(argv[1]));
+               }
+
+       return;
+noarg:
+       (void)fprintf(stderr, "%s: option requires an argument -- %c\n",
+           progname, c);
+       usage();
+}
+
+/*-
+ * 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.
+ *
+ * Input:
+ *     line            Line to fracture
+ *
+ * Results:
+ *     None
+ *
+ * Side Effects:
+ *     Only those that come from the various arguments.
+ */
+void
+Main_ParseArgLine(const char *line)
+{
+       char **argv;                    /* Manufactured argument vector */
+       int argc;                       /* Number of arguments in argv */
+       char *args;                     /* Space used by the args */
+       char *buf, *p1;
+       char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1);
+       size_t len;
+
+       if (line == NULL)
+               return;
+       for (; *line == ' '; ++line)
+               continue;
+       if (!*line)
+               return;
+
+       buf = bmake_malloc(len = strlen(line) + strlen(argv0) + 2);
+       (void)snprintf(buf, len, "%s %s", argv0, line);
+       if (p1)
+               free(p1);
+
+       argv = brk_string(buf, &argc, TRUE, &args);
+       if (argv == NULL) {
+               Error("Unterminated quoted string [%s]", buf);
+               free(buf);
+               return;
+       }
+       free(buf);
+       MainParseArgs(argc, argv);
+
+       free(args);
+       free(argv);
+}
+
+Boolean
+Main_SetObjdir(const char *path)
+{
+       struct stat sb;
+       char *p = NULL;
+       char buf[MAXPATHLEN + 1];
+       Boolean rc = FALSE;
+
+       /* expand variable substitutions */
+       if (strchr(path, '$') != 0) {
+               snprintf(buf, MAXPATHLEN, "%s", path);
+               path = p = Var_Subst(NULL, buf, VAR_GLOBAL, 0);
+       }
+
+       if (path[0] != '/') {
+               snprintf(buf, MAXPATHLEN, "%s/%s", curdir, path);
+               path = buf;
+       }
+
+       /* look for the directory and try to chdir there */
+       if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) {
+               if (chdir(path)) {
+                       (void)fprintf(stderr, "make warning: %s: %s.\n",
+                                     path, strerror(errno));
+               } else {
+                       strncpy(objdir, path, MAXPATHLEN);
+                       Var_Set(".OBJDIR", objdir, VAR_GLOBAL, 0);
+                       setenv("PWD", objdir, 1);
+                       Dir_InitDot();
+                       rc = TRUE;
+               }
+       }
+
+       if (p)
+               free(p);
+       return rc;
+}
+
+/*-
+ * ReadAllMakefiles --
+ *     wrapper around ReadMakefile() to read all.
+ *
+ * Results:
+ *     TRUE if ok, FALSE on error
+ */
+static int
+ReadAllMakefiles(const void *p, const void *q)
+{
+       return (ReadMakefile(p, q) == 0);
+}
+
+#ifdef SIGINFO
+/*ARGSUSED*/
+static void
+siginfo(int signo)
+{
+       char dir[MAXPATHLEN];
+       char str[2 * MAXPATHLEN];
+       int len;
+       if (getcwd(dir, sizeof(dir)) == NULL)
+               return;
+       len = snprintf(str, sizeof(str), "%s: Working in: %s\n", progname, dir);
+       if (len > 0)
+               (void)write(STDERR_FILENO, str, (size_t)len);
+}
+#endif
+
+/*-
+ * 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 = FALSE;      /* FALSE if all targets up to date */
+       struct stat sb, sa;
+       char *p1, *path, *pwd;
+       char mdpath[MAXPATHLEN];
+       char *machine = getenv("MACHINE");
+       const char *machine_arch = getenv("MACHINE_ARCH");
+       char *syspath = getenv("MAKESYSPATH");
+       Lst sysMkPath;                  /* Path of sys.mk */
+       char *cp = NULL, *start;
+                                       /* avoid faults on read-only strings */
+       static char defsyspath[] = _PATH_DEFSYSPATH;
+       char found_path[MAXPATHLEN + 1];        /* for searching for sys.mk */
+       struct timeval rightnow;                /* to initialize random seed */
+#ifdef MAKE_NATIVE
+       struct utsname utsname;
+#endif
+
+       /* default to writing debug to stderr */
+       debug_file = stderr;
+
+#ifdef SIGINFO
+       (void)signal(SIGINFO, siginfo);
+#endif
+       /*
+        * Set the seed to produce a different random sequence
+        * on each program execution.
+        */
+       gettimeofday(&rightnow, NULL);
+       srandom(rightnow.tv_sec + rightnow.tv_usec);
+       
+       if ((progname = strrchr(argv[0], '/')) != NULL)
+               progname++;
+       else
+               progname = argv[0];
+#if defined(RLIMIT_NOFILE) && !defined(__minix)
+       /*
+        * 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
+
+       /*
+        * 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 both MACHINE and MACHINE_ARCH are decided at
+        * run-time.
+        */
+       if (!machine) {
+#ifdef MAKE_NATIVE
+           if (uname(&utsname) == -1) {
+               (void)fprintf(stderr, "%s: uname failed (%s).\n", progname,
+                   strerror(errno));
+               exit(2);
+           }
+           machine = utsname.machine;
+#else
+#ifdef MAKE_MACHINE
+           machine = MAKE_MACHINE;
+#else
+           machine = "unknown";
+#endif
+#endif
+       }
+
+       if (!machine_arch) {
+#ifndef MACHINE_ARCH
+#ifdef MAKE_MACHINE_ARCH
+            machine_arch = MAKE_MACHINE_ARCH;
+#else
+           machine_arch = "unknown";
+#endif
+#else
+           machine_arch = MACHINE_ARCH;
+#endif
+       }
+
+       /*
+        * Just in case MAKEOBJDIR wants us to do something tricky.
+        */
+       Var_Init();             /* Initialize the lists of variables for
+                                * parsing arguments */
+       Var_Set("MACHINE", machine, VAR_GLOBAL, 0);
+       Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL, 0);
+#ifdef MAKE_VERSION
+       Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL, 0);
+#endif
+       Var_Set(".newline", "\n", VAR_GLOBAL, 0); /* handy for :@ loops */
+
+       create = Lst_Init(FALSE);
+       makefiles = Lst_Init(FALSE);
+       printVars = FALSE;
+       variables = Lst_Init(FALSE);
+       beSilent = FALSE;               /* Print commands as executed */
+       ignoreErrors = FALSE;           /* Pay attention to non-zero returns */
+       noExecute = FALSE;              /* Execute all commands */
+       noRecursiveExecute = FALSE;     /* Execute all .MAKE targets */
+       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 */
+       debug = 0;                      /* No debug verbosity, please. */
+       jobsRunning = FALSE;
+
+       maxJobs = DEFMAXLOCAL;          /* Set default local max concurrency */
+       maxJobTokens = maxJobs;
+       compatMake = FALSE;             /* No compat mode */
+       ignorePWD = FALSE;
+
+       /*
+        * Initialize the parsing, directory and variable modules to prepare
+        * for the reading of inclusion paths and variable settings on the
+        * command line
+        */
+
+       /*
+        * 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.
+        */
+       Parse_Init();
+       Var_Set("MAKE", argv[0], VAR_GLOBAL, 0);
+       Var_Set(".MAKE", argv[0], VAR_GLOBAL, 0);
+       Var_Set(MAKEFLAGS, "", VAR_GLOBAL, 0);
+       Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL, 0);
+       Var_Set("MFLAGS", "", VAR_GLOBAL, 0);
+       Var_Set(".ALLTARGETS", "", VAR_GLOBAL, 0);
+
+       /*
+        * Set some other useful macros
+        */
+       {
+           char tmp[64];
+           const char *ep;
+
+           if (!(ep = getenv(MAKE_LEVEL))) {
+               ep = "0";
+           }
+           Var_Set(MAKE_LEVEL, ep, VAR_GLOBAL, 0);
+           snprintf(tmp, sizeof(tmp), "%u", getpid());
+           Var_Set(".MAKE.PID", tmp, VAR_GLOBAL, 0);
+           snprintf(tmp, sizeof(tmp), "%u", getppid());
+           Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL, 0);
+       }
+       Job_SetPrefix();
+
+       /*
+        * 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);
+
+       /*
+        * Find where we are (now) and take care of PWD for the automounter...
+        * All this code is so that we know where we are when we start up
+        * on a different machine with pmake.
+        */
+       if (getcwd(curdir, MAXPATHLEN) == NULL) {
+               (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno));
+               exit(2);
+       }
+
+       if (stat(curdir, &sa) == -1) {
+           (void)fprintf(stderr, "%s: %s: %s.\n",
+                progname, curdir, strerror(errno));
+           exit(2);
+       }
+
+       /*
+        * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX
+        * since the value of curdir can vary depending on how we got
+        * here.  Ie sitting at a shell prompt (shell that provides $PWD)
+        * or via subdir.mk in which case its likely a shell which does
+        * not provide it.
+        * So, to stop it breaking this case only, we ignore PWD if
+        * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a transform.
+        */
+       if (!ignorePWD &&
+           (pwd = getenv("PWD")) != NULL &&
+           getenv("MAKEOBJDIRPREFIX") == NULL) {
+               const char *makeobjdir = getenv("MAKEOBJDIR");
+
+               if (makeobjdir == NULL || !strchr(makeobjdir, '$')) {
+                       if (stat(pwd, &sb) == 0 && sa.st_ino == sb.st_ino &&
+                           sa.st_dev == sb.st_dev)
+                               (void)strncpy(curdir, pwd, MAXPATHLEN);
+               }
+       }
+       Var_Set(".CURDIR", curdir, VAR_GLOBAL, 0);
+
+       /*
+        * Find the .OBJDIR.  If MAKEOBJDIRPREFIX, or failing that,
+        * MAKEOBJDIR is set in the environment, try only that value
+        * and fall back to .CURDIR if it does not exist.
+        *
+        * Otherwise, try _PATH_OBJDIR.MACHINE, _PATH_OBJDIR, and
+        * finally _PATH_OBJDIRPREFIX`pwd`, in that order.  If none
+        * of these paths exist, just use .CURDIR.
+        */
+       Dir_Init(curdir);
+       (void)Main_SetObjdir(curdir);
+
+       if ((path = getenv("MAKEOBJDIRPREFIX")) != NULL) {
+               (void)snprintf(mdpath, MAXPATHLEN, "%s%s", path, curdir);
+               (void)Main_SetObjdir(mdpath);
+       } else if ((path = getenv("MAKEOBJDIR")) != NULL) {
+               (void)Main_SetObjdir(path);
+       } else {
+               (void)snprintf(mdpath, MAXPATHLEN, "%s.%s", _PATH_OBJDIR, machine);
+               if (!Main_SetObjdir(mdpath) && !Main_SetObjdir(_PATH_OBJDIR)) {
+                       (void)snprintf(mdpath, MAXPATHLEN, "%s%s", 
+                                       _PATH_OBJDIRPREFIX, curdir);
+                       (void)Main_SetObjdir(mdpath);
+               }
+       }
+
+       /*
+        * 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();
+       Trace_Init(tracefile);
+
+       DEFAULT = NULL;
+       (void)time(&now);
+
+       Trace_Log(MAKESTART, NULL);
+       
+       /*
+        * 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, 0);
+
+
+       /*
+        * 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 (syspath == NULL || *syspath == '\0')
+               syspath = defsyspath;
+       else
+               syspath = bmake_strdup(syspath);
+
+       for (start = syspath; *start != '\0'; start = cp) {
+               for (cp = start; *cp != '\0' && *cp != ':'; cp++)
+                       continue;
+               if (*cp == ':') {
+                       *cp++ = '\0';
+               }
+               /* look for magic parent directory search string */
+               if (strncmp(".../", start, 4) != 0) {
+                       (void)Dir_AddDir(defIncPath, start);
+               } else {
+                       if (Dir_FindHereOrAbove(curdir, start+4, 
+                           found_path, sizeof(found_path))) {
+                               (void)Dir_AddDir(defIncPath, found_path);
+                       }
+               }
+       }
+       if (syspath != defsyspath)
+               free(syspath);
+
+       /*
+        * Read in the built-in rules first, followed by the specified
+        * makefile, if it was (makefile != 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,
+                          Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath,
+                          sysMkPath);
+               if (Lst_IsEmpty(sysMkPath))
+                       Fatal("%s: no system rules (%s).", progname,
+                           _PATH_DEFSYSMK);
+               ln = Lst_Find(sysMkPath, NULL, ReadMakefile);
+               if (ln == NULL)
+                       Fatal("%s: cannot open %s.", progname,
+                           (char *)Lst_Datum(ln));
+       }
+
+       if (!Lst_IsEmpty(makefiles)) {
+               LstNode ln;
+
+               ln = Lst_Find(makefiles, NULL, ReadAllMakefiles);
+               if (ln != NULL)
+                       Fatal("%s: cannot open %s.", progname, 
+                           (char *)Lst_Datum(ln));
+       } else if (ReadMakefile("makefile", NULL) != 0)
+               (void)ReadMakefile("Makefile", NULL);
+
+       /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */
+       if (!noBuiltins || !printVars) {
+               doing_depend = TRUE;
+               (void)ReadMakefile(".depend", NULL);
+               doing_depend = FALSE;
+       }
+
+       Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL);
+       if (p1)
+           free(p1);
+
+       if (!compatMake)
+           Job_ServerStart(maxJobTokens, jp_0, jp_1);
+       if (DEBUG(JOB))
+           fprintf(debug_file, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n",
+               jp_0, jp_1, maxJobs, maxJobTokens, compatMake);
+
+       Main_ExportMAKEFLAGS(TRUE);     /* initial export */
+
+       Check_Cwd_av(0, NULL, 0);       /* initialize it */
+       
+
+       /*
+        * 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 */
+                       (void)Dir_AddDir(dirSearchPath, path);
+                       *cp = savec;
+                       path = cp + 1;
+               } while (savec == ':');
+               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();
+
+       /*
+        * Propagate attributes through :: dependency lists.
+        */
+       Targ_Propagate();
+
+       /* 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 (printVars) {
+               LstNode ln;
+
+               for (ln = Lst_First(variables); ln != NULL;
+                   ln = Lst_Succ(ln)) {
+                       char *var = (char *)Lst_Datum(ln);
+                       char *value;
+                       
+                       if (strchr(var, '$')) {
+                               value = p1 = Var_Subst(NULL, var, VAR_GLOBAL, 0);
+                       } else {
+                               value = Var_Value(var, 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) {
+                               Job_Init();
+                               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);
+               }
+       }
+
+#ifdef CLEANUP
+       Lst_Destroy(targs, NULL);
+       Lst_Destroy(variables, NULL);
+       Lst_Destroy(makefiles, NULL);
+       Lst_Destroy(create, (FreeProc *)free);
+#endif
+
+       /* print the graph now it's been processed if the user requested it */
+       if (DEBUG(GRAPH2))
+               Targ_PrintGraph(2);
+
+       Trace_Log(MAKEEND, 0);
+
+       Suff_End();
+        Targ_End();
+       Arch_End();
+       Var_End();
+       Parse_End();
+       Dir_End();
+       Job_End();
+       Trace_End();
+
+       return outOfDate ? 1 : 0;
+}
+
+/*-
+ * ReadMakefile  --
+ *     Open and parse the given makefile.
+ *
+ * Results:
+ *     0 if ok. -1 if couldn't open file.
+ *
+ * Side Effects:
+ *     lots
+ */
+static int
+ReadMakefile(const void *p, const void *q __unused)
+{
+       const char *fname = p;          /* makefile to read */
+       int fd;
+       size_t len = MAXPATHLEN;
+       char *name, *path = bmake_malloc(len);
+       int setMAKEFILE;
+
+       if (!strcmp(fname, "-")) {
+               Parse_File("(stdin)", dup(fileno(stdin)));
+               Var_Set("MAKEFILE", "", VAR_GLOBAL, 0);
+       } else {
+               setMAKEFILE = strcmp(fname, ".depend");
+
+               /* if we've chdir'd, rebuild the path name */
+               if (strcmp(curdir, objdir) && *fname != '/') {
+                       size_t plen = strlen(curdir) + strlen(fname) + 2;
+                       if (len < plen)
+                               path = bmake_realloc(path, len = 2 * plen);
+                       
+                       (void)snprintf(path, len, "%s/%s", curdir, fname);
+                       fd = open(path, O_RDONLY);
+                       if (fd != -1) {
+                               fname = path;
+                               goto found;
+                       }
+                       
+                       /* If curdir failed, try objdir (ala .depend) */
+                       plen = strlen(objdir) + strlen(fname) + 2;
+                       if (len < plen)
+                               path = bmake_realloc(path, len = 2 * plen);
+                       (void)snprintf(path, len, "%s/%s", objdir, fname);
+                       fd = open(path, O_RDONLY);
+                       if (fd != -1) {
+                               fname = path;
+                               goto found;
+                       }
+               } else {
+                       fd = open(fname, O_RDONLY);
+                       if (fd != -1)
+                               goto found;
+               }
+               /* look in -I and system include directories. */
+               name = Dir_FindFile(fname, parseIncPath);
+               if (!name)
+                       name = Dir_FindFile(fname,
+                               Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath);
+               if (!name || (fd = open(name, O_RDONLY)) == -1) {
+                       if (name)
+                               free(name);
+                       free(path);
+                       return(-1);
+               }
+               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", fname, VAR_GLOBAL, 0);
+               Parse_File(fname, fd);
+       }
+       free(path);
+       return(0);
+}
+
+
+/*
+ * If MAKEOBJDIRPREFIX is in use, make ends up not in .CURDIR
+ * in situations that would not arrise with ./obj (links or not).
+ * This tends to break things like:
+ *
+ * build:
+ *     ${MAKE} includes
+ *
+ * This function spots when ${.MAKE:T} or ${.MAKE} is a command (as
+ * opposed to an argument) in a command line and if so returns
+ * ${.CURDIR} so caller can chdir() so that the assumptions made by
+ * the Makefile hold true.
+ *
+ * If ${.MAKE} does not contain any '/', then ${.MAKE:T} is skipped.
+ *
+ * The chdir() only happens in the child process, and does nothing if
+ * MAKEOBJDIRPREFIX and MAKEOBJDIR are not in the environment so it
+ * should not break anything.  Also if NOCHECKMAKECHDIR is set we
+ * do nothing - to ensure historic semantics can be retained.
+ */
+static int  Check_Cwd_Off = 0;
+
+static char *
+Check_Cwd_av(int ac, char **av, int copy)
+{
+    static char *make[4];
+    static char *cur_dir = NULL;
+    char **mp;
+    char *cp;
+    int is_cmd, next_cmd;
+    int i;
+    int n;
+
+    if (Check_Cwd_Off) {
+       if (DEBUG(CWD))
+           fprintf(debug_file, "check_cwd: check is off.\n");
+       return NULL;
+    }
+    
+    if (make[0] == NULL) {
+       if (Var_Exists("NOCHECKMAKECHDIR", VAR_GLOBAL)) {
+           Check_Cwd_Off = 1;
+           if (DEBUG(CWD))
+               fprintf(debug_file, "check_cwd: turning check off.\n");
+           return NULL;
+       }
+           
+        make[1] = Var_Value(".MAKE", VAR_GLOBAL, &cp);
+        if ((make[0] = strrchr(make[1], '/')) == NULL) {
+            make[0] = make[1];
+            make[1] = NULL;
+        } else
+            ++make[0];
+        make[2] = NULL;
+        cur_dir = Var_Value(".CURDIR", VAR_GLOBAL, &cp);
+    }
+    if (ac == 0 || av == NULL) {
+       if (DEBUG(CWD))
+           fprintf(debug_file, "check_cwd: empty command.\n");
+        return NULL;                   /* initialization only */
+    }
+
+    if (getenv("MAKEOBJDIR") == NULL &&
+        getenv("MAKEOBJDIRPREFIX") == NULL) {
+       if (DEBUG(CWD))
+           fprintf(debug_file, "check_cwd: no obj dirs.\n");
+        return NULL;
+    }
+
+    
+    next_cmd = 1;
+    for (i = 0; i < ac; ++i) {
+       is_cmd = next_cmd;
+
+       n = strlen(av[i]);
+       cp = &(av[i])[n - 1];
+       if (strspn(av[i], "|&;") == (size_t)n) {
+           next_cmd = 1;
+           continue;
+       } else if (*cp == ';' || *cp == '&' || *cp == '|' || *cp == ')') {
+           next_cmd = 1;
+           if (copy) {
+               do {
+                   *cp-- = '\0';
+               } while (*cp == ';' || *cp == '&' || *cp == '|' ||
+                        *cp == ')' || *cp == '}') ;
+           } else {
+               /*
+                * XXX this should not happen.
+                */
+               fprintf(stderr, "%s: WARNING: raw arg ends in shell meta '%s'\n",
+                   progname, av[i]);
+           }
+       } else
+           next_cmd = 0;
+
+       cp = av[i];
+       if (*cp == ';' || *cp == '&' || *cp == '|')
+           is_cmd = 1;
+       
+       if (DEBUG(CWD))
+           fprintf(debug_file, "av[%d] == %s '%s'",
+               i, (is_cmd) ? "cmd" : "arg", av[i]);
+       if (is_cmd != 0) {
+           if (*cp == '(' || *cp == '{' ||
+               *cp == ';' || *cp == '&' || *cp == '|') {
+               do {
+                   ++cp;
+               } while (*cp == '(' || *cp == '{' ||
+                        *cp == ';' || *cp == '&' || *cp == '|');
+               if (*cp == '\0') {
+                   next_cmd = 1;
+                   continue;
+               }
+           }
+           if (strcmp(cp, "cd") == 0 || strcmp(cp, "chdir") == 0) {
+               if (DEBUG(CWD))
+                   fprintf(debug_file, " == cd, done.\n");
+               return NULL;
+           }
+           for (mp = make; *mp != NULL; ++mp) {
+               n = strlen(*mp);
+               if (strcmp(cp, *mp) == 0) {
+                   if (DEBUG(CWD))
+                       fprintf(debug_file, " %s == '%s', chdir(%s)\n",
+                           cp, *mp, cur_dir);
+                   return cur_dir;
+               }
+           }
+       }
+       if (DEBUG(CWD))
+           fprintf(debug_file, "\n");
+    }
+    return NULL;
+}
+
+char *
+Check_Cwd_Cmd(const char *cmd)
+{
+    char *cp, *bp;
+    char **av;
+    int ac;
+
+    if (Check_Cwd_Off)
+       return NULL;
+    
+    if (cmd) {
+       av = brk_string(cmd, &ac, TRUE, &bp);
+       if (DEBUG(CWD))
+           fprintf(debug_file, "splitting: '%s' -> %d words\n",
+               cmd, ac);
+    } else {
+       ac = 0;
+       av = NULL;
+       bp = NULL;
+    }
+    cp = Check_Cwd_av(ac, av, 1);
+    if (bp)
+       free(bp);
+    if (av)
+       free(av);
+    return cp;
+}
+
+void
+Check_Cwd(const char **argv)
+{
+    char *cp;
+    int ac;
+    
+    if (Check_Cwd_Off)
+       return;
+    
+    for (ac = 0; argv[ac] != NULL; ++ac)
+       /* NOTHING */;
+    if (ac == 3 && *argv[1] == '-') {
+       cp =  Check_Cwd_Cmd(argv[2]);
+    } else {
+       cp = Check_Cwd_av(ac, UNCONST(argv), 0);
+    }
+    if (cp) {
+       chdir(cp);
+    }
+}
+
+/*-
+ * 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 errnum is not NULL, it contains the reason for the command failure
+ *
+ * Side Effects:
+ *     The string must be freed by the caller.
+ */
+char *
+Cmd_Exec(const char *cmd, const char **errnum)
+{
+    const 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;
+
+
+    *errnum = NULL;
+
+    if (!shellName)
+       Shell_Init();
+    /*
+     * Set up arguments for shell
+     */
+    args[0] = shellName;
+    args[1] = "-c";
+    args[2] = cmd;
+    args[3] = NULL;
+
+    /*
+     * Open a pipe for fetching its output
+     */
+    if (pipe(fds) == -1) {
+       *errnum = "Couldn't create pipe for \"%s\"";
+       goto bad;
+    }
+
+    /*
+     * Fork
+     */
+#if defined(__minix)
+    switch (cpid = fork()) {
+#else
+    switch (cpid = vfork()) {
+#endif
+    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]);
+
+       Var_ExportVars();
+
+       (void)execv(shellPath, UNCONST(args));
+       _exit(1);
+       /*NOTREACHED*/
+
+    case -1:
+       *errnum = "Couldn't exec \"%s\"";
+       goto bad;
+
+    default:
+       /*
+        * No need for the writing half
+        */
+       (void)close(fds[1]);
+
+       Buf_Init(&buf, 0);
+
+       do {
+           char   result[BUFSIZ];
+           cc = read(fds[0], result, sizeof(result));
+           if (cc > 0)
+               Buf_AddBytes(&buf, cc, 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 = waitpid(cpid, &status, 0)) != cpid) && (pid >= 0))
+           continue;
+
+       cc = Buf_Size(&buf);
+       res = Buf_Destroy(&buf, FALSE);
+
+       if (cc == 0)
+           *errnum = "Couldn't read shell's output for \"%s\"";
+
+       if (WIFSIGNALED(status))
+           *errnum = "\"%s\" exited on a signal";
+       else if (WEXITSTATUS(status) != 0)
+           *errnum = "\"%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];
+
+       if (cc > 0 && *--cp == '\n') {
+           /*
+            * A final newline is just stripped
+            */
+           *cp-- = '\0';
+       }
+       while (cp >= res) {
+           if (*cp == '\n') {
+               *cp = ' ';
+           }
+           cp--;
+       }
+       break;
+    }
+    return res;
+bad:
+    res = bmake_malloc(1);
+    *res = '\0';
+    return res;
+}
+
+/*-
+ * 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;
+       FILE *err_file;
+
+       err_file = debug_file;
+       if (err_file == stdout)
+               err_file = stderr;
+       for (;;) {
+               va_start(ap, fmt);
+               fprintf(err_file, "%s: ", progname);
+               (void)vfprintf(err_file, fmt, ap);
+               va_end(ap);
+               (void)fprintf(err_file, "\n");
+               (void)fflush(err_file);
+               if (err_file == stderr)
+                       break;
+               err_file = 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);
+
+       PrintOnError(NULL);
+
+       if (DEBUG(GRAPH2) || DEBUG(GRAPH3))
+               Targ_PrintGraph(2);
+       Trace_Log(MAKEERROR, 0);
+       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, "%s: ", progname);
+       (void)vfprintf(stderr, fmt, ap);
+       va_end(ap);
+       (void)fprintf(stderr, "\n");
+       (void)fflush(stderr);
+
+       PrintOnError(NULL);
+
+       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);
+       Trace_Log(MAKEERROR, 0);
+       exit(2);                /* Not 1, so -q can distinguish error */
+}
+
+/*
+ * Finish --
+ *     Called when aborting due to errors in child shell to signal
+ *     abnormal exit.
+ *
+ * Results:
+ *     None
+ *
+ * Side Effects:
+ *     The program exits
+ */
+void
+Finish(int errors)
+                       /* number of errors encountered in Make_Make */
+{
+       Fatal("%d error%s", errors, errors == 1 ? "" : "s");
+}
+
+/*
+ * 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);
+}
+
+/*
+ * execError --
+ *     Print why exec failed, avoiding stdio.
+ */
+void
+execError(const char *af, const char *av)
+{
+#ifdef USE_IOVEC
+       int i = 0;
+       struct iovec iov[8];
+#define IOADD(s) \
+       (void)(iov[i].iov_base = UNCONST(s), \
+           iov[i].iov_len = strlen(iov[i].iov_base), \
+           i++)
+#else
+#define        IOADD(void)write(2, s, strlen(s))
+#endif
+
+       IOADD(progname);
+       IOADD(": ");
+       IOADD(af);
+       IOADD("(");
+       IOADD(av);
+       IOADD(") failed (");
+       IOADD(strerror(errno));
+       IOADD(")\n");
+
+#ifdef USE_IOVEC
+       (void)writev(2, iov, 8);
+#endif
+}
+
+/*
+ * usage --
+ *     exit with usage message
+ */
+static void
+usage(void)
+{
+       (void)fprintf(stderr,
+"usage: %s [-BeikNnqrstWX] \n\
+            [-C directory] [-D variable] [-d flags] [-f makefile]\n\
+            [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n\
+            [-V variable] [variable=value] [target ...]\n", progname);
+       exit(2);
+}
+
+
+int
+PrintAddr(void *a, void *b)
+{
+    printf("%lx ", (unsigned long) a);
+    return b ? 0 : 0;
+}
+
+
+
+void
+PrintOnError(const char *s)
+{
+    char tmp[64];
+    char *cp;
+
+    if (s)
+           printf("%s", s);
+       
+    printf("\n%s: stopped in %s\n", progname, curdir);
+    strncpy(tmp, "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}",
+           sizeof(tmp) - 1);
+    cp = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
+    if (cp) {
+           if (*cp)
+                   printf("%s", cp);
+           free(cp);
+    }
+}
+
+void
+Main_ExportMAKEFLAGS(Boolean first)
+{
+    static int once = 1;
+    char tmp[64];
+    char *s;
+
+    if (once != first)
+       return;
+    once = 0;
+    
+    strncpy(tmp, "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}",
+           sizeof(tmp));
+    s = Var_Subst(NULL, tmp, VAR_CMD, 0);
+    if (s && *s) {
+#ifdef POSIX
+       setenv("MAKEFLAGS", s, 1);
+#else
+       setenv("MAKE", s, 1);
+#endif
+    }
+}
diff --git a/commands/bmake/make.1 b/commands/bmake/make.1
new file mode 100644 (file)
index 0000000..efd7d0a
--- /dev/null
@@ -0,0 +1,1846 @@
+.\"    $NetBSD: make.1,v 1.166 2009/11/19 06:48:37 wiz Exp $
+.\"
+.\" 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. 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: @(#)make.1        8.4 (Berkeley) 3/19/94
+.\"
+.Dd November 15, 2009
+.Dt MAKE 1
+.Os
+.Sh NAME
+.Nm make
+.Nd maintain program dependencies
+.Sh SYNOPSIS
+.Nm
+.Op Fl BeikNnqrstWX
+.Bk -words
+.Op Fl C Ar directory
+.Ek
+.Bk -words
+.Op Fl D Ar variable
+.Ek
+.Bk -words
+.Op Fl d Ar flags
+.Ek
+.Bk -words
+.Op Fl f Ar makefile
+.Ek
+.Bk -words
+.Op Fl I Ar directory
+.Ek
+.Bk -words
+.Op Fl J Ar private
+.Ek
+.Bk -words
+.Op Fl j Ar max_jobs
+.Ek
+.Bk -words
+.Op Fl m Ar directory
+.Ek
+.Bk -words
+.Op Fl T Ar file
+.Ek
+.Bk -words
+.Op Fl V Ar variable
+.Ek
+.Op Ar variable=value
+.Bk -words
+.Op Ar target ...
+.Ek
+.Sh DESCRIPTION
+.Nm
+is a program designed to simplify the maintenance of other programs.
+Its input is a list of specifications as to the files upon which programs
+and other files depend.
+If no
+.Fl f Ar makefile
+makefile option is given,
+.Nm
+will try to open
+.Ql Pa makefile
+then
+.Ql Pa Makefile
+in order to find the specifications.
+If the file
+.Ql Pa .depend
+exists, it is read (see
+.Xr mkdep 1 ) .
+.Pp
+This manual page is intended as a reference document only.
+For a more thorough description of
+.Nm
+and makefiles, please refer to
+.%T "Make \- A Tutorial" .
+.Pp
+.Nm
+will prepend the contents of the
+.Va MAKEFLAGS
+environment variable to the command line arguments before parsing them.
+.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.
+.It Fl C Ar directory
+Change to
+.Ar directory
+before reading the makefiles or doing anything else.
+If multiple
+.Fl C
+options are specified, each is interpreted relative to the previous one:
+.Fl C Pa / Fl C Pa etc
+is equivalent to
+.Fl C Pa /etc .
+.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.
+Unless the flags are preceded by
+.Ql -
+they are added to the
+.Va MAKEFLAGS
+environment variable and will be processed by any child make processes.
+By default, debugging information is printed to standard error,
+but this can be changed using the
+.Ar F
+debugging flag.
+The debugging output is always unbuffered; in addition, if debugging
+is enabled but debugging output is not directed to standard output,
+then the standard output is line buffered.
+.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 current working directory.
+.It Ar c
+Print debugging information about conditional evaluation.
+.It Ar d
+Print debugging information about directory searching and caching.
+.It Ar e
+Print debugging information about failed commands and targets.
+.It Ar F Ns Oo Sy \&+ Oc Ns Ar filename
+Specify where debugging output is written.
+This must be the last flag, because it consumes the remainder of
+the argument.
+If the character immediately after the
+.Ql F
+flag is
+.Ql \&+ ,
+then the file will be opened in append mode;
+otherwise the file will be overwritten.
+If the file name is
+.Ql stdout
+or
+.Ql stderr
+then debugging output will be written to the
+standard output or standard error output file descriptors respectively
+(and the
+.Ql \&+
+option has no effect).
+Otherwise, the output will be written to the named file.
+If the file name ends
+.Ql .%d
+then the
+.Ql %d
+is replaced by the pid.
+.It Ar f
+Print debugging information about loop evaluation.
+.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 "g3"
+Print the input graph 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
+.Ql @
+or other "quiet" flags.
+Also known as "loud" behavior.
+.It Ar m
+Print debugging information about making targets, including modification
+dates.
+.It Ar n
+Don't delete the temporary command scripts created when running commands.
+These temporary scripts are created in the directory
+referred to by the
+.Ev TMPDIR
+environment variable, or in
+.Pa /tmp
+if
+.Ev TMPDIR
+is unset or set to the empty string.
+The temporary scripts are created by
+.Xr mkstemp 3 ,
+and have names of the form
+.Pa makeXXXXXX .
+.Em NOTE :
+This can create many files in
+.Ev TMPDIR
+or
+.Pa /tmp ,
+so use with care.
+.It Ar p
+Print debugging information about makefile parsing.
+.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.
+.It Ar x
+Run shell commands with
+.Fl x
+so the actual commands are printed as they are executed.
+.El
+.It Fl e
+Specify that environment variables override macro assignments within
+makefiles.
+.It Fl f Ar makefile
+Specify a makefile to read instead of the default
+.Ql Pa makefile .
+If
+.Ar makefile
+is
+.Ql Fl ,
+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 Fl
+before each command line in the makefile.
+.It Fl J Ar private
+This option should
+.Em not
+be specified by the user.
+.Pp
+When the
+.Ar j
+option is in use in a recursive build, this option is passed by a make
+to child makes to allow all the make processes in the build to
+cooperate to avoid overloading the system.
+.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.
+When compatibility mode is off, all commands associated with a
+target are executed in a single shell invocation as opposed to the
+traditional one shell invocation per line.
+This can break traditional scripts which change directories on each
+command invocation and then expect to start with a fresh environment
+on the next line.
+It is more efficient to correct the scripts rather than turn backwards
+compatibility on.
+.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 sys.mk and makefiles included
+via the
+.Ao Ar file Ac Ns -style
+include statement.
+The
+.Fl m
+option can be used multiple times to form a search path.
+This path will override the default system include path: /etc/mk.
+Furthermore the system include path will be appended to the search path used
+for
+.Qo Ar file Qc Ns -style
+include statements (see the
+.Fl I
+option).
+.Pp
+If a file or directory name in the
+.Fl m
+argument (or the
+.Ev MAKESYSPATH
+environment variable) starts with the string
+.Qq \&.../
+then
+.Nm
+will search for the specified file or directory named in the remaining part
+of the argument string.
+The search starts with the current directory of
+the Makefile and then works upward towards the root of the filesystem.
+If the search is successful, then the resulting directory replaces the
+.Qq \&.../
+specification in the
+.Fl m
+argument.
+If used, this feature allows
+.Nm
+to easily search in the current source tree for customized sys.mk files
+(e.g., by using
+.Qq \&.../mk/sys.mk
+as an argument).
+.It Fl n
+Display the commands that would have been executed, but do not
+actually execute them unless the target depends on the .MAKE special
+source (see below).
+.It Fl N
+Display the commands which would have been executed, but do not
+actually execute any of them; useful for debugging top-level makefiles
+without descending into subdirectories.
+.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
+Do not echo any commands as they are executed.
+Equivalent to specifying
+.Ql Ic @
+before each command line in the makefile.
+.It Fl T Ar tracefile
+When used with the
+.Fl j
+flag,
+append a trace record to
+.Ar tracefile
+for each job started and completed.
+.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.
+If
+.Ar variable
+contains a
+.Ql \&$
+then the value will be expanded before printing.
+.It Fl W
+Treat any warnings during makefile parsing as errors.
+.It Fl X
+Don't export variables passed on the command line to the environment
+individually.
+Variables passed on the command line are still exported
+via the
+.Va MAKEFLAGS
+environment variable.
+This option may be useful on systems which have a small limit on the
+size of command arguments.
+.It Ar variable=value
+Set the value of the variable
+.Ar variable
+to
+.Ar value .
+Normally, all values passed on the command line are also exported to
+sub-makes in the environment.
+The
+.Fl X
+flag disables this behavior.
+Variable assignments should follow options for POSIX compatibility
+but no ordering is enforced.
+.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 values
+.Ql \&? ,
+.Ql * ,
+.Ql [] ,
+and
+.Ql {} .
+The values
+.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 value
+.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 characters of the command line are any combination of
+.Ql Ic @ ,
+.Ql Ic + ,
+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 the command to be executed even when
+.Fl n
+is given.
+This is similar to the effect of the .MAKE special source,
+except that the effect can be limited to a single line of a script.
+A
+.Ql Ic \-
+causes any non-zero exit status of the command line to be ignored.
+.Sh VARIABLE ASSIGNMENTS
+Variables in make are much like variables in the shell, and, by tradition,
+consist of all upper-case letters.
+.Ss Variable assignment modifiers
+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.
+.Em NOTE :
+References to undefined variables are
+.Em not
+expanded.
+This can cause problems when variable modifiers are used.
+.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 white-space 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
+If the variable name contains a dollar, then the name itself is expanded first.
+This allows almost arbitrary variable names, however names containing dollar,
+braces, parenthesis, or whitespace are really best avoided!
+.Pp
+If the result of expanding a variable contains a dollar sign
+.Pq Ql \&$
+the string is expanded again.
+.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.
+.Ss Variable classes
+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 \&\*[Gt] .
+.It Va .ARCHIVE
+The name of the archive file.
+.It Va .IMPSRC
+In suffix-transformation rules, the name/path of the source from which the
+target is to be transformed (the
+.Dq implied
+source); also known as
+.Ql Va \&\*[Lt] .
+It is not defined in explicit rules.
+.It Va .MEMBER
+The name of the archive member.
+.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 \&\*[Lt] ,
+.Ql Va \&\*[Gt] ,
+and
+.Ql Va *
+are permitted for backward
+compatibility with historical makefiles and are not recommended.
+The six variables
+.Ql Va "@F" ,
+.Ql Va "@D" ,
+.Ql Va "\*[Lt]F" ,
+.Ql Va "\*[Lt]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
+.Ss Additional built-in variables
+In addition,
+.Nm
+sets or knows about the following variables:
+.Bl -tag -width .MAKEOVERRIDES
+.It Va \&$
+A single dollar sign
+.Ql \&$ ,
+i.e.
+.Ql \&$$
+expands to a single dollar
+sign.
+.It Va .ALLTARGETS
+The list of all targets encountered in the Makefile.
+If evaluated during
+Makefile parsing, lists only those targets encountered thus far.
+.It Va .CURDIR
+A path to the directory where
+.Nm
+was executed.
+Refer to the description of
+.Ql Ev PWD
+for more details.
+.It Ev MAKE
+The name that
+.Nm
+was executed with
+.Pq Va argv[0] .
+For compatibility
+.Nm
+also sets
+.Va .MAKE
+with the same value.
+The preferred variable to use is the environment variable
+.Ev MAKE
+because it is more compatible with other versions of
+.Nm
+and cannot be confused with the special target with the same name.
+.It Va .MAKE.EXPORTED
+The list of variables exported by
+.Nm .
+.It Va .MAKE.MAKEFILES
+The list of makefiles read by
+.Nm ,
+which is useful for tracking dependencies.
+Each makefile is recorded only once, regardless of the number of times read.
+.It Va .MAKE.LEVEL
+The recursion depth of
+.Nm .
+The initial instance of
+.Nm
+will be 0, and an incremented value is put into the environment
+to be seen by the next generation.
+This allows tests like:
+.Li .if ${.MAKE.LEVEL} == 0
+to protect things which should only be evaluated in the initial instance of
+.Nm .
+.It Va .MAKE.PID
+The process-id of
+.Nm .
+.It Va .MAKE.PPID
+The parent process-id of
+.Nm .
+.It Va .MAKE.JOB.PREFIX
+If
+.Nm
+is run with
+.Ar j
+then output for each target is prefixed with a token
+.Ql --- target ---
+the first part of which can be controlled via
+.Va .MAKE.JOB.PREFIX .
+.br
+For example:
+.Li .MAKE.JOB.PREFIX=${.newline}---${.MAKE:T}[${.MAKE.PID}]
+would produce tokens like
+.Ql ---make[1234] target ---
+making it easier to track the degree of parallelism being achieved.
+.It Ev MAKEFLAGS
+The environment variable
+.Ql Ev MAKEFLAGS
+may contain anything that
+may be specified on
+.Nm Ns 's
+command line.
+Anything specified on
+.Nm Ns 's
+command line is appended to the
+.Ql Ev MAKEFLAGS
+variable which is then
+entered into the environment for all programs which
+.Nm
+executes.
+.It Va .MAKEOVERRIDES
+This variable is used to record the names of variables assigned to
+on the command line, so that they may be exported as part of
+.Ql Ev MAKEFLAGS .
+This behaviour can be disabled by assigning an empty value to
+.Ql Va .MAKEOVERRIDES
+within a makefile.
+Extra variables can be exported from a makefile
+by appending their names to
+.Ql Va .MAKEOVERRIDES .
+.Ql Ev MAKEFLAGS
+is re-exported whenever
+.Ql Va .MAKEOVERRIDES
+is modified.
+.It Va MAKE_PRINT_VAR_ON_ERROR
+When
+.Nm
+stops due to an error, it prints its name and the value of
+.Ql Va .CURDIR
+as well as the value of any variables named in
+.Ql Va MAKE_PRINT_VAR_ON_ERROR .
+.It Va .newline
+This variable is simply assigned a newline character as its value.
+This allows expansions using the
+.Cm \&:@
+modifier to put a newline between
+iterations of the loop rather than a space.
+For example, the printing of
+.Ql Va MAKE_PRINT_VAR_ON_ERROR
+could be done as ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@}.
+.It Va .OBJDIR
+A path to the directory where the targets are built.
+Its value is determined by trying to
+.Xr chdir 2
+to the following directories in order and using the first match:
+.Bl -enum
+.It
+.Ev ${MAKEOBJDIRPREFIX}${.CURDIR}
+.Pp
+(Only if
+.Ql Ev MAKEOBJDIRPREFIX
+is set in the environment or on the command line.)
+.It
+.Ev ${MAKEOBJDIR}
+.Pp
+(Only if
+.Ql Ev MAKEOBJDIR
+is set in the environment or on the command line.)
+.It
+.Ev ${.CURDIR} Ns Pa /obj. Ns Ev ${MACHINE}
+.It
+.Ev ${.CURDIR} Ns Pa /obj
+.It
+.Pa /usr/obj/ Ns Ev ${.CURDIR}
+.It
+.Ev ${.CURDIR}
+.El
+.Pp
+Variable expansion is performed on the value before it's used,
+so expressions such as
+.Dl ${.CURDIR:C,^/usr/src,/var/obj,}
+may be used.
+.Pp
+.Ql Va .OBJDIR
+may be modified in the makefile as a global variable.
+In all cases,
+.Nm
+will
+.Xr chdir 2
+to
+.Ql Va .OBJDIR
+and set
+.Ql Ev PWD
+to that directory before executing any targets.
+.
+.It Va .PARSEDIR
+A path to the directory of the current
+.Ql Pa Makefile
+being parsed.
+.It Va .PARSEFILE
+The basename of the current
+.Ql Pa Makefile
+being parsed.
+This variable and
+.Ql Va .PARSEDIR
+are both set only while the
+.Ql Pa Makefiles
+are being parsed.
+.It Va .PATH
+A variable that represents the list of directories that
+.Nm
+will search for files.
+The search list should be updated using the target
+.Ql Va .PATH
+rather than the variable.
+.It Ev PWD
+Alternate path to the current directory.
+.Nm
+normally sets
+.Ql Va .CURDIR
+to the canonical path given by
+.Xr getcwd 3 .
+However, if the environment variable
+.Ql Ev PWD
+is set and gives a path to the current directory, then
+.Nm
+sets
+.Ql Va .CURDIR
+to the value of
+.Ql Ev PWD
+instead.
+This behaviour is disabled if
+.Ql Ev MAKEOBJDIRPREFIX
+is set or
+.Ql Ev MAKEOBJDIR
+contains a variable transform.
+.Ql Ev PWD
+is set to the value of
+.Ql Va .OBJDIR
+for all programs which
+.Nm
+executes.
+.It Ev VPATH
+Colon-separated
+.Pq Dq \&:
+lists of directories that
+.Nm
+will search for files.
+The variable is supported for compatibility with old make programs only,
+use
+.Ql Va .PATH
+instead.
+.El
+.Ss Variable modifiers
+Variable expansion may be modified to select or modify each word of the
+variable (where a
+.Dq word
+is white-space 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,
+which may be escaped with a backslash
+.Pq Ql \e .
+.Pp
+A set of modifiers can be specified via a variable, as follows:
+.Pp
+.Dl modifier_variable=modifier[:...]
+.Dl ${variable:${modifier_variable}[:...]}
+.Pp
+In this case the first modifier in the modifier_variable does not
+start with a colon, since that must appear in the referencing
+variable.
+If any of the modifiers in the modifier_variable contain a dollar sign
+.Pq Ql $ ,
+these must be doubled to avoid early expansion.
+.Pp
+The supported modifiers are:
+.Bl -tag -width EEE
+.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 \&:M Ns Ar pattern
+Select only those words that match
+.Ar pattern .
+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
+.Ql Cm \&:M ,
+but selects all words which do not match
+.Ar pattern .
+.It Cm \&:O
+Order every word in variable alphabetically.
+To sort words in
+reverse order use the
+.Ql Cm \&:O:[-1..1]
+combination of modifiers.
+.It Cm \&:Ox
+Randomize words in variable.
+The results will be different each time you are referring to the
+modified variable; use the assignment with expansion
+.Pq Ql Cm \&:=
+to prevent such behaviour.
+For example,
+.Bd -literal -offset indent
+LIST=                  uno due tre quattro
+RANDOM_LIST=           ${LIST:Ox}
+STATIC_RANDOM_LIST:=   ${LIST:Ox}
+
+all:
+       @echo "${RANDOM_LIST}"
+       @echo "${RANDOM_LIST}"
+       @echo "${STATIC_RANDOM_LIST}"
+       @echo "${STATIC_RANDOM_LIST}"
+.Ed
+may produce output similar to:
+.Bd -literal -offset indent
+quattro due tre uno
+tre due quattro uno
+due uno quattro tre
+due uno quattro tre
+.Ed
+.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.
+.It Cm \&:tl
+Converts variable to lower-case letters.
+.It Cm \&:ts Ns Ar c
+Words in the variable are normally separated by a space on expansion.
+This modifier sets the separator to the character
+.Ar c .
+If
+.Ar c
+is omitted, then no separator is used.
+.It Cm \&:tu
+Converts variable to upper-case letters.
+.It Cm \&:tW
+Causes the value to be treated as a single word
+(possibly containing embedded white space).
+See also
+.Ql Cm \&:[*] .
+.It Cm \&:tw
+Causes the value to be treated as a sequence of
+words delimited by white space.
+See also
+.Ql Cm \&:[@] .
+.Sm off
+.It Cm \&:S No \&/ Ar old_string No \&/ Ar new_string No \&/ Op Cm 1gW
+.Sm on
+Modify the first occurrence of
+.Ar old_string
+in 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 a
+.Ql 1
+is appended to the last slash of the pattern, only the first word
+is affected.
+If a
+.Ql W
+is appended to the last slash of the pattern,
+then the value is treated as a single word
+(possibly containing embedded white space).
+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 \*[Am]
+is replaced by
+.Ar old_string
+(without any
+.Ql ^
+or
+.Ql \&$ ) .
+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.
+.Sm off
+.It Cm \&:C No \&/ Ar pattern No \&/ Ar replacement No \&/ Op Cm 1gW
+.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 a regular expression (see
+.Xr regex 3 )
+string
+.Ar pattern
+and an
+.Xr ed 1 Ns \-style
+string
+.Ar replacement .
+Normally, the first occurrence of the pattern
+.Ar pattern
+in each word of the value is substituted with
+.Ar replacement .
+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
+.Ar pattern
+as occur in the word or words it is found in; the
+.Ql W
+modifier causes the value to be treated as a single word
+(possibly containing embedded white space).
+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 \&:T
+Replaces each word in the variable with its last component.
+.It Cm \&:u
+Remove adjacent duplicate words (like
+.Xr uniq 1 ) .
+.Sm off
+.It Cm \&:\&? Ar true_string Cm \&: Ar false_string
+.Sm on
+If the variable name (not its value), when parsed as a .if conditional
+expression, evaluates to true, return as its value the
+.Ar true_string ,
+otherwise return the
+.Ar false_string .
+Since the variable name is used as the expression, \&:\&? must be the
+first modifier after the variable name itself - which will, of course,
+usually contain variable expansions.
+A common error is trying to use expressions like
+.Dl ${NUMBERS:M42:?match:no}
+which actually tests defined(NUMBERS),
+to determine is any words match "42" you need to use something like:
+.Dl ${${NUMBERS:M42} != "":?match:no} .
+.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 .
+.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.
+.Sm off
+.It Cm \&:@ Ar temp Cm @ Ar string Cm @
+.Sm on
+This is the loop expansion mechanism from the OSF Development
+Environment (ODE) make.
+Unlike
+.Cm \&.for
+loops expansion occurs at the time of
+reference.
+Assign
+.Ar temp
+to each word in the variable and evaluate
+.Ar string .
+The ODE convention is that
+.Ar temp
+should start and end with a period.
+For example.
+.Dl ${LINKS:@.LINK.@${LN} ${TARGET} ${.LINK.}@}
+.It Cm \&:U Ns Ar newval
+If the variable is undefined
+.Ar newval
+is the value.
+If the variable is defined, the existing value is returned.
+This is another ODE make feature.
+It is handy for setting per-target CFLAGS for instance:
+.Dl ${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}}
+If a value is only required if the variable is undefined, use:
+.Dl ${VAR:D:Unewval}
+.It Cm \&:D Ns Ar newval
+If the variable is defined
+.Ar newval
+is the value.
+.It Cm \&:L
+The name of the variable is the value.
+.It Cm \&:P
+The path of the node which has the same name as the variable
+is the value.
+If no such node exists or its path is null, then the
+name of the variable is used.
+.Sm off
+.It Cm \&:\&! Ar cmd Cm \&!
+.Sm on
+The output of running
+.Ar cmd
+is the value.
+.It Cm \&:sh
+If the variable is non-empty it is run as a command and the output
+becomes the new value.
+.It Cm \&::= Ns Ar str
+The variable is assigned the value
+.Ar str
+after substitution.
+This modifier and its variations are useful in
+obscure situations such as wanting to set a variable when shell commands
+are being parsed.
+These assignment modifiers always expand to
+nothing, so if appearing in a rule line by themselves should be
+preceded with something to keep
+.Nm
+happy.
+.Pp
+The
+.Ql Cm \&::
+helps avoid false matches with the
+.At V
+style
+.Cm \&:=
+modifier and since substitution always occurs the
+.Cm \&::=
+form is vaguely appropriate.
+.It Cm \&::?= Ns Ar str
+As for
+.Cm \&::=
+but only if the variable does not already have a value.
+.It Cm \&::+= Ns Ar str
+Append
+.Ar str
+to the variable.
+.It Cm \&::!= Ns Ar cmd
+Assign the output of
+.Ar cmd
+to the variable.
+.It Cm \&:\&[ Ns Ar range Ns Cm \&]
+Selects one or more words from the value,
+or performs other operations related to the way in which the
+value is divided into words.
+.Pp
+Ordinarily, a value is treated as a sequence of words
+delimited by white space.
+Some modifiers suppress this behaviour,
+causing a value to be treated as a single word
+(possibly containing embedded white space).
+An empty value, or a value that consists entirely of white-space,
+is treated as a single word.
+For the purposes of the
+.Ql Cm \&:[]
+modifier, the words are indexed both forwards using positive integers
+(where index 1 represents the first word),
+and backwards using negative integers
+(where index -1 represents the last word).
+.Pp
+The
+.Ar range
+is subjected to variable expansion, and the expanded result is
+then interpreted as follows:
+.Bl -tag -width index
+.\" :[n]
+.It Ar index
+Selects a single word from the value.
+.\" :[start..end]
+.It Ar start Ns Cm \&.. Ns Ar end
+Selects all words from
+.Ar start
+to
+.Ar end ,
+inclusive.
+For example,
+.Ql Cm \&:[2..-1]
+selects all words from the second word to the last word.
+If
+.Ar start
+is greater than
+.Ar end ,
+then the words are output in reverse order.
+For example,
+.Ql Cm \&:[-1..1]
+selects all the words from last to first.
+.\" :[*]
+.It Cm \&*
+Causes subsequent modifiers to treat the value as a single word
+(possibly containing embedded white space).
+Analogous to the effect of
+\&"$*\&"
+in Bourne shell.
+.\" :[0]
+.It 0
+Means the same as
+.Ql Cm \&:[*] .
+.\" :[*]
+.It Cm \&@
+Causes subsequent modifiers to treat the value as a sequence of words
+delimited by white space.
+Analogous to the effect of
+\&"$@\&"
+in Bourne shell.
+.\" :[#]
+.It Cm \&#
+Returns the number of words in the value.
+.El \" :[range]
+.El
+.Sh INCLUDE STATEMENTS, CONDITIONALS AND FOR LOOPS
+Makefile inclusion, conditional structures 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.
+Files are included with either
+.Cm \&.include Aq Ar file
+or
+.Cm \&.include Pf \*q Ar file Ns \*q .
+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.
+For compatibility with other versions of
+.Nm
+.Ql include file ...
+is also accepted.
+If the include statement is written as
+.Cm .-include
+or as
+.Cm .sinclude
+then errors locating and/or opening include files are ignored.
+.Pp
+Conditional expressions are also preceded by a single dot as the first
+character of a line.
+The possible conditionals are as follows:
+.Bl -tag -width Ds
+.It Ic .export Ar variable ...
+Export the specified global variable.
+If no variable list is provided, all globals are exported
+except for internal variables (those that start with
+.Ql \&. ) .
+This is not affected by the
+.Fl X
+flag, so should be used with caution.
+.Pp
+Appending a variable name to
+.Va .MAKE.EXPORTED
+is equivalent to exporting a variable.
+.It Ic .unexport Ar variable ...
+The opposite of
+.Ql .export .
+The specified global
+.Va variable
+will be removed from
+.Va .MAKE.EXPORTED .
+If no variable list is provided, all globals are unexported,
+and
+.Va .MAKE.EXPORTED
+deleted.
+.It Ic .unexport-env
+Unexport all globals previously exported and
+clear the environment inherited from the parent.
+This operation will cause a memory leak of the original environment,
+so should be used sparingly.
+Testing for
+.Va .MAKE.LEVEL
+being 0, would make sense.
+Also note that any variables which originated in the parent environment
+should be explicitly preserved if desired.
+For example:
+.Bd -literal -offset indent
+.Li .if ${.MAKE.LEVEL} == 0
+PATH := ${PATH}
+.Li .unexport-env
+.Li .export PATH
+.Li .endif
+.Pp
+.Ed
+Would result in an environment containing only
+.Ql Ev PATH ,
+which is the minimal useful environment.
+Actually
+.Ql Ev .MAKE.LEVEL
+will also be pushed into the new environment.
+.It Ic .undef Ar variable
+Un-define the specified global variable.
+Only global variables may be un-defined.
+.It Ic \&.if Oo \&! Oc Ns Ar expression Op Ar operator expression ...
+Test the value of an expression.
+.It Ic .ifdef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+Test the value of a variable.
+.It Ic .ifndef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+Test the value of a variable.
+.It Ic .ifmake Oo \&! Oc Ns Ar target Op Ar operator target ...
+Test the target being built.
+.It Ic .ifnmake Oo \&! Ns Oc Ar target Op Ar operator target ...
+Test the target being built.
+.It Ic .else
+Reverse the sense of the last conditional.
+.It Ic .elif Oo \&! Ns Oc Ar expression Op Ar operator expression ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .if .
+.It Ic .elifdef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifdef .
+.It Ic .elifndef Oo \&! Oc Ns Ar variable Op Ar operator variable ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifndef .
+.It Ic .elifmake Oo \&! Oc Ns Ar target Op Ar operator target ...
+A combination of
+.Ql Ic .else
+followed by
+.Ql Ic .ifmake .
+.It Ic .elifnmake Oo \&! Oc Ns Ar target Op Ar operator target ...
+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 OR.
+.It Cm \&\*[Am]\*[Am]
+Logical
+.Tn AND ;
+of higher precedence than
+.Dq \&|\&| .
+.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 \&\*[Am]\*[Am] .
+.Pp
+The value of
+.Ar expression
+may be any of the following:
+.Bl -tag -width defined
+.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.
+.It Ic commands
+Takes a target name as an argument and evaluates to true if the target
+has been defined and has commands associated with it.
+.El
+.Pp
+.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 or an empty string in the case
+of a string comparison.
+.Pp
+When
+.Nm
+is evaluating one of these conditional expressions, and it encounters
+a (white-space separated) 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 ,
+.Ql Ic .ifndef ,
+or
+.Ql Ic .if
+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 -compact -width Ds
+.It Ic \&.for Ar variable Oo Ar variable ... Oc Ic in Ar expression
+.It Aq make-rules
+.It Ic \&.endfor
+.El
+.Pp
+After the for
+.Ic expression
+is evaluated, it is split into words.
+On each iteration of the loop, one word is taken and assigned to each
+.Ic variable ,
+in order, and these
+.Ic variables
+are substituted into the
+.Ic make-rules
+inside the body of the for loop.
+The number of words must come out even; that is, if there are three
+iteration variables, the number of words provided must be a multiple
+of three.
+.Sh COMMENTS
+Comments begin with a hash
+.Pq Ql \&#
+character, anywhere but in a shell
+command line, and continue to the end of an unescaped new line.
+.Sh SPECIAL SOURCES (ATTRIBUTES)
+.Bl -tag -width .IGNOREx
+.It Ic .EXEC
+Target is never out of date, but always execute commands anyway.
+.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 .INVISIBLE
+.\" XXX
+.\" .It Ic .JOIN
+.\" XXX
+.It Ic .MADE
+Mark all sources of this target as being up-to-date.
+.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 .NOPATH
+Do not search for the target in the directories specified by
+.Ic .PATH .
+.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 .PHONY
+The target does not
+correspond to an actual file; it is always considered to be out of date,
+and will not be created with the
+.Fl t
+option.
+.It Ic .PRECIOUS
+When
+.Nm
+is interrupted, it normally removes any partially made targets.
+This source prevents the target from being removed.
+.It Ic .RECURSIVE
+Synonym for
+.Ic .MAKE .
+.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 .USEBEFORE
+Exactly like
+.Ic .USE ,
+but prepend the
+.Ic .USEBEFORE
+target commands to the target.
+.It Ic .WAIT
+If
+.Ic .WAIT
+appears in a dependency line, the sources that precede it are
+made before the sources that succeed it in the line.
+Since the dependents of files are not made until the file itself
+could be made, this also stops the dependents being built unless they
+are needed for another branch of the dependency tree.
+So given:
+.Bd -literal
+x: a .WAIT b
+       echo x
+a:
+       echo a
+b: b1
+       echo b
+b1:
+       echo b1
+
+.Ed
+the output is always
+.Ql a ,
+.Ql b1 ,
+.Ql b ,
+.Ql x .
+.br
+The ordering imposed by
+.Ic .WAIT
+is only relevant for parallel makes.
+.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 .BEGINx
+.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 .INTERRUPT
+If
+.Nm
+is interrupted, the commands for this target will be executed.
+.It Ic .MAIN
+If no target is specified when
+.Nm
+is invoked, this target will be built.
+.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 .NOPATH
+Apply the
+.Ic .NOPATH
+attribute to any specified sources.
+.It Ic .NOTPARALLEL
+Disable parallel mode.
+.It Ic .NO_PARALLEL
+Synonym for
+.Ic .NOTPARALLEL ,
+for compatibility with other pmake variants.
+.It Ic .ORDER
+The named targets are made in sequence.
+This ordering does not add targets to the list of targets to be made.
+Since the dependents of a target do not get built until the target itself
+could be built, unless
+.Ql a
+is built by another part of the dependency graph,
+the following is a dependency loop:
+.Bd -literal
+\&.ORDER: a b
+b: a
+.Ed
+.Pp
+The ordering imposed by
+.Ic .ORDER
+is only relevant for parallel makes.
+.\" 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.
+If the source is the special
+.Ic .DOTLAST
+target, then the current working
+directory is searched last.
+.It Ic .PHONY
+Apply the
+.Ic .PHONY
+attribute to any specified sources.
+.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 .SHELL
+Sets the shell that
+.Nm
+will use to execute commands.
+The sources are a set of
+.Ar field=value
+pairs.
+.Bl -tag -width hasErrCtls
+.It Ar name
+This is the minimal specification, used to select one of the builtin
+shell specs;
+.Ar sh ,
+.Ar ksh ,
+and
+.Ar csh .
+.It Ar path
+Specifies the path to the shell.
+.It Ar hasErrCtl
+Indicates whether the shell supports exit on error.
+.It Ar check
+The command to turn on error checking.
+.It Ar ignore
+The command to disable error checking.
+.It Ar echo
+The command to turn on echoing of commands executed.
+.It Ar quiet
+The command to turn off echoing of commands executed.
+.It Ar filter
+The output to filter after issuing the
+.Ar quiet
+command.
+It is typically identical to
+.Ar quiet .
+.It Ar errFlag
+The flag to pass the shell to enable error checking.
+.It Ar echoFlag
+The flag to pass the shell to enable command echoing.
+.It Ar newline
+The string literal to pass the shell that results in a single newline
+character when used outside of any quoting characters.
+.El
+Example:
+.Bd -literal
+\&.SHELL: name=ksh path=/bin/ksh hasErrCtl=true \\
+       check="set -e" ignore="set +e" \\
+       echo="set -v" quiet="set +v" filter="set +v" \\
+       echoFlag=v errFlag=e newline="'\\n'"
+.Ed
+.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 previously specified suffixes are deleted.
+It allows the creation of suffix-transformation rules.
+.Pp
+Example:
+.Bd -literal
+\&.SUFFIXES: .o
+\&.c.o:
+       cc -o ${.TARGET} -c ${.IMPSRC}
+.Ed
+.El
+.Sh ENVIRONMENT
+.Nm
+uses the following environment variables, if they exist:
+.Ev MACHINE ,
+.Ev MACHINE_ARCH ,
+.Ev MAKE ,
+.Ev MAKEFLAGS ,
+.Ev MAKEOBJDIR ,
+.Ev MAKEOBJDIRPREFIX ,
+.Ev MAKESYSPATH ,
+.Ev PWD ,
+and
+.Ev TMPDIR .
+.Pp
+.Ev MAKEOBJDIRPREFIX
+and
+.Ev MAKEOBJDIR
+may only be set in the environment or on the command line to
+.Nm
+and not as makefile variables;
+see the description of
+.Ql Va .OBJDIR
+for more details.
+.Sh FILES
+.Bl -tag -width /etc/mk -compact
+.It .depend
+list of dependencies
+.It Makefile
+list of dependencies
+.It makefile
+list of dependencies
+.It sys.mk
+system makefile
+.It /etc/mk
+system makefile directory
+.El
+.Sh COMPATIBILITY
+The basic make syntax is compatible between different versions of make,
+however the special variables, variable modifiers and conditionals are not.
+.Pp
+The way that parallel makes are scheduled changed in
+.Nx 4.0
+so that .ORDER and .WAIT apply recursively to the dependant nodes.
+The algorithms used may change again in the future.
+.Pp
+The way that .for loop variables are substituted changed after
+.Nx 5.0
+so that they still appear to be variable expansions.
+In particular this stops them being treated as syntax, and removes some
+obscure problems using them in .if statements.
+.Sh SEE ALSO
+.Xr mkdep 1
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v7 .
+.Sh BUGS
+The
+.Nm
+syntax is difficult to parse without actually acting of the data.
+For instance finding the end of a variable use should involve scanning each
+the modifiers using the correct terminator for each field.
+In many places
+.Nm
+just counts {} and () in order to find the end of a variable expansion.
+.Pp
+There is no way of escaping a space character in a filename.
diff --git a/commands/bmake/make.c b/commands/bmake/make.c
new file mode 100644 (file)
index 0000000..aa8f8bc
--- /dev/null
@@ -0,0 +1,1550 @@
+/*     $NetBSD: make.c,v 1.78 2009/01/23 21:26:30 dsl Exp $    */
+
+/*
+ * 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: make.c,v 1.78 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)make.c     8.1 (Berkeley) 6/6/93";
+#else
+__RCSID("$NetBSD: make.c,v 1.78 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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.
+ *
+ *     Make_ExpandUse          Expand .USE nodes
+ */
+
+#include    "make.h"
+#include    "hash.h"
+#include    "dir.h"
+#include    "job.h"
+
+static unsigned int checked = 1;/* Sequence # to detect recursion */
+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 MakeAddChild(void *, void *);
+static int MakeFindChild(void *, void *);
+static int MakeUnmark(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 *);
+static int MakeCheckOrder(void *, void *);
+static int MakeBuildChild(void *, void *);
+static int MakeBuildParent(void *, void *);
+
+static void
+make_abort(GNode *gn, int line)
+{
+    static int two = 2;
+
+    fprintf(debug_file, "make_abort from line %d\n", line);
+    Targ_PrintNode(gn, &two);
+    Lst_ForEach(toBeMade, Targ_PrintNode, &two);
+    Targ_PrintGraph(3);
+    abort();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_TimeStamp --
+ *     Set the cmtime field of a parent node based on the mtime stamp in its
+ *     child. Called from MakeOODate via Lst_ForEach.
+ *
+ * Input:
+ *     pgn             the current parent
+ *     cgn             the child we've just examined
+ *
+ * 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);
+}
+
+/*
+ * Input:
+ *     pgn             the current parent
+ *     cgn             the child we've just examined
+ *
+ */
+static int
+MakeTimeStamp(void *pgn, void *cgn)
+{
+    return Make_TimeStamp((GNode *)pgn, (GNode *)cgn);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     gn              the node to check
+ *
+ * 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_USEBEFORE|OP_EXEC)) == 0) {
+       (void)Dir_MTime(gn);
+       if (DEBUG(MAKE)) {
+           if (gn->mtime != 0) {
+               fprintf(debug_file, "modified %s...", Targ_FmtTime(gn->mtime));
+           } else {
+               fprintf(debug_file, "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-Compatibility and
+     * the strange people who wrote 'Make'.
+     */
+    if (gn->type & (OP_USE|OP_USEBEFORE)) {
+       /*
+        * If the node is a USE node it is *never* out of date
+        * no matter *what*.
+        */
+       if (DEBUG(MAKE)) {
+           fprintf(debug_file, ".USE node...");
+       }
+       oodate = FALSE;
+    } else if ((gn->type & OP_LIB) &&
+              ((gn->mtime==0) || Arch_IsLib(gn))) {
+       if (DEBUG(MAKE)) {
+           fprintf(debug_file, "library...");
+       }
+
+       /*
+        * always out of date if no children and :: target
+        * or non-existent.
+        */
+       oodate = (gn->mtime == 0 || 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.
+        */
+       if (DEBUG(MAKE)) {
+           fprintf(debug_file, ".JOIN node...");
+       }
+       if (DEBUG(MAKE)) {
+           fprintf(debug_file, "source %smade...", gn->flags & CHILDMADE ? "" : "not ");
+       }
+       oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE;
+    } 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 (DEBUG(MAKE)) {
+           if (gn->type & OP_FORCE) {
+               fprintf(debug_file, "! operator...");
+           } else if (gn->type & OP_PHONY) {
+               fprintf(debug_file, ".PHONY node...");
+           } else {
+               fprintf(debug_file, ".EXEC node...");
+           }
+       }
+       oodate = TRUE;
+    } else if (gn->mtime < gn->cmtime ||
+              (gn->cmtime == 0 &&
+               ((gn->mtime == 0 && !(gn->type & OP_OPTIONAL))
+                 || 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) and it isn't optional
+        * or was the object of a * :: operator is out-of-date.
+        * Why? Because that's the way Make does it.
+        */
+       if (DEBUG(MAKE)) {
+           if (gn->mtime < gn->cmtime) {
+               fprintf(debug_file, "modified before source...");
+           } else if (gn->mtime == 0) {
+               fprintf(debug_file, "non-existent and no sources...");
+           } else {
+               fprintf(debug_file, ":: operator and no sources...");
+           }
+       }
+       oodate = TRUE;
+    } else {
+       /* 
+        * When a non-existing child with no sources
+        * (such as a typically used FORCE source) has been made and
+        * the target of the child (usually a directory) has the same
+        * timestamp as the timestamp just given to the non-existing child
+        * after it was considered made.
+        */
+       if (DEBUG(MAKE)) {
+           if (gn->flags & FORCE)
+               fprintf(debug_file, "non existing child...");
+       }
+       oodate = (gn->flags & FORCE) ? TRUE : 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, gn);
+    }
+
+    return (oodate);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     gnp             the node to add
+ *     lp              the list to which to add it
+ *
+ * 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->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) {
+       if (DEBUG(MAKE))
+           fprintf(debug_file, "MakeAddChild: need to examine %s%s\n",
+               gn->name, gn->cohort_num);
+       (void)Lst_EnQueue(l, gn);
+    }
+    return (0);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * MakeFindChild  --
+ *     Function used by Make_Run to find the pathname of a child
+ *     that was already made.
+ *
+ * Input:
+ *     gnp             the node to find
+ *
+ * Results:
+ *     Always returns 0
+ *
+ * Side Effects:
+ *     The path and mtime of the node and the cmtime of the parent are
+ *     updated; the unmade children count of the parent is decremented.
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeFindChild(void *gnp, void *pgnp)
+{
+    GNode          *gn = (GNode *)gnp;
+    GNode          *pgn = (GNode *)pgnp;
+
+    (void)Dir_MTime(gn);
+    Make_TimeStamp(pgn, gn);
+    pgn->unmade--;
+
+    return (0);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * Make_HandleUse --
+ *     Function called by Make_Run and SuffApplyTransform on the downward
+ *     pass to handle .USE and transformation nodes. It implements the
+ *     .USE and transformation functionality by copying the node's commands,
+ *     type flags and children to the parent node.
+ *
+ *     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.
+ *
+ * Input:
+ *     cgn             The .USE node
+ *     pgn             The target of the .USE node
+ *
+ * Results:
+ *     none
+ *
+ * Side Effects:
+ *     Children and commands may be added to the parent and the parent's
+ *     type may be changed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Make_HandleUse(GNode *cgn, GNode *pgn)
+{
+    LstNode    ln;     /* An element in the children list */
+
+#ifdef DEBUG_SRC
+    if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) {
+       fprintf(debug_file, "Make_HandleUse: called for plain node %s\n", cgn->name);
+       return;
+    }
+#endif
+
+    if ((cgn->type & (OP_USE|OP_USEBEFORE)) || Lst_IsEmpty(pgn->commands)) {
+           if (cgn->type & OP_USEBEFORE) {
+               /*
+                * .USEBEFORE --
+                *      prepend the child's commands to the parent.
+                */
+               Lst cmds = pgn->commands;
+               pgn->commands = Lst_Duplicate(cgn->commands, NULL);
+               (void)Lst_Concat(pgn->commands, cmds, LST_CONCNEW);
+               Lst_Destroy(cmds, NULL);
+           } else {
+               /*
+                * .USE or 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) {
+           GNode *tgn, *gn = (GNode *)Lst_Datum(ln);
+
+           /*
+            * Expand variables in the .USE node's name
+            * and save the unexpanded form.
+            * We don't need to do this for commands.
+            * They get expanded properly when we execute.
+            */
+           if (gn->uname == NULL) {
+               gn->uname = gn->name;
+           } else {
+               if (gn->name)
+                   free(gn->name);
+           }
+           gn->name = Var_Subst(NULL, gn->uname, pgn, FALSE);
+           if (gn->name && gn->uname && strcmp(gn->name, gn->uname) != 0) {
+               /* See if we have a target for this node. */
+               tgn = Targ_FindNode(gn->name, TARG_NOCREATE);
+               if (tgn != NULL)
+                   gn = tgn;
+           }
+
+           (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_USEBEFORE|OP_TRANSFORM);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * MakeHandleUse --
+ *     Callback function for Lst_ForEach, used by Make_Run on the downward
+ *     pass to handle .USE nodes. Should be called before the children
+ *     are enqueued to be looked at by MakeAddChild.
+ *     This function calls Make_HandleUse to copy the .USE node's commands,
+ *     type flags and children to the parent node.
+ *
+ * Input:
+ *     cgnp            the child we've just examined
+ *     pgnp            the current parent
+ *
+ * Results:
+ *     returns 0.
+ *
+ * Side Effects:
+ *     After expansion, .USE child nodes are removed from the parent
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+MakeHandleUse(void *cgnp, void *pgnp)
+{
+    GNode      *cgn = (GNode *)cgnp;
+    GNode      *pgn = (GNode *)pgnp;
+    LstNode    ln;     /* An element in the children list */
+    int                unmarked;
+
+    unmarked = ((cgn->type & OP_MARK) == 0);
+    cgn->type |= OP_MARK;
+
+    if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0)
+       return (0);
+
+    if (unmarked)
+       Make_HandleUse(cgn, pgn);
+
+    /*
+     * 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 ((ln = Lst_Member(pgn->children, cgn)) != NULL) {
+       Lst_Remove(pgn->children, ln);
+       pgn->unmade--;
+    }
+    return (0);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_Recheck --
+ *     Check the modification time of a gnode, and update it as described
+ *     in the comments below.
+ *
+ * Results:
+ *     returns 0 if the gnode does not exist, or it's filesystem
+ *     time if it does.
+ *
+ * Side Effects:
+ *     the gnode's modification time and path name are affected.
+ *
+ *-----------------------------------------------------------------------
+ */
+time_t
+Make_Recheck(GNode *gn)
+{
+    time_t mtime = Dir_MTime(gn);
+
+#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 (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(gn) || (gn->type & OP_SAVE_CMDS) ||
+           (mtime == 0 && !(gn->type & OP_WAIT))) {
+       if (DEBUG(MAKE)) {
+           fprintf(debug_file, " recheck(%s): update time from %s to now\n",
+                  gn->name, Targ_FmtTime(gn->mtime));
+       }
+       gn->mtime = now;
+    }
+    else {
+       if (DEBUG(MAKE)) {
+           fprintf(debug_file, " recheck(%s): current update time: %s\n",
+                  gn->name, Targ_FmtTime(gn->mtime));
+       }
+    }
+#endif
+    return mtime;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     cgn             the child 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 flag CHILDMADE field will be
+ *     set true and its cmtime set to now.
+ *
+ *     If the child is not up-to-date and still does not exist,
+ *     set the FORCE flag on the parents.
+ *
+ *     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 */
+    time_t     mtime = -1;
+    char       *p1;
+    Lst                parents;
+    GNode      *centurion;
+
+    /* It is save to re-examine any nodes again */
+    checked++;
+
+    cname = Var_Value(TARGET, cgn, &p1);
+    if (p1)
+       free(p1);
+
+    if (DEBUG(MAKE))
+       fprintf(debug_file, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num);
+
+    /*
+     * 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) {
+       mtime = Make_Recheck(cgn);
+    }
+
+    /*
+     * If this is a `::' node, we must consult its first instance
+     * which is where all parents are linked.
+     */
+    if ((centurion = cgn->centurion) != NULL) {
+       if (!Lst_IsEmpty(cgn->parents))
+               Punt("%s%s: cohort has parents", cgn->name, cgn->cohort_num);
+       centurion->unmade_cohorts -= 1;
+       if (centurion->unmade_cohorts < 0)
+           Error("Graph cycles through centurion %s", centurion->name);
+    } else {
+       centurion = cgn;
+    }
+    parents = centurion->parents;
+
+    /* If this was a .ORDER node, schedule the RHS */
+    Lst_ForEach(centurion->order_succ, MakeBuildParent, Lst_First(toBeMade));
+
+    /* Now mark all the parents as having one less unmade child */
+    if (Lst_Open(parents) == SUCCESS) {
+       while ((ln = Lst_Next(parents)) != NULL) {
+           pgn = (GNode *)Lst_Datum(ln);
+           if (DEBUG(MAKE))
+               fprintf(debug_file, "inspect parent %s%s: flags %x, "
+                           "type %x, made %d, unmade %d ",
+                       pgn->name, pgn->cohort_num, pgn->flags,
+                       pgn->type, pgn->made, pgn->unmade-1);
+
+           if (!(pgn->flags & REMAKE)) {
+               /* This parent isn't needed */
+               if (DEBUG(MAKE))
+                   fprintf(debug_file, "- not needed\n");
+               continue;
+           }
+           if (mtime == 0 && !(cgn->type & OP_WAIT))
+               pgn->flags |= FORCE;
+
+           /*
+            * If the parent has the .MADE attribute, its timestamp got
+            * updated to that of its newest child, and its unmake
+            * child count got set to zero in Make_ExpandUse().
+            * However other things might cause us to build one of its
+            * children - and so we mustn't do any processing here when
+            * the child build finishes.
+            */
+           if (pgn->type & OP_MADE) {
+               if (DEBUG(MAKE))
+                   fprintf(debug_file, "- .MADE\n");
+               continue;
+           }
+
+           if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) {
+               if (cgn->made == MADE)
+                   pgn->flags |= CHILDMADE;
+               (void)Make_TimeStamp(pgn, cgn);
+           }
+
+           /*
+            * A parent must wait for the completion of all instances
+            * of a `::' dependency.
+            */
+           if (centurion->unmade_cohorts != 0 || centurion->made < MADE) {
+               if (DEBUG(MAKE))
+                   fprintf(debug_file,
+                           "- centurion made %d, %d unmade cohorts\n",
+                           centurion->made, centurion->unmade_cohorts);
+               continue;
+           }
+
+           /* One more child of this parent is now made */
+           pgn->unmade -= 1;
+           if (pgn->unmade < 0) {
+               if (DEBUG(MAKE)) {
+                   fprintf(debug_file, "Graph cycles through %s%s\n",
+                       pgn->name, pgn->cohort_num);
+                   Targ_PrintGraph(2);
+               }
+               Error("Graph cycles through %s%s", pgn->name, pgn->cohort_num);
+           }
+
+           /* We must always rescan the parents of .WAIT and .ORDER nodes. */
+           if (pgn->unmade != 0 && !(centurion->type & OP_WAIT)
+                   && !(centurion->flags & DONE_ORDER)) {
+               if (DEBUG(MAKE))
+                   fprintf(debug_file, "- unmade children\n");
+               continue;
+           }
+           if (pgn->made != DEFERRED) {
+               /*
+                * Either this parent is on a different branch of the tree,
+                * or it on the RHS of a .WAIT directive
+                * or it is already on the toBeMade list.
+                */
+               if (DEBUG(MAKE))
+                   fprintf(debug_file, "- not deferred\n");
+               continue;
+           }
+           if (pgn->order_pred
+                   && Lst_ForEach(pgn->order_pred, MakeCheckOrder, 0)) {
+               /* A .ORDER rule stops us building this */
+               continue;
+           }
+           if (DEBUG(MAKE)) {
+               static int two = 2;
+               fprintf(debug_file, "- %s%s made, schedule %s%s (made %d)\n",
+                       cgn->name, cgn->cohort_num,
+                       pgn->name, pgn->cohort_num, pgn->made);
+               Targ_PrintNode(pgn, &two);
+           }
+           /* Ok, we can schedule the parent again */
+           pgn->made = REQUESTED;
+           (void)Lst_EnQueue(toBeMade, pgn);
+       }
+       Lst_Close(parents);
+    }
+
+    /*
+     * Set the .PREFIX and .IMPSRC variables for all the implied parents
+     * of this node.
+     */
+    if (Lst_Open(cgn->iParents) == SUCCESS) {
+       char    *cpref = Var_Value(PREFIX, cgn, &p1);
+
+       while ((ln = Lst_Next(cgn->iParents)) != NULL) {
+           pgn = (GNode *)Lst_Datum(ln);
+           if (pgn->flags & REMAKE) {
+               Var_Set(IMPSRC, cname, pgn, 0);
+               if (cpref != NULL)
+                   Var_Set(PREFIX, cpref, pgn, 0);
+           }
+       }
+       if (p1)
+           free(p1);
+       Lst_Close(cgn->iParents);
+    }
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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...
+ *     If the child is a .JOIN node, its ALLSRC is propagated to the parent.
+ *
+ *     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
+MakeUnmark(void *cgnp, void *pgnp __unused)
+{
+    GNode      *cgn = (GNode *)cgnp;
+
+    cgn->type &= ~OP_MARK;
+    return (0);
+}
+
+/*
+ * Input:
+ *     cgnp            The child to add
+ *     pgnp            The parent to whose ALLSRC variable it should
+ *                     be added
+ *
+ */
+static int
+MakeAddAllSrc(void *cgnp, void *pgnp)
+{
+    GNode      *cgn = (GNode *)cgnp;
+    GNode      *pgn = (GNode *)pgnp;
+
+    if (cgn->type & OP_MARK)
+       return (0);
+    cgn->type |= OP_MARK;
+
+    if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) {
+       char *child, *allsrc;
+       char *p1 = NULL, *p2 = NULL;
+
+       if (cgn->type & OP_ARCHV)
+           child = Var_Value(MEMBER, cgn, &p1);
+       else
+           child = cgn->path ? cgn->path : cgn->name;
+       if (cgn->type & OP_JOIN) {
+           allsrc = Var_Value(ALLSRC, cgn, &p2);
+       } else {
+           allsrc = child;
+       }
+       if (allsrc != NULL)
+               Var_Append(ALLSRC, allsrc, pgn);
+       if (p2)
+           free(p2);
+       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);
+       }
+       if (p1)
+           free(p1);
+    }
+    return (0);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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, MakeUnmark, gn);
+    Lst_ForEach(gn->children, MakeAddAllSrc, gn);
+
+    if (!Var_Exists (OODATE, gn)) {
+       Var_Set(OODATE, "", gn, 0);
+    }
+    if (!Var_Exists (ALLSRC, gn)) {
+       Var_Set(ALLSRC, "", gn, 0);
+    }
+
+    if (gn->type & OP_JOIN) {
+       char *p1;
+       Var_Set(TARGET, Var_Value(ALLSRC, gn, &p1), gn, 0);
+       if (p1)
+           free(p1);
+    }
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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 int
+MakeCheckOrder(void *v_bn, void *ignore __unused)
+{
+    GNode *bn = v_bn;
+
+    if (bn->made >= MADE || !(bn->flags & REMAKE))
+       return 0;
+    if (DEBUG(MAKE))
+       fprintf(debug_file, "MakeCheckOrder: Waiting for .ORDER node %s%s\n",
+               bn->name, bn->cohort_num);
+    return 1;
+}
+
+static int
+MakeBuildChild(void *v_cn, void *toBeMade_next)
+{
+    GNode *cn = v_cn;
+
+    if (DEBUG(MAKE))
+       fprintf(debug_file, "MakeBuildChild: inspect %s%s, made %d, type %x\n",
+           cn->name, cn->cohort_num, cn->made, cn->type);
+    if (cn->made > DEFERRED)
+       return 0;
+
+    /* If this node is on the RHS of a .ORDER, check LHSs. */
+    if (cn->order_pred && Lst_ForEach(cn->order_pred, MakeCheckOrder, 0)) {
+       /* Can't build this (or anything else in this child list) yet */
+       cn->made = DEFERRED;
+       return 1;
+    }
+
+    if (DEBUG(MAKE))
+       fprintf(debug_file, "MakeBuildChild: schedule %s%s\n",
+               cn->name, cn->cohort_num);
+
+    cn->made = REQUESTED;
+    if (toBeMade_next == NULL)
+       Lst_AtEnd(toBeMade, cn);
+    else
+       Lst_InsertBefore(toBeMade, toBeMade_next, cn);
+
+    if (cn->unmade_cohorts != 0)
+       Lst_ForEach(cn->cohorts, MakeBuildChild, toBeMade_next);
+
+    /*
+     * If this node is a .WAIT node with unmade chlidren
+     * then don't add the next sibling.
+     */
+    return cn->type & OP_WAIT && cn->unmade > 0;
+}
+
+/* When a .ORDER RHS node completes we do this on each LHS */
+static int
+MakeBuildParent(void *v_pn, void *toBeMade_next)
+{
+    GNode *pn = v_pn;
+
+    if (pn->made != DEFERRED)
+       return 0;
+
+    if (MakeBuildChild(pn, toBeMade_next) == 0) {
+       /* Mark so that when this node is built we reschedule its parents */
+       pn->flags |= DONE_ORDER;
+    }
+
+    return 0;
+}
+
+static Boolean
+MakeStartJobs(void)
+{
+    GNode      *gn;
+    int                have_token = 0;
+
+    while (!Lst_IsEmpty (toBeMade)) {
+       /* Get token now to avoid cycling job-list when we only have 1 token */
+       if (!have_token && !Job_TokenWithdraw())
+           break;
+       have_token = 1;
+
+       gn = (GNode *)Lst_DeQueue(toBeMade);
+       if (DEBUG(MAKE))
+           fprintf(debug_file, "Examining %s%s...\n",
+                   gn->name, gn->cohort_num);
+
+       if (gn->made != REQUESTED) {
+           if (DEBUG(MAKE))
+               fprintf(debug_file, "state %d\n", gn->made);
+
+           make_abort(gn, __LINE__);
+       }
+
+       if (gn->checked == checked) {
+           /* We've already looked at this node since a job finished... */
+           if (DEBUG(MAKE))
+               fprintf(debug_file, "already checked %s%s\n",
+                       gn->name, gn->cohort_num);
+           gn->made = DEFERRED;
+           continue;
+       }
+       gn->checked = checked;
+
+       if (gn->unmade != 0) {
+           /*
+            * We can't build this yet, add all unmade children to toBeMade,
+            * just before the current first element.
+            */
+           gn->made = DEFERRED;
+           Lst_ForEach(gn->children, MakeBuildChild, Lst_First(toBeMade));
+           /* and drop this node on the floor */
+           if (DEBUG(MAKE))
+               fprintf(debug_file, "dropped %s%s\n", gn->name, gn->cohort_num);
+           continue;
+       }
+
+       gn->made = BEINGMADE;
+       if (Make_OODate(gn)) {
+           if (DEBUG(MAKE)) {
+               fprintf(debug_file, "out-of-date\n");
+           }
+           if (queryFlag) {
+               return (TRUE);
+           }
+           Make_DoAllVar(gn);
+           Job_Make(gn);
+           have_token = 0;
+       } else {
+           if (DEBUG(MAKE)) {
+               fprintf(debug_file, "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);
+       }
+    }
+
+    if (have_token)
+       Job_TokenReturn();
+
+    return (FALSE);
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     gnp             Node to examine
+ *     cyclep          True if gn->unmade being non-zero implies a
+ *                     cycle in the graph, not an error in an
+ *                     inferior.
+ *
+ * Results:
+ *     Always returns 0.
+ *
+ * Side Effects:
+ *     A message may be printed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+MakePrintStatusOrder(void *ognp, void *gnp)
+{
+    GNode *ogn = ognp;
+    GNode *gn = gnp;
+
+    if (!(ogn->flags & REMAKE) || ogn->made > REQUESTED)
+       /* not waiting for this one */
+       return 0;
+
+    printf("    `%s%s' has .ORDER dependency against %s%s "
+               "(made %d, flags %x, type %x)\n",
+           gn->name, gn->cohort_num,
+           ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type);
+    if (DEBUG(MAKE) && debug_file != stdout)
+       fprintf(debug_file, "    `%s%s' has .ORDER dependency against %s%s "
+                   "(made %d, flags %x, type %x)\n",
+               gn->name, gn->cohort_num,
+               ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type);
+    return 0;
+}
+
+static int
+MakePrintStatus(void *gnp, void *v_errors)
+{
+    GNode      *gn = (GNode *)gnp;
+    int        *errors = v_errors;
+
+    if (gn->flags & DONECYCLE)
+       /* We've completely processed this node before, don't do it again. */
+       return 0;
+
+    if (gn->unmade == 0) {
+       gn->flags |= DONECYCLE;
+       switch (gn->made) {
+       case UPTODATE:
+           printf("`%s%s' is up to date.\n", gn->name, gn->cohort_num);
+           break;
+       case MADE:
+           break;
+       case UNMADE:
+       case DEFERRED:
+       case REQUESTED:
+       case BEINGMADE:
+           (*errors)++;
+           printf("`%s%s' was not built (made %d, flags %x, type %x)!\n",
+                   gn->name, gn->cohort_num, gn->made, gn->flags, gn->type);
+           if (DEBUG(MAKE) && debug_file != stdout)
+               fprintf(debug_file,
+                       "`%s%s' was not built (made %d, flags %x, type %x)!\n",
+                       gn->name, gn->cohort_num, gn->made, gn->flags, gn->type);
+           /* Most likely problem is actually caused by .ORDER */
+           Lst_ForEach(gn->order_pred, MakePrintStatusOrder, gn);
+           break;
+       default:
+           /* Errors - already counted */
+           printf("`%s%s' not remade because of errors.\n",
+                   gn->name, gn->cohort_num);
+           if (DEBUG(MAKE) && debug_file != stdout)
+               fprintf(debug_file, "`%s%s' not remade because of errors.\n",
+                       gn->name, gn->cohort_num);
+           break;
+       }
+       return 0;
+    }
+
+    if (DEBUG(MAKE))
+       fprintf(debug_file, "MakePrintStatus: %s%s has %d unmade children\n",
+               gn->name, gn->cohort_num, gn->unmade);
+    /*
+     * If printing cycles and came to one that has unmade children,
+     * print out the cycle by recursing on its children.
+     */
+    if (!(gn->flags & CYCLE)) {
+       /* Fist time we've seen this node, check all children */
+       gn->flags |= CYCLE;
+       Lst_ForEach(gn->children, MakePrintStatus, errors);
+       /* Mark that this node needn't be processed again */
+       gn->flags |= DONECYCLE;
+       return 0;
+    }
+
+    /* Only output the error once per node */
+    gn->flags |= DONECYCLE;
+    Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num);
+    if ((*errors)++ > 100)
+       /* Abandon the whole error report */
+       return 1;
+
+    /* Reporting for our children will give the rest of the loop */
+    Lst_ForEach(gn->children, MakePrintStatus, errors);
+    return 0;
+}
+\f
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_ExpandUse --
+ *     Expand .USE nodes and create a new targets list
+ *
+ * Input:
+ *     targs           the initial list of targets
+ *
+ * Side Effects:
+ *-----------------------------------------------------------------------
+ */
+void
+Make_ExpandUse(Lst targs)
+{
+    GNode  *gn;                /* a temporary pointer */
+    Lst    examine;    /* List of targets to examine */
+
+    examine = Lst_Duplicate(targs, NULL);
+
+    /*
+     * 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->flags & REMAKE)
+           /* We've looked at this one already */
+           continue;
+       gn->flags |= REMAKE;
+       if (DEBUG(MAKE))
+           fprintf(debug_file, "Make_ExpandUse: examine %s%s\n",
+                   gn->name, gn->cohort_num);
+
+       if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) {
+           /* Append all the 'cohorts' to the list of things to examine */
+           Lst new;
+           new = Lst_Duplicate(gn->cohorts, NULL);
+           Lst_Concat(new, examine, LST_CONCLINK);
+           examine = new;
+       }
+
+       /*
+        * Apply any .USE rules before looking for implicit dependencies
+        * to make sure everything has commands that should...
+        * Make sure that the TARGET is set, so that we can make
+        * expansions.
+        */
+       if (gn->type & OP_ARCHV) {
+           char *eoa, *eon;
+           eoa = strchr(gn->name, '(');
+           eon = strchr(gn->name, ')');
+           if (eoa == NULL || eon == NULL)
+               continue;
+           *eoa = '\0';
+           *eon = '\0';
+           Var_Set(MEMBER, eoa + 1, gn, 0);
+           Var_Set(ARCHIVE, gn->name, gn, 0);
+           *eoa = '(';
+           *eon = ')';
+       }
+
+       (void)Dir_MTime(gn);
+       Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
+       Lst_ForEach(gn->children, MakeUnmark, gn);
+       Lst_ForEach(gn->children, MakeHandleUse, gn);
+
+       if ((gn->type & OP_MADE) == 0)
+           Suff_FindDeps(gn);
+       else {
+           /* Pretend we made all this node's children */
+           Lst_ForEach(gn->children, MakeFindChild, gn);
+           if (gn->unmade != 0)
+                   printf("Warning: %s%s still has %d unmade children\n",
+                           gn->name, gn->cohort_num, gn->unmade);
+       }
+
+       if (gn->unmade != 0)
+           Lst_ForEach(gn->children, MakeAddChild, examine);
+    }
+
+    Lst_Destroy(examine, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Make_ProcessWait --
+ *     Convert .WAIT nodes into dependencies
+ *
+ * Input:
+ *     targs           the initial list of targets
+ *
+ *-----------------------------------------------------------------------
+ */
+
+static int
+link_parent(void *cnp, void *pnp)
+{
+    GNode *cn = cnp;
+    GNode *pn = pnp;
+
+    Lst_AtEnd(pn->children, cn);
+    Lst_AtEnd(cn->parents, pn);
+    pn->unmade++;
+    return 0;
+}
+
+static int
+add_wait_dep(void *v_cn, void *v_wn)
+{
+    GNode *cn = v_cn;
+    GNode *wn = v_wn;
+
+    if (cn == wn)
+       return 1;
+
+    if (cn == NULL || wn == NULL) {
+       printf("bad wait dep %p %p\n", cn, wn);
+       exit(4);
+    }
+    if (DEBUG(MAKE))
+        fprintf(debug_file, ".WAIT: add dependency %s%s -> %s\n",
+               cn->name, cn->cohort_num, wn->name);
+
+    Lst_AtEnd(wn->children, cn);
+    wn->unmade++;
+    Lst_AtEnd(cn->parents, wn);
+    return 0;
+}
+
+static void
+Make_ProcessWait(Lst targs)
+{
+    GNode  *pgn;       /* 'parent' node we are examining */
+    GNode  *cgn;       /* Each child in turn */
+    LstNode owln;      /* Previous .WAIT node */
+    Lst    examine;    /* List of targets to examine */
+    LstNode ln;
+
+    /*
+     * We need all the nodes to have a common parent in order for the
+     * .WAIT and .ORDER scheduling to work.
+     * Perhaps this should be done earlier...
+     */
+
+    pgn = Targ_NewGN(".MAIN");
+    pgn->flags = REMAKE;
+    pgn->type = OP_PHONY | OP_DEPENDS;
+    /* Get it displayed in the diag dumps */
+    Lst_AtFront(Targ_List(), pgn);
+
+    Lst_ForEach(targs, link_parent, pgn);
+
+    /* Start building with the 'dummy' .MAIN' node */
+    MakeBuildChild(pgn, NULL);
+
+    examine = Lst_Init(FALSE);
+    Lst_AtEnd(examine, pgn);
+
+    while (!Lst_IsEmpty (examine)) {
+       pgn = Lst_DeQueue(examine);
+   
+       /* We only want to process each child-list once */
+       if (pgn->flags & DONE_WAIT)
+           continue;
+       pgn->flags |= DONE_WAIT;
+       if (DEBUG(MAKE))
+           fprintf(debug_file, "Make_ProcessWait: examine %s\n", pgn->name);
+
+       if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts)) {
+           /* Append all the 'cohorts' to the list of things to examine */
+           Lst new;
+           new = Lst_Duplicate(pgn->cohorts, NULL);
+           Lst_Concat(new, examine, LST_CONCLINK);
+           examine = new;
+       }
+
+       owln = Lst_First(pgn->children);
+       Lst_Open(pgn->children);
+       for (; (ln = Lst_Next(pgn->children)) != NULL; ) {
+           cgn = Lst_Datum(ln);
+           if (cgn->type & OP_WAIT) {
+               /* Make the .WAIT node depend on the previous children */
+               Lst_ForEachFrom(pgn->children, owln, add_wait_dep, cgn);
+               owln = ln;
+           } else {
+               Lst_AtEnd(examine, cgn);
+           }
+       }
+       Lst_Close(pgn->children);
+    }
+
+    Lst_Destroy(examine, NULL);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     targs           the initial list of targets
+ *
+ * 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)
+{
+    int                    errors;     /* Number of errors the Job module reports */
+
+    /* Start trying to make the current targets... */
+    toBeMade = Lst_Init(FALSE);
+
+    Make_ExpandUse(targs);
+    Make_ProcessWait(targs);
+
+    if (DEBUG(MAKE)) {
+        fprintf(debug_file, "#***# full graph\n");
+        Targ_PrintGraph(1);
+    }
+
+    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());
+    }
+    /*
+     * 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 (!Lst_IsEmpty(toBeMade) || jobTokensRunning > 0) {
+       Job_CatchOutput();
+       (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.
+     */
+    if (DEBUG(MAKE))
+        fprintf(debug_file, "done: errors %d\n", errors);
+    if (errors == 0) {
+       Lst_ForEach(targs, MakePrintStatus, &errors);
+       if (DEBUG(MAKE)) {
+           fprintf(debug_file, "done: errors %d\n", errors);
+           if (errors)
+               Targ_PrintGraph(4);
+       }
+    }
+    return errors != 0;
+}
diff --git a/commands/bmake/make.h b/commands/bmake/make.h
new file mode 100644 (file)
index 0000000..582dae4
--- /dev/null
@@ -0,0 +1,467 @@
+/*     $NetBSD: make.h,v 1.79 2009/09/08 17:29:20 sjg Exp $    */
+
+/*
+ * 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. 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: @(#)make.h        8.3 (Berkeley) 6/13/95
+ */
+
+/*
+ * 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.
+ *
+ *     from: @(#)make.h        8.3 (Berkeley) 6/13/95
+ */
+
+/*-
+ * make.h --
+ *     The global definitions for pmake
+ */
+
+#ifndef _MAKE_H_
+#define _MAKE_H_
+
+#include <sys/types.h>
+#include <sys/param.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef BSD4_4
+# include <sys/cdefs.h>
+#endif
+
+#if !defined(__GNUC_PREREQ__)
+#if defined(__GNUC__)
+#define        __GNUC_PREREQ__(x, y)                                           \
+       ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) ||                  \
+        (__GNUC__ > (x)))
+#else /* defined(__GNUC__) */
+#define        __GNUC_PREREQ__(x, y)   0
+#endif /* defined(__GNUC__) */
+#endif /* !defined(__GNUC_PREREQ__) */
+
+#if !defined(__unused)
+#if __GNUC_PREREQ__(2, 7)
+#define __unused        __attribute__((__unused__))
+#else
+#define __unused        /* delete */
+#endif
+#endif
+
+#include "sprite.h"
+#include "lst.h"
+#include "hash.h"
+#include "config.h"
+#include "buf.h"
+#include "make_malloc.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 file system.
+ *     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 (parents)
+ *     11) a list of nodes on which this depends (children)
+ *     12) a list of nodes that depend on this, as gleaned from the
+ *         transformation rules (iParents)
+ *     13) a list of ancestor nodes, which includes parents, iParents,
+ *         and recursive parents of parents
+ *     14) a list of nodes of the same name created by the :: operator
+ *     15) a list of nodes that must be made (if they're made) before
+ *         this node can be, but that do not enter into the datedness of
+ *         this node.
+ *     16) a list of nodes that must be made (if they're made) before
+ *         this node or any child of this node can be, but that do not
+ *         enter into the datedness of this node.
+ *     17) 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.
+ *     18) a Lst of ``local'' variables that are specific to this target
+ *        and this target only (qv. var.c [$@ $< $?, etc.])
+ *     19) 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            *uname;            /* The unexpanded name of a .USE node */
+    char           *path;      /* The full pathname of the file */
+    int             type;              /* Its type (see the OP flags, below) */
+
+    int             flags;
+#define REMAKE         0x1     /* this target needs to be (re)made */
+#define        CHILDMADE       0x2     /* children of this target were made */
+#define FORCE          0x4     /* children don't exist, and we pretend made */
+#define DONE_WAIT      0x8     /* Set by Make_ProcessWait() */
+#define DONE_ORDER     0x10    /* Build requested by .ORDER processing */
+#define FROM_DEPEND    0x20    /* Node created from .depend */
+#define CYCLE          0x1000  /* Used by MakePrintStatus */
+#define DONECYCLE      0x2000  /* Used by MakePrintStatus */
+    enum enum_made {
+       UNMADE, DEFERRED, REQUESTED, BEINGMADE,
+       MADE, UPTODATE, ERROR, ABORTED
+    }              made;       /* Set to reflect the state of processing
+                                * on this node:
+                                *  UNMADE - Not examined yet
+                                *  DEFERRED - Examined once (building child)
+                                *  REQUESTED - on toBeMade list
+                                *  BEINGMADE - Target is already being made.
+                                *      Indicates a cycle in the graph.
+                                *  MADE - Was out-of-date and has been made
+                                *  UPTODATE - Was already up-to-date
+                                *  ERROR - An error occurred while it was being
+                                *      made (used only in compat mode)
+                                *  ABORTED - The target was aborted due to
+                                *      an error making an inferior (compat).
+                                */
+    int             unmade;            /* The number of unmade children */
+
+    time_t          mtime;             /* Its modification time */
+    time_t                 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             order_pred;        /* .ORDER nodes we need made */
+    Lst             order_succ;        /* .ORDER nodes who need us */
+
+    char           cohort_num[8]; /* #n for this cohort */
+    int                    unmade_cohorts;/* # of unmade instances on the
+                                     cohorts list */
+    struct GNode    *centurion;        /* Pointer to the first instance of a ::
+                                  node; only set when on a cohorts list */
+    unsigned int    checked;    /* Last time we tried to makle this node */
+
+    Hash_Table      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) */
+    const char     *fname;     /* filename where the GNode got defined */
+    int                     lineno;    /* line number where the GNode got defined */
+} 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 recursive 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_MADE         0x00000800  /* Assume the children of the node have
+                                    * been already made */
+#define OP_SPECIAL     0x00001000  /* Special .BEGIN, .END, .INTERRUPT */
+#define        OP_USEBEFORE    0x00002000  /* Like .USE, only prepend commands */
+#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 */
+#define OP_NOPATH      0x00020000  /* Don't search for file in the path */
+#define OP_WAIT        0x00040000  /* .WAIT phony node */
+/* 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 */
+#define        OP_MARK         0x01000000  /* Node found while expanding .ALLSRC */
+
+#define NoExecute(gn) ((gn->type & OP_MAKE) ? noRecursiveExecute : noExecute)
+/*
+ * 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)
+
+#define OP_NOTARGET (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM)
+
+/*
+ * 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_NOCREATE  0x00      /* don't create it */
+#define TARG_CREATE    0x01      /* create node if not found */
+#define TARG_NOHASH    0x02      /* don't look in/add to hash table */
+
+/*
+ * 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_ADDSLASH   0x02    /* 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 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  noExecute;     /* True if should execute nothing */
+extern Boolean  noRecursiveExecute;            /* 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         queryFlag;      /* TRUE if we aren't supposed to really make
+                                * anything, just see if the targets are out-
+                                * of-date */
+extern Boolean doing_depend;   /* TRUE if processing .depend */
+
+extern Boolean checkEnvFirst;  /* TRUE if environment should be searched for
+                                * variables before the global context */
+extern Boolean jobServer;      /* a jobServer already exists */
+
+extern Boolean parseWarnFatal; /* TRUE if makefile parsing warnings are
+                                * treated as errors */
+
+extern Boolean varNoExportEnv; /* TRUE if we should not export variables
+                                * set on the command line to the env. */
+
+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 GNode   *VAR_FOR;       /* Iteration variables */
+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. */
+extern Lst     defIncPath;     /* The default include path. */
+
+extern char    *progname;      /* The program name */
+
+#define        MAKEFLAGS       ".MAKEFLAGS"
+#define        MAKEOVERRIDES   ".MAKEOVERRIDES"
+#define        MAKE_JOB_PREFIX ".MAKE.JOB.PREFIX" /* prefix for job target output */
+#define        MAKE_EXPORTED   ".MAKE.EXPORTED"   /* variables we export */
+#define        MAKE_MAKEFILES  ".MAKE.MAKEFILES"  /* all the makefiles we read */
+#define        MAKE_LEVEL      ".MAKE.LEVEL"      /* recursion level */
+
+/*
+ * debug control:
+ *     There is one bit per module.  It is up to the module what debug
+ *     information to print.
+ */
+FILE *debug_file;              /* Output written here - default stdout */
+extern int debug;
+#define        DEBUG_ARCH      0x00001
+#define        DEBUG_COND      0x00002
+#define        DEBUG_DIR       0x00004
+#define        DEBUG_GRAPH1    0x00008
+#define        DEBUG_GRAPH2    0x00010
+#define        DEBUG_JOB       0x00020
+#define        DEBUG_MAKE      0x00040
+#define        DEBUG_SUFF      0x00080
+#define        DEBUG_TARG      0x00100
+#define        DEBUG_VAR       0x00200
+#define DEBUG_FOR      0x00400
+#define DEBUG_SHELL    0x00800
+#define DEBUG_ERROR    0x01000
+#define DEBUG_LOUD     0x02000
+#define DEBUG_GRAPH3   0x10000
+#define DEBUG_SCRIPT   0x20000
+#define DEBUG_PARSE    0x40000
+#define DEBUG_CWD      0x80000
+
+#define CONCAT(a,b)    a##b
+
+#define        DEBUG(module)   (debug & CONCAT(DEBUG_,module))
+
+#include "nonints.h"
+
+int Make_TimeStamp(GNode *, GNode *);
+Boolean Make_OODate(GNode *);
+void Make_ExpandUse(Lst);
+time_t Make_Recheck(GNode *);
+void Make_HandleUse(GNode *, GNode *);
+void Make_Update(GNode *);
+void Make_DoAllVar(GNode *);
+Boolean Make_Run(Lst);
+char * Check_Cwd_Cmd(const char *);
+void Check_Cwd(const char **);
+void PrintOnError(const char *);
+void Main_ExportMAKEFLAGS(Boolean);
+Boolean Main_SetObjdir(const char *);
+
+#ifdef __GNUC__
+#define UNCONST(ptr)   ({              \
+    union __unconst {                  \
+       const void *__cp;               \
+       void *__p;                      \
+    } __d;                             \
+    __d.__cp = ptr, __d.__p; })
+#else
+#define UNCONST(ptr)   (void *)(ptr)
+#endif
+
+#ifndef MIN
+#define MIN(a, b) ((a < b) ? a : b)
+#endif
+#ifndef MAX
+#define MAX(a, b) ((a > b) ? a : b)
+#endif
+
+#endif /* _MAKE_H_ */
diff --git a/commands/bmake/make_malloc.c b/commands/bmake/make_malloc.c
new file mode 100644 (file)
index 0000000..ac90d26
--- /dev/null
@@ -0,0 +1,119 @@
+/*     $NetBSD: make_malloc.c,v 1.5 2009/01/24 23:19:50 dsl Exp $      */
+
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifdef MAKE_NATIVE
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: make_malloc.c,v 1.5 2009/01/24 23:19:50 dsl Exp $");
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "make_malloc.h"
+
+#ifndef USE_EMALLOC
+/*
+ * enomem --
+ *     die when out of memory.
+ */
+static void
+enomem(void)
+{
+       extern char *progname;
+
+       (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno));
+       exit(2);
+}
+
+/*
+ * bmake_malloc --
+ *     malloc, but die on error.
+ */
+void *
+bmake_malloc(size_t len)
+{
+       void *p;
+
+       if ((p = malloc(len)) == NULL)
+               enomem();
+       return(p);
+}
+
+/*
+ * bmake_strdup --
+ *     strdup, but die on error.
+ */
+char *
+bmake_strdup(const char *str)
+{
+       size_t len;
+       char *p;
+
+       len = strlen(str) + 1;
+       if ((p = malloc(len)) == NULL)
+               enomem();
+       return memcpy(p, str, len);
+}
+
+/*
+ * bmake_strndup --
+ *     strndup, but die on error.
+ */
+char *
+bmake_strndup(const char *str, size_t max_len)
+{
+       size_t len;
+       char *p;
+
+       if (str == NULL)
+               return NULL;
+
+       len = strlen(str);
+       if (len > max_len)
+               len = max_len;
+       p = bmake_malloc(len + 1);
+       memcpy(p, str, len);
+       p[len] = '\0';
+
+       return(p);
+}
+
+/*
+ * bmake_realloc --
+ *     realloc, but die on error.
+ */
+void *
+bmake_realloc(void *ptr, size_t size)
+{
+       if ((ptr = realloc(ptr, size)) == NULL)
+               enomem();
+       return(ptr);
+}
+#endif
diff --git a/commands/bmake/make_malloc.h b/commands/bmake/make_malloc.h
new file mode 100644 (file)
index 0000000..36d3eff
--- /dev/null
@@ -0,0 +1,41 @@
+/*     $NetBSD: make_malloc.h,v 1.4 2009/01/24 14:43:29 dsl Exp $      */
+
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef USE_EMALLOC
+void *bmake_malloc(size_t);
+void *bmake_realloc(void *, size_t);
+char *bmake_strdup(const char *);
+char *bmake_strndup(const char *, size_t);
+#else
+#include <util.h>
+#define bmake_malloc(x)         emalloc(x)
+#define bmake_realloc(x,y)      erealloc(x,y)
+#define bmake_strdup(x)         estrdup(x)
+#define bmake_strndup(x,y)      estrndup(x,y)
+#endif
+
diff --git a/commands/bmake/nonints.h b/commands/bmake/nonints.h
new file mode 100644 (file)
index 0000000..c22307c
--- /dev/null
@@ -0,0 +1,196 @@
+/*     $NetBSD: nonints.h,v 1.57 2009/11/19 00:30:24 sjg Exp $ */
+
+/*-
+ * 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. 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: @(#)nonints.h     8.3 (Berkeley) 3/19/94
+ */
+
+/*-
+ * 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.
+ *
+ *     from: @(#)nonints.h     8.3 (Berkeley) 3/19/94
+ */
+
+#ifndef MAKE_NATIVE
+#undef __attribute__
+#define __attribute__(x)
+#endif
+
+/* arch.c */
+ReturnStatus Arch_ParseArchive(char **, Lst, GNode *);
+void Arch_Touch(GNode *);
+void Arch_TouchLib(GNode *);
+time_t Arch_MTime(GNode *);
+time_t Arch_MemMTime(GNode *);
+void Arch_FindLib(GNode *, Lst);
+Boolean Arch_LibOODate(GNode *);
+void Arch_Init(void);
+void Arch_End(void);
+int Arch_IsLib(GNode *);
+
+/* compat.c */
+int CompatRunCommand(void *, void *);
+void Compat_Run(Lst);
+int Compat_Make(void *, void *);
+
+/* cond.c */
+struct If;
+int Cond_EvalExpression(const struct If *, char *, Boolean *, int);
+int Cond_Eval(char *);
+void Cond_restore_depth(unsigned int);
+unsigned int Cond_save_depth(void);
+
+/* for.c */
+int For_Eval(char *);
+int For_Accum(char *);
+void For_Run(int);
+
+/* main.c */
+void Main_ParseArgLine(const char *);
+int main(int, char **);
+char *Cmd_Exec(const char *, const char **);
+void Error(const char *, ...) __attribute__((__format__(__printf__, 1, 2)));
+void Fatal(const char *, ...)
+    __attribute__((__format__(__printf__, 1, 2),__noreturn__));
+void Punt(const char *, ...)
+    __attribute__((__format__(__printf__, 1, 2),__noreturn__));
+void DieHorribly(void) __attribute__((__noreturn__));
+int PrintAddr(void *, void *);
+void Finish(int);
+int eunlink(const char *);
+void execError(const char *, const char *);
+
+/* parse.c */
+void Parse_Error(int, const char *, ...)
+     __attribute__((__format__(__printf__, 2, 3)));
+Boolean Parse_AnyExport(void);
+Boolean Parse_IsVar(char *);
+void Parse_DoVar(char *, GNode *);
+void Parse_AddIncludeDir(char *);
+void Parse_File(const char *, int);
+void Parse_Init(void);
+void Parse_End(void);
+void Parse_SetInput(const char *, int, int, char *(*)(void *), void *);
+Lst Parse_MainName(void);
+
+/* str.c */
+char *str_concat(const char *, const char *, int);
+char **brk_string(const char *, int *, Boolean, char **);
+char *Str_FindSubstring(const char *, const char *);
+int Str_Match(const char *, const char *);
+char *Str_SYSVMatch(const char *, const 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 *, GNode **);
+Lst Suff_GetPath(char *);
+void Suff_DoPaths(void);
+void Suff_AddInclude(char *);
+void Suff_AddLib(char *);
+void Suff_FindDeps(GNode *);
+Lst Suff_FindPath(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);
+Lst Targ_List(void);
+GNode *Targ_NewGN(const char *);
+GNode *Targ_FindNode(const 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 *);
+int Targ_PrintNode(void *, void *);
+char *Targ_FmtTime(time_t);
+void Targ_PrintType(int);
+void Targ_PrintGraph(int);
+void Targ_Propagate(void);
+void Targ_Propagate_Wait(void);
+
+/* var.c */
+void Var_Delete(const char *, GNode *);
+void Var_Set(const char *, const char *, GNode *, int);
+void Var_Append(const char *, const char *, GNode *);
+Boolean Var_Exists(const char *, GNode *);
+char *Var_Value(const char *, GNode *, char **);
+char *Var_Parse(const char *, GNode *, Boolean, int *, void **);
+char *Var_Subst(const char *, const char *, GNode *, Boolean);
+char *Var_GetTail(const char *);
+char *Var_GetHead(const char *);
+void Var_Init(void);
+void Var_End(void);
+void Var_Dump(GNode *);
+void Var_ExportVars(void);
+void Var_Export(char *, int);
+void Var_UnExport(char *);
diff --git a/commands/bmake/parse.c b/commands/bmake/parse.c
new file mode 100644 (file)
index 0000000..49bc79c
--- /dev/null
@@ -0,0 +1,2759 @@
+/*     $NetBSD: parse.c,v 1.160 2009/11/19 00:30:25 sjg Exp $  */
+
+/*
+ * 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: parse.c,v 1.160 2009/11/19 00:30:25 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)parse.c    8.3 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: parse.c,v 1.160 2009/11/19 00:30:25 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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 <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.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 */
+#ifdef CLEANUP
+static Lst                 targCmds;   /* command lines for targets */
+#endif
+static Boolean     inLine;     /* true if currently in a dependency
+                                * line or its commands */
+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 */
+typedef struct IFile {
+    const char      *fname;         /* name of file */
+    int             lineno;         /* current line number in file */
+    int             first_lineno;   /* line number of start of text */
+    int             fd;             /* the open file */
+    int             cond_depth;     /* 'if' nesting when file opened */
+    char            *P_str;         /* point to base of string buffer */
+    char            *P_ptr;         /* point to next char of string buffer */
+    char            *P_end;         /* point to the end of string buffer */
+    int             P_buflen;       /* current size of file buffer */
+    char            *(*nextbuf)(void *); /* Function to get more data */
+    void            *nextbuf_arg;   /* Opaque arg for nextbuf() */
+} IFile;
+
+#define IFILE_BUFLEN 0x8000
+static IFile       *curFile;
+
+
+/*
+ * Definitions for handling #include specifications
+ */
+
+static Lst      includes;      /* stack of IFiles generated by .includes */
+Lst            parseIncPath;   /* list of directories for "..." includes */
+Lst            sysIncPath;     /* list of directories for <...> includes */
+Lst            defIncPath;     /* default 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 */
+    NoPath,        /* .NOPATH */
+    Not,           /* Not special */
+    NotParallel,    /* .NOTPARALLEL */
+    Null,          /* .NULL */
+    ExObjdir,      /* .OBJDIR */
+    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;
+
+#define        LPAREN  '('
+#define        RPAREN  ')'
+/*
+ * 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 {
+    const 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 },
+{ ".MADE",       Attribute,    OP_MADE },
+{ ".MAIN",       Main,         0 },
+{ ".MAKE",       Attribute,    OP_MAKE },
+{ ".MAKEFLAGS",          MFlags,       0 },
+{ ".MFLAGS",     MFlags,       0 },
+{ ".NOPATH",     NoPath,       OP_NOPATH },
+{ ".NOTMAIN",    Attribute,    OP_NOTMAIN },
+{ ".NOTPARALLEL", NotParallel, 0 },
+{ ".NO_PARALLEL", NotParallel, 0 },
+{ ".NULL",       Null,         0 },
+{ ".OBJDIR",     ExObjdir,     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 },
+{ ".USEBEFORE",   Attribute,           OP_USEBEFORE },
+{ ".WAIT",       Wait,         0 },
+};
+
+static int ParseIsEscaped(const char *, const char *);
+static void ParseErrorInternal(const char *, size_t, int, const char *, ...)
+     __attribute__((__format__(__printf__, 4, 5)));
+static void ParseVErrorInternal(FILE *, const char *, size_t, int, const char *, va_list)
+     __attribute__((__format__(__printf__, 5, 0)));
+static int ParseFindKeyword(const char *);
+static int ParseLinkSrc(void *, void *);
+static int ParseDoOp(void *, void *);
+static void ParseDoSrc(int, const char *);
+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 void ParseHasCommands(void *);
+static void ParseDoInclude(char *);
+static void ParseSetParseFile(const char *);
+#ifdef SYSVINCLUDE
+static void ParseTraditionalInclude(char *);
+#endif
+static int ParseEOF(void);
+static char *ParseReadLine(void);
+static void ParseFinishLine(void);
+static void ParseMark(GNode *);
+
+extern int  maxJobs;
+
+
+/*-
+ *----------------------------------------------------------------------
+ * ParseIsEscaped --
+ *     Check if the current character is escaped on the current line
+ *
+ * Results:
+ *     0 if the character is not backslash escaped, 1 otherwise
+ *
+ * Side Effects:
+ *     None
+ *----------------------------------------------------------------------
+ */
+static int
+ParseIsEscaped(const char *line, const char *c)
+{
+    int active = 0;
+    for (;;) {
+       if (line == c)
+           return active;
+       if (*--c != '\\')
+           return active;
+       active = !active;
+    }
+}
+
+/*-
+ *----------------------------------------------------------------------
+ * ParseFindKeyword --
+ *     Look in the table of keywords for one matching the given string.
+ *
+ * Input:
+ *     str             String to find
+ *
+ * Results:
+ *     The index of the keyword, or -1 if it isn't there.
+ *
+ * Side Effects:
+ *     None
+ *----------------------------------------------------------------------
+ */
+static int
+ParseFindKeyword(const 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);
+}
+
+/*-
+ * ParseVErrorInternal  --
+ *     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 */
+static void
+ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, int type,
+    const char *fmt, va_list ap)
+{
+       static Boolean fatal_warning_error_printed = FALSE;
+
+       (void)fprintf(f, "%s: ", progname);
+
+       if (cfname != NULL) {
+               (void)fprintf(f, "\"");
+               if (*cfname != '/' && strcmp(cfname, "(stdin)") != 0) {
+                       char *cp;
+                       const char *dir;
+
+                       /*
+                        * Nothing is more anoying than not knowing
+                        * which Makefile is the culprit.
+                        */
+                       dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &cp);
+                       if (dir == NULL || *dir == '\0' ||
+                           (*dir == '.' && dir[1] == '\0'))
+                               dir = Var_Value(".CURDIR", VAR_GLOBAL, &cp);
+                       if (dir == NULL)
+                               dir = ".";
+
+                       (void)fprintf(f, "%s/%s", dir, cfname);
+               } else
+                       (void)fprintf(f, "%s", cfname);
+
+               (void)fprintf(f, "\" line %d: ", (int)clineno);
+       }
+       if (type == PARSE_WARNING)
+               (void)fprintf(f, "warning: ");
+       (void)vfprintf(f, fmt, ap);
+       (void)fprintf(f, "\n");
+       (void)fflush(f);
+       if (type == PARSE_FATAL || parseWarnFatal)
+               fatals += 1;
+       if (parseWarnFatal && !fatal_warning_error_printed) {
+               Error("parsing warnings being treated as errors");
+               fatal_warning_error_printed = TRUE;
+       }
+}
+
+/*-
+ * ParseErrorInternal  --
+ *     Error function
+ *
+ * Results:
+ *     None
+ *
+ * Side Effects:
+ *     None
+ */
+/* VARARGS */
+static void
+ParseErrorInternal(const char *cfname, size_t clineno, int type,
+    const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap);
+       va_end(ap);
+
+       if (debug_file != stderr && debug_file != stdout) {
+               va_start(ap, fmt);
+               ParseVErrorInternal(debug_file, cfname, clineno, type, fmt, ap);
+               va_end(ap);
+       }
+}
+
+/*-
+ * Parse_Error  --
+ *     External interface to ParseErrorInternal; uses the default filename
+ *     Line number.
+ *
+ * Results:
+ *     None
+ *
+ * Side Effects:
+ *     None
+ */
+/* VARARGS */
+void
+Parse_Error(int type, const char *fmt, ...)
+{
+       va_list ap;
+       const char *fname;
+       size_t lineno;
+
+       if (curFile == NULL) {
+               fname = NULL;
+               lineno = 0;
+       } else {
+               fname = curFile->fname;
+               lineno = curFile->lineno;
+       }
+
+       va_start(ap, fmt);
+       ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap);
+       va_end(ap);
+
+       if (debug_file != stderr && debug_file != stdout) {
+               va_start(ap, fmt);
+               ParseVErrorInternal(debug_file, fname, lineno, type, fmt, ap);
+               va_end(ap);
+       }
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     pgnp            The parent node
+ *     cgpn            The child node
+ *
+ * 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 ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts))
+       pgn = (GNode *)Lst_Datum(Lst_Last(pgn->cohorts));
+    (void)Lst_AtEnd(pgn->children, cgn);
+    if (specType == Not)
+           (void)Lst_AtEnd(cgn->parents, pgn);
+    pgn->unmade += 1;
+    if (DEBUG(PARSE)) {
+       fprintf(debug_file, "# ParseLinkSrc: added child %s - %s\n", pgn->name, cgn->name);
+       Targ_PrintNode(pgn, 0);
+       Targ_PrintNode(cgn, 0);
+    }
+    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.
+ *
+ * Input:
+ *     gnp             The node to which the operator is to be applied
+ *     opp             The operator to apply
+ *
+ * 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;
+
+       /*
+        * Propagate copied bits to the initial node.  They'll be propagated
+        * back to the rest of the cohorts later.
+        */
+       gn->type |= op & ~OP_OPMASK;
+
+       cohort = Targ_FindNode(gn->name, TARG_NOHASH);
+       /*
+        * 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. (I think this is pointless now, since the relevant list
+        * traversals will no longer see this node anyway. -mycroft)
+        */
+       cohort->type = op | OP_INVISIBLE;
+       (void)Lst_AtEnd(gn->cohorts, cohort);
+       cohort->centurion = gn;
+       gn->unmade_cohorts += 1;
+       snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d",
+               gn->unmade_cohorts);
+    } else {
+       /*
+        * 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);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * 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'
+ *
+ * Input:
+ *     tOp             operator (if any) from special targets
+ *     src             name of the source to handle
+ *
+ * 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, const char *src)
+{
+    GNode      *gn = NULL;
+    static int wait_number = 0;
+    char wait_src[16];
+
+    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, &op);
+               return;
+           }
+           if (parseKeywords[keywd].spec == Wait) {
+               /*
+                * We add a .WAIT node in the dependency list.
+                * After any dynamic dependencies (and filename globbing)
+                * have happened, it is given a dependency on the each
+                * previous child back to and previous .WAIT node.
+                * The next child won't be scheduled until the .WAIT node
+                * is built.
+                * We give each .WAIT node a unique name (mainly for diag).
+                */
+               snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number);
+               gn = Targ_FindNode(wait_src, TARG_NOHASH);
+               gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN;
+               Lst_ForEach(targets, ParseLinkSrc, gn);
+               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, bmake_strdup(src));
+       /*
+        * Add the name to the .TARGETS variable as well, so the user can
+        * 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->order_succ, gn);
+           (void)Lst_AtEnd(gn->order_pred, predecessor);
+           if (DEBUG(PARSE)) {
+               fprintf(debug_file, "# ParseDoSrc: added Order dependency %s - %s\n",
+                       predecessor->name, gn->name);
+               Targ_PrintNode(predecessor, 0);
+               Targ_PrintNode(gn, 0);
+           }
+       }
+       /*
+        * 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.
+        */
+
+       /* Find/create the 'src' node and attach to all targets */
+       gn = Targ_FindNode(src, TARG_CREATE);
+       if (tOp) {
+           gn->type |= tOp;
+       } else {
+           Lst_ForEach(targets, ParseLinkSrc, gn);
+       }
+       break;
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     gnp             Node to examine
+ *
+ * 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)
+{
+    GNode        *gn = (GNode *)gnp;
+    if ((gn->type & OP_NOTARGET) == 0) {
+       mainNode = gn;
+       Targ_SetMain(gn);
+       return (dummy ? 1 : 1);
+    } else {
+       return (dummy ? 0 : 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)
+{
+    (void)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)
+{
+    Dir_ClearPath((Lst) path);
+    return(dummy ? 0 : 0);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoDependency  --
+ *     Parse the dependency line in line.
+ *
+ * Input:
+ *     line            the line to parse
+ *
+ * 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 = NULL;  /* 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 */
+    char          *lstart = line;
+
+    if (DEBUG(PARSE))
+       fprintf(debug_file, "ParseDoDependency(%s)\n", line);
+    tOp = 0;
+
+    specType = Not;
+    paths = (Lst)NULL;
+
+    curTargs = Lst_Init(FALSE);
+
+    do {
+       for (cp = line; *cp && (ParseIsEscaped(lstart, cp) ||
+                    !(isspace((unsigned char)*cp) ||
+                        *cp == '!' || *cp == ':' || *cp == LPAREN));
+                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;
+               void    *freeIt;
+               char    *result;
+
+               result = Var_Parse(cp, VAR_CMD, TRUE, &length, &freeIt);
+               if (freeIt)
+                   free(freeIt);
+               cp += length-1;
+           }
+       }
+
+       if (!ParseIsEscaped(lstart, cp) && *cp == LPAREN) {
+           /*
+            * 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);
+               goto out;
+           } 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, lstart[0] == '.' ? "Unknown directive"
+                                    : "Need an operator");
+           goto out;
+       }
+       *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");
+                   goto out;
+               }
+
+               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
+                *      .NOPATH         Don't search for file in the path
+                *      .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, 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|OP_SPECIAL;
+                       (void)Lst_AtEnd(targets, gn);
+                       break;
+                   case Default:
+                       gn = Targ_NewGN(".DEFAULT");
+                       gn->type |= (OP_NOTMAIN|OP_TRANSFORM);
+                       (void)Lst_AtEnd(targets, gn);
+                       DEFAULT = gn;
+                       break;
+                   case NotParallel:
+                       maxJobs = 1;
+                       break;
+                   case SingleShell:
+                       compatMake = TRUE;
+                       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]);
+                   goto out;
+               } else {
+                   if (paths == (Lst)NULL) {
+                       paths = Lst_Init(FALSE);
+                   }
+                   (void)Lst_AtEnd(paths, 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, 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, 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 warning = FALSE;
+
+           while (*cp && (ParseIsEscaped(lstart, cp) ||
+               ((*cp != '!') && (*cp != ':')))) {
+               if (ParseIsEscaped(lstart, cp) ||
+                   (*cp != ' ' && *cp != '\t')) {
+                   warning = TRUE;
+               }
+               cp++;
+           }
+           if (warning) {
+               Parse_Error(PARSE_WARNING, "Extra target ignored");
+           }
+       } else {
+           while (*cp && isspace ((unsigned char)*cp)) {
+               cp++;
+           }
+       }
+       line = cp;
+    } while (*line && (ParseIsEscaped(lstart, line) ||
+       ((*line != '!') && (*line != ':'))));
+
+    /*
+     * Don't need the list of target names anymore...
+     */
+    Lst_Destroy(curTargs, NULL);
+    curTargs = NULL;
+
+    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, lstart[0] == '.' ? "Unknown directive"
+                   : "Missing dependency operator");
+       goto out;
+    }
+
+    cp++;                      /* Advance beyond operator */
+
+    Lst_ForEach(targets, ParseDoOp, &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, NULL);
+               Dir_SetPATH();
+               break;
+#ifdef POSIX
+            case Posix:
+                Var_Set("%POSIX", "1003.2", VAR_GLOBAL, 0);
+                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");
+           goto out;
+       }
+       *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) || (specType == ExObjdir))
+    {
+       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.
+            *
+            * If it was .OBJDIR, the source is a new definition for .OBJDIR,
+            * and will cause make to do a new chdir to that path.
+            */
+           while (*cp && !isspace ((unsigned char)*cp)) {
+               cp++;
+           }
+           savec = *cp;
+           *cp = '\0';
+           switch (specType) {
+               case Suffixes:
+                   Suff_AddSuffix(line, &mainNode);
+                   break;
+               case ExPath:
+                   Lst_ForEach(paths, ParseAddDir, line);
+                   break;
+               case Includes:
+                   Suff_AddInclude(line);
+                   break;
+               case Libs:
+                   Suff_AddLib(line);
+                   break;
+               case Null:
+                   Suff_SetNull(line);
+                   break;
+               case ExObjdir:
+                   Main_SetObjdir(line);
+                   break;
+               default:
+                   break;
+           }
+           *cp = savec;
+           if (savec != '\0') {
+               cp++;
+           }
+           while (*cp && isspace ((unsigned char)*cp)) {
+               cp++;
+           }
+           line = cp;
+       }
+       if (paths) {
+           Lst_Destroy(paths, NULL);
+       }
+       if (specType == ExPath)
+           Dir_SetPATH();
+    } 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.
+            */
+           for (; *cp && !isspace ((unsigned char)*cp); cp++) {
+               if ((*cp == LPAREN) && (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;
+               }
+           }
+
+           if (*cp == LPAREN) {
+               sources = Lst_Init(FALSE);
+               if (Arch_ParseArchive(&line, sources, VAR_CMD) != SUCCESS) {
+                   Parse_Error(PARSE_FATAL,
+                                "Error in source archive spec \"%s\"", line);
+                   goto out;
+               }
+
+               while (!Lst_IsEmpty (sources)) {
+                   gn = (GNode *)Lst_DeQueue(sources);
+                   ParseDoSrc(tOp, gn->name);
+               }
+               Lst_Destroy(sources, NULL);
+               cp = line;
+           } else {
+               if (*cp) {
+                   *cp = '\0';
+                   cp += 1;
+               }
+
+               ParseDoSrc(tOp, line);
+           }
+           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, NULL);
+    }
+
+out:
+    if (curTargs)
+           Lst_Destroy(curTargs, NULL);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     line            the line to check
+ *
+ * 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 */
+    char ch;
+    int level = 0;
+#define ISEQOPERATOR(c) \
+       (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!'))
+
+    /*
+     * Skip to variable name
+     */
+    for (;(*line == ' ') || (*line == '\t'); line++)
+       continue;
+
+    /* Scan for one of the assignment operators outside a variable expansion */
+    while ((ch = *line++) != 0) {
+       if (ch == '(' || ch == '{') {
+           level++;
+           continue;
+       }
+       if (ch == ')' || ch == '}') {
+           level--;
+           continue;
+       }
+       if (level != 0)
+           continue;
+       while (ch == ' ' || ch == '\t') {
+           ch = *line++;
+           wasSpace = TRUE;
+       }
+       if (ch == '=')
+           return TRUE;
+       if (*line == '=' && ISEQOPERATOR(ch))
+           return TRUE;
+       if (wasSpace)
+           return FALSE;
+    }
+
+    return FALSE;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * 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++ =".
+ *
+ * Input:
+ *     line            a line guaranteed to be a variable assignment.
+ *                     This reduces error checks
+ *     ctxt            Context in which to do the assignment
+ *
+ * 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 */
+    Boolean       freeCp = FALSE; /* TRUE if cp needs to be freed,
+                                   * i.e. if any variable expansion was
+                                   * performed */
+    int depth;
+
+    /*
+     * Skip to variable name
+     */
+    while ((*line == ' ') || (*line == '\t')) {
+       line++;
+    }
+
+    /*
+     * Skip to operator character, nulling out whitespace as we go
+     * XXX Rather than counting () and {} we should look for $ and
+     * then expand the variable.
+     */
+    for (depth = 0, cp = line + 1; depth != 0 || *cp != '='; cp++) {
+       if (*cp == '(' || *cp == '{') {
+           depth++;
+           continue;
+       }
+       if (*cp == ')' || *cp == '}') {
+           depth--;
+           continue;
+       }
+       if (depth == 0 && 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 > line && *opc != ':')
+               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, 0);
+
+       cp = Var_Subst(NULL, cp, ctxt, FALSE);
+       oldVars = oldOldVars;
+       freeCp = TRUE;
+
+       Var_Set(line, cp, ctxt, 0);
+    } else if (type == VAR_SHELL) {
+       char *res;
+       const char *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);
+           freeCp = TRUE;
+       }
+
+       res = Cmd_Exec(cp, &error);
+       Var_Set(line, res, ctxt, 0);
+       free(res);
+
+       if (error)
+           Parse_Error(PARSE_WARNING, error, cp);
+    } else {
+       /*
+        * Normal assignment -- just do it.
+        */
+       Var_Set(line, cp, ctxt, 0);
+    }
+    if (strcmp(line, MAKEOVERRIDES) == 0)
+       Main_ExportMAKEFLAGS(FALSE);    /* re-export MAKEFLAGS */
+    else if (strcmp(line, ".CURDIR") == 0) {
+       /*
+        * Somone is being (too?) clever...
+        * Let's pretend they know what they are doing and
+        * re-initialize the 'cur' Path.
+        */
+       Dir_InitCur(cp);
+       Dir_SetPATH();
+    } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) {
+       Job_SetPrefix();
+    } else if (strcmp(line, MAKE_EXPORTED) == 0) {
+       Var_Export(cp, 0);
+    }
+    if (freeCp)
+       free(cp);
+}
+
+
+/*-
+ * ParseAddCmd  --
+ *     Lst_ForEach function to add a command line to all targets
+ *
+ * Input:
+ *     gnp             the node to which the command is to be added
+ *     cmd             the command to add
+ *
+ * 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;
+
+    /* Add to last (ie current) cohort for :: targets */
+    if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts))
+       gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts));
+
+    /* if target already supplied, ignore commands */
+    if (!(gn->type & OP_HAS_COMMANDS)) {
+       (void)Lst_AtEnd(gn->commands, cmd);
+       ParseMark(gn);
+    } else {
+#ifdef notyet
+       /* XXX: We cannot do this until we fix the tree */
+       (void)Lst_AtEnd(gn->commands, cmd);
+       Parse_Error(PARSE_WARNING,
+                    "overriding commands for target \"%s\"; "
+                    "previous commands defined at %s: %d ignored",
+                    gn->name, gn->fname, gn->lineno);
+#else
+       Parse_Error(PARSE_WARNING,
+                    "duplicate script for target \"%s\" ignored",
+                    gn->name);
+       ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING,
+                           "using previous script for \"%s\" defined here",
+                           gn->name);
+#endif
+    }
+    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.
+ *
+ * Input:
+ *     gnp             Node to examine
+ *
+ * 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
+ *
+ * Input:
+ *     dir             The name of the directory to add
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     The directory is appended to the list.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Parse_AddIncludeDir(char *dir)
+{
+    (void)Dir_AddDir(parseIncPath, dir);
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseDoInclude  --
+ *     Push to another file.
+ *
+ *     The input is the line minus the `.'. 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
+Parse_include_file(char *file, Boolean isSystem, int silent)
+{
+    char          *fullname;   /* full pathname of file */
+    char          *newName;
+    char          *prefEnd, *incdir;
+    int           fd;
+    int           i;
+
+    /*
+     * 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.
+     */
+    fullname = file[0] == '/' ? bmake_strdup(file) : NULL;
+
+    if (fullname == NULL && !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.
+        */
+
+       incdir = bmake_strdup(curFile->fname);
+       prefEnd = strrchr(incdir, '/');
+       if (prefEnd != NULL) {
+           *prefEnd = '\0';
+           /* Now do lexical processing of leading "../" on the filename */
+           for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) {
+               prefEnd = strrchr(incdir + 1, '/');
+               if (prefEnd == NULL || strcmp(prefEnd, "/..") == 0)
+                   break;
+               *prefEnd = '\0';
+           }
+           newName = str_concat(incdir, file + i, STR_ADDSLASH);
+           fullname = Dir_FindFile(newName, parseIncPath);
+           if (fullname == NULL)
+               fullname = Dir_FindFile(newName, dirSearchPath);
+           free(newName);
+       }
+       free(incdir);
+
+       if (fullname == NULL) {
+           /*
+            * 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.
+            * If we have a suffix specific path we should use that.
+            */
+           char *suff;
+           Lst suffPath = NULL;
+
+           if ((suff = strrchr(file, '.'))) {
+               suffPath = Suff_GetPath(suff);
+               if (suffPath != NULL) {
+                   fullname = Dir_FindFile(file, suffPath);
+               }
+           }
+           if (fullname == NULL) {
+               fullname = Dir_FindFile(file, parseIncPath);
+               if (fullname == NULL) {
+                   fullname = Dir_FindFile(file, dirSearchPath);
+               }
+           }
+       }
+    }
+
+    /* Looking for a system file or file still not found */
+    if (fullname == NULL) {
+       /*
+        * Look for it on the system path
+        */
+       fullname = Dir_FindFile(file,
+                   Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath);
+    }
+
+    if (fullname == NULL) {
+       if (!silent)
+           Parse_Error(PARSE_FATAL, "Could not find %s", file);
+       return;
+    }
+
+    /* Actually open the file... */
+    fd = open(fullname, O_RDONLY);
+    if (fd == -1) {
+       if (!silent)
+           Parse_Error(PARSE_FATAL, "Cannot open %s", fullname);
+       free(fullname);
+       return;
+    }
+
+    /* Start reading from this file next */
+    Parse_SetInput(fullname, 0, fd, NULL, NULL);
+}
+
+static void
+ParseDoInclude(char *line)
+{
+    char          endc;                /* the character which ends the file spec */
+    char          *cp;         /* current position in file spec */
+    int                  silent = (*line != 'i') ? 1 : 0;
+    char         *file = &line[7 + silent];
+
+    /* 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 == '<') {
+       endc = '>';
+    } else {
+       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);
+
+    Parse_include_file(file, endc == '>', silent);
+    free(file);
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseSetParseFile  --
+ *     Set the .PARSEDIR and .PARSEFILE variables to the dirname and
+ *     basename of the given filename
+ *
+ * Results:
+ *     None
+ *
+ * Side Effects:
+ *     The .PARSEDIR and .PARSEFILE variables are overwritten by the
+ *     dirname and basename of the given filename.
+ *---------------------------------------------------------------------
+ */
+static void
+ParseSetParseFile(const char *filename)
+{
+    char *slash;
+    char *dirname;
+    int len;
+
+    slash = strrchr(filename, '/');
+    if (slash == NULL) {
+       Var_Set(".PARSEDIR", ".", VAR_GLOBAL, 0);
+       Var_Set(".PARSEFILE", filename, VAR_GLOBAL, 0);
+    } else {
+       len = slash - filename;
+       dirname = bmake_malloc(len + 1);
+       memcpy(dirname, filename, len);
+       dirname[len] = 0;
+       Var_Set(".PARSEDIR", dirname, VAR_GLOBAL, 0);
+       Var_Set(".PARSEFILE", slash+1, VAR_GLOBAL, 0);
+       free(dirname);
+    }
+}
+
+/*
+ * Track the makefiles we read - so makefiles can
+ * set dependencies on them.
+ * Avoid adding anything more than once.
+ */
+
+static void
+ParseTrackInput(const char *name)
+{
+    char *old;
+    char *fp = NULL;
+    size_t name_len = strlen(name);
+    
+    old = Var_Value(MAKE_MAKEFILES, VAR_GLOBAL, &fp);
+    if (old) {
+       /* does it contain name? */
+       for (; old != NULL; old = strchr(old, ' ')) {
+           if (*old == ' ')
+               old++;
+           if (memcmp(old, name, name_len) == 0
+                   && (old[name_len] == 0 || old[name_len] == ' '))
+               goto cleanup;
+       }
+    }
+    Var_Append (MAKE_MAKEFILES, name, VAR_GLOBAL);
+ cleanup:
+    if (fp) {
+       free(fp);
+    }
+}
+
+
+/*-
+ *---------------------------------------------------------------------
+ * Parse_setInput  --
+ *     Start Parsing from the given source
+ *
+ * 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_SetInput(const char *name, int line, int fd, char *(*nextbuf)(void *), void *arg)
+{
+    char *buf;
+
+    if (name == NULL)
+       name = curFile->fname;
+    else
+       ParseTrackInput(name);
+
+    if (DEBUG(PARSE))
+       fprintf(debug_file, "Parse_SetInput: file %s, line %d, fd %d, nextbuf %p, arg %p\n",
+               name, line, fd, nextbuf, arg);
+
+    if (fd == -1 && nextbuf == NULL)
+       /* sanity */
+       return;
+
+    if (curFile != NULL)
+       /* Save exiting file info */
+       Lst_AtFront(includes, curFile);
+
+    /* Allocate and fill in new structure */
+    curFile = bmake_malloc(sizeof *curFile);
+
+    /*
+     * 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.
+     */
+    curFile->fname = name;
+    curFile->lineno = line;
+    curFile->first_lineno = line;
+    curFile->fd = fd;
+    curFile->nextbuf = nextbuf;
+    curFile->nextbuf_arg = arg;
+
+    if (nextbuf == NULL) {
+       /*
+        * Allocate a 32k data buffer (as stdio seems to).
+        * Set pointers so that first ParseReadc has to do a file read.
+        */
+       buf = bmake_malloc(IFILE_BUFLEN);
+       buf[0] = 0;
+       curFile->P_str = buf;
+       curFile->P_ptr = buf;
+       curFile->P_end = buf;
+       curFile->P_buflen = IFILE_BUFLEN;
+    } else {
+       /* Get first block of input data */
+       buf = curFile->nextbuf(curFile->nextbuf_arg);
+       if (buf == NULL) {
+           /* Was all a waste of time ... */
+           free(curFile);
+           return;
+       }
+       curFile->P_str = buf;
+       curFile->P_ptr = buf;
+       curFile->P_end = NULL;
+    }
+
+    curFile->cond_depth = Cond_save_depth();
+    ParseSetParseFile(name);
+}
+
+#ifdef SYSVINCLUDE
+/*-
+ *---------------------------------------------------------------------
+ * ParseTraditionalInclude  --
+ *     Push to another file.
+ *
+ *     The input is the current line. The file name(s) are
+ *     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 *line)
+{
+    char          *cp;         /* current position in file spec */
+    int                   done = 0;
+    int                   silent = (line[0] != 'i') ? 1 : 0;
+    char         *file = &line[silent + 7];
+    char         *all_files;
+
+    if (DEBUG(PARSE)) {
+           fprintf(debug_file, "ParseTraditionalInclude: %s\n", file);
+    }
+
+    /*
+     * Skip over whitespace
+     */
+    while (isspace((unsigned char)*file))
+       file++;
+
+    /*
+     * Substitute for any variables in the file name before trying to
+     * find the thing.
+     */
+    all_files = Var_Subst(NULL, file, VAR_CMD, FALSE);
+
+    if (*file == '\0') {
+       Parse_Error(PARSE_FATAL,
+                    "Filename missing from \"include\"");
+       return;
+    }
+
+    for (file = all_files; !done; file = cp + 1) {
+       /* Skip to end of line or next whitespace */
+       for (cp = file; *cp && !isspace((unsigned char) *cp); cp++)
+           continue;
+
+       if (*cp)
+           *cp = '\0';
+       else
+           done = 1;
+
+       Parse_include_file(file, FALSE, silent);
+    }
+    free(all_files);
+}
+#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(void)
+{
+    char *ptr;
+
+    if (curFile->nextbuf != NULL) {
+       /* eg .for loop data, get next iteration */
+       ptr = curFile->nextbuf(curFile->nextbuf_arg);
+       curFile->P_ptr = ptr;
+       curFile->P_str = ptr;
+       curFile->lineno = curFile->first_lineno;
+       if (ptr != NULL) {
+           /* Iterate again */
+           return CONTINUE;
+       }
+    }
+
+    /* Ensure the makefile (or loop) didn't have mismatched conditionals */
+    Cond_restore_depth(curFile->cond_depth);
+
+    /* Dispose of curFile info */
+    /* Leak curFile->fname because all the gnodes have pointers to it */
+    if (curFile->fd != -1)
+       close(curFile->fd);
+    free(curFile->P_str);
+    free(curFile);
+
+    curFile = Lst_DeQueue(includes);
+
+    if (curFile == NULL) {
+       /* We've run out of input */
+       Var_Delete(".PARSEDIR", VAR_GLOBAL);
+       Var_Delete(".PARSEFILE", VAR_GLOBAL);
+       return DONE;
+    }
+
+    if (DEBUG(PARSE))
+       fprintf(debug_file, "ParseEOF: returning to file %s, line %d, fd %d\n",
+           curFile->fname, curFile->lineno, curFile->fd);
+
+    /* Restore the PARSEDIR/PARSEFILE variables */
+    ParseSetParseFile(curFile->fname);
+    return (CONTINUE);
+}
+
+#define PARSE_RAW 1
+#define PARSE_SKIP 2
+
+static char *
+ParseGetLine(int flags, int *length)
+{
+    IFile *cf = curFile;
+    char *ptr;
+    char ch;
+    char *line;
+    char *line_end;
+    char *escaped;
+    char *comment;
+    char *tp;
+    int len, dist;
+
+    /* Loop through blank lines and comment lines */
+    for (;;) {
+       cf->lineno++;
+       line = cf->P_ptr;
+       ptr = line;
+       line_end = line;
+       escaped = NULL;
+       comment = NULL;
+       for (;;) {
+           ch = *ptr;
+           if (ch == 0 || (ch == '\\' && ptr[1] == 0)) {
+               if (cf->P_end == NULL)
+                   /* End of string (aka for loop) data */
+                   break;
+               /* End of data read from file, read more data */
+               if (ptr != cf->P_end && (ch != '\\' || ptr + 1 != cf->P_end)) {
+                   Parse_Error(PARSE_FATAL, "Zero byte read from file");
+                   return NULL;
+               }
+               /* Move existing data to (near) start of file buffer */
+               len = cf->P_end - cf->P_ptr;
+               tp = cf->P_str + 32;
+               memmove(tp, cf->P_ptr, len);
+               dist = cf->P_ptr - tp;
+               /* Update all pointers to reflect moved data */
+               ptr -= dist;
+               line -= dist;
+               line_end -= dist;
+               if (escaped)
+                   escaped -= dist;
+               if (comment)
+                   comment -= dist;
+               cf->P_ptr = tp;
+               tp += len;
+               cf->P_end = tp;
+               /* Try to read more data from file into buffer space */
+               len = cf->P_str + cf->P_buflen - tp - 32;
+               if (len <= 0) {
+                   /* We need a bigger buffer to hold this line */
+                   tp = bmake_realloc(cf->P_str, cf->P_buflen + IFILE_BUFLEN);
+                   cf->P_ptr = cf->P_ptr - cf->P_str + tp;
+                   cf->P_end = cf->P_end - cf->P_str + tp;
+                   ptr = ptr - cf->P_str + tp;
+                   line = line - cf->P_str + tp;
+                   line_end = line_end - cf->P_str + tp;
+                   if (escaped)
+                       escaped = escaped - cf->P_str + tp;
+                   if (comment)
+                       comment = comment - cf->P_str + tp;
+                   cf->P_str = tp;
+                   tp = cf->P_end;
+                   len += IFILE_BUFLEN;
+                   cf->P_buflen += IFILE_BUFLEN;
+               }
+               len = read(cf->fd, tp, len);
+               if (len <= 0) {
+                   if (len < 0) {
+                       Parse_Error(PARSE_FATAL, "Makefile read error: %s",
+                               strerror(errno));
+                       return NULL;
+                   }
+                   /* End of file */
+                   break;
+               }
+               /* 0 terminate the data, and update end pointer */
+               tp += len;
+               cf->P_end = tp;
+               *tp = 0;
+               /* Process newly read characters */
+               continue;
+           }
+
+           if (ch == '\\') {
+               /* Don't treat next character as special, remember first one */
+               if (escaped == NULL)
+                   escaped = ptr;
+               if (ptr[1] == '\n')
+                   cf->lineno++;
+               ptr += 2;
+               line_end = ptr;
+               continue;
+           }
+           if (ch == '#' && comment == NULL) {
+               /* Remember first '#' for comment stripping */
+               comment = line_end;
+           }
+           ptr++;
+           if (ch == '\n')
+               break;
+           if (!isspace((unsigned char)ch))
+               /* We are not interested in trailing whitespace */
+               line_end = ptr;
+       }
+
+       /* Save next 'to be processed' location */
+       cf->P_ptr = ptr;
+
+       /* Check we have a non-comment, non-blank line */
+       if (line_end == line || comment == line) {
+           if (ch == 0)
+               /* At end of file */
+               return NULL;
+           /* Parse another line */
+           continue;
+       }
+
+       /* We now have a line of data */
+       *line_end = 0;
+
+       if (flags & PARSE_RAW) {
+           /* Leave '\' (etc) in line buffer (eg 'for' lines) */
+           *length = line_end - line;
+           return line;
+       }
+
+       if (flags & PARSE_SKIP) {
+           /* Completely ignore non-directives */
+           if (line[0] != '.')
+               continue;
+           /* We could do more of the .else/.elif/.endif checks here */
+       }
+       break;
+    }
+
+    /* Brutally ignore anything after a non-escaped '#' in non-commands */
+    if (comment != NULL && line[0] != '\t') {
+       line_end = comment;
+       *line_end = 0;
+    }
+
+    /* If we didn't see a '\\' then the in-situ data is fine */
+    if (escaped == NULL) {
+       *length = line_end - line;
+       return line;
+    }
+
+    /* Remove escapes from '\n' and '#' */
+    tp = ptr = escaped;
+    escaped = line;
+    for (; ; *tp++ = ch) {
+       ch = *ptr++;
+       if (ch != '\\') {
+           if (ch == 0)
+               break;
+           continue;
+       }
+
+       ch = *ptr++;
+       if (ch == 0) {
+           /* Delete '\\' at end of buffer */
+           tp--;
+           break;
+       }
+
+       if (ch == '#' && line[0] != '\t')
+           /* Delete '\\' from before '#' on non-command lines */
+           continue;
+
+       if (ch != '\n') {
+           /* Leave '\\' in buffer for later */
+           *tp++ = '\\';
+           /* Make sure we don't delete an escaped ' ' from the line end */
+           escaped = tp + 1;
+           continue;
+       }
+
+       /* Escaped '\n' replace following whitespace with a single ' ' */
+       while (ptr[0] == ' ' || ptr[0] == '\t')
+           ptr++;
+       ch = ' ';
+    }
+
+    /* Delete any trailing spaces - eg from empty continuations */
+    while (tp > escaped && isspace((unsigned char)tp[-1]))
+       tp--;
+
+    *tp = 0;
+    *length = tp - line;
+    return line;
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * ParseReadLine --
+ *     Read an entire line from the input file. Called only by Parse_File.
+ *
+ * Results:
+ *     A line w/o its newline
+ *
+ * Side Effects:
+ *     Only those associated with reading a character
+ *---------------------------------------------------------------------
+ */
+static char *
+ParseReadLine(void)
+{
+    char         *line;        /* Result */
+    int                  lineLength;   /* Length of result */
+    int                  lineno;       /* Saved line # */
+    int                  rval;
+
+    for (;;) {
+       line = ParseGetLine(0, &lineLength);
+       if (line == NULL)
+           return NULL;
+
+       if (line[0] != '.')
+           return line;
+
+       /*
+        * 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 {
+               line = ParseGetLine(PARSE_SKIP, &lineLength);
+           } while (line && Cond_Eval(line) != COND_PARSE);
+           if (line == NULL)
+               break;
+           continue;
+       case COND_PARSE:
+           continue;
+       case COND_INVALID:    /* Not a conditional line */
+           /* Check for .for loops */
+           rval = For_Eval(line);
+           if (rval == 0)
+               /* Not a .for line */
+               break;
+           if (rval < 0)
+               /* Syntax error - error printed, ignore line */
+               continue;
+           /* Start of a .for loop */
+           lineno = curFile->lineno;
+           /* Accumulate loop lines until matching .endfor */
+           do {
+               line = ParseGetLine(PARSE_RAW, &lineLength);
+               if (line == NULL) {
+                   Parse_Error(PARSE_FATAL,
+                            "Unexpected end of file in for loop.\n");
+                   break;
+               }
+           } while (For_Accum(line));
+           /* Stash each iteration as a new 'input file' */
+           For_Run(lineno);
+           /* Read next line from for-loop buffer */
+           continue;
+       }
+       return (line);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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, 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
+ *
+ * Input:
+ *     name            the name of the file being read
+ *     fd              Open file to makefile to parse
+ *
+ * Results:
+ *     None
+ *
+ * Side Effects:
+ *     closes fd.
+ *     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(const char *name, int fd)
+{
+    char         *cp;          /* pointer into the line */
+    char          *line;       /* the line we're working on */
+
+    inLine = FALSE;
+    fatals = 0;
+
+    Parse_SetInput(name, 0, fd, NULL, NULL);
+
+    do {
+       for (; (line = ParseReadLine()) != NULL; ) {
+           if (DEBUG(PARSE))
+               fprintf(debug_file, "ParseReadLine (%d): '%s'\n",
+                       curFile->lineno, line);
+           if (*line == '.') {
+               /*
+                * Lines that begin with the special character may be
+                * include or undef directives.
+                * On the other hand they can be suffix rules (.c.o: ...)
+                * or just dependencies for filenames that start '.'.
+                */
+               for (cp = line + 1; isspace((unsigned char)*cp); cp++) {
+                   continue;
+               }
+               if (strncmp(cp, "include", 7) == 0 ||
+                       ((cp[0] == 's' || cp[0] == '-') &&
+                           strncmp(&cp[1], "include", 7) == 0)) {
+                   ParseDoInclude(cp);
+                   continue;
+               }
+               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);
+                   continue;
+               } else if (strncmp(cp, "export", 6) == 0) {
+                   for (cp += 6; isspace((unsigned char) *cp); cp++)
+                       continue;
+                   Var_Export(cp, 1);
+                   continue;
+               } else if (strncmp(cp, "unexport", 8) == 0) {
+                   Var_UnExport(cp);
+                   continue;
+               }
+           }
+
+           if (*line == '\t') {
+               /*
+                * If a line starts with a tab, it can only hope to be
+                * a creation command.
+                */
+               cp = line + 1;
+             shellCommand:
+               for (; isspace ((unsigned char)*cp); cp++) {
+                   continue;
+               }
+               if (*cp) {
+                   if (!inLine)
+                       Parse_Error(PARSE_FATAL,
+                                    "Unassociated shell command \"%s\"",
+                                    cp);
+                   /*
+                    * 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
+                    */
+                   if (targets) {
+                       cp = bmake_strdup(cp);
+                       Lst_ForEach(targets, ParseAddCmd, cp);
+#ifdef CLEANUP
+                       Lst_AtEnd(targCmds, cp);
+#endif
+                   }
+               }
+               continue;
+           }
+
+#ifdef SYSVINCLUDE
+           if (((strncmp(line, "include", 7) == 0 &&
+                   isspace((unsigned char) line[7])) ||
+                       ((line[0] == 's' || line[0] == '-') &&
+                           strncmp(&line[1], "include", 7) == 0 &&
+                           isspace((unsigned char) line[8]))) &&
+                   strchr(line, ':') == NULL) {
+               /*
+                * It's an S3/S5-style "include".
+                */
+               ParseTraditionalInclude(line);
+               continue;
+           }
+#endif
+           if (Parse_IsVar(line)) {
+               ParseFinishLine();
+               Parse_DoVar(line, VAR_GLOBAL);
+               continue;
+           }
+
+#ifndef POSIX
+           /*
+            * 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.
+            */
+           cp = line;
+           if (isspace((unsigned char) line[0])) {
+               while ((*cp != '\0') && isspace((unsigned char) *cp))
+                   cp++;
+               while (*cp && (ParseIsEscaped(line, cp) ||
+                       (*cp != ':') && (*cp != '!'))) {
+                   cp++;
+               }
+               if (*cp == '\0') {
+                   if (inLine) {
+                       Parse_Error(PARSE_WARNING,
+                                    "Shell command needs a leading tab");
+                       goto shellCommand;
+                   }
+               }
+           }
+#endif
+           ParseFinishLine();
+
+           /*
+            * For some reason - probably to make the parser impossible -
+            * a ';' can be used to separate commands from dependencies.
+            * Attempt to avoid ';' inside substitution patterns.
+            */
+           {
+               int level = 0;
+
+               for (cp = line; *cp != 0; cp++) {
+                   if (*cp == '\\' && cp[1] != 0) {
+                       cp++;
+                       continue;
+                   }
+                   if (*cp == '$' &&
+                       (cp[1] == '(' || cp[1] == '{')) {
+                       level++;
+                       continue;
+                   }
+                   if (level > 0) {
+                       if (*cp == ')' || *cp == '}') {
+                           level--;
+                           continue;
+                       }
+                   } else if (*cp == ';') {
+                       break;
+                   }
+               }
+           }
+           if (*cp != 0)
+               /* Terminate the dependency list at the ';' */
+               *cp++ = 0;
+           else
+               cp = NULL;
+
+           /*
+            * 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...
+            */
+           line = Var_Subst(NULL, line, VAR_CMD, TRUE);
+
+           /*
+            * Need a non-circular list for the target nodes
+            */
+           if (targets)
+               Lst_Destroy(targets, NULL);
+
+           targets = Lst_Init(FALSE);
+           inLine = TRUE;
+
+           ParseDoDependency(line);
+           free(line);
+
+           /* If there were commands after a ';', add them now */
+           if (cp != NULL) {
+               goto shellCommand;
+           }
+       }
+       /*
+        * Reached EOF, but it may be just EOF of an include file...
+        */
+    } while (ParseEOF() == CONTINUE);
+
+    if (fatals) {
+       (void)fprintf(stderr,
+           "%s: Fatal errors encountered -- cannot continue\n",
+           progname);
+       PrintOnError(NULL);
+       exit(1);
+    }
+}
+
+/*-
+ *---------------------------------------------------------------------
+ * 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);
+    defIncPath = Lst_Init(FALSE);
+    includes = Lst_Init(FALSE);
+#ifdef CLEANUP
+    targCmds = Lst_Init(FALSE);
+#endif
+}
+
+void
+Parse_End(void)
+{
+#ifdef CLEANUP
+    Lst_Destroy(targCmds, (FreeProc *)free);
+    if (targets)
+       Lst_Destroy(targets, NULL);
+    Lst_Destroy(defIncPath, Dir_Destroy);
+    Lst_Destroy(sysIncPath, Dir_Destroy);
+    Lst_Destroy(parseIncPath, Dir_Destroy);
+    Lst_Destroy(includes, NULL);       /* Should be empty now */
+#endif
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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           mainList;    /* result list */
+
+    mainList = Lst_Init(FALSE);
+
+    if (mainNode == NULL) {
+       Punt("no target to make.");
+       /*NOTREACHED*/
+    } else if (mainNode->type & OP_DOUBLEDEP) {
+       (void)Lst_AtEnd(mainList, mainNode);
+       Lst_Concat(mainList, mainNode->cohorts, LST_CONCNEW);
+    }
+    else
+       (void)Lst_AtEnd(mainList, mainNode);
+    Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL);
+    return (mainList);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * ParseMark --
+ *     Add the filename and lineno to the GNode so that we remember
+ *     where it was first defined.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+ParseMark(GNode *gn)
+{
+    gn->fname = curFile->fname;
+    gn->lineno = curFile->lineno;
+}
diff --git a/commands/bmake/pathnames.h b/commands/bmake/pathnames.h
new file mode 100644 (file)
index 0000000..e90fbf6
--- /dev/null
@@ -0,0 +1,57 @@
+/*     $NetBSD: pathnames.h,v 1.17 2009/04/11 09:41:18 apb Exp $       */
+
+/*
+ * 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. 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: @(#)pathnames.h   5.2 (Berkeley) 6/1/90
+ */
+
+#ifndef MAKE_NATIVE
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+#else
+#include <paths.h>
+#endif
+
+#define        _PATH_OBJDIR            "obj"
+#define        _PATH_OBJDIRPREFIX      "/usr/obj"
+#ifndef _PATH_DEFSHELLDIR
+#define        _PATH_DEFSHELLDIR       "/bin"
+#endif
+#define        _PATH_DEFSYSMK          "sys.mk"
+#ifndef _PATH_DEFSYSPATH
+#if defined(__minix)
+#define        _PATH_DEFSYSPATH        "/etc/mk"
+#else
+#define        _PATH_DEFSYSPATH        "/usr/share/mk"
+#endif
+#endif
+#ifndef _PATH_TMP
+#define        _PATH_TMP               "/tmp/"         /* with trailing slash */
+#endif
diff --git a/commands/bmake/sprite.h b/commands/bmake/sprite.h
new file mode 100644 (file)
index 0000000..6ec4fe2
--- /dev/null
@@ -0,0 +1,116 @@
+/*     $NetBSD: sprite.h,v 1.11 2009/01/23 21:26:30 dsl Exp $  */
+
+/*
+ * 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. 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: @(#)sprite.h      8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * 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.
+ *
+ *     from: @(#)sprite.h      8.1 (Berkeley) 6/6/93
+ */
+
+/*
+ * 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 */
+
+/*
+ * Functions that must return a status can return a ReturnStatus to
+ * indicate success or type of failure.
+ */
+
+typedef int  ReturnStatus;
+
+/*
+ * The following statuses overlap with the first 2 generic statuses
+ * defined in status.h:
+ *
+ * SUCCESS                     There was no error.
+ * FAILURE                     There was a general error.
+ */
+
+#define        SUCCESS                 0x00000000
+#define        FAILURE                 0x00000001
+
+#endif /* _SPRITE */
diff --git a/commands/bmake/str.c b/commands/bmake/str.c
new file mode 100644 (file)
index 0000000..f1b93a0
--- /dev/null
@@ -0,0 +1,506 @@
+/*     $NetBSD: str.c,v 1.33 2009/02/25 21:17:21 sno Exp $     */
+
+/*-
+ * 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. 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.
+ */
+
+/*-
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: str.c,v 1.33 2009/02/25 21:17:21 sno Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char     sccsid[] = "@(#)str.c  5.8 (Berkeley) 6/1/90";
+#else
+__RCSID("$NetBSD: str.c,v 1.33 2009/02/25 21:17:21 sno Exp $");
+#endif
+#endif                         /* not lint */
+#endif
+
+#include "make.h"
+
+/*-
+ * 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(const char *s1, const 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 = bmake_malloc((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);
+
+       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.
+ *
+ * If expand is TRUE, quotes are removed and escape sequences
+ *  such as \r, \t, etc... are expanded.
+ *
+ * returns --
+ *     Pointer to the array of pointers to the words.
+ *      Memory containing the actual words in *buffer.
+ *             Both of these must be free'd by the caller.
+ *      Number of words in *store_argc.
+ */
+char **
+brk_string(const char *str, int *store_argc, Boolean expand, char **buffer)
+{
+       int argc, ch;
+       char inquote, *start, *t;
+       const char *p;
+       int len;
+       int argmax = 50, curlen = 0;
+       char **argv = bmake_malloc((argmax + 1) * sizeof(char *));
+
+       /* skip leading space chars. */
+       for (; *str == ' ' || *str == '\t'; ++str)
+               continue;
+
+       /* allocate room for a copy of the string */
+       if ((len = strlen(str) + 1) > curlen)
+               *buffer = bmake_malloc(curlen = len);
+
+       /*
+        * copy the string; at the same time, parse backslashes,
+        * quotes and build the argument list.
+        */
+       argc = 0;
+       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) {
+                                       if (!expand) {
+                                               start = t;
+                                               *t++ = ch;
+                                       } else
+                                               start = t + 1;
+                                       p++;
+                                       inquote = '\0';
+                                       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 **)bmake_realloc(argv,
+                                   (argmax + 1) * sizeof(char *));
+                       }
+                       argv[argc++] = start;
+                       start = NULL;
+                       if (ch == '\n' || ch == '\0') {
+                               if (expand && inquote) {
+                                       free(argv);
+                                       free(*buffer);
+                                       *buffer = NULL;
+                                       return NULL;
+                               }
+                               goto done;
+                       }
+                       continue;
+               case '\\':
+                       if (!expand) {
+                               if (!start)
+                                       start = t;
+                               *t++ = '\\';
+                               if (*(p+1) == '\0') /* catch '\' at end of line */
+                                       continue;
+                               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;
+                       }
+                       break;
+               }
+               if (!start)
+                       start = t;
+               *t++ = (char) ch;
+       }
+done:  argv[argc] = NULL;
+       *store_argc = argc;
+       return(argv);
+}
+
+/*
+ * Str_FindSubstring -- See if a string contains a particular substring.
+ *
+ * Input:
+ *     string          String to search.
+ *     substring       Substring to find in string.
+ *
+ * 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.
+ */
+char *
+Str_FindSubstring(const char *string, const char *substring)
+{
+       const 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 UNCONST(string);
+                       if (*a++ != *b++)
+                               break;
+               }
+               b = substring;
+       }
+       return 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(const char *string, const 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),
+ *
+ * Input:
+ *     word            Word to examine
+ *     pattern         Pattern to examine against
+ *     len             Number of characters to substitute
+ *
+ * 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(const char *word, const char *pattern, int *len)
+{
+    const char *p = pattern;
+    const char *w = word;
+    const char *m;
+
+    if (*p == '\0') {
+       /* Null pattern is the whole string */
+       *len = strlen(w);
+       return UNCONST(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 UNCONST(w);
+       }
+    }
+
+    m = w;
+
+    /* Find a matching tail */
+    do
+       if (strcmp(p, w) == 0) {
+           *len = w - m;
+           return UNCONST(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, pat);
+       /* skip the % */
+       pat = m + 1;
+    }
+
+    /* Copy the pattern */
+    Buf_AddBytes(buf, len, src);
+
+    /* append the rest */
+    Buf_AddBytes(buf, strlen(pat), pat);
+}
diff --git a/commands/bmake/strlist.c b/commands/bmake/strlist.c
new file mode 100644 (file)
index 0000000..3fb2f7d
--- /dev/null
@@ -0,0 +1,93 @@
+/*     $NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $  */
+
+/*-
+ * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David Laight.
+ *
+ * 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. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $");
+#endif /* not lint */
+#endif
+
+#include <stddef.h>
+#include <stdlib.h>
+#include "strlist.h"
+#include "make_malloc.h"
+
+void
+strlist_init(strlist_t *sl)
+{
+       sl->sl_num = 0;
+       sl->sl_max = 0;
+       sl->sl_items = NULL;
+}
+
+void
+strlist_clean(strlist_t *sl)
+{
+       char *str;
+       int i;
+
+       STRLIST_FOREACH(str, sl, i)
+               free(str);
+       free(sl->sl_items);
+
+       sl->sl_num = 0;
+       sl->sl_max = 0;
+       sl->sl_items = NULL;
+}
+
+void
+strlist_add_str(strlist_t *sl, char *str, unsigned int info)
+{
+       unsigned int n;
+       strlist_item_t *items;
+
+       if (str == NULL)
+           return;
+
+       n = sl->sl_num + 1;
+       sl->sl_num = n;
+       items = sl->sl_items;
+       if (n >= sl->sl_max) {
+           items = bmake_realloc(items, (n + 7) * sizeof *sl->sl_items);
+           sl->sl_items = items;
+           sl->sl_max = n + 6;
+       }
+       items += n - 1;
+       items->si_str = str;
+       items->si_info = info;
+       items[1].si_str = NULL;         /* STRLIST_FOREACH() terminator */
+}
diff --git a/commands/bmake/strlist.h b/commands/bmake/strlist.h
new file mode 100644 (file)
index 0000000..2fc049e
--- /dev/null
@@ -0,0 +1,62 @@
+/*     $NetBSD: strlist.h,v 1.3 2009/01/16 21:15:34 dsl Exp $  */
+
+/*-
+ * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David Laight.
+ *
+ * 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. Neither the name of The NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#ifndef _STRLIST_H
+#define _STRLIST_H
+
+typedef struct {
+    char          *si_str;
+    unsigned int  si_info;
+} strlist_item_t;
+
+typedef struct {
+    unsigned int    sl_num;
+    unsigned int    sl_max;
+    strlist_item_t  *sl_items;
+} strlist_t;
+
+void strlist_init(strlist_t *);
+void strlist_clean(strlist_t *);
+void strlist_add_str(strlist_t *, char *, unsigned int);
+
+#define strlist_num(sl) ((sl)->sl_num)
+#define strlist_str(sl, n)  ((sl)->sl_items[n].si_str)
+#define strlist_info(sl, n)  ((sl)->sl_items[n].si_info)
+#define strlist_set_info(sl, n, v)  ((void)((sl)->sl_items[n].si_info = (v)))
+
+#define STRLIST_FOREACH(v, sl, index) \
+    if ((sl)->sl_items != NULL) \
+       for (index = 0; (v = strlist_str(sl, index)) != NULL; index++)
+
+#endif /* _STRLIST_H */
diff --git a/commands/bmake/suff.c b/commands/bmake/suff.c
new file mode 100644 (file)
index 0000000..a4cd626
--- /dev/null
@@ -0,0 +1,2645 @@
+/*     $NetBSD: suff.c,v 1.67 2009/01/23 21:58:28 dsl Exp $    */
+
+/*
+ * 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: suff.c,v 1.67 2009/01/23 21:58:28 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)suff.c     8.4 (Berkeley) 3/21/94";
+#else
+__RCSID("$NetBSD: suff.c,v 1.67 2009/01/23 21:58:28 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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.
+ *
+ *     Suff_FindPath           Return the appropriate path to search in
+ *                             order to find the node.
+ */
+
+#include         <stdio.h>
+#include         <strings.h>
+#include         "make.h"
+#include         "hash.h"
+#include         "dir.h"
+
+static Lst       sufflist;     /* Lst of suffixes */
+#ifdef CLEANUP
+static Lst      suffClean;     /* Lst of suffixes to be cleaned */
+#endif
+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;
+
+/*
+ * for SuffSuffIsSuffix
+ */
+typedef struct {
+    char       *ename;         /* The end of the name */
+    int                 len;           /* Length of the name */
+} SuffixCmpData;
+
+/*
+ * 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;
+
+typedef struct {
+    GNode        **gn;
+    Suff          *s;
+    Boolean        r;
+} GNodeSuff;
+
+static Suff        *suffNull;  /* The NULL suffix for this run */
+static Suff        *emptySuff; /* The empty suffix required for POSIX
+                                * single-suffix transformation rules */
+
+
+static const char *SuffStrIsPrefix(const char *, const char *);
+static char *SuffSuffIsSuffix(const Suff *, const SuffixCmpData *);
+static int SuffSuffIsSuffixP(const void *, const void *);
+static int SuffSuffHasNameP(const void *, const void *);
+static int SuffSuffIsPrefix(const void *, const void *);
+static int SuffGNHasNameP(const void *, const void *);
+static void SuffUnRef(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 SuffScanTargets(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 void SuffExpandChildren(LstNode, GNode *);
+static void SuffExpandWildcards(LstNode, GNode *);
+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.
+ *
+ * Input:
+ *     pref            possible prefix
+ *     str             string to check
+ *
+ * Results:
+ *     NULL if it ain't, pointer to character in str after prefix if so
+ *
+ * Side Effects:
+ *     None
+ *-----------------------------------------------------------------------
+ */
+static const char *
+SuffStrIsPrefix(const char *pref, const char *str)
+{
+    while (*str && *pref == *str) {
+       pref++;
+       str++;
+    }
+
+    return (*pref ? NULL : str);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffIsSuffix  --
+ *     See if suff is a suffix of str. sd->ename should point to THE END
+ *     of the string to check. (THE END == the null byte)
+ *
+ * Input:
+ *     s               possible suffix
+ *     sd              string to examine
+ *
+ * Results:
+ *     NULL if it ain't, pointer to character in str before suffix if
+ *     it is.
+ *
+ * Side Effects:
+ *     None
+ *-----------------------------------------------------------------------
+ */
+static char *
+SuffSuffIsSuffix(const Suff *s, const SuffixCmpData *sd)
+{
+    char  *p1;         /* Pointer into suffix name */
+    char  *p2;         /* Pointer into string being examined */
+
+    if (sd->len < s->nameLen)
+       return NULL;            /* this string is shorter than the suffix */
+
+    p1 = s->name + s->nameLen;
+    p2 = sd->ename;
+
+    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(const void *s, const void *sd)
+{
+    return(!SuffSuffIsSuffix(s, sd));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffSuffHasNameP --
+ *     Callback procedure for finding a suffix based on its name. Used by
+ *     Suff_GetPath.
+ *
+ * Input:
+ *     s               Suffix to check
+ *     sd              Desired name
+ *
+ * Results:
+ *     0 if the suffix is of the given name. non-zero otherwise.
+ *
+ * Side Effects:
+ *     None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffHasNameP(const void *s, const void *sname)
+{
+    return (strcmp(sname, ((const 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...
+ *
+ * Input:
+ *     s               suffix to compare
+ *     str             string to examine
+ *
+ * Results:
+ *     0 if s is a prefix of str. non-zero otherwise
+ *
+ * Side Effects:
+ *     None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffSuffIsPrefix(const void *s, const void *str)
+{
+    return SuffStrIsPrefix(((const Suff *)s)->name, str) == NULL;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffGNHasNameP  --
+ *     See if the graph node has the desired name
+ *
+ * Input:
+ *     gn              current node we're looking at
+ *     name            name we're looking for
+ *
+ * Results:
+ *     0 if it does. non-zero if it doesn't
+ *
+ * Side Effects:
+ *     None
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffGNHasNameP(const void *gn, const void *name)
+{
+    return (strcmp(name, ((const GNode *)gn)->name));
+}
+
+           /*********** Maintenance Functions ************/
+
+static void
+SuffUnRef(void *lp, void *sp)
+{
+    Lst l = (Lst) lp;
+
+    LstNode ln = Lst_Member(l, sp);
+    if (ln != NULL) {
+       Lst_Remove(l, ln);
+       ((Suff *)sp)->refCount--;
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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;
+
+#ifdef notdef
+    /* We don't delete suffixes in order, so we cannot use this */
+    if (s->refCount)
+       Punt("Internal error deleting suffix `%s' with refcount = %d", s->name,
+           s->refCount);
+#endif
+
+    Lst_Destroy(s->ref, NULL);
+    Lst_Destroy(s->children, NULL);
+    Lst_Destroy(s->parents, NULL);
+    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 and the
+ *     suffix is possibly freed
+ *-----------------------------------------------------------------------
+ */
+static void
+SuffRemove(Lst l, Suff *s)
+{
+    SuffUnRef(l, s);
+    if (s->refCount == 0) {
+       SuffUnRef(sufflist, s);
+       SuffFree(s);
+    }
+}
+\f
+/*-
+ *-----------------------------------------------------------------------
+ * SuffInsert  --
+ *     Insert the suffix into the list keeping the list ordered by suffix
+ *     numbers.
+ *
+ * Input:
+ *     l               the list where in s should be inserted
+ *     s               the suffix to insert
+ *
+ * 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;
+       }
+    }
+
+    Lst_Close(l);
+    if (DEBUG(SUFF)) {
+       fprintf(debug_file, "inserting %s(%d)...", s->name, s->sNum);
+    }
+    if (ln == NULL) {
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "at end of list\n");
+       }
+       (void)Lst_AtEnd(l, s);
+       s->refCount++;
+       (void)Lst_AtEnd(s->ref, l);
+    } else if (s2->sNum != s->sNum) {
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "before %s(%d)\n", s2->name, s2->sNum);
+       }
+       (void)Lst_InsertBefore(l, ln, s);
+       s->refCount++;
+       (void)Lst_AtEnd(s->ref, l);
+    } else if (DEBUG(SUFF)) {
+       fprintf(debug_file, "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)
+{
+#ifdef CLEANUP
+    Lst_Concat(suffClean, sufflist, LST_CONCLINK);
+#endif
+    sufflist = Lst_Init(FALSE);
+    sNum = 0;
+    suffNull = emptySuff;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffParseTransform --
+ *     Parse a transformation string to find its two component suffixes.
+ *
+ * Input:
+ *     str             String being parsed
+ *     srcPtr          Place to store source of trans.
+ *     targPtr         Place to store target of trans.
+ *
+ * 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, str, SuffSuffIsPrefix);
+       } else {
+           srcLn = Lst_FindFrom(sufflist, Lst_Succ(srcLn), 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, 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
+ *
+ *
+ * Input:
+ *     str             string to check
+ *
+ * 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
+ *
+ * Input:
+ *     line            name of transformation to add
+ *
+ * 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, 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, 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, NULL);
+       Lst_Destroy(gn->children, NULL);
+       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
+     */
+    if (DEBUG(SUFF)) {
+       fprintf(debug_file, "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
+ *
+ * Input:
+ *     gnp             Node for transformation
+ *     dummy           Node for transformation
+ *
+ * Results:
+ *     === 0
+ *
+ * Side Effects:
+ *     If the node has no commands or children, the children and parents
+ *     lists of the affected suffixes are altered.
+ *
+ *-----------------------------------------------------------------------
+ */
+int
+Suff_EndTransform(void *gnp, void *dummy)
+{
+    GNode *gn = (GNode *)gnp;
+
+    if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts))
+       gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts));
+    if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) &&
+       Lst_IsEmpty(gn->children))
+    {
+       Suff    *s, *t;
+
+       /*
+        * SuffParseTransform() may fail for special rules which are not
+        * actual transformation rules. (e.g. .DEFAULT)
+        */
+       if (SuffParseTransform(gn->name, &s, &t)) {
+           Lst  p;
+
+           if (DEBUG(SUFF)) {
+               fprintf(debug_file, "deleting transformation from `%s' to `%s'\n",
+               s->name, t->name);
+           }
+
+           /*
+            * Store s->parents because s could be deleted in SuffRemove
+            */
+           p = s->parents;
+
+           /*
+            * Remove the source from the target's children list. We check for a
+            * nil 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(p, t);
+       }
+    } else if ((gn->type & OP_TRANSFORM) && DEBUG(SUFF)) {
+       fprintf(debug_file, "transformation %s complete\n", gn->name);
+    }
+
+    return(dummy ? 0 : 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.
+ *
+ * Input:
+ *     transformp      Transformation to test
+ *     sp              Suffix to rebuild
+ *
+ * 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;
+    SuffixCmpData sd;
+
+    /*
+     * First see if it is a transformation from this suffix.
+     */
+    cp = UNCONST(SuffStrIsPrefix(s->name, transform->name));
+    if (cp != NULL) {
+       ln = Lst_Find(sufflist, cp, SuffSuffHasNameP);
+       if (ln != NULL) {
+           /*
+            * Found target. Link in and return, since it can't be anything
+            * else.
+            */
+           s2 = (Suff *)Lst_Datum(ln);
+           SuffInsert(s2->children, s);
+           SuffInsert(s->parents, s2);
+           return(0);
+       }
+    }
+
+    /*
+     * Not from, maybe to?
+     */
+    sd.len = strlen(transform->name);
+    sd.ename = transform->name + sd.len;
+    cp = SuffSuffIsSuffix(s, &sd);
+    if (cp != NULL) {
+       /*
+        * Null-terminate the source suffix in order to find it.
+        */
+       cp[1] = '\0';
+       ln = Lst_Find(sufflist, 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);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffScanTargets --
+ *     Called from Suff_AddSuffix via Lst_ForEach to search through the
+ *     list of existing targets and find if any of the existing targets
+ *     can be turned into a transformation rule.
+ *
+ * Results:
+ *     1 if a new main target has been selected, 0 otherwise.
+ *
+ * Side Effects:
+ *     If such a target is found and the target is the current main
+ *     target, the main target is set to NULL and the next target
+ *     examined (if that exists) becomes the main target.
+ *
+ *-----------------------------------------------------------------------
+ */
+static int
+SuffScanTargets(void *targetp, void *gsp)
+{
+    GNode      *target = (GNode *)targetp;
+    GNodeSuff  *gs = (GNodeSuff *)gsp;
+    Suff       *s, *t;
+    char       *ptr;
+
+    if (*gs->gn == NULL && gs->r && (target->type & OP_NOTARGET) == 0) {
+       *gs->gn = target;
+       Targ_SetMain(target);
+       return 1;
+    }
+
+    if ((unsigned int)target->type == OP_TRANSFORM)
+       return 0;
+
+    if ((ptr = strstr(target->name, gs->s->name)) == NULL ||
+       ptr == target->name)
+       return 0;
+
+    if (SuffParseTransform(target->name, &s, &t)) {
+       if (*gs->gn == target) {
+           gs->r = TRUE;
+           *gs->gn = NULL;
+           Targ_SetMain(NULL);
+       }
+       Lst_Destroy(target->children, NULL);
+       target->children = Lst_Init(FALSE);
+       target->type = OP_TRANSFORM;
+       /*
+        * link the two together in the proper relationship and order
+        */
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "defining transformation from `%s' to `%s'\n",
+               s->name, t->name);
+       }
+       SuffInsert(t->children, s);
+       SuffInsert(s->parents, t);
+    }
+    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...
+ *
+ * Input:
+ *     str             the name of the suffix to add
+ *
+ * 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.
+ *     The mainNode passed can be modified if a target mutated into a
+ *     transform and that target happened to be the main target.
+ *-----------------------------------------------------------------------
+ */
+void
+Suff_AddSuffix(char *str, GNode **gn)
+{
+    Suff          *s;      /* new suffix descriptor */
+    LstNode      ln;
+    GNodeSuff    gs;
+
+    ln = Lst_Find(sufflist, str, SuffSuffHasNameP);
+    if (ln == NULL) {
+       s = bmake_malloc(sizeof(Suff));
+
+       s->name =       bmake_strdup(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 =   1;
+
+       (void)Lst_AtEnd(sufflist, s);
+       /*
+        * We also look at our existing targets list to see if adding
+        * this suffix will make one of our current targets mutate into
+        * a suffix rule. This is ugly, but other makes treat all targets
+        * that start with a . as suffix rules.
+        */
+       gs.gn = gn;
+       gs.s  = s;
+       gs.r  = FALSE;
+       Lst_ForEach(Targ_List(), SuffScanTargets, &gs);
+       /*
+        * Look for any existing transformations from or to this suffix.
+        * XXX: Only do this after a Suff_ClearSuffixes?
+        */
+       Lst_ForEach(transforms, SuffRebuildGraph, 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, 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, 0);
+    free(ptr);
+    Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL, 0);
+    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.
+ *
+ * Input:
+ *     sname           Name of the suffix to mark
+ *
+ * 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, 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.
+ *
+ * Input:
+ *     sname           Name of the suffix to mark
+ *
+ * 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, 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.
+ *
+ * Input:
+ *     sp              suffix for which to create a Src structure
+ *     lsp             list and parent for the new Src
+ *
+ * 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 = bmake_malloc(sizeof(Src));
+       s2->file =      bmake_strdup(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, s2);
+#ifdef DEBUG_SRC
+       s2->cp = Lst_Init(FALSE);
+       Lst_AtEnd(targ->cp, s2);
+       fprintf(debug_file, "1 add %x %x to %x:", targ, s2, ls->l);
+       Lst_ForEach(ls->l, PrintAddr, NULL);
+       fprintf(debug_file, "\n");
+#endif
+    }
+    s2 = bmake_malloc(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, s2);
+#ifdef DEBUG_SRC
+    s2->cp = Lst_Init(FALSE);
+    Lst_AtEnd(targ->cp, s2);
+    fprintf(debug_file, "2 add %x %x to %x:", targ, s2, ls->l);
+    Lst_ForEach(ls->l, PrintAddr, NULL);
+    fprintf(debug_file, "\n");
+#endif
+
+    return(0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffAddLevel  --
+ *     Add all the children of targ as Src structures to the given list
+ *
+ * Input:
+ *     l               list to which to add the new level
+ *     targ            Src structure to use as the parent
+ *
+ * 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, &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
+    fprintf(debug_file, "cleaning %lx: ", (unsigned long) l);
+    Lst_ForEach(l, PrintAddr, NULL);
+    fprintf(debug_file, "\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, s);
+               if (ln != NULL)
+                   Lst_Remove(s->parent->cp, ln);
+#endif
+               --s->parent->children;
+           }
+#ifdef DEBUG_SRC
+           fprintf(debug_file, "free: [l=%x] p=%x %d\n", l, s, s->children);
+           Lst_Destroy(s->cp, NULL);
+#endif
+           Lst_Remove(l, ln);
+           free(s);
+           t |= 1;
+           Lst_Close(l);
+           return TRUE;
+       }
+#ifdef DEBUG_SRC
+       else {
+           fprintf(debug_file, "keep: [l=%x] p=%x %d: ", l, s, s->children);
+           Lst_ForEach(s->cp, PrintAddr, NULL);
+           fprintf(debug_file, "\n");
+       }
+#endif
+    }
+
+    Lst_Close(l);
+
+    return t;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindThem --
+ *     Find the first existing file/target in the list srcs
+ *
+ * Input:
+ *     srcs            list of Src structures to search through
+ *
+ * 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 = NULL;
+
+    while (!Lst_IsEmpty (srcs)) {
+       s = (Src *)Lst_DeQueue(srcs);
+
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "\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
+           fprintf(debug_file, "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
+           fprintf(debug_file, "remove %x from %x\n", s, srcs);
+#endif
+           free(ptr);
+           break;
+       }
+
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "not there\n");
+       }
+
+       SuffAddLevel(srcs, s);
+       Lst_AtEnd(slst, s);
+    }
+
+    if (DEBUG(SUFF) && rs) {
+       fprintf(debug_file, "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.
+ *
+ * Input:
+ *     targ            Src structure to play with
+ *
+ * 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);
+
+    for (;;) {
+       ln = Lst_Next(t->children);
+       if (ln == NULL) {
+           Lst_Close(t->children);
+           return NULL;
+       }
+       s = (GNode *)Lst_Datum(ln);
+
+       if (s->type & OP_OPTIONAL && Lst_IsEmpty(t->commands)) {
+           /*
+            * We haven't looked to see if .OPTIONAL files exist yet, so
+            * don't use one as the implicit source.
+            * This allows us to use .OPTIONAL in .depend files so make won't
+            * complain "don't know how to make xxx.h' when a dependant file
+            * has been moved/deleted.
+            */
+           continue;
+       }
+
+       cp = strrchr(s->name, '/');
+       if (cp == NULL) {
+           cp = s->name;
+       } else {
+           cp++;
+       }
+       if (strncmp(cp, targ->pref, prefLen) != 0)
+           continue;
+       /*
+        * The node matches the prefix ok, see if it has a known
+        * suffix.
+        */
+       ln = Lst_Find(sufflist, &cp[prefLen], SuffSuffHasNameP);
+       if (ln == NULL)
+           continue;
+       /*
+        * 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, targ->suff) != NULL)
+           break;
+    }
+
+    /*
+     * 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 = bmake_malloc(sizeof(Src));
+    ret->file = bmake_strdup(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);
+    fprintf(debug_file, "3 add %x %x\n", targ, ret);
+    Lst_AtEnd(targ->cp, ret);
+#endif
+    Lst_AtEnd(slst, ret);
+    if (DEBUG(SUFF)) {
+       fprintf(debug_file, "\tusing existing source %s\n", s->name);
+    }
+    return (ret);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffExpandChildren --
+ *     Expand the names of any children of a given node that contain
+ *     variable invocations or file wildcards into actual targets.
+ *
+ * Input:
+ *     cln             Child to examine
+ *     pgn             Parent node being processed
+ *
+ * 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 void
+SuffExpandChildren(LstNode cln, GNode *pgn)
+{
+    GNode      *cgn = (GNode *)Lst_Datum(cln);
+    GNode      *gn;        /* New source 8) */
+    char       *cp;        /* Expanded value */
+
+    if (!Lst_IsEmpty(cgn->order_pred) || !Lst_IsEmpty(cgn->order_succ))
+       /* It is all too hard to process the result of .ORDER */
+       return;
+
+    if (cgn->type & OP_WAIT)
+       /* Ignore these (& OP_PHONY ?) */
+       return;
+
+    /*
+     * 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, '$') == NULL) {
+       SuffExpandWildcards(cln, pgn);
+       return;
+    }
+
+    if (DEBUG(SUFF)) {
+       fprintf(debug_file, "Expanding \"%s\"...", cgn->name);
+    }
+    cp = Var_Subst(NULL, cgn->name, pgn, TRUE);
+
+    if (cp != 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, 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;
+                   void        *freeIt;
+
+                   junk = Var_Parse(cp, pgn, TRUE, &len, &freeIt);
+                   if (junk != var_Error) {
+                       cp += len - 1;
+                   }
+
+                   if (freeIt)
+                       free(freeIt);
+               } 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, 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);
+
+           if (DEBUG(SUFF)) {
+               fprintf(debug_file, "%s...", gn->name);
+           }
+           /* Add gn to the parents child list before the original child */
+           (void)Lst_InsertBefore(pgn->children, cln, gn);
+           (void)Lst_AtEnd(gn->parents, pgn);
+           pgn->unmade++;
+           /* Expand wildcards on new node */
+           SuffExpandWildcards(Lst_Prev(cln), pgn);
+       }
+       Lst_Destroy(members, NULL);
+
+       /*
+        * Free the result
+        */
+       free(cp);
+    }
+    if (DEBUG(SUFF)) {
+       fprintf(debug_file, "\n");
+    }
+
+    /*
+     * Now the source is expanded, remove it from the list of children to
+     * keep it from being processed.
+     */
+    pgn->unmade--;
+    Lst_Remove(pgn->children, cln);
+    Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn));
+}
+
+static void
+SuffExpandWildcards(LstNode cln, GNode *pgn)
+{
+    GNode      *cgn = (GNode *)Lst_Datum(cln);
+    GNode      *gn;        /* New source 8) */
+    char       *cp;        /* Expanded value */
+    Lst        explist;    /* List of expansions */
+
+    if (!Dir_HasWildcards(cgn->name))
+       return;
+
+    /*
+     * Expand the word along the chosen path
+     */
+    explist = Lst_Init(FALSE);
+    Dir_Expand(cgn->name, Suff_FindPath(cgn), explist);
+
+    while (!Lst_IsEmpty(explist)) {
+       /*
+        * Fetch next expansion off the list and find its GNode
+        */
+       cp = (char *)Lst_DeQueue(explist);
+
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "%s...", cp);
+       }
+       gn = Targ_FindNode(cp, TARG_CREATE);
+
+       /* Add gn to the parents child list before the original child */
+       (void)Lst_InsertBefore(pgn->children, cln, gn);
+       (void)Lst_AtEnd(gn->parents, pgn);
+       pgn->unmade++;
+    }
+
+    /*
+     * Nuke what's left of the list
+     */
+    Lst_Destroy(explist, NULL);
+
+    if (DEBUG(SUFF)) {
+       fprintf(debug_file, "\n");
+    }
+
+    /*
+     * Now the source is expanded, remove it from the list of children to
+     * keep it from being processed.
+     */
+    pgn->unmade--;
+    Lst_Remove(pgn->children, cln);
+    Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_FindPath --
+ *     Find a path along which to expand the node.
+ *
+ *     If the word has a known suffix, use that path.
+ *     If it has no known suffix, use the default system search path.
+ *
+ * Input:
+ *     gn              Node being examined
+ *
+ * Results:
+ *     The appropriate path to search for the GNode.
+ *
+ * Side Effects:
+ *     XXX: We could set the suffix here so that we don't have to scan
+ *     again.
+ *
+ *-----------------------------------------------------------------------
+ */
+Lst
+Suff_FindPath(GNode* gn)
+{
+    Suff *suff = gn->suffix;
+
+    if (suff == NULL) {
+       SuffixCmpData sd;   /* Search string data */
+       LstNode ln;
+       sd.len = strlen(gn->name);
+       sd.ename = gn->name + sd.len;
+       ln = Lst_Find(sufflist, &sd, SuffSuffIsSuffixP);
+
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "Wildcard expanding \"%s\"...", gn->name);
+       }
+       if (ln != NULL)
+           suff = (Suff *)Lst_Datum(ln);
+       /* XXX: Here we can save the suffix so we don't have to do this again */
+    }
+
+    if (suff != NULL) {
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "suffix is \"%s\"...", suff->name);
+       }
+       return suff->searchPath;
+    } else {
+       /*
+        * Use default search path
+        */
+       return dirSearchPath;
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffApplyTransform --
+ *     Apply a transformation rule, given the source and target nodes
+ *     and suffixes.
+ *
+ * Input:
+ *     tGn             Target node
+ *     sGn             Source node
+ *     t               Target suffix
+ *     s               Source suffix
+ *
+ * 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, nln;    /* General node */
+    char       *tname;     /* Name of transformation rule */
+    GNode      *gn;        /* Node for same */
+
+    /*
+     * Form the proper links between the target and source.
+     */
+    (void)Lst_AtEnd(tGn->children, sGn);
+    (void)Lst_AtEnd(sGn->parents, tGn);
+    tGn->unmade += 1;
+
+    /*
+     * Locate the transformation rule itself
+     */
+    tname = str_concat(s->name, t->name, 0);
+    ln = Lst_Find(transforms, 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);
+
+    if (DEBUG(SUFF)) {
+       fprintf(debug_file, "\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
+     */
+    for (ln = Lst_Succ(ln); ln != NULL; ln = nln) {
+       nln = Lst_Succ(ln);
+       SuffExpandChildren(ln, 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, tGn);
+
+    return(TRUE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * SuffFindArchiveDeps --
+ *     Locate dependencies for an OP_ARCHV node.
+ *
+ * Input:
+ *     gn              Node for which to locate dependencies
+ *
+ * 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 const 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
+     */
+    (void)Lst_AtEnd(gn->children, mem);
+    (void)Lst_AtEnd(mem->parents, 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, 0);
+       if (p1)
+           free(p1);
+
+    }
+
+    ms = mem->suffix;
+    if (ms == NULL) {
+       /*
+        * Didn't know what it was -- use .NULL suffix if not in make mode
+        */
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "using null suffix\n");
+       }
+       ms = suffNull;
+    }
+
+
+    /*
+     * Set the other two local variables required for this target.
+     */
+    Var_Set(MEMBER, name, gn, 0);
+    Var_Set(ARCHIVE, gn->name, gn, 0);
+
+    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;
+       SuffixCmpData   sd;             /* Search string data */
+
+       /*
+        * Use first matching suffix...
+        */
+       sd.len = eoarch - gn->name;
+       sd.ename = eoarch;
+       ln = Lst_Find(ms->parents, &sd, SuffSuffIsSuffixP);
+
+       if (ln != NULL) {
+           /*
+            * Got one -- apply it
+            */
+           if (!SuffApplyTransform(gn, mem, (Suff *)Lst_Datum(ln), ms) &&
+               DEBUG(SUFF))
+           {
+               fprintf(debug_file, "\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.
+ *
+ * Input:
+ *     gn              Node for which to find sources
+ *
+ * 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, nln;    /* 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 */
+    SuffixCmpData sd;      /* Search string data */
+
+
+    sd.len = strlen(gn->name);
+    sd.ename = eoname = gn->name + sd.len;
+
+    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, &sd, SuffSuffIsSuffixP);
+
+       if (ln != NULL) {
+           int     prefLen;        /* Length of the prefix */
+
+           /*
+            * Allocate a Src structure to which things can be transformed
+            */
+           targ = bmake_malloc(sizeof(Src));
+           targ->file = bmake_strdup(gn->name);
+           targ->suff = (Suff *)Lst_Datum(ln);
+           targ->suff->refCount++;
+           targ->node = gn;
+           targ->parent = NULL;
+           targ->children = 0;
+#ifdef DEBUG_SRC
+           targ->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 - targ->suff->nameLen) - sopref;
+           targ->pref = bmake_malloc(prefLen + 1);
+           memcpy(targ->pref, sopref, prefLen);
+           targ->pref[prefLen] = '\0';
+
+           /*
+            * Add nodes from which the target can be made
+            */
+           SuffAddLevel(srcs, targ);
+
+           /*
+            * Record the target so we can nuke it
+            */
+           (void)Lst_AtEnd(targs, targ);
+
+           /*
+            * Search from this suffix's successor...
+            */
+           ln = Lst_Succ(ln);
+       }
+    }
+
+    /*
+     * Handle target of unknown suffix...
+     */
+    if (Lst_IsEmpty(targs) && suffNull != NULL) {
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name);
+       }
+
+       targ = bmake_malloc(sizeof(Src));
+       targ->file = bmake_strdup(gn->name);
+       targ->suff = suffNull;
+       targ->suff->refCount++;
+       targ->node = gn;
+       targ->parent = NULL;
+       targ->children = 0;
+       targ->pref = bmake_strdup(sopref);
+#ifdef DEBUG_SRC
+       targ->cp = Lst_Init(FALSE);
+#endif
+
+       /*
+        * Only use the default suffix rules if we don't have commands
+        * defined for this gnode; traditional make programs used to
+        * not define suffix rules if the gnode had children but we
+        * don't do this anymore.
+        */
+       if (Lst_IsEmpty(gn->commands))
+           SuffAddLevel(srcs, targ);
+       else {
+           if (DEBUG(SUFF))
+               fprintf(debug_file, "not ");
+       }
+
+       if (DEBUG(SUFF))
+           fprintf(debug_file, "adding suffix rules\n");
+
+       (void)Lst_AtEnd(targs, 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 == 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 = 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;
+    }
+
+    Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0);
+
+    pref = (targ != NULL) ? targ->pref : gn->name;
+    Var_Set(PREFIX, pref, gn, 0);
+
+    /*
+     * Now we've got the important local variables set, expand any sources
+     * that still contain variables or wildcards in their names.
+     */
+    for (ln = Lst_First(gn->children); ln != NULL; ln = nln) {
+       nln = Lst_Succ(ln);
+       SuffExpandChildren(ln, gn);
+    }
+
+    if (targ == NULL) {
+       if (DEBUG(SUFF)) {
+           fprintf(debug_file, "\tNo valid suffix on %s\n", gn->name);
+       }
+
+sfnd_abort:
+       /*
+        * Deal with finding the thing on the default search path. We
+        * always do that, not only if the node is only a source (not
+        * on the lhs of a dependency operator or [XXX] it has neither
+        * children or commands) as the old pmake did.
+        */
+       if ((gn->type & (OP_PHONY|OP_NOPATH)) == 0) {
+           free(gn->path);
+           gn->path = Dir_FindFile(gn->name,
+                                   (targ == NULL ? dirSearchPath :
+                                    targ->suff->searchPath));
+           if (gn->path != NULL) {
+               char *ptr;
+               Var_Set(TARGET, gn->path, gn, 0);
+
+               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, 0);
+
+                   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, 0);
+               }
+           }
+       }
+
+       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 != 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, bottom) == NULL) {
+                   Lst_AtEnd(slst, 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 != 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, 0);
+
+           Var_Set(TARGET, targ->node->name, targ->node, 0);
+       }
+    }
+
+    if (gn->suffix)
+       gn->suffix->refCount--;
+    gn->suffix = src->suff;
+    gn->suffix->refCount++;
+
+    /*
+     * Nuke the transformation path and the Src structures left over in the
+     * two lists.
+     */
+sfnd_return:
+    if (bottom)
+       if (Lst_Member(slst, bottom) == NULL)
+           Lst_AtEnd(slst, 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;
+}
+
+
+/*
+ * Input:
+ *     gn              node we're dealing with
+ *
+ */
+static void
+SuffFindDeps(GNode *gn, Lst slst)
+{
+    if (gn->type & (OP_DEPS_FOUND|OP_PHONY)) {
+       /*
+        * If dependencies already found, no need to do it again...
+        * If this is a .PHONY target, we do not apply suffix rules.
+        */
+       return;
+    } else {
+       gn->type |= OP_DEPS_FOUND;
+    }
+
+    if (DEBUG(SUFF)) {
+       fprintf(debug_file, "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, 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, 0);
+       }
+       /*
+        * 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, 0);
+    } else {
+       SuffFindNormalDeps(gn, slst);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Suff_SetNull --
+ *     Define which suffix is the null suffix.
+ *
+ * Input:
+ *     name            Name of 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, name, SuffSuffHasNameP);
+    if (ln != NULL) {
+       s = (Suff *)Lst_Datum(ln);
+       if (suffNull != 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);
+#ifdef CLEANUP
+    suffClean = Lst_Init(FALSE);
+#endif
+    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 = bmake_malloc(sizeof(Suff));
+
+    suffNull->name =               bmake_strdup("");
+    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)
+{
+#ifdef CLEANUP
+    Lst_Destroy(sufflist, SuffFree);
+    Lst_Destroy(suffClean, SuffFree);
+    if (suffNull)
+       SuffFree(suffNull);
+    Lst_Destroy(srclist, NULL);
+    Lst_Destroy(transforms, NULL);
+#endif
+}
+
+
+/********************* DEBUGGING FUNCTIONS **********************/
+
+static int SuffPrintName(void *s, void *dummy)
+{
+    fprintf(debug_file, "%s ", ((Suff *)s)->name);
+    return (dummy ? 0 : 0);
+}
+
+static int
+SuffPrintSuff(void *sp, void *dummy)
+{
+    Suff    *s = (Suff *)sp;
+    int            flags;
+    int            flag;
+
+    fprintf(debug_file, "# `%s' [%d] ", s->name, s->refCount);
+
+    flags = s->flags;
+    if (flags) {
+       fputs(" (", debug_file);
+       while (flags) {
+           flag = 1 << (ffs(flags) - 1);
+           flags &= ~flag;
+           switch (flag) {
+               case SUFF_NULL:
+                   fprintf(debug_file, "NULL");
+                   break;
+               case SUFF_INCLUDE:
+                   fprintf(debug_file, "INCLUDE");
+                   break;
+               case SUFF_LIBRARY:
+                   fprintf(debug_file, "LIBRARY");
+                   break;
+           }
+           fputc(flags ? '|' : ')', debug_file);
+       }
+    }
+    fputc('\n', debug_file);
+    fprintf(debug_file, "#\tTo: ");
+    Lst_ForEach(s->parents, SuffPrintName, NULL);
+    fputc('\n', debug_file);
+    fprintf(debug_file, "#\tFrom: ");
+    Lst_ForEach(s->children, SuffPrintName, NULL);
+    fputc('\n', debug_file);
+    fprintf(debug_file, "#\tSearch Path: ");
+    Dir_PrintPath(s->searchPath);
+    fputc('\n', debug_file);
+    return (dummy ? 0 : 0);
+}
+
+static int
+SuffPrintTrans(void *tp, void *dummy)
+{
+    GNode   *t = (GNode *)tp;
+
+    fprintf(debug_file, "%-16s: ", t->name);
+    Targ_PrintType(t->type);
+    fputc('\n', debug_file);
+    Lst_ForEach(t->commands, Targ_PrintCmd, NULL);
+    fputc('\n', debug_file);
+    return(dummy ? 0 : 0);
+}
+
+void
+Suff_PrintAll(void)
+{
+    fprintf(debug_file, "#*** Suffixes:\n");
+    Lst_ForEach(sufflist, SuffPrintSuff, NULL);
+
+    fprintf(debug_file, "#*** Transformations:\n");
+    Lst_ForEach(transforms, SuffPrintTrans, NULL);
+}
diff --git a/commands/bmake/targ.c b/commands/bmake/targ.c
new file mode 100644 (file)
index 0000000..b0f63d9
--- /dev/null
@@ -0,0 +1,848 @@
+/*     $NetBSD: targ.c,v 1.55 2009/01/23 21:26:30 dsl Exp $    */
+
+/*
+ * 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: targ.c,v 1.55 2009/01/23 21:26:30 dsl Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)targ.c     8.2 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: targ.c,v 1.55 2009/01/23 21:26:30 dsl Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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_List               Return the list of all targets so far.
+ *
+ *     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.
+ *
+ *     Targ_Propagate          Propagate information between related
+ *                             nodes.  Should be called after the
+ *                             makefiles are parsed but before any
+ *                             action is taken.
+ *
+ * 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         <strings.h>
+#include         <time.h>
+
+#include         "make.h"
+#include         "hash.h"
+#include         "dir.h"
+
+static Lst        allTargets;  /* the list of all targets found so far */
+#ifdef CLEANUP
+static Lst       allGNs;       /* List of all the GNodes */
+#endif
+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 *);
+#ifdef CLEANUP
+static void TargFreeGN(void *);
+#endif
+static int TargPropagateCohort(void *, void *);
+static int TargPropagateNode(void *, 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)
+{
+#ifdef CLEANUP
+    Lst_Destroy(allTargets, NULL);
+    if (allGNs)
+       Lst_Destroy(allGNs, TargFreeGN);
+    Hash_DeleteTable(&targets);
+#endif
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_List --
+ *     Return the list of all targets
+ *
+ * Results:
+ *     The list of all targets.
+ *
+ * Side Effects:
+ *     None
+ *-----------------------------------------------------------------------
+ */
+Lst
+Targ_List(void)
+{
+    return allTargets;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_NewGN  --
+ *     Create and initialize a new graph node
+ *
+ * Input:
+ *     name            the name to stick in the new 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(const char *name)
+{
+    GNode *gn;
+
+    gn = bmake_malloc(sizeof(GNode));
+    gn->name = bmake_strdup(name);
+    gn->uname = NULL;
+    gn->path = NULL;
+    if (name[0] == '-' && name[1] == 'l') {
+       gn->type = OP_LIB;
+    } else {
+       gn->type = 0;
+    }
+    gn->unmade =       0;
+    gn->unmade_cohorts = 0;
+    gn->cohort_num[0] = 0;
+    gn->centurion =            NULL;
+    gn->made =                 UNMADE;
+    gn->flags =        0;
+    gn->checked = 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->order_pred =   Lst_Init(FALSE);
+    gn->order_succ =   Lst_Init(FALSE);
+    Hash_InitTable(&gn->context, 0);
+    gn->commands =     Lst_Init(FALSE);
+    gn->suffix =       NULL;
+    gn->lineno =       0;
+    gn->fname =        NULL;
+
+#ifdef CLEANUP
+    if (allGNs == NULL)
+       allGNs = Lst_Init(FALSE);
+    Lst_AtEnd(allGNs, gn);
+#endif
+
+    return (gn);
+}
+
+#ifdef CLEANUP
+/*-
+ *-----------------------------------------------------------------------
+ * TargFreeGN  --
+ *     Destroy a GNode
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     None.
+ *-----------------------------------------------------------------------
+ */
+static void
+TargFreeGN(void *gnp)
+{
+    GNode *gn = (GNode *)gnp;
+
+
+    free(gn->name);
+    if (gn->uname)
+       free(gn->uname);
+    if (gn->path)
+       free(gn->path);
+    /* gn->fname points to name allocated when file was opened, don't free */
+
+    Lst_Destroy(gn->iParents, NULL);
+    Lst_Destroy(gn->cohorts, NULL);
+    Lst_Destroy(gn->parents, NULL);
+    Lst_Destroy(gn->children, NULL);
+    Lst_Destroy(gn->order_succ, NULL);
+    Lst_Destroy(gn->order_pred, NULL);
+    Hash_DeleteTable(&gn->context);
+    Lst_Destroy(gn->commands, NULL);
+    free(gn);
+}
+#endif
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FindNode  --
+ *     Find a node in the list using the given name for matching
+ *
+ * Input:
+ *     name            the name to find
+ *     flags           flags governing events when target not
+ *                     found
+ *
+ * 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(const 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 | TARG_NOHASH))) {
+       he = Hash_FindEntry(&targets, name);
+       if (he == NULL)
+           return NULL;
+       return (GNode *)Hash_GetValue(he);
+    }
+
+    if (!(flags & TARG_NOHASH)) {
+       he = Hash_CreateEntry(&targets, name, &isNew);
+       if (!isNew)
+           return (GNode *)Hash_GetValue(he);
+    }
+
+    gn = Targ_NewGN(name);
+    if (!(flags & TARG_NOHASH))
+       Hash_SetValue(he, gn);
+    Var_Append(".ALLTARGETS", name, VAR_GLOBAL);
+    (void)Lst_AtEnd(allTargets, gn);
+    if (doing_depend)
+       gn->flags |= FROM_DEPEND;
+    return gn;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_FindList --
+ *     Make a complete list of GNodes from the given list of names
+ *
+ * Input:
+ *     name            list of names to find
+ *     flags           flags used if no node is found for a given name
+ *
+ * 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, gn);
+       } 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
+ *
+ * Input:
+ *     gn              node to check for
+ *
+ * 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
+ *
+ * Input:
+ *     gn              node to check for
+ *
+ * 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
+ *
+ * Input:
+ *     gn              the node to check
+ *
+ * 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.
+ *
+ * Input:
+ *     gn              The main target we'll create
+ *
+ * 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 *pflags __unused)
+{
+    GNode *gn = (GNode *)gnp;
+
+    fprintf(debug_file, "%s%s ", gn->name, gn->cohort_num);
+
+    return 0;
+}
+
+
+int
+Targ_PrintCmd(void *cmd, void *dummy)
+{
+    fprintf(debug_file, "\t%s\n", (char *)cmd);
+    return (dummy ? 0 : 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 tm)
+{
+    struct tm          *parts;
+    static char                buf[128];
+
+    parts = localtime(&tm);
+    (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts);
+    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): fprintf(debug_file, "." #attr " "); break
+#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))fprintf(debug_file, "." #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: if (DEBUG(TARG))fprintf(debug_file, ".MEMBER "); break;
+           PRINTDBIT(ARCHV);
+           PRINTDBIT(MADE);
+           PRINTDBIT(PHONY);
+       }
+    }
+}
+
+static const char *
+made_name(enum enum_made made)
+{
+    switch (made) {
+    case UNMADE:     return "unmade";
+    case DEFERRED:   return "deferred";
+    case REQUESTED:  return "requested";
+    case BEINGMADE:  return "being made";
+    case MADE:       return "made";
+    case UPTODATE:   return "up-to-date";
+    case ERROR:      return "error when made";
+    case ABORTED:    return "aborted";
+    default:         return "unknown enum_made value";
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPrintNode --
+ *     print the contents of a node
+ *-----------------------------------------------------------------------
+ */
+int
+Targ_PrintNode(void *gnp, void *passp)
+{
+    GNode         *gn = (GNode *)gnp;
+    int                  pass = passp ? *(int *)passp : 0;
+
+    fprintf(debug_file, "# %s%s, flags %x, type %x, made %d\n",
+           gn->name, gn->cohort_num, gn->flags, gn->type, gn->made);
+    if (gn->flags == 0)
+       return 0;
+
+    if (!OP_NOP(gn->type)) {
+       fprintf(debug_file, "#\n");
+       if (gn == mainTarg) {
+           fprintf(debug_file, "# *** MAIN TARGET ***\n");
+       }
+       if (pass >= 2) {
+           if (gn->unmade) {
+               fprintf(debug_file, "# %d unmade children\n", gn->unmade);
+           } else {
+               fprintf(debug_file, "# No unmade children\n");
+           }
+           if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) {
+               if (gn->mtime != 0) {
+                   fprintf(debug_file, "# last modified %s: %s\n",
+                             Targ_FmtTime(gn->mtime),
+                             made_name(gn->made));
+               } else if (gn->made != UNMADE) {
+                   fprintf(debug_file, "# non-existent (maybe): %s\n",
+                             made_name(gn->made));
+               } else {
+                   fprintf(debug_file, "# unmade\n");
+               }
+           }
+           if (!Lst_IsEmpty (gn->iParents)) {
+               fprintf(debug_file, "# implicit parents: ");
+               Lst_ForEach(gn->iParents, TargPrintName, NULL);
+               fprintf(debug_file, "\n");
+           }
+       } else {
+           if (gn->unmade)
+               fprintf(debug_file, "# %d unmade children\n", gn->unmade);
+       }
+       if (!Lst_IsEmpty (gn->parents)) {
+           fprintf(debug_file, "# parents: ");
+           Lst_ForEach(gn->parents, TargPrintName, NULL);
+           fprintf(debug_file, "\n");
+       }
+       if (!Lst_IsEmpty (gn->order_pred)) {
+           fprintf(debug_file, "# order_pred: ");
+           Lst_ForEach(gn->order_pred, TargPrintName, NULL);
+           fprintf(debug_file, "\n");
+       }
+       if (!Lst_IsEmpty (gn->order_succ)) {
+           fprintf(debug_file, "# order_succ: ");
+           Lst_ForEach(gn->order_succ, TargPrintName, NULL);
+           fprintf(debug_file, "\n");
+       }
+
+       fprintf(debug_file, "%-16s", gn->name);
+       switch (gn->type & OP_OPMASK) {
+           case OP_DEPENDS:
+               fprintf(debug_file, ": "); break;
+           case OP_FORCE:
+               fprintf(debug_file, "! "); break;
+           case OP_DOUBLEDEP:
+               fprintf(debug_file, ":: "); break;
+       }
+       Targ_PrintType(gn->type);
+       Lst_ForEach(gn->children, TargPrintName, NULL);
+       fprintf(debug_file, "\n");
+       Lst_ForEach(gn->commands, Targ_PrintCmd, NULL);
+       fprintf(debug_file, "\n\n");
+       if (gn->type & OP_DOUBLEDEP) {
+           Lst_ForEach(gn->cohorts, Targ_PrintNode, &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))
+       return 0;
+
+    fprintf(debug_file, "#\t%s [%s] ",
+           gn->name, gn->path ? gn->path : gn->name);
+    Targ_PrintType(gn->type);
+    fprintf(debug_file, "\n");
+
+    return 0;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_PrintGraph --
+ *     print the entire graph. heh heh
+ *
+ * Input:
+ *     pass            Which pass this is. 1 => no processing
+ *                     2 => processing done
+ *
+ * Results:
+ *     none
+ *
+ * Side Effects:
+ *     lots o' output
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_PrintGraph(int pass)
+{
+    fprintf(debug_file, "#*** Input graph:\n");
+    Lst_ForEach(allTargets, Targ_PrintNode, &pass);
+    fprintf(debug_file, "\n\n");
+    fprintf(debug_file, "#\n#   Files that are only sources:\n");
+    Lst_ForEach(allTargets, TargPrintOnlySrc, NULL);
+    fprintf(debug_file, "#*** Global Variables:\n");
+    Var_Dump(VAR_GLOBAL);
+    fprintf(debug_file, "#*** Command-line Variables:\n");
+    Var_Dump(VAR_CMD);
+    fprintf(debug_file, "\n");
+    Dir_PrintDirectories();
+    fprintf(debug_file, "\n");
+    Suff_PrintAll();
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPropagateNode --
+ *     Propagate information from a single node to related nodes if
+ *     appropriate.
+ *
+ * Input:
+ *     gnp             The node that we are processing.
+ *
+ * Results:
+ *     Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ *     Information is propagated from this node to cohort or child
+ *     nodes.
+ *
+ *     If the node was defined with "::", then TargPropagateCohort()
+ *     will be called for each cohort node.
+ *
+ *     If the node has recursive predecessors, then
+ *     TargPropagateRecpred() will be called for each recursive
+ *     predecessor.
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPropagateNode(void *gnp, void *junk __unused)
+{
+    GNode        *gn = (GNode *)gnp;
+
+    if (gn->type & OP_DOUBLEDEP)
+       Lst_ForEach(gn->cohorts, TargPropagateCohort, gnp);
+    return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * TargPropagateCohort --
+ *     Propagate some bits in the type mask from a node to
+ *     a related cohort node.
+ *
+ * Input:
+ *     cnp             The node that we are processing.
+ *     gnp             Another node that has cnp as a cohort.
+ *
+ * Results:
+ *     Always returns 0, for the benefit of Lst_ForEach().
+ *
+ * Side Effects:
+ *     cnp's type bitmask is modified to incorporate some of the
+ *     bits from gnp's type bitmask.  (XXX need a better explanation.)
+ *-----------------------------------------------------------------------
+ */
+static int
+TargPropagateCohort(void *cgnp, void *pgnp)
+{
+    GNode        *cgn = (GNode *)cgnp;
+    GNode        *pgn = (GNode *)pgnp;
+
+    cgn->type |= pgn->type & ~OP_OPMASK;
+    return (0);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Targ_Propagate --
+ *     Propagate information between related nodes.  Should be called
+ *     after the makefiles are parsed but before any action is taken.
+ *
+ * Results:
+ *     none
+ *
+ * Side Effects:
+ *     Information is propagated between related nodes throughout the
+ *     graph.
+ *-----------------------------------------------------------------------
+ */
+void
+Targ_Propagate(void)
+{
+    Lst_ForEach(allTargets, TargPropagateNode, NULL);
+}
diff --git a/commands/bmake/trace.c b/commands/bmake/trace.c
new file mode 100644 (file)
index 0000000..0640e5b
--- /dev/null
@@ -0,0 +1,123 @@
+/*     $NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $      */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Bill Sommerfeld
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $");
+#endif /* not lint */
+#endif
+
+/*-
+ * trace.c --
+ *     handle logging of trace events generated by various parts of make.
+ *
+ * Interface:
+ *     Trace_Init              Initialize tracing (called once during
+ *                             the lifetime of the process)
+ *
+ *     Trace_End               Finalize tracing (called before make exits)
+ *
+ *     Trace_Log               Log an event about a particular make job.
+ */
+
+#include <sys/time.h>
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "make.h"
+#include "job.h"
+#include "trace.h"
+
+static FILE *trfile;
+static pid_t trpid;
+char *trwd;
+
+static const char *evname[] = {
+       "BEG",
+       "END",
+       "ERR",
+       "JOB",
+       "DON",
+       "INT",
+};
+
+void
+Trace_Init(const char *pathname)
+{
+       char *p1;
+       if (pathname != NULL) {
+               trpid = getpid();
+               trwd = Var_Value(".CURDIR", VAR_GLOBAL, &p1);
+
+               trfile = fopen(pathname, "a");
+       }
+}
+
+void
+Trace_Log(TrEvent event, Job *job)
+{
+       struct timeval rightnow;
+       
+       if (trfile == NULL)
+               return;
+
+       gettimeofday(&rightnow, NULL);
+
+#if defined(__minix)
+       fprintf(trfile, "%ld.%06ld %d %s %d %s",
+           (long)rightnow.tv_sec, (long)rightnow.tv_usec,
+           jobTokensRunning,
+           evname[event], trpid, trwd);
+#else
+       fprintf(trfile, "%lld.%06ld %d %s %d %s",
+           (long long)rightnow.tv_sec, (long)rightnow.tv_usec,
+           jobTokensRunning,
+           evname[event], trpid, trwd);
+#endif
+       if (job != NULL) {
+               fprintf(trfile, " %s %d %x %x", job->node->name,
+                   job->pid, job->flags, job->node->type);
+       }
+       fputc('\n', trfile);
+       fflush(trfile);
+}
+
+void
+Trace_End(void)
+{
+       if (trfile != NULL)
+               fclose(trfile);
+}
diff --git a/commands/bmake/trace.h b/commands/bmake/trace.h
new file mode 100644 (file)
index 0000000..dc0fc6c
--- /dev/null
@@ -0,0 +1,49 @@
+/*     $NetBSD: trace.h,v 1.3 2008/04/28 20:24:14 martin Exp $ */
+
+/*-
+ * Copyright (c) 2000 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Bill Sommerfeld
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+/*-
+ * trace.h --
+ *     Definitions pertaining to the tracing of jobs in parallel mode.
+ */
+
+typedef enum {
+       MAKESTART,
+       MAKEEND,
+       MAKEERROR,
+       JOBSTART,
+       JOBEND,
+       MAKEINTR
+} TrEvent;
+
+void Trace_Init(const char *);
+void Trace_Log(TrEvent, Job *);
+void Trace_End(void);
+
diff --git a/commands/bmake/unit-tests/Makefile b/commands/bmake/unit-tests/Makefile
new file mode 100644 (file)
index 0000000..b3fd0a8
--- /dev/null
@@ -0,0 +1,73 @@
+# $NetBSD: Makefile,v 1.25 2009/11/19 00:30:25 sjg Exp $
+#
+# Unit tests for make(1)
+# The main targets are:
+# 
+# all: run all the tests
+# test:        run 'all', capture output and compare to expected results
+# accept: move generated output to expected results
+#
+# Adding a test case.  
+# Each feature should get its own set of tests in its own suitably
+# named makefile which should be added to SUBFILES to hook it in.
+# 
+
+.MAIN: all
+
+UNIT_TESTS:= ${.PARSEDIR}
+
+# Simple sub-makefiles - we run them as a black box
+# keep the list sorted.
+SUBFILES= \
+       comment \
+       cond1 \
+       export \
+       export-all \
+       dotwait \
+       forsubst \
+       moderrs \
+       modmatch \
+       modmisc \
+       modorder \
+       modts \
+       modword \
+       posix \
+       qequals \
+       ternary \
+       unexport \
+       unexport-env \
+       varcmd
+
+all: ${SUBFILES}
+
+# the tests are actually done with sub-makes.
+.PHONY: ${SUBFILES}
+.PRECIOUS: ${SUBFILES}
+${SUBFILES}:
+       -@${.MAKE} -k -f ${UNIT_TESTS}/$@
+
+clean:
+       rm -f *.out *.fail *.core
+
+.include <bsd.obj.mk>
+
+TEST_MAKE?= ${.MAKE}
+
+# The driver.
+# We always pretend .MAKE was called 'make' 
+# and strip ${.CURDIR}/ from the output
+# and replace anything after 'stopped in' with unit-tests
+# so the results can be compared.
+test:
+       @echo "${TEST_MAKE} -f ${MAKEFILE} > ${.TARGET}.out 2>&1"
+       @cd ${.OBJDIR} && ${TEST_MAKE} -f ${MAKEFILE} 2>&1 | \
+       ${TOOL_SED} -e 's,^${TEST_MAKE:T:C/\./\\\./g}:,make:,' \
+       -e '/stopped/s, /.*, unit-tests,' \
+       -e 's,${.CURDIR:C/\./\\\./g}/,,g' \
+       -e 's,${UNIT_TESTS:C/\./\\\./g}/,,g' > ${.TARGET}.out || { \
+       tail ${.TARGET}.out; mv ${.TARGET}.out ${.TARGET}.fail; exit 1; }
+       diff -u ${UNIT_TESTS}/${.TARGET}.exp ${.TARGET}.out
+
+accept:
+       mv test.out ${.CURDIR}/test.exp
+
diff --git a/commands/bmake/unit-tests/comment b/commands/bmake/unit-tests/comment
new file mode 100644 (file)
index 0000000..7dd7dbb
--- /dev/null
@@ -0,0 +1,31 @@
+# This is a comment
+.if ${MACHINE_ARCH} == something
+FOO=bar
+.endif
+
+#\
+       Multiline comment
+
+BAR=# defined
+FOOBAR= # defined 
+
+# This is an escaped comment \
+that keeps going until the end of this line
+
+# Another escaped comment \
+that \
+goes \
+on
+
+# This is NOT an escaped comment due to the double backslashes \\
+all: hi foo bar
+       @echo comment testing done
+
+hi:
+       @echo comment testing start
+
+foo:
+       @echo this is $@
+
+bar:
+       @echo This is how a comment looks: '# comment'
diff --git a/commands/bmake/unit-tests/cond1 b/commands/bmake/unit-tests/cond1
new file mode 100644 (file)
index 0000000..cf51b5c
--- /dev/null
@@ -0,0 +1,104 @@
+# $Id: cond1,v 1.4 2008/10/29 15:37:08 sjg Exp $
+
+# hard code these!
+TEST_UNAME_S= NetBSD
+TEST_UNAME_M= sparc
+TEST_MACHINE= i386
+
+.if ${TEST_UNAME_S}
+Ok=var,
+.endif
+.if ("${TEST_UNAME_S}")
+Ok+=(\"var\"),
+.endif
+.if (${TEST_UNAME_M} != ${TEST_MACHINE})
+Ok+=(var != var),
+.endif
+.if ${TEST_UNAME_M} != ${TEST_MACHINE}
+Ok+= var != var,
+.endif
+.if !((${TEST_UNAME_M} != ${TEST_MACHINE}) && defined(X))
+Ok+= !((var != var) && defined(name)),
+.endif
+# from bsd.obj.mk
+MKOBJ?=no
+.if ${MKOBJ} == "no"
+o= no
+Ok+= var == "quoted",
+.else
+.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR)
+.if defined(notMAKEOBJDIRPREFIX)
+o=${MAKEOBJDIRPREFIX}${__curdir}
+.else
+o= ${MAKEOBJDIR}
+.endif
+.endif
+o= o
+.endif
+
+# repeat the above to check we get the same result
+.if ${MKOBJ} == "no"
+o2= no
+.else
+.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR)
+.if defined(notMAKEOBJDIRPREFIX)
+o2=${MAKEOBJDIRPREFIX}${__curdir}
+.else
+o2= ${MAKEOBJDIR}
+.endif
+.endif
+o2= o
+.endif
+
+PRIMES=2 3 5 7 11
+NUMBERS=1 2 3 4 5
+
+n=2
+.if ${PRIMES:M$n} == ""
+X=not
+.else
+X=
+.endif
+
+.if ${MACHINE_ARCH} == no-such
+A=one
+.else
+.if ${MACHINE_ARCH} == not-this
+.if ${MACHINE_ARCH} == something-else
+A=unlikely
+.else
+A=no
+.endif
+.endif
+A=other
+# We expect an extra else warning - we're not skipping here
+.else
+A=this should be an error
+.endif
+
+.if $X != ""
+.if $X == not
+B=one
+.else
+B=other
+# We expect an extra else warning - we are skipping here
+.else
+B=this should be an error
+.endif
+.else
+B=unknown
+.endif
+
+.if "quoted" == quoted
+C=clever
+.else
+C=dim
+.endif
+
+all:
+       @echo "$n is $X prime"
+       @echo "A='$A' B='$B' C='$C' o='$o,${o2}'"
+       @echo "Passed:${.newline} ${Ok:S/,/${.newline}/}"
+       @echo "${NUMBERS:@n@$n is ${("${PRIMES:M$n}" == ""):?not:} prime${.newline}@}"
+       @echo "${"${DoNotQuoteHere:U0}" > 0:?OK:No}"
+       @echo "${${NoSuchNumber:U42} > 0:?OK:No}"
diff --git a/commands/bmake/unit-tests/dotwait b/commands/bmake/unit-tests/dotwait
new file mode 100644 (file)
index 0000000..43706af
--- /dev/null
@@ -0,0 +1,61 @@
+# $NetBSD: dotwait,v 1.1 2006/02/26 22:45:46 apb Exp $
+
+THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE}
+
+TESTS= simple recursive shared cycle
+PAUSE= sleep 1
+
+# Use a .for loop rather than dependencies here, to ensure
+# that the tests are run one by one, with parallelism
+# only within tests.
+# Ignore "--- target ---" lines printed by parallel make.
+all:
+.for t in ${TESTS}
+       @${.MAKE} -f ${THISMAKEFILE} -j4 $t | grep -v "^--- "
+.endfor
+
+#
+# Within each test, the names of the sub-targets follow these
+# conventions:
+# * If it's expected that two or more targets may be made in parallel,
+#   then the target names will differ only in an alphabetic component
+#   such as ".a" or ".b".
+# * If it's expected that two or more targets should be made in sequence
+#   then the target names will differ in numeric components, such that
+#   lexical ordering of the target names matches the expected order
+#   in which the targets should be made.
+#
+# Targets may echo ${PARALLEL_TARG} to print a modified version
+# of their own name, in which alphabetic components like ".a" or ".b"
+# are converted to ".*".  Two targets that are expected to
+# be made in parallel will thus print the same strings, so that the
+# output is independent of the order in which these targets are made.
+#
+PARALLEL_TARG= ${.TARGET:C/\.[a-z]/.*/g:Q}
+.DEFAULT:
+       @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG}
+_ECHOUSE: .USE
+       @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG}
+
+# simple: no recursion, no cycles
+simple: simple.1 .WAIT simple.2
+
+# recursive: all children of the left hand side of the .WAIT
+# must be made before any child of the right hand side.
+recursive: recursive.1.99 .WAIT recursive.2.99
+recursive.1.99: recursive.1.1.a recursive.1.1.b _ECHOUSE
+recursive.2.99: recursive.2.1.a recursive.2.1.b _ECHOUSE
+
+# shared: both shared.1.99 and shared.2.99 depend on shared.0.
+# shared.0 must be made first, even though it is a child of
+# the right hand side of the .WAIT.
+shared: shared.1.99 .WAIT shared.2.99
+shared.1.99: shared.0 _ECHOUSE
+shared.2.99: shared.2.1 shared.0 _ECHOUSE
+
+# cycle: the cyclic dependency must not cause infinite recursion
+# leading to stack overflow and a crash.
+cycle: cycle.1.99 .WAIT cycle.2.99
+cycle.2.99: cycle.2.98 _ECHOUSE
+cycle.2.98: cycle.2.97 _ECHOUSE
+cycle.2.97: cycle.2.99 _ECHOUSE
diff --git a/commands/bmake/unit-tests/export b/commands/bmake/unit-tests/export
new file mode 100644 (file)
index 0000000..eb4d32f
--- /dev/null
@@ -0,0 +1,22 @@
+# $Id: export,v 1.1 2007/10/05 15:27:46 sjg Exp $
+
+UT_TEST=export
+UT_FOO=foo${BAR}
+UT_FU=fubar
+UT_ZOO=hoopie
+UT_NO=all
+# belive it or not, we expect this one to come out with $UT_FU unexpanded.
+UT_DOLLAR= This is $$UT_FU
+
+.export UT_FU UT_FOO
+.export UT_DOLLAR
+# this one will be ignored
+.export .MAKE.PID
+
+BAR=bar is ${UT_FU}
+
+.MAKE.EXPORTED+= UT_ZOO UT_TEST
+
+all:
+       @env | grep '^UT_' | sort
+
diff --git a/commands/bmake/unit-tests/export-all b/commands/bmake/unit-tests/export-all
new file mode 100644 (file)
index 0000000..0542937
--- /dev/null
@@ -0,0 +1,11 @@
+# $Id: export-all,v 1.1 2007/10/05 15:27:46 sjg Exp $
+
+UT_OK=good
+UT_F=fine
+
+.export
+
+.include "export"
+
+UT_TEST=export-all
+UT_ALL=even this gets exported
diff --git a/commands/bmake/unit-tests/forsubst b/commands/bmake/unit-tests/forsubst
new file mode 100644 (file)
index 0000000..981fb88
--- /dev/null
@@ -0,0 +1,10 @@
+# $Id: forsubst,v 1.1 2009/10/07 16:40:30 sjg Exp $
+
+all: for-subst
+
+here := ${.PARSEDIR}
+# this should not run foul of the parser
+.for file in ${.PARSEFILE}
+for-subst:       ${file:S;^;${here}/;g}
+       @echo ".for with :S;... OK"
+.endfor
diff --git a/commands/bmake/unit-tests/moderrs b/commands/bmake/unit-tests/moderrs
new file mode 100644 (file)
index 0000000..9c69810
--- /dev/null
@@ -0,0 +1,31 @@
+# $Id: moderrs,v 1.2 2006/05/11 18:48:33 sjg Exp $
+#
+# various modifier error tests
+
+VAR=TheVariable
+# incase we have to change it ;-)
+MOD_UNKN=Z
+MOD_TERM=S,V,v
+MOD_S:= ${MOD_TERM},
+
+all:   modunkn modunknV varterm vartermV modtermV
+
+modunkn:
+       @echo "Expect: Unknown modifier 'Z'"
+       @echo "VAR:Z=${VAR:Z}"
+
+modunknV:
+       @echo "Expect: Unknown modifier 'Z'"
+       @echo "VAR:${MOD_UNKN}=${VAR:${MOD_UNKN}}"
+
+varterm:
+       @echo "Expect: Unclosed variable specification for VAR"
+       @echo VAR:S,V,v,=${VAR:S,V,v,
+
+vartermV:
+       @echo "Expect: Unclosed variable specification for VAR"
+       @echo VAR:${MOD_TERM},=${VAR:${MOD_S}
+
+modtermV:
+       @echo "Expect: Unclosed substitution for VAR (, missing)"
+       -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}"
diff --git a/commands/bmake/unit-tests/modmatch b/commands/bmake/unit-tests/modmatch
new file mode 100644 (file)
index 0000000..48a1bef
--- /dev/null
@@ -0,0 +1,25 @@
+
+X=a b c d e
+
+.for x in $X
+LIB${x:tu}=/tmp/lib$x.a
+.endfor
+
+X_LIBS= ${LIBA} ${LIBD} ${LIBE}
+
+LIB?=a
+
+var = head
+res = no
+.if !empty(var:M${:Uhead\:tail:C/:.*//})
+res = OK
+.endif
+
+all:
+       @for x in $X; do ${.MAKE} -f ${MAKEFILE} show LIB=$$x; done
+       @echo "Mscanner=${res}"
+
+show:
+       @echo 'LIB=${LIB} X_LIBS:M$${LIB$${LIB:tu}} is "${X_LIBS:M${LIB${LIB:tu}}}"'
+       @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a is "${X_LIBS:M*/lib${LIB}.a}"'
+       @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a:tu is "${X_LIBS:M*/lib${LIB}.a:tu}"'
diff --git a/commands/bmake/unit-tests/modmisc b/commands/bmake/unit-tests/modmisc
new file mode 100644 (file)
index 0000000..11f22ea
--- /dev/null
@@ -0,0 +1,33 @@
+# $Id: modmisc,v 1.4 2006/05/11 15:37:07 sjg Exp $
+#
+# miscellaneous modifier tests
+
+path=:/bin:/usr/bin::/sbin:/usr/sbin:.:/home/user/bin:.
+# strip cwd from path.
+MOD_NODOT=S/:/ /g:N.:ts:
+# and decorate, note that $'s need to be doubled. Also note that 
+# the modifier_variable can be used with other modifiers.
+MOD_NODOTX=S/:/ /g:N.:@d@'$$d'@
+# another mod - pretend it is more interesting
+MOD_HOMES=S,/home/,/homes/,
+MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@
+MOD_SEP=S,:, ,g
+
+all:   modvar modvarloop
+
+modvar:
+       @echo "path='${path}'"
+       @echo "path='${path:${MOD_NODOT}}'"
+       @echo "path='${path:S,home,homes,:${MOD_NODOT}}'"
+       @echo "path=${path:${MOD_NODOTX}:ts:}"
+       @echo "path=${path:${MOD_HOMES}:${MOD_NODOTX}:ts:}"
+
+.for d in ${path:${MOD_SEP}:N.} /usr/xbin
+path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/
+paths+= ${d:${MOD_OPT}:${MOD_HOMES}}
+.endfor
+
+modvarloop:
+       @echo "path_/usr/xbin=${path_/usr/xbin}"
+       @echo "paths=${paths}"
+       @echo "PATHS=${paths:tu}"
diff --git a/commands/bmake/unit-tests/modorder b/commands/bmake/unit-tests/modorder
new file mode 100644 (file)
index 0000000..68b66fb
--- /dev/null
@@ -0,0 +1,22 @@
+# $NetBSD: modorder,v 1.2 2007/10/05 15:27:46 sjg Exp $
+
+LIST=          one two three four five six seven eight nine ten
+LISTX=         ${LIST:Ox}
+LISTSX:=       ${LIST:Ox}
+TEST_RESULT= && echo Ok || echo Failed
+
+# unit-tests have to produce the same results on each run
+# so we cannot actually include :Ox output.
+all:
+       @echo "LIST      = ${LIST}"
+       @echo "LIST:O    = ${LIST:O}"
+       # Note that 1 in every 10! trials two independently generated
+       # randomized orderings will be the same.  The test framework doesn't
+       # support checking probabilistic output, so we accept that the test
+       # will incorrectly fail with probability 2.8E-7.
+       @echo "LIST:Ox   = `test '${LIST:Ox}' != '${LIST:Ox}' ${TEST_RESULT}`"
+       @echo "LIST:O:Ox = `test '${LIST:O:Ox}' != '${LIST:O:Ox}' ${TEST_RESULT}`"
+       @echo "LISTX     = `test '${LISTX}' != '${LISTX}' ${TEST_RESULT}`"
+       @echo "LISTSX    = `test '${LISTSX}' = '${LISTSX}' ${TEST_RESULT}`"
+       @echo "BADMOD 1  = ${LIST:OX}"
+       @echo "BADMOD 2  = ${LIST:OxXX}"
diff --git a/commands/bmake/unit-tests/modts b/commands/bmake/unit-tests/modts
new file mode 100644 (file)
index 0000000..d0efd6d
--- /dev/null
@@ -0,0 +1,32 @@
+
+LIST= one two three
+LIST+= four five six
+
+FU_mod-ts = a / b / cool
+
+AAA= a a a
+B.aaa= Baaa
+
+all:   mod-ts
+
+mod-ts:
+       @echo 'LIST="${LIST}"'
+       @echo 'LIST:ts,="${LIST:ts,}"'
+       @echo 'LIST:ts/:tu="${LIST:ts/:tu}"'
+       @echo 'LIST:ts::tu="${LIST:ts::tu}"'
+       @echo 'LIST:ts:tu="${LIST:ts:tu}"'
+       @echo 'LIST:tu:ts/="${LIST:tu:ts/}"'
+       @echo 'LIST:ts:="${LIST:ts:}"'
+       @echo 'LIST:ts="${LIST:ts}"'
+       @echo 'LIST:ts:S/two/2/="${LIST:ts:S/two/2/}"'
+       @echo 'LIST:S/two/2/:ts="${LIST:S/two/2/:ts}"'
+       @echo 'LIST:ts/:S/two/2/="${LIST:ts/:S/two/2/}"'
+       @echo "Pretend the '/' in '/n' etc. below are back-slashes."
+       @echo 'LIST:ts/n="${LIST:ts\n}"'
+       @echo 'LIST:ts/t="${LIST:ts\t}"'
+       @echo 'LIST:ts/012:tu="${LIST:ts\012:tu}"'
+       @echo 'LIST:tx="${LIST:tx}"'
+       @echo 'LIST:ts/x:tu="${LIST:ts\x:tu}"'
+       @echo 'FU_$@="${FU_${@:ts}:ts}"'
+       @echo 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?'
+       @echo 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?'
diff --git a/commands/bmake/unit-tests/modword b/commands/bmake/unit-tests/modword
new file mode 100644 (file)
index 0000000..47f1677
--- /dev/null
@@ -0,0 +1,151 @@
+# $Id: modword,v 1.1 2003/09/27 21:29:37 sjg Exp $
+#
+# Test behaviour of new :[] modifier
+
+all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw
+
+LIST= one two three
+LIST+= four five six
+LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
+
+EMPTY= # the space should be ignored
+ESCAPEDSPACE=\ # escaped space before the '#'
+REALLYSPACE:=${EMPTY:C/^/ /W}
+HASH= \#
+AT= @
+STAR= *
+ZERO= 0
+ONE= 1
+MINUSONE= -1
+
+mod-squarebrackets: mod-squarebrackets-0-star-at \
+       mod-squarebrackets-hash \
+       mod-squarebrackets-n \
+       mod-squarebrackets-start-end \
+       mod-squarebrackets-nested
+
+mod-squarebrackets-0-star-at:
+       @echo 'LIST:[]="${LIST:[]}" is an error'
+       @echo 'LIST:[0]="${LIST:[0]}"'
+       @echo 'LIST:[0x0]="${LIST:[0x0]}"'
+       @echo 'LIST:[000]="${LIST:[000]}"'
+       @echo 'LIST:[*]="${LIST:[*]}"'
+       @echo 'LIST:[@]="${LIST:[@]}"'
+       @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"'
+       @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"'
+       @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"'
+       @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"'
+       @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"'
+       @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"'
+       @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"'
+       @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"'
+       @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"'
+       @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"'
+       @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"'
+       @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"'
+       @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"'
+
+mod-squarebrackets-hash:
+       @echo 'EMPTY="${EMPTY}"'
+       @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?'
+       @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
+       @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?'
+       @echo 'REALLYSPACE="${REALLYSPACE}"'
+       @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?'
+       @echo 'LIST:[#]="${LIST:[#]}"'
+       @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?'
+       @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?'
+       @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"'
+       @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"'
+       @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"'
+
+mod-squarebrackets-n:
+       @echo 'EMPTY:[1]="${EMPTY:[1]}"'
+       @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"'
+       @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"'
+       @echo 'REALLYSPACE="${REALLYSPACE}"'
+       @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?'
+       @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?'
+       @echo 'LIST:[1]="${LIST:[1]}"'
+       @echo 'LIST:[1.]="${LIST:[1.]}" is an error'
+       @echo 'LIST:[1].="${LIST:[1].}" is an error'
+       @echo 'LIST:[2]="${LIST:[2]}"'
+       @echo 'LIST:[6]="${LIST:[6]}"'
+       @echo 'LIST:[7]="${LIST:[7]}"'
+       @echo 'LIST:[999]="${LIST:[999]}"'
+       @echo 'LIST:[-]="${LIST:[-]}" is an error'
+       @echo 'LIST:[--]="${LIST:[--]}" is an error'
+       @echo 'LIST:[-1]="${LIST:[-1]}"'
+       @echo 'LIST:[-2]="${LIST:[-2]}"'
+       @echo 'LIST:[-6]="${LIST:[-6]}"'
+       @echo 'LIST:[-7]="${LIST:[-7]}"'
+       @echo 'LIST:[-999]="${LIST:[-999]}"'
+       @echo 'LONGLIST:[17]="${LONGLIST:[17]}"'
+       @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"'
+       @echo 'LONGLIST:[021]="${LONGLIST:[021]}"'
+       @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"'
+       @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"'
+       @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"'
+       @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"'
+       @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"'
+       @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"'
+       @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"'
+       @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"'
+       @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"'
+
+mod-squarebrackets-start-end:
+       @echo 'LIST:[1.]="${LIST:[1.]}" is an error'
+       @echo 'LIST:[1..]="${LIST:[1..]}" is an error'
+       @echo 'LIST:[1..1]="${LIST:[1..1]}"'
+       @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error'
+       @echo 'LIST:[1..2]="${LIST:[1..2]}"'
+       @echo 'LIST:[2..1]="${LIST:[2..1]}"'
+       @echo 'LIST:[3..-2]="${LIST:[3..-2]}"'
+       @echo 'LIST:[-4..4]="${LIST:[-4..4]}"'
+       @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error'
+       @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error'
+       @echo 'LIST:[-1..1]="${LIST:[-1..1]}"'
+       @echo 'LIST:[0..0]="${LIST:[0..0]}"'
+       @echo 'LIST:[3..99]="${LIST:[3..99]}"'
+       @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"'
+       @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"'
+
+mod-squarebrackets-nested:
+       @echo 'HASH="${HASH}" == "#" ?'
+       @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"'
+       @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"'
+       @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"'
+       @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"'
+       @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"'
+       @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"'
+       @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"'
+       @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error'
+       @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"'
+       @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"'
+       @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"'
+
+mod-C-W:
+       @echo 'LIST:C/ /,/="${LIST:C/ /,/}"'
+       @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"'
+       @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"'
+       @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"'
+       @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"'
+
+mod-S-W:
+       @echo 'LIST:S/ /,/="${LIST:S/ /,/}"'
+       @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"'
+       @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"'
+       @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"'
+       @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"'
+
+mod-tW-tw:
+       @echo 'LIST:tW="${LIST:tW}"'
+       @echo 'LIST:tw="${LIST:tw}"'
+       @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"'
+       @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"'
+       @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"'
+       @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"'
+       @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"'
+       @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"'
+       @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"'
+       @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"'
diff --git a/commands/bmake/unit-tests/posix b/commands/bmake/unit-tests/posix
new file mode 100644 (file)
index 0000000..b685c64
--- /dev/null
@@ -0,0 +1,24 @@
+# $Id: posix,v 1.1 2004/05/07 08:12:16 sjg Exp $
+
+all:   x plus subs err
+
+x:
+       @echo "Posix says we should execute the command as if run by system(3)"
+       @echo "Expect 'Hello,' and 'World!'"
+       @echo Hello,; false; echo "World!"
+
+plus:
+       @echo a command
+       +@echo "a command prefixed by '+' executes even with -n"
+       @echo another command
+
+subs:
+       @echo make -n
+       @${.MAKE} -f ${MAKEFILE} -n plus
+       @echo make -n -j1
+       @${.MAKE} -f ${MAKEFILE} -n -j1 plus
+
+err:
+       @(echo Now we expect an error...; exit 1)
+       @echo "Oops! you shouldn't see this!"
+
diff --git a/commands/bmake/unit-tests/qequals b/commands/bmake/unit-tests/qequals
new file mode 100644 (file)
index 0000000..2010c08
--- /dev/null
@@ -0,0 +1,8 @@
+# $Id: qequals,v 1.1 2008/03/31 00:12:21 sjg Exp $
+
+M= i386
+V.i386= OK
+V.$M ?= bug
+
+all:
+       @echo 'V.$M ?= ${V.$M}'
diff --git a/commands/bmake/unit-tests/ternary b/commands/bmake/unit-tests/ternary
new file mode 100644 (file)
index 0000000..77f8349
--- /dev/null
@@ -0,0 +1,8 @@
+
+all:
+       @for x in "" A= A=42; do ${.MAKE} -f ${MAKEFILE} show $$x; done
+
+show:
+       @echo "The answer is ${A:?known:unknown}"
+       @echo "The answer is ${A:?$A:unknown}"
+       @echo "The answer is ${empty(A):?empty:$A}"
diff --git a/commands/bmake/unit-tests/test.exp b/commands/bmake/unit-tests/test.exp
new file mode 100644 (file)
index 0000000..c242a04
--- /dev/null
@@ -0,0 +1,316 @@
+comment testing start
+this is foo
+This is how a comment looks: # comment
+comment testing done
+make: "cond1" line 75: warning: extra else
+make: "cond1" line 85: warning: extra else
+2 is  prime
+A='other' B='unknown' C='clever' o='no,no'
+Passed:
+ var
+ ("var")
+ (var != var)
+ var != var
+ !((var != var) && defined(name))
+ var == quoted
+
+1 is not prime
+2 is  prime
+3 is  prime
+4 is not prime
+5 is  prime
+
+make: warning: String comparison operator should be either == or !=
+make: Bad conditional expression `"0" > 0' in "0" > 0?OK:No
+
+OK
+UT_DOLLAR=This is $UT_FU
+UT_FOO=foobar is fubar
+UT_FU=fubar
+UT_TEST=export
+UT_ZOO=hoopie
+UT_ALL=even this gets exported
+UT_DOLLAR=This is $UT_FU
+UT_F=fine
+UT_FOO=foobar is fubar
+UT_FU=fubar
+UT_NO=all
+UT_OK=good
+UT_TEST=export-all
+UT_ZOO=hoopie
+simple.1
+simple.1
+simple.2
+simple.2
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.1.*
+recursive.1.99
+recursive.1.99
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.1.*
+recursive.2.99
+recursive.2.99
+shared.0
+shared.0
+shared.1.99
+shared.1.99
+shared.2.1
+shared.2.1
+shared.2.99
+shared.2.99
+make: Graph cycles through `cycle.2.99'
+make: Graph cycles through `cycle.2.98'
+make: Graph cycles through `cycle.2.97'
+cycle.1.99
+cycle.1.99
+.for with :S;... OK
+Expect: Unknown modifier 'Z'
+make: Unknown modifier 'Z'
+VAR:Z=
+Expect: Unknown modifier 'Z'
+make: Unknown modifier 'Z'
+VAR:Z=
+Expect: Unclosed variable specification for VAR
+make: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S
+VAR:S,V,v,=Thevariable
+Expect: Unclosed variable specification for VAR
+make: Unclosed variable specification after complex modifier (expecting '}') for VAR
+VAR:S,V,v,=Thevariable
+Expect: Unclosed substitution for VAR (, missing)
+make: Unclosed substitution for VAR (, missing)
+VAR:S,V,v=
+LIB=a X_LIBS:M${LIB${LIB:tu}} is "/tmp/liba.a"
+LIB=a X_LIBS:M*/lib${LIB}.a is "/tmp/liba.a"
+LIB=a X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBA.A"
+LIB=b X_LIBS:M${LIB${LIB:tu}} is ""
+LIB=b X_LIBS:M*/lib${LIB}.a is ""
+LIB=b X_LIBS:M*/lib${LIB}.a:tu is ""
+LIB=c X_LIBS:M${LIB${LIB:tu}} is ""
+LIB=c X_LIBS:M*/lib${LIB}.a is ""
+LIB=c X_LIBS:M*/lib${LIB}.a:tu is ""
+LIB=d X_LIBS:M${LIB${LIB:tu}} is "/tmp/libd.a"
+LIB=d X_LIBS:M*/lib${LIB}.a is "/tmp/libd.a"
+LIB=d X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBD.A"
+LIB=e X_LIBS:M${LIB${LIB:tu}} is "/tmp/libe.a"
+LIB=e X_LIBS:M*/lib${LIB}.a is "/tmp/libe.a"
+LIB=e X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBE.A"
+Mscanner=OK
+path=':/bin:/usr/bin::/sbin:/usr/sbin:.:/home/user/bin:.'
+path='/bin:/usr/bin:/sbin:/usr/sbin:/home/user/bin'
+path='/bin:/usr/bin:/sbin:/usr/sbin:/homes/user/bin'
+path='/bin':'/usr/bin':'/sbin':'/usr/sbin':'/home/user/bin'
+path='/bin':'/usr/bin':'/sbin':'/usr/sbin':'/homes/user/bin'
+path_/usr/xbin=/opt/xbin/
+paths=/bin /usr/bin /sbin /usr/sbin /homes/user/bin /opt/xbin
+PATHS=/BIN /USR/BIN /SBIN /USR/SBIN /HOMES/USER/BIN /OPT/XBIN
+LIST      = one two three four five six seven eight nine ten
+LIST:O    = eight five four nine one seven six ten three two
+LIST:Ox   = Ok
+LIST:O:Ox = Ok
+LISTX     = Ok
+LISTSX    = Ok
+make: Bad modifier `:OX' for LIST
+BADMOD 1  = }
+make: Bad modifier `:OxXX' for LIST
+BADMOD 2  = XX}
+LIST="one two three four five six"
+LIST:ts,="one,two,three,four,five,six"
+LIST:ts/:tu="ONE/TWO/THREE/FOUR/FIVE/SIX"
+LIST:ts::tu="ONE:TWO:THREE:FOUR:FIVE:SIX"
+LIST:ts:tu="ONETWOTHREEFOURFIVESIX"
+LIST:tu:ts/="ONE/TWO/THREE/FOUR/FIVE/SIX"
+LIST:ts:="one:two:three:four:five:six"
+LIST:ts="onetwothreefourfivesix"
+LIST:ts:S/two/2/="one2threefourfivesix"
+LIST:S/two/2/:ts="one2threefourfivesix"
+LIST:ts/:S/two/2/="one/2/three/four/five/six"
+Pretend the '/' in '/n' etc. below are back-slashes.
+LIST:ts/n="one
+two
+three
+four
+five
+six"
+LIST:ts/t="one two     three   four    five    six"
+LIST:ts/012:tu="ONE
+TWO
+THREE
+FOUR
+FIVE
+SIX"
+make: Bad modifier `:tx' for LIST
+LIST:tx="}"
+make: Bad modifier `:ts\x' for LIST
+LIST:ts/x:tu="\x:tu}"
+FU_mod-ts="a/b/cool"
+FU_mod-ts:ts:T="cool" == cool?
+B.${AAA:ts}="Baaa" == Baaa?
+make: Bad modifier `:[]' for LIST
+LIST:[]="" is an error
+LIST:[0]="one two three four five six"
+LIST:[0x0]="one two three four five six"
+LIST:[000]="one two three four five six"
+LIST:[*]="one two three four five six"
+LIST:[@]="one two three four five six"
+LIST:[0]:C/ /,/="one,two three four five six"
+LIST:[0]:C/ /,/g="one,two,three,four,five,six"
+LIST:[0]:C/ /,/1g="one,two,three,four,five,six"
+LIST:[*]:C/ /,/="one,two three four five six"
+LIST:[*]:C/ /,/g="one,two,three,four,five,six"
+LIST:[*]:C/ /,/1g="one,two,three,four,five,six"
+LIST:[@]:C/ /,/="one two three four five six"
+LIST:[@]:C/ /,/g="one two three four five six"
+LIST:[@]:C/ /,/1g="one two three four five six"
+LIST:[@]:[0]:C/ /,/="one,two three four five six"
+LIST:[0]:[@]:C/ /,/="one two three four five six"
+LIST:[@]:[*]:C/ /,/="one,two three four five six"
+LIST:[*]:[@]:C/ /,/="one two three four five six"
+EMPTY=""
+EMPTY:[#]="1" == 1 ?
+ESCAPEDSPACE="\ "
+ESCAPEDSPACE:[#]="1" == 1 ?
+REALLYSPACE=" "
+REALLYSPACE:[#]="1" == 1 ?
+LIST:[#]="6"
+LIST:[0]:[#]="1" == 1 ?
+LIST:[*]:[#]="1" == 1 ?
+LIST:[@]:[#]="6"
+LIST:[1]:[#]="1"
+LIST:[1..3]:[#]="3"
+EMPTY:[1]=""
+ESCAPEDSPACE="\ "
+ESCAPEDSPACE:[1]="\ "
+REALLYSPACE=" "
+REALLYSPACE:[1]="" == "" ?
+REALLYSPACE:[*]:[1]=" " == " " ?
+LIST:[1]="one"
+make: Bad modifier `:[1.]' for LIST
+LIST:[1.]="" is an error
+make: Bad modifier `:[1].' for LIST
+LIST:[1].="}" is an error
+LIST:[2]="two"
+LIST:[6]="six"
+LIST:[7]=""
+LIST:[999]=""
+make: Bad modifier `:[-]' for LIST
+LIST:[-]="" is an error
+make: Bad modifier `:[--]' for LIST
+LIST:[--]="" is an error
+LIST:[-1]="six"
+LIST:[-2]="five"
+LIST:[-6]="one"
+LIST:[-7]=""
+LIST:[-999]=""
+LONGLIST:[17]="17"
+LONGLIST:[0x11]="17"
+LONGLIST:[021]="17"
+LIST:[0]:[1]="one two three four five six"
+LIST:[*]:[1]="one two three four five six"
+LIST:[@]:[1]="one"
+LIST:[0]:[2]=""
+LIST:[*]:[2]=""
+LIST:[@]:[2]="two"
+LIST:[*]:C/ /,/:[2]=""
+LIST:[*]:C/ /,/:[*]:[2]=""
+LIST:[*]:C/ /,/:[@]:[2]="three"
+make: Bad modifier `:[1.]' for LIST
+LIST:[1.]="" is an error
+make: Bad modifier `:[1..]' for LIST
+LIST:[1..]="" is an error
+LIST:[1..1]="one"
+make: Bad modifier `:[1..1.]' for LIST
+LIST:[1..1.]="" is an error
+LIST:[1..2]="one two"
+LIST:[2..1]="two one"
+LIST:[3..-2]="three four five"
+LIST:[-4..4]="three four"
+make: Bad modifier `:[0..1]' for LIST
+LIST:[0..1]="" is an error
+make: Bad modifier `:[-1..0]' for LIST
+LIST:[-1..0]="" is an error
+LIST:[-1..1]="six five four three two one"
+LIST:[0..0]="one two three four five six"
+LIST:[3..99]="three four five six"
+LIST:[-3..-99]="four three two one"
+LIST:[-99..-3]="one two three four"
+HASH="#" == "#" ?
+LIST:[${HASH}]="6"
+LIST:[${ZERO}]="one two three four five six"
+LIST:[${ZERO}x${ONE}]="one"
+LIST:[${ONE}]="one"
+LIST:[${MINUSONE}]="six"
+LIST:[${STAR}]="one two three four five six"
+LIST:[${AT}]="one two three four five six"
+make: Bad modifier `:[${EMPTY' for LIST
+LIST:[${EMPTY}]="" is an error
+LIST:[${LONGLIST:[21]:S/2//}]="one"
+LIST:[${LIST:[#]}]="six"
+LIST:[${LIST:[${HASH}]}]="six"
+LIST:S/ /,/="one two three four five six"
+LIST:S/ /,/W="one,two three four five six"
+LIST:S/ /,/gW="one,two,three,four,five,six"
+EMPTY:S/^/,/=","
+EMPTY:S/^/,/W=","
+LIST:C/ /,/="one two three four five six"
+LIST:C/ /,/W="one,two three four five six"
+LIST:C/ /,/gW="one,two,three,four,five,six"
+EMPTY:C/^/,/=","
+EMPTY:C/^/,/W=","
+LIST:tW="one two three four five six"
+LIST:tw="one two three four five six"
+LIST:tW:C/ /,/="one,two three four five six"
+LIST:tW:C/ /,/g="one,two,three,four,five,six"
+LIST:tW:C/ /,/1g="one,two,three,four,five,six"
+LIST:tw:C/ /,/="one two three four five six"
+LIST:tw:C/ /,/g="one two three four five six"
+LIST:tw:C/ /,/1g="one two three four five six"
+LIST:tw:tW:C/ /,/="one,two three four five six"
+LIST:tW:tw:C/ /,/="one two three four five six"
+Posix says we should execute the command as if run by system(3)
+Expect 'Hello,' and 'World!'
+Hello,
+World!
+a command
+a command prefixed by '+' executes even with -n
+another command
+make -n
+echo a command
+echo "a command prefixed by '+' executes even with -n"
+a command prefixed by '+' executes even with -n
+echo another command
+make -n -j1
+{ echo a command 
+} || exit $?
+echo "a command prefixed by '+' executes even with -n"
+a command prefixed by '+' executes even with -n
+{ echo another command 
+} || exit $?
+Now we expect an error...
+*** Error code 1 (continuing)
+`all' not remade because of errors.
+V.i386 ?= OK
+The answer is unknown
+The answer is unknown
+The answer is empty
+The answer is known
+The answer is 
+The answer is empty
+The answer is known
+The answer is 42
+The answer is 42
+UT_DOLLAR=This is $UT_FU
+UT_FU=fubar
+UT_TEST=unexport
+UT_TEST=unexport-env
+default FU=<v>fu</v> FOO=<v>foo</v> VAR=<v></v>
+two FU=<v>bar</v> FOO=<v>goo</v> VAR=<v></v>
+three FU=<v>bar</v> FOO=<v>goo</v> VAR=<v></v>
+four FU=<v>bar</v> FOO=<v>goo</v> VAR=<v>Internal</v>
+five FU=<v>bar</v> FOO=<v>goo</v> VAR=<v>Internal</v>
+five v=is x k=is x
+six v=is y k=is y
+show-v v=override k=override
diff --git a/commands/bmake/unit-tests/unexport b/commands/bmake/unit-tests/unexport
new file mode 100644 (file)
index 0000000..34c88c9
--- /dev/null
@@ -0,0 +1,8 @@
+# $Id: unexport,v 1.1 2009/11/19 00:30:25 sjg Exp $
+
+# pick up a bunch of exported vars
+.include "export"
+
+.unexport UT_ZOO UT_FOO
+
+UT_TEST = unexport
diff --git a/commands/bmake/unit-tests/unexport-env b/commands/bmake/unit-tests/unexport-env
new file mode 100644 (file)
index 0000000..2388856
--- /dev/null
@@ -0,0 +1,14 @@
+# $Id: unexport-env,v 1.1 2009/11/19 00:30:25 sjg Exp $
+
+# pick up a bunch of exported vars
+.include "export"
+
+# an example of setting up a minimal environment.
+PATH = /bin:/usr/bin:/sbin:/usr/sbin
+
+# now clobber the environment to just PATH and UT_TEST
+UT_TEST = unexport-env
+
+# this removes everything
+.unexport-env
+.export PATH UT_TEST
diff --git a/commands/bmake/unit-tests/varcmd b/commands/bmake/unit-tests/varcmd
new file mode 100644 (file)
index 0000000..60343ac
--- /dev/null
@@ -0,0 +1,49 @@
+# $Id: varcmd,v 1.3 2008/05/14 14:27:02 sjg Exp $
+#
+# Test behaviour of recursive make and vars set on command line.
+
+FU=fu
+FOO?=foo
+.if !empty(.TARGETS)
+TAG=${.TARGETS}
+.endif
+TAG?=default
+
+all:   one
+
+show:
+       @echo "${TAG} FU=<v>${FU}</v> FOO=<v>${FOO}</v> VAR=<v>${VAR}</v>"
+
+one:   show
+       @${.MAKE} -f ${MAKEFILE} FU=bar FOO=goo two
+
+two:   show
+       @${.MAKE} -f ${MAKEFILE} three
+
+three: show
+       @${.MAKE} -f ${MAKEFILE} four
+
+
+.ifmake four
+VAR=Internal
+.MAKEOVERRIDES+= VAR
+.endif
+
+four:  show
+       @${.MAKE} -f ${MAKEFILE} five
+
+M = x
+V.y = is y
+V.x = is x
+V := ${V.$M}
+K := ${V}
+
+show-v:
+       @echo '${TAG} v=${V} k=${K}'
+
+five:  show show-v
+       @${.MAKE} -f ${MAKEFILE} M=y six
+
+six:   show-v
+       @${.MAKE} -f ${MAKEFILE} V=override show-v
+
diff --git a/commands/bmake/util.c b/commands/bmake/util.c
new file mode 100644 (file)
index 0000000..5b51e2c
--- /dev/null
@@ -0,0 +1,504 @@
+/*     $NetBSD: util.c,v 1.48 2009/01/29 09:03:04 dholland Exp $       */
+
+/*
+ * Missing stuff from OS's
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: util.c,v 1.48 2009/01/29 09:03:04 dholland Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: util.c,v 1.48 2009/01/29 09:03:04 dholland Exp $");
+#endif
+#endif
+
+#include <sys/param.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <time.h>
+
+#include "make.h"
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR)
+extern int errno, sys_nerr;
+extern char *sys_errlist[];
+
+char *
+strerror(int e)
+{
+    static char buf[100];
+    if (e < 0 || e >= sys_nerr) {
+       snprintf(buf, sizeof(buf), "Unknown error %d", e);
+       return buf;
+    }
+    else
+       return sys_errlist[e];
+}
+#endif
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_SETENV)
+extern char **environ;
+
+static char *
+findenv(const char *name, int *offset)
+{
+       size_t i, len;
+       char *p, *q;
+
+       for (i = 0; (q = environ[i]); i++) {
+               char *p = strchr(q, '=');
+               if (p == NULL)
+                       continue;
+               if (strncmp(name, q, len = p - q) == 0) {
+                       *offset = i;
+                       return q + len + 1;
+               }
+       }
+       *offset = i;
+       return NULL;
+}
+
+int
+unsetenv(const char *name)
+{
+       char **p;
+       int offset;
+
+       if (name == NULL || *name == '\0' || strchr(name, '=') != NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       while (findenv(name, &offset))  { /* if set multiple times */
+               for (p = &environ[offset];; ++p)
+                       if (!(*p = *(p + 1)))
+                               break;
+       }
+       return 0;
+}
+
+int
+setenv(const char *name, const char *value, int rewrite)
+{
+       static char **saveenv;  /* copy of previously allocated space */
+       char *c, **newenv;
+       const char *cc;
+       size_t l_value, size;
+       int offset;
+
+       if (name == NULL || value == NULL) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (*value == '=')                      /* no `=' in value */
+               ++value;
+       l_value = strlen(value);
+
+       /* find if already exists */
+       if ((c = findenv(name, &offset))) {
+               if (!rewrite)
+                       return 0;
+               if (strlen(c) >= l_value)       /* old larger; copy over */
+                       goto copy;
+       } else {                                        /* create new slot */
+               size = sizeof(char *) * (offset + 2);
+               if (saveenv == environ) {               /* just increase size */
+                       if ((newenv = realloc(saveenv, size)) == NULL)
+                               return -1;
+                       saveenv = newenv;
+               } else {                                /* get new space */
+                       /*
+                        * We don't free here because we don't know if
+                        * the first allocation is valid on all OS's
+                        */
+                       if ((saveenv = malloc(size)) == NULL)
+                               return -1;
+                       (void)memcpy(saveenv, environ, size - sizeof(char *));
+               }
+               environ = saveenv;
+               environ[offset + 1] = NULL;
+       }
+       for (cc = name; *cc && *cc != '='; ++cc)        /* no `=' in name */
+               continue;
+       size = cc - name;
+       /* name + `=' + value */
+       if ((environ[offset] = malloc(size + l_value + 2)) == NULL)
+               return -1;
+       c = environ[offset];
+       (void)memcpy(c, name, size);
+       c += size;
+       *c++ = '=';
+copy:
+       (void)memcpy(c, value, l_value + 1);
+       return 0;
+}
+
+#ifdef TEST
+int
+main(int argc, char *argv[])
+{
+       setenv(argv[1], argv[2], 0);
+       printf("%s\n", getenv(argv[1]));
+       unsetenv(argv[1]);
+       printf("%s\n", getenv(argv[1]));
+       return 0;
+}
+#endif
+
+#endif
+
+#if defined(__hpux__) || defined(__hpux)
+/* strrcpy():
+ *     Like strcpy, going backwards and returning the new pointer
+ */
+static char *
+strrcpy(char *ptr, char *str)
+{
+    int len = strlen(str);
+
+    while (len)
+       *--ptr = str[--len];
+
+    return (ptr);
+} /* end strrcpy */
+
+char    *sys_siglist[] = {
+        "Signal 0",
+        "Hangup",                       /* SIGHUP    */
+        "Interrupt",                    /* SIGINT    */
+        "Quit",                         /* SIGQUIT   */
+        "Illegal instruction",          /* SIGILL    */
+        "Trace/BPT trap",               /* SIGTRAP   */
+        "IOT trap",                     /* SIGIOT    */
+        "EMT trap",                     /* SIGEMT    */
+        "Floating point exception",     /* SIGFPE    */
+        "Killed",                       /* SIGKILL   */
+        "Bus error",                    /* SIGBUS    */
+        "Segmentation fault",           /* SIGSEGV   */
+        "Bad system call",              /* SIGSYS    */
+        "Broken pipe",                  /* SIGPIPE   */
+        "Alarm clock",                  /* SIGALRM   */
+        "Terminated",                   /* SIGTERM   */
+        "User defined signal 1",        /* SIGUSR1   */
+        "User defined signal 2",        /* SIGUSR2   */
+        "Child exited",                 /* SIGCLD    */
+        "Power-fail restart",           /* SIGPWR    */
+        "Virtual timer expired",        /* SIGVTALRM */
+        "Profiling timer expired",      /* SIGPROF   */
+        "I/O possible",                 /* SIGIO     */
+        "Window size changes",          /* SIGWINDOW */
+        "Stopped (signal)",             /* SIGSTOP   */
+        "Stopped",                      /* SIGTSTP   */
+        "Continued",                    /* SIGCONT   */
+        "Stopped (tty input)",          /* SIGTTIN   */
+        "Stopped (tty output)",         /* SIGTTOU   */
+        "Urgent I/O condition",         /* SIGURG    */
+        "Remote lock lost (NFS)",       /* SIGLOST   */
+        "Signal 31",                    /* reserved  */
+        "DIL signal"                    /* SIGDIL    */
+};
+#endif /* __hpux__ || __hpux */
+
+#if defined(__hpux__) || defined(__hpux)
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+int
+killpg(int pid, int sig)
+{
+    return kill(-pid, sig);
+}
+
+#if !defined(__hpux__) && !defined(__hpux)
+void
+srandom(long seed)
+{
+    srand48(seed);
+}
+
+long
+random(void)
+{
+    return lrand48();
+}
+#endif
+
+/* turn into bsd signals */
+void (*
+signal(int s, void (*a)(int)))(int)
+{
+    struct sigvec osv, sv;
+
+    (void)sigvector(s, NULL, &osv);
+    sv = osv;
+    sv.sv_handler = a;
+#ifdef SV_BSDSIG
+    sv.sv_flags = SV_BSDSIG;
+#endif
+
+    if (sigvector(s, &sv, NULL) == -1)
+        return (BADSIG);
+    return (osv.sv_handler);
+}
+
+#if !defined(__hpux__) && !defined(__hpux)
+int
+utimes(char *file, struct timeval tvp[2])
+{
+    struct utimbuf t;
+
+    t.actime  = tvp[0].tv_sec;
+    t.modtime = tvp[1].tv_sec;
+    return(utime(file, &t));
+}
+#endif
+
+#if !defined(BSD) && !defined(d_fileno)
+# define d_fileno d_ino
+#endif
+
+#ifndef DEV_DEV_COMPARE
+# define DEV_DEV_COMPARE(a, b) ((a) == (b))
+#endif
+#define ISDOT(c) ((c)[0] == '.' && (((c)[1] == '\0') || ((c)[1] == '/')))
+#define ISDOTDOT(c) ((c)[0] == '.' && ISDOT(&((c)[1])))
+
+char *
+getwd(char *pathname)
+{
+    DIR    *dp;
+    struct dirent *d;
+    extern int errno;
+
+    struct stat st_root, st_cur, st_next, st_dotdot;
+    char    pathbuf[MAXPATHLEN], nextpathbuf[MAXPATHLEN * 2];
+    char   *pathptr, *nextpathptr, *cur_name_add;
+
+    /* find the inode of root */
+    if (stat("/", &st_root) == -1) {
+       (void)sprintf(pathname,
+                       "getwd: Cannot stat \"/\" (%s)", strerror(errno));
+       return NULL;
+    }
+    pathbuf[MAXPATHLEN - 1] = '\0';
+    pathptr = &pathbuf[MAXPATHLEN - 1];
+    nextpathbuf[MAXPATHLEN - 1] = '\0';
+    cur_name_add = nextpathptr = &nextpathbuf[MAXPATHLEN - 1];
+
+    /* find the inode of the current directory */
+    if (lstat(".", &st_cur) == -1) {
+       (void)sprintf(pathname,
+                       "getwd: Cannot stat \".\" (%s)", strerror(errno));
+       return NULL;
+    }
+    nextpathptr = strrcpy(nextpathptr, "../");
+
+    /* Descend to root */
+    for (;;) {
+
+       /* look if we found root yet */
+       if (st_cur.st_ino == st_root.st_ino &&
+           DEV_DEV_COMPARE(st_cur.st_dev, st_root.st_dev)) {
+           (void)strcpy(pathname, *pathptr != '/' ? "/" : pathptr);
+           return (pathname);
+       }
+
+       /* open the parent directory */
+       if (stat(nextpathptr, &st_dotdot) == -1) {
+           (void)sprintf(pathname,
+                           "getwd: Cannot stat directory \"%s\" (%s)",
+                           nextpathptr, strerror(errno));
+           return NULL;
+       }
+       if ((dp = opendir(nextpathptr)) == NULL) {
+           (void)sprintf(pathname,
+                           "getwd: Cannot open directory \"%s\" (%s)",
+                           nextpathptr, strerror(errno));
+           return NULL;
+       }
+
+       /* look in the parent for the entry with the same inode */
+       if (DEV_DEV_COMPARE(st_dotdot.st_dev, st_cur.st_dev)) {
+           /* Parent has same device. No need to stat every member */
+           for (d = readdir(dp); d != NULL; d = readdir(dp))
+               if (d->d_fileno == st_cur.st_ino)
+                   break;
+       }
+       else {
+           /*
+            * Parent has a different device. This is a mount point so we
+            * need to stat every member
+            */
+           for (d = readdir(dp); d != NULL; d = readdir(dp)) {
+               if (ISDOT(d->d_name) || ISDOTDOT(d->d_name))
+                   continue;
+               (void)strcpy(cur_name_add, d->d_name);
+               if (lstat(nextpathptr, &st_next) == -1) {
+                   (void)sprintf(pathname,
+                       "getwd: Cannot stat \"%s\" (%s)",
+                       d->d_name, strerror(errno));
+                   (void)closedir(dp);
+                   return NULL;
+               }
+               /* check if we found it yet */
+               if (st_next.st_ino == st_cur.st_ino &&
+                   DEV_DEV_COMPARE(st_next.st_dev, st_cur.st_dev))
+                   break;
+           }
+       }
+       if (d == NULL) {
+           (void)sprintf(pathname,
+               "getwd: Cannot find \".\" in \"..\"");
+           (void)closedir(dp);
+           return NULL;
+       }
+       st_cur = st_dotdot;
+       pathptr = strrcpy(pathptr, d->d_name);
+       pathptr = strrcpy(pathptr, "/");
+       nextpathptr = strrcpy(nextpathptr, "../");
+       (void)closedir(dp);
+       *cur_name_add = '\0';
+    }
+} /* end getwd */
+#endif /* __hpux */
+
+#if defined(sun) && defined(__svr4__)
+#include <signal.h>
+
+/* turn into bsd signals */
+void (*
+signal(int s, void (*a)(int)))(int)
+{
+    struct sigaction sa, osa;
+
+    sa.sa_handler = a;
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = SA_RESTART;
+
+    if (sigaction(s, &sa, &osa) == -1)
+       return SIG_ERR;
+    else
+       return osa.sa_handler;
+}
+#endif
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_VSNPRINTF)
+#include <stdarg.h>
+
+#if !defined(__osf__)
+#ifdef _IOSTRG
+#define STRFLAG        (_IOSTRG|_IOWRT)        /* no _IOWRT: avoid stdio bug */
+#else
+#if 0
+#define STRFLAG        (_IOREAD)               /* XXX: Assume svr4 stdio */
+#endif
+#endif /* _IOSTRG */
+#endif /* __osf__ */
+
+int
+vsnprintf(char *s, size_t n, const char *fmt, va_list args)
+{
+#ifdef STRFLAG
+       FILE fakebuf;
+
+       fakebuf._flag = STRFLAG;
+       /*
+        * Some os's are char * _ptr, others are unsigned char *_ptr...
+        * We cast to void * to make everyone happy.
+        */
+       fakebuf._ptr = (void *)s;
+       fakebuf._cnt = n-1;
+       fakebuf._file = -1;
+       _doprnt(fmt, args, &fakebuf);
+       fakebuf._cnt++;
+       putc('\0', &fakebuf);
+       if (fakebuf._cnt<0)
+           fakebuf._cnt = 0;
+       return (n-fakebuf._cnt-1);
+#else
+       (void)vsprintf(s, fmt, args);
+       return strlen(s);
+#endif
+}
+
+int
+snprintf(char *s, size_t n, const char *fmt, ...)
+{
+       va_list ap;
+       int rv;
+
+       va_start(ap, fmt);
+       rv = vsnprintf(s, n, fmt, ap);
+       va_end(ap);
+       return rv;
+}
+
+#if !defined(MAKE_NATIVE) && !defined(HAVE_STRFTIME)
+size_t
+strftime(char *buf, size_t len, const char *fmt, const struct tm *tm)
+{
+       static char months[][4] = {
+               "Jan", "Feb", "Mar", "Apr", "May", "Jun", 
+               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+       };
+
+       size_t s;
+       char *b = buf;
+
+       while (*fmt) {
+               if (len == 0)
+                       return buf - b;
+               if (*fmt != '%') {
+                       *buf++ = *fmt++;
+                       len--;
+                       continue;
+               }
+               switch (*fmt++) {
+               case '%':
+                       *buf++ = '%';
+                       len--;
+                       if (len == 0) return buf - b;
+                       /*FALLTHROUGH*/
+               case '\0':
+                       *buf = '%';
+                       s = 1;
+                       break;
+               case 'k':
+                       s = snprintf(buf, len, "%d", tm->tm_hour);
+                       break;
+               case 'M':
+                       s = snprintf(buf, len, "%02d", tm->tm_min);
+                       break;
+               case 'S':
+                       s = snprintf(buf, len, "%02d", tm->tm_sec);
+                       break;
+               case 'b':
+                       if (tm->tm_mon >= 12)
+                               return buf - b;
+                       s = snprintf(buf, len, "%s", months[tm->tm_mon]);
+                       break;
+               case 'd':
+                       s = snprintf(buf, len, "%02d", tm->tm_mday);
+                       break;
+               case 'Y':
+                       s = snprintf(buf, len, "%d", 1900 + tm->tm_year);
+                       break;
+               default:
+                       s = snprintf(buf, len, "Unsupported format %c",
+                           fmt[-1]);
+                       break;
+               }
+               buf += s;
+               len -= s;
+       }
+}
+#endif
+#endif
diff --git a/commands/bmake/var.c b/commands/bmake/var.c
new file mode 100644 (file)
index 0000000..ec5a576
--- /dev/null
@@ -0,0 +1,3985 @@
+/*     $NetBSD: var.c,v 1.155 2009/11/19 00:30:25 sjg Exp $    */
+
+/*
+ * 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. 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.
+ */
+
+/*
+ * 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.
+ */
+
+#ifndef MAKE_NATIVE
+static char rcsid[] = "$NetBSD: var.c,v 1.155 2009/11/19 00:30:25 sjg Exp $";
+#else
+#include <sys/cdefs.h>
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)var.c      8.3 (Berkeley) 3/19/94";
+#else
+__RCSID("$NetBSD: var.c,v 1.155 2009/11/19 00:30:25 sjg Exp $");
+#endif
+#endif /* not lint */
+#endif
+
+/*-
+ * 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.
+ */
+
+#ifndef NO_REGEX
+#include    <sys/types.h>
+#include    <regex.h>
+#endif
+#include    <ctype.h>
+#include    <stdlib.h>
+#include    <limits.h>
+
+#include    "make.h"
+#include    "buf.h"
+#include    "dir.h"
+#include    "job.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 'errnum' 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 */
+
+#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 */
+#define VAR_KEEP       8           /* Variable is VAR_JUNK, but we found
+                                    * a use for it in some modifier and
+                                    * the value is therefore valid */
+#define VAR_EXPORTED   16          /* Variable is exported */
+#define VAR_REEXPORT   32          /* Indicate if var needs re-export.
+                                    * This would be true if it contains $'s
+                                    */
+#define VAR_FROM_CMD   64          /* Variable came from command line */
+}  Var;
+
+/*
+ * Exporting vars is expensive so skip it if we can
+ */
+#define VAR_EXPORTED_NONE      0
+#define VAR_EXPORTED_YES       1
+#define VAR_EXPORTED_ALL       2
+static int var_exportedVars = VAR_EXPORTED_NONE;
+/*
+ * We pass this to Var_Export when doing the initial export
+ * or after updating an exported var.
+ */
+#define VAR_EXPORT_PARENT 1
+
+/* 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 */
+
+/* Var_Set flags */
+#define VAR_NO_EXPORT  0x01    /* do not export */
+
+typedef struct {
+    /*
+     * The following fields are set by Var_Parse() when it
+     * encounters modifiers that need to keep state for use by
+     * subsequent modifiers within the same variable expansion.
+     */
+    Byte       varSpace;       /* Word separator in expansions */
+    Boolean    oneBigWord;     /* TRUE if we will treat the variable as a
+                                * single big word, even if it contains
+                                * embedded spaces (as opposed to the
+                                * usual behaviour of treating it as
+                                * several space-separated words). */
+} Var_Parse_State;
+
+/* struct passed as 'void *' to VarSubstitute() for ":S/lhs/rhs/",
+ * to VarSYSVMatch() for ":lhs=rhs". */
+typedef struct {
+    const char   *lhs;     /* String to match */
+    int                  leftLen; /* Length of string */
+    const char   *rhs;     /* Replacement string (w/ &'s removed) */
+    int                  rightLen; /* Length of replacement */
+    int                  flags;
+} VarPattern;
+
+/* struct passed as 'void *' to VarLoopExpand() for ":@tvar@str@" */
+typedef struct {
+    GNode      *ctxt;          /* variable context */
+    char       *tvar;          /* name of temp var */
+    int                tvarLen;
+    char       *str;           /* string to expand */
+    int                strLen;
+    int                errnum;         /* errnum for not defined */
+} VarLoop_t;
+
+#ifndef NO_REGEX
+/* struct passed as 'void *' to VarRESubstitute() for ":C///" */
+typedef struct {
+    regex_t       re;
+    int                   nsub;
+    regmatch_t           *matches;
+    char         *replace;
+    int                   flags;
+} VarREPattern;
+#endif
+
+/* struct passed to VarSelectWords() for ":[start..end]" */
+typedef struct {
+    int                start;          /* first word to select */
+    int                end;            /* last word to select */
+} VarSelectWords_t;
+
+static Var *VarFind(const char *, GNode *, int);
+static void VarAdd(const char *, const char *, GNode *);
+static Boolean VarHead(GNode *, Var_Parse_State *,
+                       char *, Boolean, Buffer *, void *);
+static Boolean VarTail(GNode *, Var_Parse_State *,
+                       char *, Boolean, Buffer *, void *);
+static Boolean VarSuffix(GNode *, Var_Parse_State *,
+                       char *, Boolean, Buffer *, void *);
+static Boolean VarRoot(GNode *, Var_Parse_State *,
+                       char *, Boolean, Buffer *, void *);
+static Boolean VarMatch(GNode *, Var_Parse_State *,
+                       char *, Boolean, Buffer *, void *);
+#ifdef SYSVVARSUB
+static Boolean VarSYSVMatch(GNode *, Var_Parse_State *,
+                       char *, Boolean, Buffer *, void *);
+#endif
+static Boolean VarNoMatch(GNode *, Var_Parse_State *,
+                       char *, Boolean, Buffer *, void *);
+#ifndef NO_REGEX
+static void VarREError(int, regex_t *, const char *);
+static Boolean VarRESubstitute(GNode *, Var_Parse_State *,
+                       char *, Boolean, Buffer *, void *);
+#endif
+static Boolean VarSubstitute(GNode *, Var_Parse_State *,
+                       char *, Boolean, Buffer *, void *);
+static Boolean VarLoopExpand(GNode *, Var_Parse_State *,
+                       char *, Boolean, Buffer *, void *);
+static char *VarGetPattern(GNode *, Var_Parse_State *,
+                          int, const char **, int, int *, int *,
+                          VarPattern *);
+static char *VarQuote(char *);
+static char *VarChangeCase(char *, int);
+static char *VarModify(GNode *, Var_Parse_State *,
+    const char *,
+    Boolean (*)(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *),
+    void *);
+static char *VarOrder(const char *, const char);
+static char *VarUniq(const char *);
+static int VarWordCompare(const void *, const void *);
+static void VarPrintVar(void *);
+
+#define BROPEN '{'
+#define BRCLOSE        '}'
+#define PROPEN '('
+#define PRCLOSE        ')'
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarFind --
+ *     Find the given variable in the given context and any other contexts
+ *     indicated.
+ *
+ * Input:
+ *     name            name to find
+ *     ctxt            context in which to find it
+ *     flags           FIND_GLOBAL set means to look in the
+ *                     VAR_GLOBAL context as well. FIND_CMD set means
+ *                     to look in the VAR_CMD context also. 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(const char *name, GNode *ctxt, int flags)
+{
+    Hash_Entry                 *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;
+               }
+    /*
+     * 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 = Hash_FindEntry(&ctxt->context, name);
+
+    if ((var == NULL) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) {
+       var = Hash_FindEntry(&VAR_CMD->context, name);
+    }
+    if (!checkEnvFirst && (var == NULL) && (flags & FIND_GLOBAL) &&
+       (ctxt != VAR_GLOBAL))
+    {
+       var = Hash_FindEntry(&VAR_GLOBAL->context, name);
+    }
+    if ((var == NULL) && (flags & FIND_ENV)) {
+       char *env;
+
+       if ((env = getenv(name)) != NULL) {
+           int         len;
+
+           v = bmake_malloc(sizeof(Var));
+           v->name = bmake_strdup(name);
+
+           len = strlen(env);
+
+           Buf_Init(&v->val, len + 1);
+           Buf_AddBytes(&v->val, len, env);
+
+           v->flags = VAR_FROM_ENV;
+           return (v);
+       } else if (checkEnvFirst && (flags & FIND_GLOBAL) &&
+                  (ctxt != VAR_GLOBAL))
+       {
+           var = Hash_FindEntry(&VAR_GLOBAL->context, name);
+           if (var == NULL) {
+               return NULL;
+           } else {
+               return ((Var *)Hash_GetValue(var));
+           }
+       } else {
+           return NULL;
+       }
+    } else if (var == NULL) {
+       return NULL;
+    } else {
+       return ((Var *)Hash_GetValue(var));
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarFreeEnv  --
+ *     If the variable is an environment variable, free it
+ *
+ * Input:
+ *     v               the variable
+ *     destroy         true if the value buffer should be destroyed.
+ *
+ * Results:
+ *     1 if it is an environment variable 0 ow.
+ *
+ * Side Effects:
+ *     The variable is free'ed if it is an environent variable.
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarFreeEnv(Var *v, Boolean destroy)
+{
+    if ((v->flags & VAR_FROM_ENV) == 0)
+       return FALSE;
+    free(v->name);
+    Buf_Destroy(&v->val, destroy);
+    free(v);
+    return TRUE;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarAdd  --
+ *     Add a new variable of name name and value val to the given context
+ *
+ * Input:
+ *     name            name of variable to add
+ *     val             value to set it to
+ *     ctxt            context in which to set it
+ *
+ * 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(const char *name, const char *val, GNode *ctxt)
+{
+    Var          *v;
+    int                  len;
+    Hash_Entry    *h;
+
+    v = bmake_malloc(sizeof(Var));
+
+    len = val ? strlen(val) : 0;
+    Buf_Init(&v->val, len+1);
+    Buf_AddBytes(&v->val, len, val);
+
+    v->flags = 0;
+
+    h = Hash_CreateEntry(&ctxt->context, name, NULL);
+    Hash_SetValue(h, v);
+    v->name = h->name;
+    if (DEBUG(VAR)) {
+       fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val);
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Delete --
+ *     Remove a variable from a context.
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     The Var structure is removed and freed.
+ *
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Delete(const char *name, GNode *ctxt)
+{
+    Hash_Entry           *ln;
+
+    ln = Hash_FindEntry(&ctxt->context, name);
+    if (DEBUG(VAR)) {
+       fprintf(debug_file, "%s:delete %s%s\n",
+           ctxt->name, name, ln ? "" : " (not found)");
+    }
+    if (ln != NULL) {
+       Var       *v;
+
+       v = (Var *)Hash_GetValue(ln);
+       if ((v->flags & VAR_EXPORTED)) {
+           unsetenv(v->name);
+       }
+       if (strcmp(MAKE_EXPORTED, v->name) == 0) {
+           var_exportedVars = VAR_EXPORTED_NONE;
+       }
+       if (v->name != ln->name)
+               free(v->name);
+       Hash_DeleteEntry(&ctxt->context, ln);
+       Buf_Destroy(&v->val, TRUE);
+       free(v);
+    }
+}
+
+
+/*
+ * Export a var.
+ * We ignore make internal variables (those which start with '.')
+ * Also we jump through some hoops to avoid calling setenv
+ * more than necessary since it can leak.
+ * We only manipulate flags of vars if 'parent' is set.
+ */
+static int
+Var_Export1(const char *name, int parent)
+{
+    char tmp[BUFSIZ];
+    Var *v;
+    char *val = NULL;
+    int n;
+
+    if (*name == '.')
+       return 0;                       /* skip internals */
+    if (!name[1]) {
+       /*
+        * A single char.
+        * If it is one of the vars that should only appear in
+        * local context, skip it, else we can get Var_Subst
+        * into a loop.
+        */
+       switch (name[0]) {
+       case '@':
+       case '%':
+       case '*':
+       case '!':
+           return 0;
+       }
+    }
+    v = VarFind(name, VAR_GLOBAL, 0);
+    if (v == NULL) {
+       return 0;
+    }
+    if (!parent &&
+       (v->flags & (VAR_EXPORTED|VAR_REEXPORT)) == VAR_EXPORTED) {
+       return 0;                       /* nothing to do */
+    }
+    val = Buf_GetAll(&v->val, NULL);
+    if (strchr(val, '$')) {
+       if (parent) {
+           /*
+            * Flag this as something we need to re-export.
+            * No point actually exporting it now though,
+            * the child can do it at the last minute.
+            */
+           v->flags |= (VAR_EXPORTED|VAR_REEXPORT);
+           return 1;
+       }
+       n = snprintf(tmp, sizeof(tmp), "${%s}", name);
+       if (n < (int)sizeof(tmp)) {
+           val = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
+           setenv(name, val, 1);
+           free(val);
+       }
+    } else {
+       if (parent) {
+           v->flags &= ~VAR_REEXPORT;  /* once will do */
+       }
+       if (parent || !(v->flags & VAR_EXPORTED)) {
+           setenv(name, val, 1);
+       }
+    }
+    /*
+     * This is so Var_Set knows to call Var_Export again...
+     */
+    if (parent) {
+       v->flags |= VAR_EXPORTED;
+    }
+    return 1;
+}
+
+/*
+ * This gets called from our children.
+ */
+void
+Var_ExportVars(void)
+{
+    char tmp[BUFSIZ];
+    Hash_Entry                 *var;
+    Hash_Search        state;
+    Var *v;
+    char *val;
+    int n;
+
+    if (VAR_EXPORTED_NONE == var_exportedVars)
+       return;
+
+    if (VAR_EXPORTED_ALL == var_exportedVars) {
+       /*
+        * Ouch! This is crazy...
+        */
+       for (var = Hash_EnumFirst(&VAR_GLOBAL->context, &state);
+            var != NULL;
+            var = Hash_EnumNext(&state)) {
+           v = (Var *)Hash_GetValue(var);
+           Var_Export1(v->name, 0);
+       }
+       return;
+    }
+    /*
+     * We have a number of exported vars,
+     */
+    n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}");
+    if (n < (int)sizeof(tmp)) {
+       char **av;
+       char *as;
+       int ac;
+       int i;
+
+       val = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
+       av = brk_string(val, &ac, FALSE, &as);
+       for (i = 0; i < ac; i++) {
+           Var_Export1(av[i], 0);
+       }
+       free(val);
+       free(as);
+       free(av);
+    }
+}
+
+/*
+ * This is called when .export is seen or
+ * .MAKE.EXPORTED is modified.
+ * It is also called when any exported var is modified.
+ */
+void
+Var_Export(char *str, int isExport)
+{
+    char *name;
+    char *val;
+    char **av;
+    char *as;
+    int ac;
+    int i;
+
+    if (isExport && (!str || !str[0])) {
+       var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */
+       return;
+    }
+
+    val = Var_Subst(NULL, str, VAR_GLOBAL, 0);
+    av = brk_string(val, &ac, FALSE, &as);
+    for (i = 0; i < ac; i++) {
+       name = av[i];
+       if (!name[1]) {
+           /*
+            * A single char.
+            * If it is one of the vars that should only appear in
+            * local context, skip it, else we can get Var_Subst
+            * into a loop.
+            */
+           switch (name[0]) {
+           case '@':
+           case '%':
+           case '*':
+           case '!':
+               continue;
+           }
+       }
+       if (Var_Export1(name, VAR_EXPORT_PARENT)) {
+           if (VAR_EXPORTED_ALL != var_exportedVars)
+               var_exportedVars = VAR_EXPORTED_YES;
+           if (isExport) {
+               Var_Append(MAKE_EXPORTED, name, VAR_GLOBAL);
+           }
+       }
+    }
+    free(val);
+    free(as);
+    free(av);
+}
+
+
+/*
+ * This is called when .unexport[-env] is seen.
+ */
+void
+Var_UnExport(char *str)
+{
+    char tmp[BUFSIZ];
+    char *vlist;
+    char *cp;
+    Boolean unexport_env;
+    int n;
+
+    if (!str || !str[0]) {
+       return;                         /* assert? */
+    }
+
+    vlist = NULL;
+
+    str += 8;
+    unexport_env = (strncmp(str, "-env", 4) == 0);
+    if (unexport_env) {
+       extern char **environ;
+       static char **savenv;
+       char **newenv;
+
+       cp = getenv(MAKE_LEVEL);        /* we should preserve this */
+       if (environ == savenv) {
+           /* we have been here before! */
+           newenv = bmake_realloc(environ, 2 * sizeof(char *));
+       } else {
+           if (savenv) {
+               free(savenv);
+               savenv = NULL;
+           }
+           newenv = bmake_malloc(2 * sizeof(char *));
+       }
+       if (!newenv)
+           return;
+       /* Note: we cannot safely free() the original environ. */
+       environ = savenv = newenv;
+       newenv[0] = NULL;
+       newenv[1] = NULL;
+       setenv(MAKE_LEVEL, cp, 1);
+    } else {
+       for (; *str != '\n' && isspace((unsigned char) *str); str++)
+           continue;
+       if (str[0] && str[0] != '\n') {
+           vlist = str;
+       }
+    }
+
+    if (!vlist) {
+       /* Using .MAKE.EXPORTED */
+       n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}");
+       if (n < (int)sizeof(tmp)) {
+           vlist = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
+       }
+    }
+    if (vlist) {
+       Var *v;
+       char **av;
+       char *as;
+       int ac;
+       int i;
+
+       av = brk_string(vlist, &ac, FALSE, &as);
+       for (i = 0; i < ac; i++) {
+           v = VarFind(av[i], VAR_GLOBAL, 0);
+           if (!v)
+               continue;
+           if (!unexport_env &&
+               (v->flags & (VAR_EXPORTED|VAR_REEXPORT)) == VAR_EXPORTED) {
+               unsetenv(v->name);
+           }
+           v->flags &= ~(VAR_EXPORTED|VAR_REEXPORT);
+           /*
+            * If we are unexporting a list,
+            * remove each one from .MAKE.EXPORTED.
+            * If we are removing them all,
+            * just delete .MAKE.EXPORTED below.
+            */
+           if (vlist == str) {
+               n = snprintf(tmp, sizeof(tmp),
+                            "${" MAKE_EXPORTED ":N%s}", v->name);
+               if (n < (int)sizeof(tmp)) {
+                   cp = Var_Subst(NULL, tmp, VAR_GLOBAL, 0);
+                   Var_Set(MAKE_EXPORTED, cp, VAR_GLOBAL, 0);
+                   free(cp);
+               }
+           }
+       }
+       free(as);
+       free(av);
+       if (vlist != str) {
+           Var_Delete(MAKE_EXPORTED, VAR_GLOBAL);
+           free(vlist);
+       }
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Set --
+ *     Set the variable name to the value val in the given context.
+ *
+ * Input:
+ *     name            name of variable to set
+ *     val             value to give to the variable
+ *     ctxt            context in which to set it
+ *
+ * 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 $(<).
+ *     If the context is VAR_GLOBAL though, we check if the variable
+ *     was set in VAR_CMD from the command line and skip it if so.
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Set(const char *name, const char *val, GNode *ctxt, int flags)
+{
+    Var   *v;
+    char *expanded_name = NULL;
+
+    /*
+     * 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...
+     */
+    if (strchr(name, '$') != NULL) {
+       expanded_name = Var_Subst(NULL, name, ctxt, 0);
+       if (expanded_name[0] == 0) {
+           if (DEBUG(VAR)) {
+               fprintf(debug_file, "Var_Set(\"%s\", \"%s\", ...) "
+                       "name expands to empty string - ignored\n",
+                       name, val);
+           }
+           free(expanded_name);
+           return;
+       }
+       name = expanded_name;
+    }
+    if (ctxt == VAR_GLOBAL) {
+       v = VarFind(name, VAR_CMD, 0);
+       if (v != NULL) {
+           if ((v->flags & VAR_FROM_CMD)) {
+               if (DEBUG(VAR)) {
+                   fprintf(debug_file, "%s:%s = %s ignored!\n", ctxt->name, name, val);
+               }
+               goto out;
+           }
+           VarFreeEnv(v, TRUE);
+       }
+    }
+    v = VarFind(name, ctxt, 0);
+    if (v == NULL) {
+       VarAdd(name, val, ctxt);
+    } else {
+       Buf_Empty(&v->val);
+       Buf_AddBytes(&v->val, strlen(val), val);
+
+       if (DEBUG(VAR)) {
+           fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val);
+       }
+       if ((v->flags & VAR_EXPORTED)) {
+           Var_Export1(name, VAR_EXPORT_PARENT);
+       }
+    }
+    /*
+     * Any variables given on the command line are automatically exported
+     * to the environment (as per POSIX standard)
+     */
+    if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) {
+       if (v == NULL) {
+           /* we just added it */
+           v = VarFind(name, ctxt, 0);
+       }
+       if (v != NULL)
+           v->flags |= VAR_FROM_CMD;
+       /*
+        * If requested, don't export these in the environment
+        * individually.  We still put them in MAKEOVERRIDES so
+        * that the command-line settings continue to override
+        * Makefile settings.
+        */
+       if (varNoExportEnv != TRUE)
+           setenv(name, val, 1);
+
+       Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL);
+    }
+    /*
+     * Another special case.
+     * Several make's support this sort of mechanism for tracking
+     * recursion - but each uses a different name.
+     * We allow the makefiles to update .MAKE.LEVEL and ensure
+     * children see a correctly incremented value.
+     */
+    if (ctxt == VAR_GLOBAL && strcmp(MAKE_LEVEL, name) == 0) {
+       char tmp[64];
+       int level;
+       
+       level = atoi(val);
+       snprintf(tmp, sizeof(tmp), "%u", level + 1);
+       setenv(MAKE_LEVEL, tmp, 1);
+    }
+       
+       
+ out:
+    if (expanded_name != NULL)
+       free(expanded_name);
+    if (v != NULL)
+       VarFreeEnv(v, TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Append --
+ *     The variable of the given name has the given value appended to it in
+ *     the given context.
+ *
+ * Input:
+ *     name            name of variable to modify
+ *     val             String to append to it
+ *     ctxt            Context in which this should occur
+ *
+ * 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(const char *name, const char *val, GNode *ctxt)
+{
+    Var                   *v;
+    Hash_Entry    *h;
+    char *expanded_name = NULL;
+
+    if (strchr(name, '$') != NULL) {
+       expanded_name = Var_Subst(NULL, name, ctxt, 0);
+       if (expanded_name[0] == 0) {
+           if (DEBUG(VAR)) {
+               fprintf(debug_file, "Var_Append(\"%s\", \"%s\", ...) "
+                       "name expands to empty string - ignored\n",
+                       name, val);
+           }
+           free(expanded_name);
+           return;
+       }
+       name = expanded_name;
+    }
+
+    v = VarFind(name, ctxt, (ctxt == VAR_GLOBAL) ? FIND_ENV : 0);
+
+    if (v == NULL) {
+       VarAdd(name, val, ctxt);
+    } else {
+       Buf_AddByte(&v->val, ' ');
+       Buf_AddBytes(&v->val, strlen(val), val);
+
+       if (DEBUG(VAR)) {
+           fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name,
+                  Buf_GetAll(&v->val, 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;
+           h = Hash_CreateEntry(&ctxt->context, name, NULL);
+           Hash_SetValue(h, v);
+       }
+    }
+    if (expanded_name != NULL)
+       free(expanded_name);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Exists --
+ *     See if the given variable exists.
+ *
+ * Input:
+ *     name            Variable to find
+ *     ctxt            Context in which to start search
+ *
+ * Results:
+ *     TRUE if it does, FALSE if it doesn't
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+Boolean
+Var_Exists(const char *name, GNode *ctxt)
+{
+    Var                  *v;
+    char          *cp;
+
+    if ((cp = strchr(name, '$')) != NULL) {
+       cp = Var_Subst(NULL, name, ctxt, FALSE);
+    }
+    v = VarFind(cp ? cp : name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV);
+    if (cp != NULL) {
+       free(cp);
+    }
+    if (v == NULL) {
+       return(FALSE);
+    } else {
+       (void)VarFreeEnv(v, TRUE);
+    }
+    return(TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Value --
+ *     Return the value of the named variable in the given context
+ *
+ * Input:
+ *     name            name to find
+ *     ctxt            context in which to search for it
+ *
+ * Results:
+ *     The value if the variable exists, NULL if it doesn't
+ *
+ * Side Effects:
+ *     None
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_Value(const char *name, GNode *ctxt, char **frp)
+{
+    Var            *v;
+
+    v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+    *frp = NULL;
+    if (v != NULL) {
+       char *p = (Buf_GetAll(&v->val, NULL));
+       if (VarFreeEnv(v, FALSE))
+           *frp = p;
+       return p;
+    } else {
+       return NULL;
+    }
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarHead --
+ *     Remove the tail of the given word and place the result in the given
+ *     buffer.
+ *
+ * Input:
+ *     word            Word to trim
+ *     addSpace        True if need to add a space to the buffer
+ *                     before sticking in the head
+ *     buf             Buffer in which to store it
+ *
+ * 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(GNode *ctx __unused, Var_Parse_State *vpstate,
+       char *word, Boolean addSpace, Buffer *buf,
+       void *dummy)
+{
+    char *slash;
+
+    slash = strrchr(word, '/');
+    if (slash != NULL) {
+       if (addSpace && vpstate->varSpace) {
+           Buf_AddByte(buf, vpstate->varSpace);
+       }
+       *slash = '\0';
+       Buf_AddBytes(buf, strlen(word), word);
+       *slash = '/';
+       return (TRUE);
+    } else {
+       /*
+        * If no directory part, give . (q.v. the POSIX standard)
+        */
+       if (addSpace && vpstate->varSpace)
+           Buf_AddByte(buf, vpstate->varSpace);
+       Buf_AddByte(buf, '.');
+    }
+    return(dummy ? TRUE : TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarTail --
+ *     Remove the head of the given word and place the result in the given
+ *     buffer.
+ *
+ * Input:
+ *     word            Word to trim
+ *     addSpace        True if need to add a space to the buffer
+ *                     before adding the tail
+ *     buf             Buffer in which to store it
+ *
+ * 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(GNode *ctx __unused, Var_Parse_State *vpstate,
+       char *word, Boolean addSpace, Buffer *buf,
+       void *dummy)
+{
+    char *slash;
+
+    if (addSpace && vpstate->varSpace) {
+       Buf_AddByte(buf, vpstate->varSpace);
+    }
+
+    slash = strrchr(word, '/');
+    if (slash != NULL) {
+       *slash++ = '\0';
+       Buf_AddBytes(buf, strlen(slash), slash);
+       slash[-1] = '/';
+    } else {
+       Buf_AddBytes(buf, strlen(word), word);
+    }
+    return (dummy ? TRUE : TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSuffix --
+ *     Place the suffix of the given word in the given buffer.
+ *
+ * Input:
+ *     word            Word to trim
+ *     addSpace        TRUE if need to add a space before placing the
+ *                     suffix in the buffer
+ *     buf             Buffer in which to store it
+ *
+ * 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(GNode *ctx __unused, Var_Parse_State *vpstate,
+         char *word, Boolean addSpace, Buffer *buf,
+         void *dummy)
+{
+    char *dot;
+
+    dot = strrchr(word, '.');
+    if (dot != NULL) {
+       if (addSpace && vpstate->varSpace) {
+           Buf_AddByte(buf, vpstate->varSpace);
+       }
+       *dot++ = '\0';
+       Buf_AddBytes(buf, strlen(dot), dot);
+       dot[-1] = '.';
+       addSpace = TRUE;
+    }
+    return (dummy ? addSpace : addSpace);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarRoot --
+ *     Remove the suffix of the given word and place the result in the
+ *     buffer.
+ *
+ * Input:
+ *     word            Word to trim
+ *     addSpace        TRUE if need to add a space to the buffer
+ *                     before placing the root in it
+ *     buf             Buffer in which to store it
+ *
+ * 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(GNode *ctx __unused, Var_Parse_State *vpstate,
+       char *word, Boolean addSpace, Buffer *buf,
+       void *dummy)
+{
+    char *dot;
+
+    if (addSpace && vpstate->varSpace) {
+       Buf_AddByte(buf, vpstate->varSpace);
+    }
+
+    dot = strrchr(word, '.');
+    if (dot != NULL) {
+       *dot = '\0';
+       Buf_AddBytes(buf, strlen(word), word);
+       *dot = '.';
+    } else {
+       Buf_AddBytes(buf, strlen(word), word);
+    }
+    return (dummy ? TRUE : TRUE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarMatch --
+ *     Place the word in the buffer if it matches the given pattern.
+ *     Callback function for VarModify to implement the :M modifier.
+ *
+ * Input:
+ *     word            Word to examine
+ *     addSpace        TRUE if need to add a space to the buffer
+ *                     before adding the word, if it matches
+ *     buf             Buffer in which to store it
+ *     pattern         Pattern 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(GNode *ctx __unused, Var_Parse_State *vpstate,
+        char *word, Boolean addSpace, Buffer *buf,
+        void *pattern)
+{
+    if (DEBUG(VAR))
+       fprintf(debug_file, "VarMatch [%s] [%s]\n", word, (char *)pattern);
+    if (Str_Match(word, (char *)pattern)) {
+       if (addSpace && vpstate->varSpace) {
+           Buf_AddByte(buf, vpstate->varSpace);
+       }
+       addSpace = TRUE;
+       Buf_AddBytes(buf, strlen(word), 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.
+ *
+ * Input:
+ *     word            Word to examine
+ *     addSpace        TRUE if need to add a space to the buffer
+ *                     before adding the word, if it matches
+ *     buf             Buffer in which to store it
+ *     patp            Pattern 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
+VarSYSVMatch(GNode *ctx, Var_Parse_State *vpstate,
+            char *word, Boolean addSpace, Buffer *buf,
+            void *patp)
+{
+    int len;
+    char *ptr;
+    VarPattern           *pat = (VarPattern *)patp;
+    char *varexp;
+
+    if (addSpace && vpstate->varSpace)
+       Buf_AddByte(buf, vpstate->varSpace);
+
+    addSpace = TRUE;
+
+    if ((ptr = Str_SYSVMatch(word, pat->lhs, &len)) != NULL) {
+        varexp = Var_Subst(NULL, pat->rhs, ctx, 0);
+       Str_SYSVSubst(buf, varexp, ptr, len);
+       free(varexp);
+    } else {
+       Buf_AddBytes(buf, strlen(word), 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.
+ *
+ * Input:
+ *     word            Word to examine
+ *     addSpace        TRUE if need to add a space to the buffer
+ *                     before adding the word, if it matches
+ *     buf             Buffer in which to store it
+ *     pattern         Pattern 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
+VarNoMatch(GNode *ctx __unused, Var_Parse_State *vpstate,
+          char *word, Boolean addSpace, Buffer *buf,
+          void *pattern)
+{
+    if (!Str_Match(word, (char *)pattern)) {
+       if (addSpace && vpstate->varSpace) {
+           Buf_AddByte(buf, vpstate->varSpace);
+       }
+       addSpace = TRUE;
+       Buf_AddBytes(buf, strlen(word), word);
+    }
+    return(addSpace);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSubstitute --
+ *     Perform a string-substitution on the given word, placing the
+ *     result in the passed buffer.
+ *
+ * Input:
+ *     word            Word to modify
+ *     addSpace        True if space should be added before
+ *                     other characters
+ *     buf             Buffer for result
+ *     patternp        Pattern for substitution
+ *
+ * Results:
+ *     TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarSubstitute(GNode *ctx __unused, Var_Parse_State *vpstate,
+             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 ((pattern->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) !=
+       (VAR_SUB_ONE|VAR_SUB_MATCHED)) {
+       /*
+        * Still substituting -- break it 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 && vpstate->varSpace) {
+                               Buf_AddByte(buf, vpstate->varSpace);
+                           }
+                           addSpace = TRUE;
+                           Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+                       }
+                       pattern->flags |= VAR_SUB_MATCHED;
+               } 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 && vpstate->varSpace) {
+                           Buf_AddByte(buf, vpstate->varSpace);
+                       }
+                       addSpace = TRUE;
+                   }
+                   Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+                   Buf_AddBytes(buf, wordLen - pattern->leftLen,
+                                (word + pattern->leftLen));
+                   pattern->flags |= VAR_SUB_MATCHED;
+               }
+       } 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 && vpstate->varSpace) {
+                       Buf_AddByte(buf, vpstate->varSpace);
+                   }
+                   addSpace = TRUE;
+               }
+               Buf_AddBytes(buf, cp - word, word);
+               Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+               pattern->flags |= VAR_SUB_MATCHED;
+           } 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 != NULL) {
+                   if (addSpace && (((cp - word) + pattern->rightLen) != 0)){
+                       Buf_AddByte(buf, vpstate->varSpace);
+                       addSpace = FALSE;
+                   }
+                   Buf_AddBytes(buf, cp-word, word);
+                   Buf_AddBytes(buf, pattern->rightLen, pattern->rhs);
+                   wordLen -= (cp - word) + pattern->leftLen;
+                   word = cp + pattern->leftLen;
+                   if (wordLen == 0) {
+                       done = TRUE;
+                   }
+                   if ((pattern->flags & VAR_SUB_GLOBAL) == 0) {
+                       done = TRUE;
+                   }
+                   pattern->flags |= VAR_SUB_MATCHED;
+               } else {
+                   done = TRUE;
+               }
+           }
+           if (wordLen != 0) {
+               if (addSpace && vpstate->varSpace) {
+                   Buf_AddByte(buf, vpstate->varSpace);
+               }
+               Buf_AddBytes(buf, wordLen, 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);
+       }
+       return (addSpace);
+    }
+ nosub:
+    if (addSpace && vpstate->varSpace) {
+       Buf_AddByte(buf, vpstate->varSpace);
+    }
+    Buf_AddBytes(buf, wordLen, word);
+    return(TRUE);
+}
+
+#ifndef NO_REGEX
+/*-
+ *-----------------------------------------------------------------------
+ * VarREError --
+ *     Print the error caused by a regcomp or regexec call.
+ *
+ * Results:
+ *     None.
+ *
+ * Side Effects:
+ *     An error gets printed.
+ *
+ *-----------------------------------------------------------------------
+ */
+static void
+VarREError(int errnum, regex_t *pat, const char *str)
+{
+    char *errbuf;
+    int errlen;
+
+    errlen = regerror(errnum, pat, 0, 0);
+    errbuf = bmake_malloc(errlen);
+    regerror(errnum, 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.
+ *
+ * Results:
+ *     TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarRESubstitute(GNode *ctx __unused, Var_Parse_State *vpstate __unused,
+               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;
+               const 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);
+}
+#endif
+
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarLoopExpand --
+ *     Implements the :@<temp>@<string>@ modifier of ODE make.
+ *     We set the temp variable named in pattern.lhs to word and expand
+ *     pattern.rhs storing the result in the passed buffer.
+ *
+ * Input:
+ *     word            Word to modify
+ *     addSpace        True if space should be added before
+ *                     other characters
+ *     buf             Buffer for result
+ *     pattern         Datafor substitution
+ *
+ * Results:
+ *     TRUE if a space is needed before more characters are added.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static Boolean
+VarLoopExpand(GNode *ctx __unused, Var_Parse_State *vpstate __unused,
+             char *word, Boolean addSpace, Buffer *buf,
+             void *loopp)
+{
+    VarLoop_t  *loop = (VarLoop_t *)loopp;
+    char *s;
+    int slen;
+
+    if (word && *word) {
+        Var_Set(loop->tvar, word, loop->ctxt, VAR_NO_EXPORT);
+        s = Var_Subst(NULL, loop->str, loop->ctxt, loop->errnum);
+        if (s != NULL && *s != '\0') {
+            if (addSpace && *s != '\n')
+                Buf_AddByte(buf, ' ');
+            Buf_AddBytes(buf, (slen = strlen(s)), s);
+            addSpace = (slen > 0 && s[slen - 1] != '\n');
+            free(s);
+        }
+    }
+    return addSpace;
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarSelectWords --
+ *     Implements the :[start..end] modifier.
+ *     This is a special case of VarModify since we want to be able
+ *     to scan the list backwards if start > end.
+ *
+ * Input:
+ *     str             String whose words should be trimmed
+ *     seldata         words to select
+ *
+ * Results:
+ *     A string of all the words selected.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarSelectWords(GNode *ctx __unused, Var_Parse_State *vpstate,
+              const char *str, VarSelectWords_t *seldata)
+{
+    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 */
+    char *as;                      /* word list memory */
+    int ac, i;
+    int start, end, step;
+
+    Buf_Init(&buf, 0);
+    addSpace = FALSE;
+
+    if (vpstate->oneBigWord) {
+       /* fake what brk_string() would do if there were only one word */
+       ac = 1;
+       av = bmake_malloc((ac + 1) * sizeof(char *));
+       as = bmake_strdup(str);
+       av[0] = as;
+       av[1] = NULL;
+    } else {
+       av = brk_string(str, &ac, FALSE, &as);
+    }
+
+    /*
+     * Now sanitize seldata.
+     * If seldata->start or seldata->end are negative, convert them to
+     * the positive equivalents (-1 gets converted to argc, -2 gets
+     * converted to (argc-1), etc.).
+     */
+    if (seldata->start < 0)
+       seldata->start = ac + seldata->start + 1;
+    if (seldata->end < 0)
+       seldata->end = ac + seldata->end + 1;
+
+    /*
+     * We avoid scanning more of the list than we need to.
+     */
+    if (seldata->start > seldata->end) {
+       start = MIN(ac, seldata->start) - 1;
+       end = MAX(0, seldata->end - 1);
+       step = -1;
+    } else {
+       start = MAX(0, seldata->start - 1);
+       end = MIN(ac, seldata->end);
+       step = 1;
+    }
+
+    for (i = start;
+        (step < 0 && i >= end) || (step > 0 && i < end);
+        i += step) {
+       if (av[i] && *av[i]) {
+           if (addSpace && vpstate->varSpace) {
+               Buf_AddByte(&buf, vpstate->varSpace);
+           }
+           Buf_AddBytes(&buf, strlen(av[i]), av[i]);
+           addSpace = TRUE;
+       }
+    }
+
+    free(as);
+    free(av);
+
+    return Buf_Destroy(&buf, FALSE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarModify --
+ *     Modify each of the words of the passed string using the given
+ *     function. Used to implement all modifiers.
+ *
+ * Input:
+ *     str             String whose words should be trimmed
+ *     modProc         Function to use to modify them
+ *     datum           Datum to pass it
+ *
+ * Results:
+ *     A string of all the words modified appropriately.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarModify(GNode *ctx, Var_Parse_State *vpstate,
+    const char *str,
+    Boolean (*modProc)(GNode *, Var_Parse_State *, 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 */
+    char *as;                      /* word list memory */
+    int ac, i;
+
+    Buf_Init(&buf, 0);
+    addSpace = FALSE;
+
+    if (vpstate->oneBigWord) {
+       /* fake what brk_string() would do if there were only one word */
+       ac = 1;
+       av = bmake_malloc((ac + 1) * sizeof(char *));
+       as = bmake_strdup(str);
+       av[0] = as;
+       av[1] = NULL;
+    } else {
+       av = brk_string(str, &ac, FALSE, &as);
+    }
+
+    for (i = 0; i < ac; i++) {
+       addSpace = (*modProc)(ctx, vpstate, av[i], addSpace, &buf, datum);
+    }
+
+    free(as);
+    free(av);
+
+    return Buf_Destroy(&buf, FALSE);
+}
+
+
+static int
+VarWordCompare(const void *a, const void *b)
+{
+       int r = strcmp(*(const char * const *)a, *(const char * const *)b);
+       return r;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarOrder --
+ *     Order the words in the string.
+ *
+ * Input:
+ *     str             String whose words should be sorted.
+ *     otype           How to order: s - sort, x - random.
+ *
+ * Results:
+ *     A string containing the words ordered.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarOrder(const char *str, const char otype)
+{
+    Buffer       buf;              /* Buffer for the new string */
+    char **av;                     /* word list [first word does not count] */
+    char *as;                      /* word list memory */
+    int ac, i;
+
+    Buf_Init(&buf, 0);
+
+    av = brk_string(str, &ac, FALSE, &as);
+
+    if (ac > 0)
+       switch (otype) {
+       case 's':       /* sort alphabetically */
+           qsort(av, ac, sizeof(char *), VarWordCompare);
+           break;
+       case 'x':       /* randomize */
+       {
+           int rndidx;
+           char *t;
+
+           /*
+            * We will use [ac..2] range for mod factors. This will produce
+            * random numbers in [(ac-1)..0] interval, and minimal
+            * reasonable value for mod factor is 2 (the mod 1 will produce
+            * 0 with probability 1).
+            */
+           for (i = ac-1; i > 0; i--) {
+               rndidx = random() % (i + 1);
+               if (i != rndidx) {
+                   t = av[i];
+                   av[i] = av[rndidx];
+                   av[rndidx] = t;
+               }
+           }
+       }
+       } /* end of switch */
+
+    for (i = 0; i < ac; i++) {
+       Buf_AddBytes(&buf, strlen(av[i]), av[i]);
+       if (i != ac - 1)
+           Buf_AddByte(&buf, ' ');
+    }
+
+    free(as);
+    free(av);
+
+    return Buf_Destroy(&buf, FALSE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarUniq --
+ *     Remove adjacent duplicate words.
+ *
+ * Input:
+ *     str             String whose words should be sorted
+ *
+ * Results:
+ *     A string containing the resulting words.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarUniq(const char *str)
+{
+    Buffer       buf;              /* Buffer for new string */
+    char       **av;               /* List of words to affect */
+    char        *as;               /* Word list memory */
+    int          ac, i, j;
+
+    Buf_Init(&buf, 0);
+    av = brk_string(str, &ac, FALSE, &as);
+
+    if (ac > 1) {
+       for (j = 0, i = 1; i < ac; i++)
+           if (strcmp(av[i], av[j]) != 0 && (++j != i))
+               av[j] = av[i];
+       ac = j + 1;
+    }
+
+    for (i = 0; i < ac; i++) {
+       Buf_AddBytes(&buf, strlen(av[i]), av[i]);
+       if (i != ac - 1)
+           Buf_AddByte(&buf, ' ');
+    }
+
+    free(as);
+    free(av);
+
+    return Buf_Destroy(&buf, FALSE);
+}
+
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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, Var_Parse_State *vpstate __unused,
+             int errnum, const char **tstr, int delim, int *flags,
+             int *length, VarPattern *pattern)
+{
+    const char *cp;
+    char *rstr;
+    Buffer buf;
+    int junk;
+
+    Buf_Init(&buf, 0);
+    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, cp[1]);
+           cp++;
+       } else if (*cp == '$') {
+           if (cp[1] == delim) {
+               if (flags == NULL)
+                   Buf_AddByte(&buf, *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;
+                   void   *freeIt;
+
+                   /*
+                    * If unescaped dollar sign not before the
+                    * delimiter, assume it's a variable
+                    * substitution and recurse.
+                    */
+                   cp2 = Var_Parse(cp, ctxt, errnum, &len, &freeIt);
+                   Buf_AddBytes(&buf, strlen(cp2), cp2);
+                   if (freeIt)
+                       free(freeIt);
+                   cp += len - 1;
+               } else {
+                   const char *cp2 = &cp[1];
+
+                   if (*cp2 == PROPEN || *cp2 == BROPEN) {
+                       /*
+                        * 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 == PROPEN) ? PRCLOSE : BRCLOSE;
+                       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, cp);
+                       cp = --cp2;
+                   } else
+                       Buf_AddByte(&buf, *cp);
+               }
+           }
+       }
+       else if (pattern && *cp == '&')
+           Buf_AddBytes(&buf, pattern->leftLen, pattern->lhs);
+       else
+           Buf_AddByte(&buf, *cp);
+    }
+
+    if (*cp != delim) {
+       *tstr = cp;
+       *length = 0;
+       return NULL;
+    }
+
+    *tstr = ++cp;
+    *length = Buf_Size(&buf);
+    rstr = Buf_Destroy(&buf, FALSE);
+    if (DEBUG(VAR))
+       fprintf(debug_file, "Modifier pattern: \"%s\"\n", rstr);
+    return rstr;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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 const char meta[] = "\n \t'`\";&<>()|*?{}[]\\$!#^~";
+    const char *newline;
+    size_t len, nlen;
+
+    if ((newline = Shell_GetNewline()) == NULL)
+           newline = "\\\n";
+    nlen = strlen(newline);
+
+    Buf_Init(&buf, 0);
+    while (*str != '\0') {
+       if ((len = strcspn(str, meta)) != 0) {
+           Buf_AddBytes(&buf, len, str);
+           str += len;
+       } else if (*str == '\n') {
+           Buf_AddBytes(&buf, nlen, newline);
+           ++str;
+       } else {
+           Buf_AddByte(&buf, '\\');
+           Buf_AddByte(&buf, *str);
+           ++str;
+       }
+    }
+    str = Buf_Destroy(&buf, FALSE);
+    if (DEBUG(VAR))
+       fprintf(debug_file, "QuoteMeta: [%s]\n", str);
+    return str;
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * VarChangeCase --
+ *      Change the string to all uppercase or all lowercase
+ *
+ * Input:
+ *     str             String to modify
+ *     upper           TRUE -> uppercase, else lowercase
+ *
+ * Results:
+ *      The string with case changed
+ *
+ * Side Effects:
+ *      None.
+ *
+ *-----------------------------------------------------------------------
+ */
+static char *
+VarChangeCase(char *str, int upper)
+{
+   Buffer         buf;
+   int            (*modProc)(int);
+
+   modProc = (upper ? toupper : tolower);
+   Buf_Init(&buf, 0);
+   for (; *str ; str++) {
+       Buf_AddByte(&buf, modProc(*str));
+   }
+   return Buf_Destroy(&buf, FALSE);
+}
+
+/*
+ * 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.
+ *       :N<pattern>   words which do not match the given <pattern>.
+ *       :S<d><pat1><d><pat2><d>[1gW]
+ *                     Substitute <pat2> for <pat1> in the value
+ *       :C<d><pat1><d><pat2><d>[1gW]
+ *                     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).
+ *       :O            ("Order") Alphabeticaly sort words in variable.
+ *       :Ox           ("intermiX") Randomize words in variable.
+ *       :u            ("uniq") Remove adjacent duplicate words.
+ *       :tu           Converts the variable contents to uppercase.
+ *       :tl           Converts the variable contents to lowercase.
+ *       :ts[c]        Sets varSpace - the char used to
+ *                     separate words to 'c'. If 'c' is
+ *                     omitted then no separation is used.
+ *       :tW           Treat the variable contents as a single
+ *                     word, even if it contains spaces.
+ *                     (Mnemonic: one big 'W'ord.)
+ *       :tw           Treat the variable contents as multiple
+ *                     space-separated words.
+ *                     (Mnemonic: many small 'w'ords.)
+ *       :[index]      Select a single word from the value.
+ *       :[start..end] Select multiple words from the value.
+ *       :[*] or :[0]  Select the entire value, as a single
+ *                     word.  Equivalent to :tW.
+ *       :[@]          Select the entire value, as multiple
+ *                     words.  Undoes the effect of :[*].
+ *                     Equivalent to :tw.
+ *       :[#]          Returns the number of words in the value.
+ *
+ *       :?<true-value>:<false-value>
+ *                     If the variable evaluates to true, return
+ *                     true value, else return the second value.
+ *       :lhs=rhs      Like :S, but the rhs goes to the end of
+ *                     the invocation.
+ *       :sh           Treat the current value as a command
+ *                     to be run, new value is its output.
+ * The following added so we can handle ODE makefiles.
+ *       :@<tmpvar>@<newval>@
+ *                     Assign a temporary local variable <tmpvar>
+ *                     to the current value of each word in turn
+ *                     and replace each word with the result of
+ *                     evaluating <newval>
+ *       :D<newval>    Use <newval> as value if variable defined
+ *       :U<newval>    Use <newval> as value if variable undefined
+ *       :L            Use the name of the variable as the value.
+ *       :P            Use the path of the node that has the same
+ *                     name as the variable as the value.  This
+ *                     basically includes an implied :L so that
+ *                     the common method of refering to the path
+ *                     of your dependent 'x' in a rule is to use
+ *                     the form '${x:P}'.
+ *       :!<cmd>!      Run cmd much the same as :sh run's the
+ *                     current value of the variable.
+ * The ::= modifiers, actually assign a value to the variable.
+ * Their main purpose is in supporting modifiers of .for loop
+ * iterators and other obscure uses.  They always expand to
+ * nothing.  In a target rule that would otherwise expand to an
+ * empty line they can be preceded with @: to keep make happy.
+ * Eg.
+ *
+ * foo:        .USE
+ * .for i in ${.TARGET} ${.TARGET:R}.gz
+ *     @: ${t::=$i}
+ *     @echo blah ${t:T}
+ * .endfor
+ *
+ *       ::=<str>      Assigns <str> as the new value of variable.
+ *       ::?=<str>     Assigns <str> as value of variable if
+ *                     it was not already set.
+ *       ::+=<str>     Appends <str> to variable.
+ *       ::!=<cmd>     Assigns output of <cmd> as the new value of
+ *                     variable.
+ */
+
+static char *
+ApplyModifiers(char *nstr, const char *tstr,
+              int startc, int endc,
+              Var *v, GNode *ctxt, Boolean errnum,
+              int *lengthPtr, void **freePtr)
+{
+    const char            *start;
+    const char     *cp;        /* Secondary pointer into str (place marker
+                                * for tstr) */
+    char          *newStr;     /* New value to return */
+    char           termc;      /* Character which terminated scan */
+    int             cnt;       /* Used to count brace pairs when variable in
+                                * in parens or braces */
+    char       delim;
+    int                modifier;       /* that we are processing */
+    Var_Parse_State parsestate; /* Flags passed to helper functions */
+
+    delim = '\0';
+    parsestate.oneBigWord = FALSE;
+    parsestate.varSpace = ' '; /* word separator */
+
+    start = cp = tstr;
+
+    while (*tstr && *tstr != endc) {
+
+       if (*tstr == '$') {
+           /*
+            * We have some complex modifiers in a variable.
+            */
+           void *freeIt;
+           char *rval;
+           int rlen;
+
+           rval = Var_Parse(tstr, ctxt, errnum, &rlen, &freeIt);
+
+           if (DEBUG(VAR)) {
+               fprintf(debug_file, "Got '%s' from '%.*s'%.*s\n",
+                      rval, rlen, tstr, rlen, tstr + rlen);
+           }
+
+           tstr += rlen;
+
+           if (rval != NULL && *rval) {
+               int used;
+
+               nstr = ApplyModifiers(nstr, rval,
+                                     0, 0,
+                                     v, ctxt, errnum, &used, freePtr);
+               if (nstr == var_Error
+                   || (nstr == varNoError && errnum == 0)
+                   || strlen(rval) != (size_t) used) {
+                   if (freeIt)
+                       free(freeIt);
+                   goto out;           /* error already reported */
+               }
+           }
+           if (freeIt)
+               free(freeIt);
+           if (*tstr == ':')
+               tstr++;
+           else if (!*tstr && endc) {
+               Error("Unclosed variable specification after complex modifier (expecting '%c') for %s", endc, v->name);
+               goto out;
+           }
+           continue;
+       }
+       if (DEBUG(VAR)) {
+           fprintf(debug_file, "Applying :%c to \"%s\"\n", *tstr, nstr);
+       }
+       newStr = var_Error;
+       switch ((modifier = *tstr)) {
+       case ':':
+           {
+               if (tstr[1] == '=' ||
+                   (tstr[2] == '=' &&
+                    (tstr[1] == '!' || tstr[1] == '+' || tstr[1] == '?'))) {
+                   /*
+                    * "::=", "::!=", "::+=", or "::?="
+                    */
+                   GNode *v_ctxt;              /* context where v belongs */
+                   const char *emsg;
+                   char *sv_name;
+                   VarPattern  pattern;
+                   int how;
+
+                   if (v->name[0] == 0)
+                       goto bad_modifier;
+
+                   v_ctxt = ctxt;
+                   sv_name = NULL;
+                   ++tstr;
+                   if (v->flags & VAR_JUNK) {
+                       /*
+                        * We need to bmake_strdup() it incase
+                        * VarGetPattern() recurses.
+                        */
+                       sv_name = v->name;
+                       v->name = bmake_strdup(v->name);
+                   } else if (ctxt != VAR_GLOBAL) {
+                       Var *gv = VarFind(v->name, ctxt, 0);
+                       if (gv == NULL)
+                           v_ctxt = VAR_GLOBAL;
+                       else
+                           VarFreeEnv(gv, TRUE);
+                   }
+
+                   switch ((how = *tstr)) {
+                   case '+':
+                   case '?':
+                   case '!':
+                       cp = &tstr[2];
+                       break;
+                   default:
+                       cp = ++tstr;
+                       break;
+                   }
+                   delim = BRCLOSE;
+                   pattern.flags = 0;
+
+                   pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum,
+                                               &cp, delim, NULL,
+                                               &pattern.rightLen,
+                                               NULL);
+                   if (v->flags & VAR_JUNK) {
+                       /* restore original name */
+                       free(v->name);
+                       v->name = sv_name;
+                   }
+                   if (pattern.rhs == NULL)
+                       goto cleanup;
+
+                   termc = *--cp;
+                   delim = '\0';
+
+                   switch (how) {
+                   case '+':
+                       Var_Append(v->name, pattern.rhs, v_ctxt);
+                       break;
+                   case '!':
+                       newStr = Cmd_Exec(pattern.rhs, &emsg);
+                       if (emsg)
+                           Error(emsg, nstr);
+                       else
+                           Var_Set(v->name, newStr,  v_ctxt, 0);
+                       if (newStr)
+                           free(newStr);
+                       break;
+                   case '?':
+                       if ((v->flags & VAR_JUNK) == 0)
+                           break;
+                       /* FALLTHROUGH */
+                   default:
+                       Var_Set(v->name, pattern.rhs, v_ctxt, 0);
+                       break;
+                   }
+                   free(UNCONST(pattern.rhs));
+                   newStr = var_Error;
+                   break;
+               }
+               goto default_case; /* "::<unrecognised>" */
+           }
+       case '@':
+           {
+               VarLoop_t       loop;
+               int flags = VAR_NOSUBST;
+
+               cp = ++tstr;
+               delim = '@';
+               if ((loop.tvar = VarGetPattern(ctxt, &parsestate, errnum,
+                                              &cp, delim,
+                                              &flags, &loop.tvarLen,
+                                              NULL)) == NULL)
+                   goto cleanup;
+
+               if ((loop.str = VarGetPattern(ctxt, &parsestate, errnum,
+                                             &cp, delim,
+                                             &flags, &loop.strLen,
+                                             NULL)) == NULL)
+                   goto cleanup;
+
+               termc = *cp;
+               delim = '\0';
+
+               loop.errnum = errnum;
+               loop.ctxt = ctxt;
+               newStr = VarModify(ctxt, &parsestate, nstr, VarLoopExpand,
+                                  &loop);
+               free(loop.tvar);
+               free(loop.str);
+               break;
+           }
+       case 'D':
+       case 'U':
+           {
+               Buffer  buf;            /* Buffer for patterns */
+               int         wantit;     /* want data in buffer */
+
+               /*
+                * Pass through 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).
+                * The result is left in the Buffer buf.
+                */
+               Buf_Init(&buf, 0);
+               for (cp = tstr + 1;
+                    *cp != endc && *cp != ':' && *cp != '\0';
+                    cp++) {
+                   if ((*cp == '\\') &&
+                       ((cp[1] == ':') ||
+                        (cp[1] == '$') ||
+                        (cp[1] == endc) ||
+                        (cp[1] == '\\')))
+                       {
+                           Buf_AddByte(&buf, cp[1]);
+                           cp++;
+                       } else if (*cp == '$') {
+                           /*
+                            * If unescaped dollar sign, assume it's a
+                            * variable substitution and recurse.
+                            */
+                           char    *cp2;
+                           int     len;
+                           void    *freeIt;
+
+                           cp2 = Var_Parse(cp, ctxt, errnum, &len, &freeIt);
+                           Buf_AddBytes(&buf, strlen(cp2), cp2);
+                           if (freeIt)
+                               free(freeIt);
+                           cp += len - 1;
+                       } else {
+                           Buf_AddByte(&buf, *cp);
+                       }
+               }
+
+               termc = *cp;
+
+               if (*tstr == 'U')
+                   wantit = ((v->flags & VAR_JUNK) != 0);
+               else
+                   wantit = ((v->flags & VAR_JUNK) == 0);
+               if ((v->flags & VAR_JUNK) != 0)
+                   v->flags |= VAR_KEEP;
+               if (wantit) {
+                   newStr = Buf_Destroy(&buf, FALSE);
+               } else {
+                   newStr = nstr;
+                   Buf_Destroy(&buf, TRUE);
+               }
+               break;
+           }
+       case 'L':
+           {
+               if ((v->flags & VAR_JUNK) != 0)
+                   v->flags |= VAR_KEEP;
+               newStr = bmake_strdup(v->name);
+               cp = ++tstr;
+               termc = *tstr;
+               break;
+           }
+       case 'P':
+           {
+               GNode *gn;
+
+               if ((v->flags & VAR_JUNK) != 0)
+                   v->flags |= VAR_KEEP;
+               gn = Targ_FindNode(v->name, TARG_NOCREATE);
+               if (gn == NULL || gn->type & OP_NOPATH) {
+                   newStr = NULL;
+               } else if (gn->path) {
+                   newStr = bmake_strdup(gn->path);
+               } else {
+                   newStr = Dir_FindFile(v->name, Suff_FindPath(gn));
+               }
+               if (!newStr) {
+                   newStr = bmake_strdup(v->name);
+               }
+               cp = ++tstr;
+               termc = *tstr;
+               break;
+           }
+       case '!':
+           {
+               const char *emsg;
+               VarPattern          pattern;
+               pattern.flags = 0;
+
+               delim = '!';
+
+               cp = ++tstr;
+               if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum,
+                                                &cp, delim,
+                                                NULL, &pattern.rightLen,
+                                                NULL)) == NULL)
+                   goto cleanup;
+               newStr = Cmd_Exec(pattern.rhs, &emsg);
+               free(UNCONST(pattern.rhs));
+               if (emsg)
+                   Error(emsg, nstr);
+               termc = *cp;
+               delim = '\0';
+               if (v->flags & VAR_JUNK) {
+                   v->flags |= VAR_KEEP;
+               }
+               break;
+           }
+       case '[':
+           {
+               /*
+                * Look for the closing ']', recursively
+                * expanding any embedded variables.
+                *
+                * estr is a pointer to the expanded result,
+                * which we must free().
+                */
+               char *estr;
+
+               cp = tstr+1; /* point to char after '[' */
+               delim = ']'; /* look for closing ']' */
+               estr = VarGetPattern(ctxt, &parsestate,
+                                    errnum, &cp, delim,
+                                    NULL, NULL, NULL);
+               if (estr == NULL)
+                   goto cleanup; /* report missing ']' */
+               /* now cp points just after the closing ']' */
+               delim = '\0';
+               if (cp[0] != ':' && cp[0] != endc) {
+                   /* Found junk after ']' */
+                   free(estr);
+                   goto bad_modifier;
+               }
+               if (estr[0] == '\0') {
+                   /* Found empty square brackets in ":[]". */
+                   free(estr);
+                   goto bad_modifier;
+               } else if (estr[0] == '#' && estr[1] == '\0') {
+                   /* Found ":[#]" */
+
+                   /*
+                    * We will need enough space for the decimal
+                    * representation of an int.  We calculate the
+                    * space needed for the octal representation,
+                    * and add enough slop to cope with a '-' sign
+                    * (which should never be needed) and a '\0'
+                    * string terminator.
+                    */
+                   int newStrSize =
+                       (sizeof(int) * CHAR_BIT + 2) / 3 + 2;
+
+                   newStr = bmake_malloc(newStrSize);
+                   if (parsestate.oneBigWord) {
+                       strncpy(newStr, "1", newStrSize);
+                   } else {
+                       /* XXX: brk_string() is a rather expensive
+                        * way of counting words. */
+                       char **av;
+                       char *as;
+                       int ac;
+
+                       av = brk_string(nstr, &ac, FALSE, &as);
+                       snprintf(newStr, newStrSize,  "%d", ac);
+                       free(as);
+                       free(av);
+                   }
+                   termc = *cp;
+                   free(estr);
+                   break;
+               } else if (estr[0] == '*' && estr[1] == '\0') {
+                   /* Found ":[*]" */
+                   parsestate.oneBigWord = TRUE;
+                   newStr = nstr;
+                   termc = *cp;
+                   free(estr);
+                   break;
+               } else if (estr[0] == '@' && estr[1] == '\0') {
+                   /* Found ":[@]" */
+                   parsestate.oneBigWord = FALSE;
+                   newStr = nstr;
+                   termc = *cp;
+                   free(estr);
+                   break;
+               } else {
+                   /*
+                    * We expect estr to contain a single
+                    * integer for :[N], or two integers
+                    * separated by ".." for :[start..end].
+                    */
+                   char *ep;
+
+                   VarSelectWords_t seldata = { 0, 0 };
+
+                   seldata.start = strtol(estr, &ep, 0);
+                   if (ep == estr) {
+                       /* Found junk instead of a number */
+                       free(estr);
+                       goto bad_modifier;
+                   } else if (ep[0] == '\0') {
+                       /* Found only one integer in :[N] */
+                       seldata.end = seldata.start;
+                   } else if (ep[0] == '.' && ep[1] == '.' &&
+                              ep[2] != '\0') {
+                       /* Expecting another integer after ".." */
+                       ep += 2;
+                       seldata.end = strtol(ep, &ep, 0);
+                       if (ep[0] != '\0') {
+                           /* Found junk after ".." */
+                           free(estr);
+                           goto bad_modifier;
+                       }
+                   } else {
+                       /* Found junk instead of ".." */
+                       free(estr);
+                       goto bad_modifier;
+                   }
+                   /*
+                    * Now seldata is properly filled in,
+                    * but we still have to check for 0 as
+                    * a special case.
+                    */
+                   if (seldata.start == 0 && seldata.end == 0) {
+                       /* ":[0]" or perhaps ":[0..0]" */
+                       parsestate.oneBigWord = TRUE;
+                       newStr = nstr;
+                       termc = *cp;
+                       free(estr);
+                       break;
+                   } else if (seldata.start == 0 ||
+                              seldata.end == 0) {
+                       /* ":[0..N]" or ":[N..0]" */
+                       free(estr);
+                       goto bad_modifier;
+                   }
+                   /*
+                    * Normal case: select the words
+                    * described by seldata.
+                    */
+                   newStr = VarSelectWords(ctxt, &parsestate,
+                                           nstr, &seldata);
+
+                   termc = *cp;
+                   free(estr);
+                   break;
+               }
+
+           }
+       case 't':
+           {
+               cp = tstr + 1;  /* make sure it is set */
+               if (tstr[1] != endc && tstr[1] != ':') {
+                   if (tstr[1] == 's') {
+                       /*
+                        * Use the char (if any) at tstr[2]
+                        * as the word separator.
+                        */
+                       VarPattern pattern;
+
+                       if (tstr[2] != endc &&
+                           (tstr[3] == endc || tstr[3] == ':')) {
+                           /* ":ts<unrecognised><endc>" or
+                            * ":ts<unrecognised>:" */
+                           parsestate.varSpace = tstr[2];
+                           cp = tstr + 3;
+                       } else if (tstr[2] == endc || tstr[2] == ':') {
+                           /* ":ts<endc>" or ":ts:" */
+                           parsestate.varSpace = 0; /* no separator */
+                           cp = tstr + 2;
+                       } else if (tstr[2] == '\\') {
+                           switch (tstr[3]) {
+                           case 'n':
+                               parsestate.varSpace = '\n';
+                               cp = tstr + 4;
+                               break;
+                           case 't':
+                               parsestate.varSpace = '\t';
+                               cp = tstr + 4;
+                               break;
+                           default:
+                               if (isdigit((unsigned char)tstr[3])) {
+                                   char *ep;
+
+                                   parsestate.varSpace =
+                                       strtoul(&tstr[3], &ep, 0);
+                                   if (*ep != ':' && *ep != endc)
+                                       goto bad_modifier;
+                                   cp = ep;
+                               } else {
+                                   /*
+                                    * ":ts<backslash><unrecognised>".
+                                    */
+                                   goto bad_modifier;
+                               }
+                               break;
+                           }
+                       } else {
+                           /*
+                            * Found ":ts<unrecognised><unrecognised>".
+                            */
+                           goto bad_modifier;
+                       }
+
+                       termc = *cp;
+
+                       /*
+                        * We cannot be certain that VarModify
+                        * will be used - even if there is a
+                        * subsequent modifier, so do a no-op
+                        * VarSubstitute now to for str to be
+                        * re-expanded without the spaces.
+                        */
+                       pattern.flags = VAR_SUB_ONE;
+                       pattern.lhs = pattern.rhs = "\032";
+                       pattern.leftLen = pattern.rightLen = 1;
+
+                       newStr = VarModify(ctxt, &parsestate, nstr,
+                                          VarSubstitute,
+                                          &pattern);
+                   } else if (tstr[2] == endc || tstr[2] == ':') {
+                       /*
+                        * Check for two-character options:
+                        * ":tu", ":tl"
+                        */
+                       if (tstr[1] == 'u' || tstr[1] == 'l') {
+                           newStr = VarChangeCase(nstr, (tstr[1] == 'u'));
+                           cp = tstr + 2;
+                           termc = *cp;
+                       } else if (tstr[1] == 'W' || tstr[1] == 'w') {
+                           parsestate.oneBigWord = (tstr[1] == 'W');
+                           newStr = nstr;
+                           cp = tstr + 2;
+                           termc = *cp;
+                       } else {
+                           /* Found ":t<unrecognised>:" or
+                            * ":t<unrecognised><endc>". */
+                           goto bad_modifier;
+                       }
+                   } else {
+                       /*
+                        * Found ":t<unrecognised><unrecognised>".
+                        */
+                       goto bad_modifier;
+                   }
+               } else {
+                   /*
+                    * Found ":t<endc>" or ":t:".
+                    */
+                   goto bad_modifier;
+               }
+               break;
+           }
+       case 'N':
+       case 'M':
+           {
+               char    *pattern;
+               const char *endpat; /* points just after end of pattern */
+               char    *cp2;
+               Boolean copy;   /* pattern should be, or has been, copied */
+               Boolean needSubst;
+               int nest;
+
+               copy = FALSE;
+               needSubst = FALSE;
+               nest = 1;
+               /*
+                * In the loop below, ignore ':' unless we are at
+                * (or back to) the original brace level.
+                * XXX This will likely not work right if $() and ${}
+                * are intermixed.
+                */
+               for (cp = tstr + 1;
+                    *cp != '\0' && !(*cp == ':' && nest == 1);
+                    cp++)
+                   {
+                       if (*cp == '\\' &&
+                           (cp[1] == ':' ||
+                            cp[1] == endc || cp[1] == startc)) {
+                           if (!needSubst) {
+                               copy = TRUE;
+                           }
+                           cp++;
+                           continue;
+                       }
+                       if (*cp == '$') {
+                           needSubst = TRUE;
+                       }
+                       if (*cp == '(' || *cp == '{')
+                           ++nest;
+                       if (*cp == ')' || *cp == '}') {
+                           --nest;
+                           if (nest == 0)
+                               break;
+                       }
+                   }
+               termc = *cp;
+               endpat = cp;
+               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 = bmake_malloc(cp - tstr);
+                   for (cp2 = pattern, cp = tstr + 1;
+                        cp < endpat;
+                        cp++, cp2++)
+                       {
+                           if ((*cp == '\\') && (cp+1 < endpat) &&
+                               (cp[1] == ':' || cp[1] == endc)) {
+                               cp++;
+                           }
+                           *cp2 = *cp;
+                       }
+                   *cp2 = '\0';
+                   endpat = cp2;
+               } else {
+                   /*
+                    * Either Var_Subst or VarModify will need a
+                    * nul-terminated string soon, so construct one now.
+                    */
+                   pattern = bmake_strndup(tstr+1, endpat - (tstr + 1));
+               }
+               if (needSubst) {
+                   /*
+                    * pattern contains embedded '$', so use Var_Subst to
+                    * expand it.
+                    */
+                   cp2 = pattern;
+                   pattern = Var_Subst(NULL, cp2, ctxt, errnum);
+                   free(cp2);
+               }
+               if (DEBUG(VAR))
+                   fprintf(debug_file, "Pattern for [%s] is [%s]\n", nstr,
+                       pattern);
+               if (*tstr == 'M') {
+                   newStr = VarModify(ctxt, &parsestate, nstr, VarMatch,
+                                      pattern);
+               } else {
+                   newStr = VarModify(ctxt, &parsestate, nstr, VarNoMatch,
+                                      pattern);
+               }
+               free(pattern);
+               break;
+           }
+       case 'S':
+           {
+               VarPattern          pattern;
+               Var_Parse_State tmpparsestate;
+
+               pattern.flags = 0;
+               tmpparsestate = parsestate;
+               delim = 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;
+               }
+
+               cp = tstr;
+               if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, errnum,
+                                                &cp, delim,
+                                                &pattern.flags,
+                                                &pattern.leftLen,
+                                                NULL)) == NULL)
+                   goto cleanup;
+
+               if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum,
+                                                &cp, delim, NULL,
+                                                &pattern.rightLen,
+                                                &pattern)) == NULL)
+                   goto cleanup;
+
+               /*
+                * Check for global substitution. If 'g' after the final
+                * delimiter, substitution is global and is marked that
+                * way.
+                */
+               for (;; cp++) {
+                   switch (*cp) {
+                   case 'g':
+                       pattern.flags |= VAR_SUB_GLOBAL;
+                       continue;
+                   case '1':
+                       pattern.flags |= VAR_SUB_ONE;
+                       continue;
+                   case 'W':
+                       tmpparsestate.oneBigWord = TRUE;
+                       continue;
+                   }
+                   break;
+               }
+
+               termc = *cp;
+               newStr = VarModify(ctxt, &tmpparsestate, nstr,
+                                  VarSubstitute,
+                                  &pattern);
+
+               /*
+                * Free the two strings.
+                */
+               free(UNCONST(pattern.lhs));
+               free(UNCONST(pattern.rhs));
+               delim = '\0';
+               break;
+           }
+       case '?':
+           {
+               VarPattern      pattern;
+               Boolean value;
+
+               /* find ':', and then substitute accordingly */
+
+               pattern.flags = 0;
+
+               cp = ++tstr;
+               delim = ':';
+               if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, errnum,
+                                                &cp, delim, NULL,
+                                                &pattern.leftLen,
+                                                NULL)) == NULL)
+                   goto cleanup;
+
+               /* BROPEN or PROPEN */
+               delim = endc;
+               if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, errnum,
+                                                &cp, delim, NULL,
+                                                &pattern.rightLen,
+                                                NULL)) == NULL)
+                   goto cleanup;
+
+               termc = *--cp;
+               delim = '\0';
+               if (Cond_EvalExpression(NULL, v->name, &value, 0)
+                   == COND_INVALID) {
+                   Error("Bad conditional expression `%s' in %s?%s:%s",
+                         v->name, v->name, pattern.lhs, pattern.rhs);
+                   goto cleanup;
+               }
+
+               if (value) {
+                   newStr = UNCONST(pattern.lhs);
+                   free(UNCONST(pattern.rhs));
+               } else {
+                   newStr = UNCONST(pattern.rhs);
+                   free(UNCONST(pattern.lhs));
+               }
+               if (v->flags & VAR_JUNK) {
+                   v->flags |= VAR_KEEP;
+               }
+               break;
+           }
+#ifndef NO_REGEX
+       case 'C':
+           {
+               VarREPattern    pattern;
+               char           *re;
+               int             error;
+               Var_Parse_State tmpparsestate;
+
+               pattern.flags = 0;
+               tmpparsestate = parsestate;
+               delim = tstr[1];
+               tstr += 2;
+
+               cp = tstr;
+
+               if ((re = VarGetPattern(ctxt, &parsestate, errnum, &cp, delim,
+                                       NULL, NULL, NULL)) == NULL)
+                   goto cleanup;
+
+               if ((pattern.replace = VarGetPattern(ctxt, &parsestate,
+                                                    errnum, &cp, delim, NULL,
+                                                    NULL, NULL)) == NULL){
+                   free(re);
+                   goto cleanup;
+               }
+
+               for (;; cp++) {
+                   switch (*cp) {
+                   case 'g':
+                       pattern.flags |= VAR_SUB_GLOBAL;
+                       continue;
+                   case '1':
+                       pattern.flags |= VAR_SUB_ONE;
+                       continue;
+                   case 'W':
+                       tmpparsestate.oneBigWord = TRUE;
+                       continue;
+                   }
+                   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);
+                   goto cleanup;
+               }
+
+               pattern.nsub = pattern.re.re_nsub + 1;
+               if (pattern.nsub < 1)
+                   pattern.nsub = 1;
+               if (pattern.nsub > 10)
+                   pattern.nsub = 10;
+               pattern.matches = bmake_malloc(pattern.nsub *
+                                         sizeof(regmatch_t));
+               newStr = VarModify(ctxt, &tmpparsestate, nstr,
+                                  VarRESubstitute,
+                                  &pattern);
+               regfree(&pattern.re);
+               free(pattern.replace);
+               free(pattern.matches);
+               delim = '\0';
+               break;
+           }
+#endif
+       case 'Q':
+           if (tstr[1] == endc || tstr[1] == ':') {
+               newStr = VarQuote(nstr);
+               cp = tstr + 1;
+               termc = *cp;
+               break;
+           }
+           goto default_case;
+       case 'T':
+           if (tstr[1] == endc || tstr[1] == ':') {
+               newStr = VarModify(ctxt, &parsestate, nstr, VarTail,
+                                  NULL);
+               cp = tstr + 1;
+               termc = *cp;
+               break;
+           }
+           goto default_case;
+       case 'H':
+           if (tstr[1] == endc || tstr[1] == ':') {
+               newStr = VarModify(ctxt, &parsestate, nstr, VarHead,
+                                  NULL);
+               cp = tstr + 1;
+               termc = *cp;
+               break;
+           }
+           goto default_case;
+       case 'E':
+           if (tstr[1] == endc || tstr[1] == ':') {
+               newStr = VarModify(ctxt, &parsestate, nstr, VarSuffix,
+                                  NULL);
+               cp = tstr + 1;
+               termc = *cp;
+               break;
+           }
+           goto default_case;
+       case 'R':
+           if (tstr[1] == endc || tstr[1] == ':') {
+               newStr = VarModify(ctxt, &parsestate, nstr, VarRoot,
+                                  NULL);
+               cp = tstr + 1;
+               termc = *cp;
+               break;
+           }
+           goto default_case;
+       case 'O':
+           {
+               char otype;
+
+               cp = tstr + 1;  /* skip to the rest in any case */
+               if (tstr[1] == endc || tstr[1] == ':') {
+                   otype = 's';
+                   termc = *cp;
+               } else if ( (tstr[1] == 'x') &&
+                           (tstr[2] == endc || tstr[2] == ':') ) {
+                   otype = tstr[1];
+                   cp = tstr + 2;
+                   termc = *cp;
+               } else {
+                   goto bad_modifier;
+               }
+               newStr = VarOrder(nstr, otype);
+               break;
+           }
+       case 'u':
+           if (tstr[1] == endc || tstr[1] == ':') {
+               newStr = VarUniq(nstr);
+               cp = tstr + 1;
+               termc = *cp;
+               break;
+           }
+           goto default_case;
+#ifdef SUNSHCMD
+       case 's':
+           if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) {
+               const char *emsg;
+               newStr = Cmd_Exec(nstr, &emsg);
+               if (emsg)
+                   Error(emsg, nstr);
+               cp = tstr + 2;
+               termc = *cp;
+               break;
+           }
+           goto default_case;
+#endif
+       default:
+       default_case:
+       {
+#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.
+                */
+               delim='=';
+               cp = tstr;
+               if ((pattern.lhs = VarGetPattern(ctxt, &parsestate,
+                                                errnum, &cp, delim, &pattern.flags,
+                                                &pattern.leftLen, NULL)) == NULL)
+                   goto cleanup;
+               delim = endc;
+               if ((pattern.rhs = VarGetPattern(ctxt, &parsestate,
+                                                errnum, &cp, delim, NULL, &pattern.rightLen,
+                                                &pattern)) == NULL)
+                   goto cleanup;
+
+               /*
+                * SYSV modifications happen through the whole
+                * string. Note the pattern is anchored at the end.
+                */
+               termc = *--cp;
+               delim = '\0';
+               newStr = VarModify(ctxt, &parsestate, nstr,
+                                  VarSYSVMatch,
+                                  &pattern);
+               free(UNCONST(pattern.lhs));
+               free(UNCONST(pattern.rhs));
+           } else
+#endif
+               {
+                   Error("Unknown modifier '%c'", *tstr);
+                   for (cp = tstr+1;
+                        *cp != ':' && *cp != endc && *cp != '\0';
+                        cp++)
+                       continue;
+                   termc = *cp;
+                   newStr = var_Error;
+               }
+           }
+       }
+       if (DEBUG(VAR)) {
+           fprintf(debug_file, "Result of :%c is \"%s\"\n", modifier, newStr);
+       }
+
+       if (newStr != nstr) {
+           if (*freePtr) {
+               free(nstr);
+               *freePtr = NULL;
+           }
+           nstr = newStr;
+           if (nstr != var_Error && nstr != varNoError) {
+               *freePtr = nstr;
+           }
+       }
+       if (termc == '\0' && endc != '\0') {
+           Error("Unclosed variable specification (expecting '%c') for \"%s\" (value \"%s\") modifier %c", endc, v->name, nstr, modifier);
+       } else if (termc == ':') {
+           cp++;
+       }
+       tstr = cp;
+    }
+ out:
+    *lengthPtr = tstr - start;
+    return (nstr);
+
+ bad_modifier:
+    /* "{(" */
+    Error("Bad modifier `:%.*s' for %s", (int)strcspn(tstr, ":)}"), tstr,
+         v->name);
+
+ cleanup:
+    *lengthPtr = cp - start;
+    if (delim != '\0')
+       Error("Unclosed substitution for %s (%c missing)",
+             v->name, delim);
+    if (*freePtr) {
+       free(*freePtr);
+       *freePtr = NULL;
+    }
+    return (var_Error);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Parse --
+ *     Given the start of a variable invocation, extract the variable
+ *     name and find its value, then modify it according to the
+ *     specification.
+ *
+ * Input:
+ *     str             The string to parse
+ *     ctxt            The context for the variable
+ *     errnum          TRUE if undefined variables are an error
+ *     lengthPtr       OUT: The length of the specification
+ *     freePtr         OUT: Non-NULL if caller should free *freePtr
+ *
+ * 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...?).
+ *     If *freePtr is non-NULL then it's a pointer that the caller
+ *     should pass to free() to free memory used by the result.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+/* coverity[+alloc : arg-*4] */
+char *
+Var_Parse(const char *str, GNode *ctxt, Boolean errnum, int *lengthPtr,
+         void **freePtr)
+{
+    const char    *tstr;       /* Pointer into str */
+    Var                   *v;          /* Variable in invocation */
+    Boolean        haveModifier;/* TRUE if have modifiers for the variable */
+    char           endc;       /* Ending character when variable in parens
+                                * or braces */
+    char           startc;     /* Starting character when variable in parens
+                                * or braces */
+    int                    vlen;       /* Length of variable name */
+    const char            *start;      /* Points to original start of str */
+    char          *nstr;       /* New string, used during expansion */
+    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 */
+    Var_Parse_State parsestate; /* Flags passed to helper functions */
+    char         name[2];
+
+    *freePtr = NULL;
+    dynamic = FALSE;
+    start = str;
+    parsestate.oneBigWord = FALSE;
+    parsestate.varSpace = ' '; /* word separator */
+
+    startc = str[1];
+    if (startc != PROPEN && startc != BROPEN) {
+       /*
+        * 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.
+        */
+
+       /* Error out some really stupid names */
+       if (startc == '\0' || strchr(")}:$", startc)) {
+           *lengthPtr = 1;
+           return var_Error;
+       }
+       name[0] = startc;
+       name[1] = '\0';
+
+       v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+       if (v == 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.
+                */
+               switch (str[1]) {
+                   case '@':
+                       return UNCONST("$(.TARGET)");
+                   case '%':
+                       return UNCONST("$(.ARCHIVE)");
+                   case '*':
+                       return UNCONST("$(.PREFIX)");
+                   case '!':
+                       return UNCONST("$(.MEMBER)");
+               }
+           }
+           /*
+            * Error
+            */
+           return (errnum ? var_Error : varNoError);
+       } else {
+           haveModifier = FALSE;
+           tstr = &str[1];
+           endc = str[1];
+       }
+    } else {
+       Buffer buf;     /* Holds the variable name */
+
+       endc = startc == PROPEN ? PRCLOSE : BRCLOSE;
+       Buf_Init(&buf, 0);
+
+       /*
+        * Skip to the end character or a colon, whichever comes first.
+        */
+       for (tstr = str + 2;
+            *tstr != '\0' && *tstr != endc && *tstr != ':';
+            tstr++)
+       {
+           /*
+            * A variable inside a variable, expand
+            */
+           if (*tstr == '$') {
+               int rlen;
+               void *freeIt;
+               char *rval = Var_Parse(tstr, ctxt, errnum, &rlen, &freeIt);
+               if (rval != NULL) {
+                   Buf_AddBytes(&buf, strlen(rval), rval);
+               }
+               if (freeIt)
+                   free(freeIt);
+               tstr += rlen - 1;
+           }
+           else
+               Buf_AddByte(&buf, *tstr);
+       }
+       if (*tstr == ':') {
+           haveModifier = TRUE;
+       } else if (*tstr != '\0') {
+           haveModifier = FALSE;
+       } else {
+           /*
+            * 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;
+           Buf_Destroy(&buf, TRUE);
+           return (var_Error);
+       }
+       str = Buf_GetAll(&buf, &vlen);
+
+       /*
+        * At this point, str points into newly allocated memory from
+        * buf, containing only the name of the variable.
+        *
+        * start and tstr point into the const string that was pointed
+        * to by the original value of the str parameter.  start points
+        * to the '$' at the beginning of the string, while tstr points
+        * to the char just after the end of the variable name -- this
+        * will be '\0', ':', PRCLOSE, or BRCLOSE.
+        */
+
+       v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD);
+       /*
+        * Check also for bogus D and F forms of local variables since we're
+        * in a local context and the name is the right length.
+        */
+       if ((v == NULL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) &&
+               (vlen == 2) && (str[1] == 'F' || str[1] == 'D') &&
+               strchr("@%*!<>", str[0]) != NULL) {
+           /*
+            * Well, it's local -- go look for it.
+            */
+           name[0] = *str;
+           name[1] = '\0';
+           v = VarFind(name, ctxt, 0);
+
+           if (v != NULL) {
+               /*
+                * No need for nested expansion or anything, as we're
+                * the only one who sets these things and we sure don't
+                * but nested invocations in them...
+                */
+               nstr = Buf_GetAll(&v->val, NULL);
+
+               if (str[1] == 'D') {
+                   nstr = VarModify(ctxt, &parsestate, nstr, VarHead,
+                                   NULL);
+               } else {
+                   nstr = VarModify(ctxt, &parsestate, nstr, VarTail,
+                                   NULL);
+               }
+               /*
+                * Resulting string is dynamically allocated, so
+                * tell caller to free it.
+                */
+               *freePtr = nstr;
+               *lengthPtr = tstr-start+1;
+               Buf_Destroy(&buf, TRUE);
+               VarFreeEnv(v, TRUE);
+               return nstr;
+           }
+       }
+
+       if (v == 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) {
+                   case '@':
+                   case '%':
+                   case '*':
+                   case '!':
+                       dynamic = TRUE;
+                       break;
+               }
+           } else if ((vlen > 2) && (*str == '.') &&
+                      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;
+               if (dynamic) {
+                   char *pstr = bmake_strndup(start, *lengthPtr);
+                   *freePtr = pstr;
+                   Buf_Destroy(&buf, TRUE);
+                   return(pstr);
+               } else {
+                   Buf_Destroy(&buf, TRUE);
+                   return (errnum ? 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 = bmake_malloc(sizeof(Var));
+               v->name = UNCONST(str);
+               Buf_Init(&v->val, 1);
+               v->flags = VAR_JUNK;
+               Buf_Destroy(&buf, FALSE);
+           }
+       } else
+           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.
+     */
+    nstr = Buf_GetAll(&v->val, NULL);
+    if (strchr(nstr, '$') != NULL) {
+       nstr = Var_Subst(NULL, nstr, ctxt, errnum);
+       *freePtr = nstr;
+    }
+
+    v->flags &= ~VAR_IN_USE;
+
+    if ((nstr != NULL) && haveModifier) {
+       int used;
+       /*
+        * Skip initial colon.
+        */
+       tstr++;
+
+       nstr = ApplyModifiers(nstr, tstr, startc, endc,
+                             v, ctxt, errnum, &used, freePtr);
+       tstr += used;
+    }
+    if (*tstr) {
+       *lengthPtr = tstr - start + 1;
+    } else {
+       *lengthPtr = tstr - start;
+    }
+
+    if (v->flags & VAR_FROM_ENV) {
+       Boolean   destroy = FALSE;
+
+       if (nstr != Buf_GetAll(&v->val, NULL)) {
+           destroy = TRUE;
+       } else {
+           /*
+            * Returning the value unmodified, so tell the caller to free
+            * the thing.
+            */
+           *freePtr = nstr;
+       }
+       VarFreeEnv(v, destroy);
+    } else if (v->flags & VAR_JUNK) {
+       /*
+        * Perform any free'ing needed and set *freePtr to NULL so the caller
+        * doesn't try to free a static pointer.
+        * If VAR_KEEP is also set then we want to keep str as is.
+        */
+       if (!(v->flags & VAR_KEEP)) {
+           if (*freePtr) {
+               free(nstr);
+               *freePtr = NULL;
+           }
+           if (dynamic) {
+               nstr = bmake_strndup(start, *lengthPtr);
+               *freePtr = nstr;
+           } else {
+               nstr = var_Error;
+           }
+       }
+       if (nstr != Buf_GetAll(&v->val, NULL))
+           Buf_Destroy(&v->val, TRUE);
+       free(v->name);
+       free(v);
+    }
+    return (nstr);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     var             Named variable || NULL for all
+ *     str             the string which to substitute
+ *     ctxt            the context wherein to find variables
+ *     undefErr        TRUE if undefineds are an error
+ *
+ * Results:
+ *     The resulting string.
+ *
+ * Side Effects:
+ *     None. The old string must be freed by the caller
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_Subst(const char *var, const 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      trailingBslash;   /* variable ends in \ */
+    void         *freeIt = NULL;    /* Set if it should be freed */
+    static Boolean errorReported;   /* Set true if an error has already
+                                    * been reported to prevent a plethora
+                                    * of messages when recursing */
+
+    Buf_Init(&buf, 0);
+    errorReported = FALSE;
+    trailingBslash = FALSE;
+
+    while (*str) {
+       if (*str == '\n' && trailingBslash)
+           Buf_AddByte(&buf, ' ');
+       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, *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).
+            */
+           const char  *cp;
+
+           for (cp = str++; *str != '$' && *str != '\0'; str++)
+               continue;
+           Buf_AddBytes(&buf, str - cp, cp);
+       } else {
+           if (var != NULL) {
+               int expand;
+               for (;;) {
+                   if (str[1] == '\0') {
+                       /* A trailing $ is kind of a special case */
+                       Buf_AddByte(&buf, str[0]);
+                       str++;
+                       expand = FALSE;
+                   } else if (str[1] != PROPEN && str[1] != BROPEN) {
+                       if (str[1] != *var || strlen(var) > 1) {
+                           Buf_AddBytes(&buf, 2, str);
+                           str += 2;
+                           expand = FALSE;
+                       }
+                       else
+                           expand = TRUE;
+                       break;
+                   }
+                   else {
+                       const char *p;
+
+                       /*
+                        * Scan up to the end of the variable name.
+                        */
+                       for (p = &str[2]; *p &&
+                            *p != ':' && *p != PRCLOSE && *p != BRCLOSE; 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, 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, str);
+                           str = p;
+                           expand = FALSE;
+                       }
+                       else
+                           expand = TRUE;
+                       break;
+                   }
+               }
+               if (!expand)
+                   continue;
+           }
+
+           val = Var_Parse(str, ctxt, undefErr, &length, &freeIt);
+
+           /*
+            * 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, *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.
+                */
+               length = strlen(val);
+               Buf_AddBytes(&buf, length, val);
+               trailingBslash = length > 0 && val[length - 1] == '\\';
+           }
+           if (freeIt) {
+               free(freeIt);
+               freeIt = NULL;
+           }
+       }
+    }
+
+    return Buf_Destroy(&buf, FALSE);
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_GetTail --
+ *     Return the tail from each of a list of words. Used to set the
+ *     System V local variables.
+ *
+ * Input:
+ *     file            Filename to modify
+ *
+ * Results:
+ *     The resulting string.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+#if 0
+char *
+Var_GetTail(char *file)
+{
+    return(VarModify(file, VarTail, NULL));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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.
+ *
+ * Input:
+ *     file            Filename to manipulate
+ *
+ * Results:
+ *     The leading components.
+ *
+ * Side Effects:
+ *     None.
+ *
+ *-----------------------------------------------------------------------
+ */
+char *
+Var_GetHead(char *file)
+{
+    return(VarModify(file, VarHead, NULL));
+}
+#endif
+
+/*-
+ *-----------------------------------------------------------------------
+ * 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");
+
+}
+
+
+void
+Var_End(void)
+{
+}
+
+
+/****************** PRINT DEBUGGING INFO *****************/
+static void
+VarPrintVar(void *vp)
+{
+    Var    *v = (Var *)vp;
+    fprintf(debug_file, "%-16s = %s\n", v->name, Buf_GetAll(&v->val, NULL));
+}
+
+/*-
+ *-----------------------------------------------------------------------
+ * Var_Dump --
+ *     print all variables in a context
+ *-----------------------------------------------------------------------
+ */
+void
+Var_Dump(GNode *ctxt)
+{
+    Hash_Search search;
+    Hash_Entry *h;
+
+    for (h = Hash_EnumFirst(&ctxt->context, &search);
+        h != NULL;
+        h = Hash_EnumNext(&search)) {
+           VarPrintVar(Hash_GetValue(h));
+    }
+}
diff --git a/etc/mk/sys.mk b/etc/mk/sys.mk
new file mode 100644 (file)
index 0000000..87112fb
--- /dev/null
@@ -0,0 +1,223 @@
+#      $NetBSD: sys.mk,v 1.99 2008/09/07 15:54:52 kent Exp $
+#      @(#)sys.mk      8.2 (Berkeley) 3/21/94
+
+unix?=         We run MINIX.
+
+.SUFFIXES: .a .o .ln .s .S .c .cc .cpp .cxx .C .f .F .r .p .l .y #.sh
+
+.LIBS:         .a
+
+AR?=           ar
+ARFLAGS?=      rl
+RANLIB?=       ranlib
+
+AS?=           as
+AFLAGS?=
+COMPILE.s?=    ${CC} ${AFLAGS} -c
+LINK.s?=       ${CC} ${AFLAGS} ${LDFLAGS}
+COMPILE.S?=    ${CC} ${AFLAGS} ${CPPFLAGS} -c -traditional-cpp
+LINK.S?=       ${CC} ${AFLAGS} ${CPPFLAGS} ${LDFLAGS}
+
+CC?=           cc
+.if ${MACHINE_ARCH} == "alpha" || \
+    ${MACHINE_ARCH} == "arm" || \
+    ${MACHINE_ARCH} == "x86_64" || \
+    ${MACHINE_ARCH} == "armeb" || \
+    ${MACHINE_ARCH} == "hppa" || \
+    ${MACHINE_ARCH} == "i386" || \
+    ${MACHINE_ARCH} == "m68k" || \
+    ${MACHINE_ARCH} == "mipsel" || ${MACHINE_ARCH} == "mipseb" || \
+    ${MACHINE_ARCH} == "mips64el" || ${MACHINE_ARCH} == "mips64eb" || \
+    ${MACHINE_ARCH} == "powerpc" || \
+    ${MACHINE_ARCH} == "sparc" || \
+    ${MACHINE_ARCH} == "sparc64"
+DBG?=  -O2
+.elif ${MACHINE_ARCH} == "sh3el" || ${MACHINE_ARCH} == "sh3eb"
+# -O2 is too -falign-* zealous for low-memory sh3 machines
+DBG?=  -Os -freorder-blocks
+.elif ${MACHINE_ARCH} == "vax"
+DBG?=  -O1 -fgcse -fstrength-reduce -fgcse-after-reload
+.elif ${MACHINE_ARCH} == "m68000"
+# see src/doc/HACKS for details
+DBG?=  -O1
+.else
+DBG?=  -O
+.endif
+CFLAGS?=       ${DBG}
+LDFLAGS?=
+COMPILE.c?=    ${CC} ${CFLAGS} ${CPPFLAGS} -c
+LINK.c?=       ${CC} ${CFLAGS} ${CPPFLAGS} ${LDFLAGS}
+
+CXX?=          c++
+CXXFLAGS?=     ${CFLAGS:N-Wno-traditional:N-Wstrict-prototypes:N-Wmissing-prototypes:N-Wno-pointer-sign:N-ffreestanding:N-std=gnu99}
+
+__ALLSRC1=     ${empty(DESTDIR):?${.ALLSRC}:${.ALLSRC:S|^${DESTDIR}|^destdir|}}
+__ALLSRC2=     ${empty(MAKEOBJDIR):?${__ALLSRC1}:${__ALLSRC1:S|^${MAKEOBJDIR}|^obj|}}
+__ALLSRC3=     ${empty(NETBSDSRCDIR):?${__ALLSRC2}:${__ALLSRC2:S|^${NETBSDSRCDIR}|^src|}}
+
+_CXXSEED?=     ${BUILDSEED:D-frandom-seed=${BUILDSEED:Q}/${__ALLSRC3:O:Q}/${.TARGET:Q}}
+
+COMPILE.cc?=   ${CXX} ${_CXXSEED} ${CXXFLAGS} ${CPPFLAGS} -c
+LINK.cc?=      ${CXX} ${CXXFLAGS} ${CPPFLAGS} ${LDFLAGS}
+
+OBJC?=         ${CC}
+OBJCFLAGS?=    ${CFLAGS}
+COMPILE.m?=    ${OBJC} ${OBJCFLAGS} ${CPPFLAGS} -c
+LINK.m?=       ${OBJC} ${OBJCFLAGS} ${CPPFLAGS} ${LDFLAGS}
+
+CPP?=          cpp
+CPPFLAGS?=
+
+FC?=           f77
+FFLAGS?=       -O
+RFLAGS?=
+COMPILE.f?=    ${FC} ${FFLAGS} -c
+LINK.f?=       ${FC} ${FFLAGS} ${LDFLAGS}
+COMPILE.F?=    ${FC} ${FFLAGS} ${CPPFLAGS} -c
+LINK.F?=       ${FC} ${FFLAGS} ${CPPFLAGS} ${LDFLAGS}
+COMPILE.r?=    ${FC} ${FFLAGS} ${RFLAGS} -c
+LINK.r?=       ${FC} ${FFLAGS} ${RFLAGS} ${LDFLAGS}
+
+INSTALL?=      install
+
+LD?=           ld
+
+LEX?=          lex
+LFLAGS?=
+LEX.l?=                ${LEX} ${LFLAGS}
+
+LINT?=         lint
+LINTFLAGS?=    -chapbxzFS
+
+LORDER?=       lorder
+
+MAKE?=         make
+
+NM?=           nm
+
+PC?=           pc
+PFLAGS?=
+COMPILE.p?=    ${PC} ${PFLAGS} ${CPPFLAGS} -c
+LINK.p?=       ${PC} ${PFLAGS} ${CPPFLAGS} ${LDFLAGS}
+
+SHELL?=                sh
+
+SIZE?=         size
+
+TSORT?=        tsort -q
+
+YACC?=         yacc
+YFLAGS?=
+YACC.y?=       ${YACC} ${YFLAGS}
+
+# C
+.c:
+       ${LINK.c} -o ${.TARGET} ${.IMPSRC} ${LDLIBS}
+.c.o:
+       ${COMPILE.c} ${.IMPSRC}
+.c.a:
+       ${COMPILE.c} ${.IMPSRC}
+       ${AR} ${ARFLAGS} ${.TARGET} ${.PREFIX}.o
+       rm -f ${.PREFIX}.o
+.c.ln:
+       ${LINT} ${LINTFLAGS} \
+           ${CPPFLAGS:C/-([IDU])[  ]*/-\1/Wg:M-[IDU]*} \
+           -i ${.IMPSRC}
+
+# C++
+.cc .cpp .cxx .C:
+       ${LINK.cc} -o ${.TARGET} ${.IMPSRC} ${LDLIBS}
+.cc.o .cpp.o .cxx.o .C.o:
+       ${COMPILE.cc} ${.IMPSRC}
+.cc.a .cpp.a .cxx.a .C.a:
+       ${COMPILE.cc} ${.IMPSRC}
+       ${AR} ${ARFLAGS} ${.TARGET} ${.PREFIX}.o
+       rm -f ${.PREFIX}.o
+
+# Fortran/Ratfor
+.f:
+       ${LINK.f} -o ${.TARGET} ${.IMPSRC} ${LDLIBS}
+.f.o:
+       ${COMPILE.f} ${.IMPSRC}
+.f.a:
+       ${COMPILE.f} ${.IMPSRC}
+       ${AR} ${ARFLAGS} ${.TARGET} ${.PREFIX}.o
+       rm -f ${.PREFIX}.o
+
+.F:
+       ${LINK.F} -o ${.TARGET} ${.IMPSRC} ${LDLIBS}
+.F.o:
+       ${COMPILE.F} ${.IMPSRC}
+.F.a:
+       ${COMPILE.F} ${.IMPSRC}
+       ${AR} ${ARFLAGS} ${.TARGET} ${.PREFIX}.o
+       rm -f ${.PREFIX}.o
+
+.r:
+       ${LINK.r} -o ${.TARGET} ${.IMPSRC} ${LDLIBS}
+.r.o:
+       ${COMPILE.r} ${.IMPSRC}
+.r.a:
+       ${COMPILE.r} ${.IMPSRC}
+       ${AR} ${ARFLAGS} ${.TARGET} ${.PREFIX}.o
+       rm -f ${.PREFIX}.o
+
+# Pascal
+.p:
+       ${LINK.p} -o ${.TARGET} ${.IMPSRC} ${LDLIBS}
+.p.o:
+       ${COMPILE.p} ${.IMPSRC}
+.p.a:
+       ${COMPILE.p} ${.IMPSRC}
+       ${AR} ${ARFLAGS} ${.TARGET} ${.PREFIX}.o
+       rm -f ${.PREFIX}.o
+
+# Assembly
+.s:
+       ${LINK.s} -o ${.TARGET} ${.IMPSRC} ${LDLIBS}
+.s.o:
+       ${COMPILE.s} ${.IMPSRC}
+.s.a:
+       ${COMPILE.s} ${.IMPSRC}
+       ${AR} ${ARFLAGS} ${.TARGET} ${.PREFIX}.o
+       rm -f ${.PREFIX}.o
+.S:
+       ${LINK.S} -o ${.TARGET} ${.IMPSRC} ${LDLIBS}
+.S.o:
+       ${COMPILE.S} ${.IMPSRC}
+.S.a:
+       ${COMPILE.S} ${.IMPSRC}
+       ${AR} ${ARFLAGS} ${.TARGET} ${.PREFIX}.o
+       rm -f ${.PREFIX}.o
+
+# Lex
+.l:
+       ${LEX.l} ${.IMPSRC}
+       ${LINK.c} -o ${.TARGET} lex.yy.c ${LDLIBS} -ll
+       rm -f lex.yy.c
+.l.c:
+       ${LEX.l} ${.IMPSRC}
+       mv lex.yy.c ${.TARGET}
+.l.o:
+       ${LEX.l} ${.IMPSRC}
+       ${COMPILE.c} -o ${.TARGET} lex.yy.c
+       rm -f lex.yy.c
+
+# Yacc
+.y:
+       ${YACC.y} ${.IMPSRC}
+       ${LINK.c} -o ${.TARGET} y.tab.c ${LDLIBS}
+       rm -f y.tab.c
+.y.c:
+       ${YACC.y} ${.IMPSRC}
+       mv y.tab.c ${.TARGET}
+.y.o:
+       ${YACC.y} ${.IMPSRC}
+       ${COMPILE.c} -o ${.TARGET} y.tab.c
+       rm -f y.tab.c
+
+# Shell
+# .sh:
+#      rm -f ${.TARGET}
+#      cp ${.IMPSRC} ${.TARGET}
+#      chmod a+x ${.TARGET}
diff --git a/include/ar.h b/include/ar.h
new file mode 100644 (file)
index 0000000..37d7347
--- /dev/null
@@ -0,0 +1,65 @@
+/*     $NetBSD: ar.h,v 1.5 2003/08/07 09:44:09 agc Exp $       */
+
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Hugh Smith at The University of Guelph.
+ *
+ * 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. 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.
+ *
+ *     @(#)ar.h        8.2 (Berkeley) 1/21/94
+ */
+
+#ifndef _AR_H_
+#define        _AR_H_
+
+/* Pre-4BSD archives had these magic numbers in them. */
+#define        OARMAG1 0177555
+#define        OARMAG2 0177545
+
+#define        ARMAG           "!<arch>\n"     /* ar "magic number" */
+#define        SARMAG          8               /* strlen(ARMAG); */
+
+#define        AR_EFMT1        "#1/"           /* extended format #1 */
+
+struct ar_hdr {
+       char ar_name[16];               /* name */
+       char ar_date[12];               /* modification time */
+       char ar_uid[6];                 /* user id */
+       char ar_gid[6];                 /* group id */
+       char ar_mode[8];                /* octal file permissions */
+       char ar_size[10];               /* size in bytes */
+#define        ARFMAG  "`\n"
+       char ar_fmag[2];                /* consistency check */
+};
+
+#endif /* !_AR_H_ */
index f8a794260361df339d906272d90fa271dc575598..389a078e2eefffb30c3e392ea4c91a7c152383b1 100644 (file)
@@ -1,10 +1,14 @@
-#ifndef __SYS_PARAM_H__
-#define __SYS_PARAM_H__
 /*
 sys/param.h
 */
 
+#ifndef __SYS_PARAM_H__
+#define __SYS_PARAM_H__
+
+#include <limits.h>
+
 #define MAXHOSTNAMELEN  256    /* max hostname size */
 #define NGROUPS                8       /* max number of supplementary groups */
+#define MAXPATHLEN     PATH_MAX
 
 #endif /* __SYS_PARAM_H__ */