aboutsummaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/mk/LICENSE258
-rw-r--r--src/cmd/mk/Make.FreeBSD-3867
-rw-r--r--src/cmd/mk/Make.HP-UX-90006
-rw-r--r--src/cmd/mk/Make.Linux-3867
-rw-r--r--src/cmd/mk/Make.OSF1-alpha6
-rw-r--r--src/cmd/mk/Make.SunOS-sun4u2
-rw-r--r--src/cmd/mk/Make.SunOS-sun4u-cc6
-rw-r--r--src/cmd/mk/Make.SunOS-sun4u-gcc6
-rw-r--r--src/cmd/mk/Makefile117
-rw-r--r--src/cmd/mk/Makefile.MID45
-rw-r--r--src/cmd/mk/arc.c52
-rw-r--r--src/cmd/mk/archive.c180
-rw-r--r--src/cmd/mk/bundle.ports46
-rw-r--r--src/cmd/mk/env.c149
-rw-r--r--src/cmd/mk/file.c90
-rw-r--r--src/cmd/mk/fns.h84
-rw-r--r--src/cmd/mk/graph.c279
-rw-r--r--src/cmd/mk/lex.c147
-rw-r--r--src/cmd/mk/main.c285
-rw-r--r--src/cmd/mk/match.c49
-rw-r--r--src/cmd/mk/mk.1665
-rw-r--r--src/cmd/mk/mk.c226
-rw-r--r--src/cmd/mk/mk.h203
-rw-r--r--src/cmd/mk/mkfile9
-rw-r--r--src/cmd/mk/mkfile.test5
-rw-r--r--src/cmd/mk/parse.c307
-rw-r--r--src/cmd/mk/rc.c175
-rw-r--r--src/cmd/mk/recipe.c117
-rw-r--r--src/cmd/mk/rpm.spec29
-rw-r--r--src/cmd/mk/rule.c107
-rw-r--r--src/cmd/mk/run.c296
-rw-r--r--src/cmd/mk/sh.c189
-rw-r--r--src/cmd/mk/shprint.c123
-rw-r--r--src/cmd/mk/symtab.c95
-rw-r--r--src/cmd/mk/unix.c306
-rw-r--r--src/cmd/mk/var.c41
-rw-r--r--src/cmd/mk/varsub.c256
-rw-r--r--src/cmd/mk/word.c180
-rw-r--r--src/cmd/sam/LICENSE258
-rw-r--r--src/cmd/sam/Makefile18
-rw-r--r--src/cmd/sam/address.c240
-rw-r--r--src/cmd/sam/buff.c302
-rw-r--r--src/cmd/sam/cmd.c594
-rw-r--r--src/cmd/sam/disk.c122
-rw-r--r--src/cmd/sam/error.c144
-rw-r--r--src/cmd/sam/errors.h65
-rw-r--r--src/cmd/sam/file.c631
-rw-r--r--src/cmd/sam/io.c262
-rw-r--r--src/cmd/sam/list.c47
-rw-r--r--src/cmd/sam/mesg.c821
-rw-r--r--src/cmd/sam/mesg.h131
-rw-r--r--src/cmd/sam/mkfile40
-rw-r--r--src/cmd/sam/moveto.c173
-rw-r--r--src/cmd/sam/multi.c123
-rw-r--r--src/cmd/sam/parse.h68
-rw-r--r--src/cmd/sam/plan9.c185
-rw-r--r--src/cmd/sam/plumb.c9
-rw-r--r--src/cmd/sam/rasp.c325
-rw-r--r--src/cmd/sam/regexp.c801
-rwxr-xr-xsrc/cmd/sam/sambin0 -> 267893 bytes
-rw-r--r--src/cmd/sam/sam.c739
-rw-r--r--src/cmd/sam/sam.h407
-rw-r--r--src/cmd/sam/shell.c152
-rw-r--r--src/cmd/sam/unix.c272
-rw-r--r--src/cmd/sam/xec.c508
65 files changed, 12587 insertions, 0 deletions
diff --git a/src/cmd/mk/LICENSE b/src/cmd/mk/LICENSE
new file mode 100644
index 00000000..a5d7d87d
--- /dev/null
+++ b/src/cmd/mk/LICENSE
@@ -0,0 +1,258 @@
+The Plan 9 software is provided under the terms of the
+Lucent Public License, Version 1.02, reproduced below,
+with the following exceptions:
+
+1. No right is granted to create derivative works of or
+ to redistribute (other than with the Plan 9 Operating System)
+ the screen imprinter fonts identified in subdirectory
+ /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
+ Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
+ Typewriter83), identified in subdirectory /sys/lib/postscript/font.
+ These directories contain material copyrights by B&H Inc. and Y&Y Inc.
+
+2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
+ are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
+
+3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
+ covered by the Aladdin Free Public License, reproduced in the file
+ /LICENSE.afpl.
+
+===================================================================
+
+Lucent Public License Version 1.02
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
+PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+ a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
+ Program, and
+ b. in the case of each Contributor,
+
+ i. changes to the Program, and
+ ii. additions to the Program;
+
+ where such changes and/or additions to the Program were added to the
+ Program by such Contributor itself or anyone acting on such
+ Contributor's behalf, and the Contributor explicitly consents, in
+ accordance with Section 3C, to characterization of the changes and/or
+ additions as Contributions.
+
+"Contributor" means LUCENT and any other entity that has Contributed a
+Contribution to the Program.
+
+"Distributor" means a Recipient that distributes the Program,
+modifications to the Program, or any part thereof.
+
+"Licensed Patents" mean patent claims licensable by a Contributor
+which are necessarily infringed by the use or sale of its Contribution
+alone or when combined with the Program.
+
+"Original Program" means the original version of the software
+accompanying this Agreement as released by LUCENT, including source
+code, object code and documentation, if any.
+
+"Program" means the Original Program and Contributions or any part
+thereof
+
+"Recipient" means anyone who receives the Program under this
+Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+ a. Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free copyright
+ license to reproduce, prepare derivative works of, publicly display,
+ publicly perform, distribute and sublicense the Contribution of such
+ Contributor, if any, and such derivative works, in source code and
+ object code form.
+
+ b. Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free patent
+ license under Licensed Patents to make, use, sell, offer to sell,
+ import and otherwise transfer the Contribution of such Contributor, if
+ any, in source code and object code form. The patent license granted
+ by a Contributor shall also apply to the combination of the
+ Contribution of that Contributor and the Program if, at the time the
+ Contribution is added by the Contributor, such addition of the
+ Contribution causes such combination to be covered by the Licensed
+ Patents. The patent license granted by a Contributor shall not apply
+ to (i) any other combinations which include the Contribution, nor to
+ (ii) Contributions of other Contributors. No hardware per se is
+ licensed hereunder.
+
+ c. Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity. Each
+ Contributor disclaims any liability to Recipient for claims brought by
+ any other entity based on infringement of intellectual property rights
+ or otherwise. As a condition to exercising the rights and licenses
+ granted hereunder, each Recipient hereby assumes sole responsibility
+ to secure any other intellectual property rights needed, if any. For
+ example, if a third party patent license is required to allow
+ Recipient to distribute the Program, it is Recipient's responsibility
+ to acquire that license before distributing the Program.
+
+ d. Each Contributor represents that to its knowledge it has sufficient
+ copyright rights in its Contribution, if any, to grant the copyright
+ license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A. Distributor may choose to distribute the Program in any form under
+this Agreement or under its own license agreement, provided that:
+
+ a. it complies with the terms and conditions of this Agreement;
+
+ b. if the Program is distributed in source code or other tangible
+ form, a copy of this Agreement or Distributor's own license agreement
+ is included with each copy of the Program; and
+
+ c. if distributed under Distributor's own license agreement, such
+ license agreement:
+
+ i. effectively disclaims on behalf of all Contributors all warranties
+ and conditions, express and implied, including warranties or
+ conditions of title and non-infringement, and implied warranties or
+ conditions of merchantability and fitness for a particular purpose;
+ ii. effectively excludes on behalf of all Contributors all liability
+ for damages, including direct, indirect, special, incidental and
+ consequential damages, such as lost profits; and
+ iii. states that any provisions which differ from this Agreement are
+ offered by that Contributor alone and not by any other party.
+
+B. Each Distributor must include the following in a conspicuous
+ location in the Program:
+
+ Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
+ Reserved.
+
+C. In addition, each Contributor must identify itself as the
+originator of its Contribution in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution.
+Also, each Contributor must agree that the additions and/or changes
+are intended to be a Contribution. Once a Contribution is contributed,
+it may not thereafter be revoked.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use
+of the Program, the Distributor who includes the Program in a
+commercial product offering should do so in a manner which does not
+create potential liability for Contributors. Therefore, if a
+Distributor includes the Program in a commercial product offering,
+such Distributor ("Commercial Distributor") hereby agrees to defend
+and indemnify every Contributor ("Indemnified Contributor") against
+any losses, damages and costs (collectively"Losses") arising from
+claims, lawsuits and other legal actions brought by a third party
+against the Indemnified Contributor to the extent caused by the acts
+or omissions of such Commercial Distributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement.
+In order to qualify, an Indemnified Contributor must: a) promptly
+notify the Commercial Distributor in writing of such claim, and b)
+allow the Commercial Distributor to control, and cooperate with the
+Commercial Distributor in, the defense and any related settlement
+negotiations. The Indemnified Contributor may participate in any such
+claim at its own expense.
+
+For example, a Distributor might include the Program in a commercial
+product offering, Product X. That Distributor is then a Commercial
+Distributor. If that Commercial Distributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Distributor's responsibility
+alone. Under this section, the Commercial Distributor would have to
+defend claims against the Contributors related to those performance
+claims and warranties, and if a court requires any Contributor to pay
+any damages as a result, the Commercial Distributor must pay those
+damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement, including but not limited to
+the risks and costs of program errors, compliance with applicable
+laws, damage to or loss of data, programs or equipment, and
+unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), 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 OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. EXPORT CONTROL
+
+Recipient agrees that Recipient alone is responsible for compliance
+with the United States export administration regulations (and the
+export control laws and regulation of any other countries).
+
+8. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with
+respect to a patent applicable to software (including a cross-claim or
+counterclaim in a lawsuit), then any patent licenses granted by that
+Contributor to such Recipient under this Agreement shall terminate as
+of the date such litigation is filed. In addition, if Recipient
+institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and
+survive.
+
+LUCENT may publish new versions (including revisions) of this
+Agreement from time to time. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new
+version of the Agreement is published, Contributor may elect to
+distribute the Program (including its Contributions) under the new
+version. No one other than LUCENT has the right to modify this
+Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
+Recipient receives no rights or licenses to the intellectual property
+of any Contributor under this Agreement, whether expressly, by
+implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No
+party to this Agreement will bring a legal action under this Agreement
+more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.
+
diff --git a/src/cmd/mk/Make.FreeBSD-386 b/src/cmd/mk/Make.FreeBSD-386
new file mode 100644
index 00000000..087ed3ab
--- /dev/null
+++ b/src/cmd/mk/Make.FreeBSD-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I. -I$(PREFIX)/include
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.HP-UX-9000 b/src/cmd/mk/Make.HP-UX-9000
new file mode 100644
index 00000000..edbdc111
--- /dev/null
+++ b/src/cmd/mk/Make.HP-UX-9000
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS=-O -c -Ae -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.Linux-386 b/src/cmd/mk/Make.Linux-386
new file mode 100644
index 00000000..74b0252c
--- /dev/null
+++ b/src/cmd/mk/Make.Linux-386
@@ -0,0 +1,7 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O # default, can be overriden by Make.$(SYSNAME)
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.OSF1-alpha b/src/cmd/mk/Make.OSF1-alpha
new file mode 100644
index 00000000..3d45279b
--- /dev/null
+++ b/src/cmd/mk/Make.OSF1-alpha
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I.
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.SunOS-sun4u b/src/cmd/mk/Make.SunOS-sun4u
new file mode 100644
index 00000000..c5fe67b8
--- /dev/null
+++ b/src/cmd/mk/Make.SunOS-sun4u
@@ -0,0 +1,2 @@
+include Make.SunOS-sun4u-$(CC)
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.SunOS-sun4u-cc b/src/cmd/mk/Make.SunOS-sun4u-cc
new file mode 100644
index 00000000..829301de
--- /dev/null
+++ b/src/cmd/mk/Make.SunOS-sun4u-cc
@@ -0,0 +1,6 @@
+CC=cc
+CFLAGS+=-g -c -I. -O
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Make.SunOS-sun4u-gcc b/src/cmd/mk/Make.SunOS-sun4u-gcc
new file mode 100644
index 00000000..5c415948
--- /dev/null
+++ b/src/cmd/mk/Make.SunOS-sun4u-gcc
@@ -0,0 +1,6 @@
+CC=gcc
+CFLAGS+=-Wall -Wno-missing-braces -Wno-parentheses -Wno-switch -O2 -g -c
+O=o
+AR=ar
+ARFLAGS=rvc
+NAN=nan64.$O
diff --git a/src/cmd/mk/Makefile b/src/cmd/mk/Makefile
new file mode 100644
index 00000000..df21b60d
--- /dev/null
+++ b/src/cmd/mk/Makefile
@@ -0,0 +1,117 @@
+
+# this works in gnu make
+SYSNAME:=${shell uname}
+OBJTYPE:=${shell uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'}
+
+# this works in bsd make
+SYSNAME!=uname
+OBJTYPE!=uname -m | sed 's;i.86;386;; s;/.*;;; s; ;;g'
+
+# the gnu rules will mess up bsd but not vice versa,
+# hence the gnu rules come first.
+
+include Make.$(SYSNAME)-$(OBJTYPE)
+
+PREFIX=/usr/local
+
+NUKEFILES=
+
+TGZFILES=
+
+TARG=mk
+VERSION=2.0
+PORTPLACE=devel/mk
+NAME=mk
+
+OFILES=\
+ arc.$O\
+ archive.$O\
+ bufblock.$O\
+ env.$O\
+ file.$O\
+ graph.$O\
+ job.$O\
+ lex.$O\
+ main.$O\
+ match.$O\
+ mk.$O\
+ parse.$O\
+ recipe.$O\
+ rule.$O\
+ run.$O\
+ sh.$O\
+ shprint.$O\
+ symtab.$O\
+ var.$O\
+ varsub.$O\
+ word.$O\
+ unix.$O\
+
+HFILES=\
+ mk.h\
+ fns.h\
+
+all: $(TARG)
+
+TGZFILES+=mk.pdf
+
+install: $(LIB)
+ test -d $(PREFIX)/man/man1 || mkdir $(PREFIX)/man/man1
+ test -d $(PREFIX)/doc || mkdir $(PREFIX)/doc
+ install -m 0755 mk $(PREFIX)/bin/mk
+ cat mk.1 | sed 's;DOCPREFIX;$(PREFIX);g' >mk.1a
+ install -m 0644 mk.1a $(PREFIX)/man/man1/mk.1
+ install -m 0644 mk.pdf $(PREFIX)/doc/mk.pdf
+
+
+$(TARG): $(OFILES)
+ $(CC) -o $(TARG) $(OFILES) -L$(PREFIX)/lib -lregexp9 -lbio -lfmt -lutf
+
+
+.c.$O:
+ $(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
+
+%.$O: %.c
+ $(CC) $(CFLAGS) -I$(PREFIX)/include $*.c
+
+
+$(OFILES): $(HFILES)
+
+tgz:
+ rm -rf $(NAME)-$(VERSION)
+ mkdir $(NAME)-$(VERSION)
+ cp Makefile Make.* README LICENSE NOTICE *.[ch137] rpm.spec bundle.ports $(TGZFILES) $(NAME)-$(VERSION)
+ tar cf - $(NAME)-$(VERSION) | gzip >$(NAME)-$(VERSION).tgz
+ rm -rf $(NAME)-$(VERSION)
+
+clean:
+ rm -f $(OFILES) $(LIB)
+
+nuke:
+ rm -f $(OFILES) *.tgz *.rpm $(NUKEFILES)
+
+rpm:
+ make tgz
+ cp $(NAME)-$(VERSION).tgz /usr/src/RPM/SOURCES
+ rpm -ba rpm.spec
+ cp /usr/src/RPM/SRPMS/$(NAME)-$(VERSION)-1.src.rpm .
+ cp /usr/src/RPM/RPMS/i586/$(NAME)-$(VERSION)-1.i586.rpm .
+ scp *.rpm rsc@amsterdam.lcs.mit.edu:public_html/software
+
+PORTDIR=/usr/ports/$(PORTPLACE)
+
+ports:
+ make tgz
+ rm -rf $(PORTDIR)
+ mkdir $(PORTDIR)
+ cp $(NAME)-$(VERSION).tgz /usr/ports/distfiles
+ cat bundle.ports | (cd $(PORTDIR) && awk '$$1=="---" && $$3=="---" { ofile=$$2; next} {if(ofile) print >ofile}')
+ (cd $(PORTDIR); make makesum)
+ (cd $(PORTDIR); make)
+ (cd $(PORTDIR); /usr/local/bin/portlint)
+ rm -rf $(PORTDIR)/work
+ shar `find $(PORTDIR)` > ports.shar
+ (cd $(PORTDIR); tar cf - *) | gzip >$(NAME)-$(VERSION)-ports.tgz
+ scp *.tgz rsc@amsterdam.lcs.mit.edu:public_html/software
+
+.phony: all clean nuke install tgz rpm ports
diff --git a/src/cmd/mk/Makefile.MID b/src/cmd/mk/Makefile.MID
new file mode 100644
index 00000000..f840b18e
--- /dev/null
+++ b/src/cmd/mk/Makefile.MID
@@ -0,0 +1,45 @@
+TARG=mk
+VERSION=2.0
+PORTPLACE=devel/mk
+NAME=mk
+
+OFILES=\
+ arc.$O\
+ archive.$O\
+ bufblock.$O\
+ env.$O\
+ file.$O\
+ graph.$O\
+ job.$O\
+ lex.$O\
+ main.$O\
+ match.$O\
+ mk.$O\
+ parse.$O\
+ recipe.$O\
+ rule.$O\
+ run.$O\
+ sh.$O\
+ shprint.$O\
+ symtab.$O\
+ var.$O\
+ varsub.$O\
+ word.$O\
+ unix.$O\
+
+HFILES=\
+ mk.h\
+ fns.h\
+
+all: $(TARG)
+
+TGZFILES+=mk.pdf
+
+install: $(LIB)
+ test -d $(PREFIX)/man/man1 || mkdir $(PREFIX)/man/man1
+ test -d $(PREFIX)/doc || mkdir $(PREFIX)/doc
+ install -m 0755 mk $(PREFIX)/bin/mk
+ cat mk.1 | sed 's;DOCPREFIX;$(PREFIX);g' >mk.1a
+ install -m 0644 mk.1a $(PREFIX)/man/man1/mk.1
+ install -m 0644 mk.pdf $(PREFIX)/doc/mk.pdf
+
diff --git a/src/cmd/mk/arc.c b/src/cmd/mk/arc.c
new file mode 100644
index 00000000..521ef7a7
--- /dev/null
+++ b/src/cmd/mk/arc.c
@@ -0,0 +1,52 @@
+#include "mk.h"
+
+Arc *
+newarc(Node *n, Rule *r, char *stem, Resub *match)
+{
+ Arc *a;
+
+ a = (Arc *)Malloc(sizeof(Arc));
+ a->n = n;
+ a->r = r;
+ a->stem = strdup(stem);
+ rcopy(a->match, match, NREGEXP);
+ a->next = 0;
+ a->flag = 0;
+ a->prog = r->prog;
+ return(a);
+}
+
+void
+dumpa(char *s, Arc *a)
+{
+ char buf[1024];
+
+ Bprint(&bout, "%sArc@%p: n=%p r=%p flag=0x%x stem='%s'",
+ s, a, a->n, a->r, a->flag, a->stem);
+ if(a->prog)
+ Bprint(&bout, " prog='%s'", a->prog);
+ Bprint(&bout, "\n");
+
+ if(a->n){
+ snprint(buf, sizeof(buf), "%s ", (*s == ' ')? s:"");
+ dumpn(buf, a->n);
+ }
+}
+
+void
+nrep(void)
+{
+ Symtab *sym;
+ Word *w;
+
+ sym = symlook("NREP", S_VAR, 0);
+ if(sym){
+ w = (Word *) sym->value;
+ if (w && w->s && *w->s)
+ nreps = atoi(w->s);
+ }
+ if(nreps < 1)
+ nreps = 1;
+ if(DEBUG(D_GRAPH))
+ Bprint(&bout, "nreps = %d\n", nreps);
+}
diff --git a/src/cmd/mk/archive.c b/src/cmd/mk/archive.c
new file mode 100644
index 00000000..9ba7a12d
--- /dev/null
+++ b/src/cmd/mk/archive.c
@@ -0,0 +1,180 @@
+#include "mk.h"
+#define ARMAG "!<arch>\n"
+#define SARMAG 8
+
+#define ARFMAG "`\n"
+#define SARNAME 16
+
+struct ar_hdr
+{
+ char name[SARNAME];
+ char date[12];
+ char uid[6];
+ char gid[6];
+ char mode[8];
+ char size[10];
+ char fmag[2];
+};
+#define SAR_HDR (SARNAME+44)
+
+static int dolong;
+
+static void atimes(char *);
+static char *split(char*, char**);
+
+long
+atimeof(int force, char *name)
+{
+ Symtab *sym;
+ long t;
+ char *archive, *member, buf[512];
+
+ archive = split(name, &member);
+ if(archive == 0)
+ Exit();
+
+ t = mtime(archive);
+ sym = symlook(archive, S_AGG, 0);
+ if(sym){
+ if(force || (t > (long)sym->value)){
+ atimes(archive);
+ sym->value = (void *)t;
+ }
+ }
+ else{
+ atimes(archive);
+ /* mark the aggegate as having been done */
+ symlook(strdup(archive), S_AGG, "")->value = (void *)t;
+ }
+ /* truncate long member name to sizeof of name field in archive header */
+ if(dolong)
+ snprint(buf, sizeof(buf), "%s(%s)", archive, member);
+ else
+ snprint(buf, sizeof(buf), "%s(%.*s)", archive, SARNAME, member);
+ sym = symlook(buf, S_TIME, 0);
+ if (sym)
+ return (long)sym->value; /* uggh */
+ return 0;
+}
+
+void
+atouch(char *name)
+{
+ char *archive, *member;
+ int fd, i;
+ struct ar_hdr h;
+ long t;
+
+ archive = split(name, &member);
+ if(archive == 0)
+ Exit();
+
+ fd = open(archive, ORDWR);
+ if(fd < 0){
+ fd = create(archive, OWRITE, 0666);
+ if(fd < 0){
+ fprint(2, "create %s: %r\n", archive);
+ Exit();
+ }
+ write(fd, ARMAG, SARMAG);
+ }
+ if(symlook(name, S_TIME, 0)){
+ /* hoon off and change it in situ */
+ LSEEK(fd, SARMAG, 0);
+ while(read(fd, (char *)&h, sizeof(h)) == sizeof(h)){
+ for(i = SARNAME-1; i > 0 && h.name[i] == ' '; i--)
+ ;
+ h.name[i+1]=0;
+ if(strcmp(member, h.name) == 0){
+ t = SARNAME-sizeof(h); /* ughgghh */
+ LSEEK(fd, t, 1);
+ fprint(fd, "%-12ld", time(0));
+ break;
+ }
+ t = atol(h.size);
+ if(t&01) t++;
+ LSEEK(fd, t, 1);
+ }
+ }
+ close(fd);
+}
+
+static void
+atimes(char *ar)
+{
+ struct ar_hdr h;
+ long t;
+ int fd, i;
+ char buf[BIGBLOCK];
+ char name[sizeof(h.name)+1];
+
+ fd = open(ar, OREAD);
+ if(fd < 0)
+ return;
+
+ if(read(fd, buf, SARMAG) != SARMAG){
+ close(fd);
+ return;
+ }
+ while(read(fd, (char *)&h, sizeof(h)) == sizeof(h)){
+ t = atol(h.date);
+ if(t == 0) /* as it sometimes happens; thanks ken */
+ t = 1;
+ strncpy(name, h.name, sizeof(h.name));
+ for(i = sizeof(h.name)-1; i > 0 && name[i] == ' '; i--)
+ ;
+ if(name[i] == '/') /* system V bug */
+ i--;
+ name[i+1]=0;
+ sprint(buf, "%s(%s)", ar, h.size);
+ symlook(strdup(buf), S_TIME, (void *)t)->value = (void *)t;
+ t = atol(h.size);
+ if(t&01) t++;
+ LSEEK(fd, t, 1);
+ }
+ close(fd);
+}
+
+static int
+type(char *file)
+{
+ int fd;
+ char buf[SARMAG];
+
+ fd = open(file, OREAD);
+ if(fd < 0){
+ if(symlook(file, S_BITCH, 0) == 0){
+ Bprint(&bout, "%s doesn't exist: assuming it will be an archive\n", file);
+ symlook(file, S_BITCH, (void *)file);
+ }
+ return 1;
+ }
+ if(read(fd, buf, SARMAG) != SARMAG){
+ close(fd);
+ return 0;
+ }
+ close(fd);
+ return !strncmp(ARMAG, buf, SARMAG);
+}
+
+static char*
+split(char *name, char **member)
+{
+ char *p, *q;
+
+ p = strdup(name);
+ q = utfrune(p, '(');
+ if(q){
+ *q++ = 0;
+ if(member)
+ *member = q;
+ q = utfrune(q, ')');
+ if (q)
+ *q = 0;
+ if(type(p))
+ return p;
+ free(p);
+ fprint(2, "mk: '%s' is not an archive\n", name);
+ }
+ return 0;
+}
diff --git a/src/cmd/mk/bundle.ports b/src/cmd/mk/bundle.ports
new file mode 100644
index 00000000..46498310
--- /dev/null
+++ b/src/cmd/mk/bundle.ports
@@ -0,0 +1,46 @@
+--- Makefile ---
+# New ports collection makefile for: mk
+# Date Created: 11 Feb 2003
+# Whom: rsc
+#
+# THIS LINE NEEDS REPLACING. IT'S HERE TO GET BY PORTLINT
+# $FreeBSD: ports/devel/mk/Makefile,v 1.1 2003/02/12 00:51:22 rsc Exp $
+
+PORTNAME= mk
+PORTVERSION= 2.0
+CATEGORIES= devel
+MASTER_SITES= http://pdos.lcs.mit.edu/~rsc/software/
+EXTRACT_SUFX= .tgz
+
+MAINTAINER= rsc@post.harvard.edu
+
+DEPENDS= ${PORTSDIR}/devel/libutf \
+ ${PORTSDIR}/devel/libfmt \
+ ${PORTSDIR}/devel/libbio \
+ ${PORTSDIR}/devel/libregexp9
+
+MAN1= mk.1
+USE_REINPLACE= yes
+
+.include <bsd.port.pre.mk>
+
+post-patch:
+ ${REINPLACE_CMD} -e 's,$$(PREFIX),${PREFIX},g' ${WRKSRC}/Makefile
+
+.include <bsd.port.post.mk>
+--- pkg-comment ---
+Streamlined replacement for make
+--- pkg-descr ---
+Mk is a streamlined replacement for make, written for
+Tenth Edition Research Unix by Andrew Hume.
+
+WWW: http://pdos.lcs.mit.edu/~rsc/software/#mk
+
+Russ Cox
+rsc@post.harvard.edu
+--- pkg-plist ---
+bin/mk
+doc/mk.pdf
+--- /dev/null ---
+This is just a way to make sure blank lines don't
+creep into pkg-plist.
diff --git a/src/cmd/mk/env.c b/src/cmd/mk/env.c
new file mode 100644
index 00000000..c040db58
--- /dev/null
+++ b/src/cmd/mk/env.c
@@ -0,0 +1,149 @@
+#include "mk.h"
+
+enum {
+ ENVQUANTA=10
+};
+
+Envy *envy;
+static int nextv;
+
+static char *myenv[] =
+{
+ "target",
+ "stem",
+ "prereq",
+ "pid",
+ "nproc",
+ "newprereq",
+ "alltarget",
+ "newmember",
+ "stem0", /* must be in order from here */
+ "stem1",
+ "stem2",
+ "stem3",
+ "stem4",
+ "stem5",
+ "stem6",
+ "stem7",
+ "stem8",
+ "stem9",
+ 0,
+};
+
+void
+initenv(void)
+{
+ char **p;
+
+ for(p = myenv; *p; p++)
+ symlook(*p, S_INTERNAL, (void *)"");
+ readenv(); /* o.s. dependent */
+}
+
+static void
+envinsert(char *name, Word *value)
+{
+ static int envsize;
+
+ if (nextv >= envsize) {
+ envsize += ENVQUANTA;
+ envy = (Envy *) Realloc((char *) envy, envsize*sizeof(Envy));
+ }
+ envy[nextv].name = name;
+ envy[nextv++].values = value;
+}
+
+static void
+envupd(char *name, Word *value)
+{
+ Envy *e;
+
+ for(e = envy; e->name; e++)
+ if(strcmp(name, e->name) == 0){
+ delword(e->values);
+ e->values = value;
+ return;
+ }
+ e->name = name;
+ e->values = value;
+ envinsert(0,0);
+}
+
+static void
+ecopy(Symtab *s)
+{
+ char **p;
+
+ if(symlook(s->name, S_NOEXPORT, 0))
+ return;
+ for(p = myenv; *p; p++)
+ if(strcmp(*p, s->name) == 0)
+ return;
+ envinsert(s->name, (Word *) s->value);
+}
+
+void
+execinit(void)
+{
+ char **p;
+
+ nextv = 0;
+ for(p = myenv; *p; p++)
+ envinsert(*p, stow(""));
+
+ symtraverse(S_VAR, ecopy);
+ envinsert(0, 0);
+}
+
+Envy*
+buildenv(Job *j, int slot)
+{
+ char **p, *cp, *qp;
+ Word *w, *v, **l;
+ int i;
+ char buf[256];
+
+ envupd("target", wdup(j->t));
+ if(j->r->attr&REGEXP)
+ envupd("stem",newword(""));
+ else
+ envupd("stem", newword(j->stem));
+ envupd("prereq", wdup(j->p));
+ sprint(buf, "%d", getpid());
+ envupd("pid", newword(buf));
+ sprint(buf, "%d", slot);
+ envupd("nproc", newword(buf));
+ envupd("newprereq", wdup(j->np));
+ envupd("alltarget", wdup(j->at));
+ l = &v;
+ v = w = wdup(j->np);
+ while(w){
+ cp = strchr(w->s, '(');
+ if(cp){
+ qp = strchr(cp+1, ')');
+ if(qp){
+ *qp = 0;
+ strcpy(w->s, cp+1);
+ l = &w->next;
+ w = w->next;
+ continue;
+ }
+ }
+ *l = w->next;
+ free(w->s);
+ free(w);
+ w = *l;
+ }
+ envupd("newmember", v);
+ /* update stem0 -> stem9 */
+ for(p = myenv; *p; p++)
+ if(strcmp(*p, "stem0") == 0)
+ break;
+ for(i = 0; *p; i++, p++){
+ if((j->r->attr&REGEXP) && j->match[i])
+ envupd(*p, newword(j->match[i]));
+ else
+ envupd(*p, newword(""));
+ }
+ return envy;
+}
diff --git a/src/cmd/mk/file.c b/src/cmd/mk/file.c
new file mode 100644
index 00000000..2533343f
--- /dev/null
+++ b/src/cmd/mk/file.c
@@ -0,0 +1,90 @@
+#include "mk.h"
+
+/* table-driven version in bootes dump of 12/31/96 */
+
+long
+mtime(char *name)
+{
+ return mkmtime(name);
+}
+
+long
+timeof(char *name, int force)
+{
+ Symtab *sym;
+ long t;
+
+ if(utfrune(name, '('))
+ return atimeof(force, name); /* archive */
+
+ if(force)
+ return mtime(name);
+
+
+ sym = symlook(name, S_TIME, 0);
+ if (sym)
+ return (long) sym->value; /* uggh */
+
+ t = mtime(name);
+ if(t == 0)
+ return 0;
+
+ symlook(name, S_TIME, (void*)t); /* install time in cache */
+ return t;
+}
+
+void
+touch(char *name)
+{
+ Bprint(&bout, "touch(%s)\n", name);
+ if(nflag)
+ return;
+
+ if(utfrune(name, '('))
+ atouch(name); /* archive */
+ else if(chgtime(name) < 0) {
+ fprint(2, "%s: %r\n", name);
+ Exit();
+ }
+}
+
+void
+delete(char *name)
+{
+ if(utfrune(name, '(') == 0) { /* file */
+ if(remove(name) < 0)
+ fprint(2, "remove %s: %r\n", name);
+ } else
+ fprint(2, "hoon off; mk can'tdelete archive members\n");
+}
+
+void
+timeinit(char *s)
+{
+ long t;
+ char *cp;
+ Rune r;
+ int c, n;
+
+ t = time(0);
+ while (*s) {
+ cp = s;
+ do{
+ n = chartorune(&r, s);
+ if (r == ' ' || r == ',' || r == '\n')
+ break;
+ s += n;
+ } while(*s);
+ c = *s;
+ *s = 0;
+ symlook(strdup(cp), S_TIME, (void *)t)->value = (void *)t;
+ if (c)
+ *s++ = c;
+ while(*s){
+ n = chartorune(&r, s);
+ if(r != ' ' && r != ',' && r != '\n')
+ break;
+ s += n;
+ }
+ }
+}
diff --git a/src/cmd/mk/fns.h b/src/cmd/mk/fns.h
new file mode 100644
index 00000000..2c9f46a3
--- /dev/null
+++ b/src/cmd/mk/fns.h
@@ -0,0 +1,84 @@
+void addrule(char*, Word*, char*, Word*, int, int, char*);
+void addrules(Word*, Word*, char*, int, int, char*);
+void addw(Word*, char*);
+void assert(char*, int);
+int assline(Biobuf *, Bufblock *);
+long atimeof(int,char*);
+void atouch(char*);
+void bufcpy(Bufblock *, char *, int);
+Envy *buildenv(Job*, int);
+void catchnotes(void);
+char *charin(char *, char *);
+int chgtime(char*);
+void clrmade(Node*);
+char *copyq(char*, Rune, Bufblock*);
+void delete(char*);
+void delword(Word*);
+int dorecipe(Node*);
+void dumpa(char*, Arc*);
+void dumpj(char*, Job*, int);
+void dumpn(char*, Node*);
+void dumpr(char*, Rule*);
+void dumpv(char*);
+void dumpw(char*, Word*);
+int escapetoken(Biobuf*, Bufblock*, int, int);
+void execinit(void);
+int execsh(char*, char*, Bufblock*, Envy*);
+void Exit(void);
+char *expandquote(char*, Rune, Bufblock*);
+void expunge(int, char*);
+void freebuf(Bufblock*);
+void front(char*);
+Node *graph(char*);
+void growbuf(Bufblock *);
+void initenv(void);
+void insert(Bufblock *, int);
+void ipop(void);
+void ipush(void);
+void killchildren(char*);
+void *Malloc(int);
+char *maketmp(int*);
+int match(char*, char*, char*);
+char *membername(char*, int, char*);
+void mk(char*);
+ulong mkmtime(char*);
+long mtime(char*);
+Arc *newarc(Node*, Rule*, char*, Resub*);
+Bufblock *newbuf(void);
+Job *newjob(Rule*, Node*, char*, char**, Word*, Word*, Word*, Word*);
+Word *newword(char*);
+int nextrune(Biobuf*, int);
+int nextslot(void);
+void nproc(void);
+void nrep(void);
+int outofdate(Node*, Arc*, int);
+void parse(char*, int, int);
+int pipecmd(char*, Envy*, int*);
+void prusage(void);
+void rcopy(char**, Resub*, int);
+void readenv(void);
+void *Realloc(void*, int);
+void rinsert(Bufblock *, Rune);
+char *rulecnt(void);
+void run(Job*);
+void setvar(char*, void*);
+char *shname(char*);
+void shprint(char*, Envy*, Bufblock*);
+Word *stow(char*);
+void subst(char*, char*, char*);
+void symdel(char*, int);
+void syminit(void);
+Symtab *symlook(char*, int, void*);
+void symstat(void);
+void symtraverse(int, void(*)(Symtab*));
+void timeinit(char*);
+long timeof(char*, int);
+void touch(char*);
+void update(int, Node*);
+void usage(void);
+Word *varsub(char**);
+int waitfor(char*);
+int waitup(int, int*);
+Word *wdup(Word*);
+int work(Node*, Node*, Arc*);
+char *wtos(Word*, int);
diff --git a/src/cmd/mk/graph.c b/src/cmd/mk/graph.c
new file mode 100644
index 00000000..58529259
--- /dev/null
+++ b/src/cmd/mk/graph.c
@@ -0,0 +1,279 @@
+#include "mk.h"
+
+static Node *applyrules(char *, char *);
+static void togo(Node *);
+static int vacuous(Node *);
+static Node *newnode(char *);
+static void trace(char *, Arc *);
+static void cyclechk(Node *);
+static void ambiguous(Node *);
+static void attribute(Node *);
+
+Node *
+graph(char *target)
+{
+ Node *node;
+ char *cnt;
+
+ cnt = rulecnt();
+ node = applyrules(target, cnt);
+ free(cnt);
+ cyclechk(node);
+ node->flags |= PROBABLE; /* make sure it doesn't get deleted */
+ vacuous(node);
+ ambiguous(node);
+ attribute(node);
+ return(node);
+}
+
+static Node *
+applyrules(char *target, char *cnt)
+{
+ Symtab *sym;
+ Node *node;
+ Rule *r;
+ Arc head, *a = &head;
+ Word *w;
+ char stem[NAMEBLOCK], buf[NAMEBLOCK];
+ Resub rmatch[NREGEXP];
+
+/* print("applyrules(%lux='%s')\n", target, target);*//**/
+ sym = symlook(target, S_NODE, 0);
+ if(sym)
+ return (Node *)(sym->value);
+ target = strdup(target);
+ node = newnode(target);
+ head.n = 0;
+ head.next = 0;
+ sym = symlook(target, S_TARGET, 0);
+ memset((char*)rmatch, 0, sizeof(rmatch));
+ for(r = sym? (Rule *)(sym->value):0; r; r = r->chain){
+ if(r->attr&META) continue;
+ if(strcmp(target, r->target)) continue;
+ if((!r->recipe || !*r->recipe) && (!r->tail || !r->tail->s || !*r->tail->s)) continue; /* no effect; ignore */
+ if(cnt[r->rule] >= nreps) continue;
+ cnt[r->rule]++;
+ node->flags |= PROBABLE;
+
+/* if(r->attr&VIR)
+ * node->flags |= VIRTUAL;
+ * if(r->attr&NOREC)
+ * node->flags |= NORECIPE;
+ * if(r->attr&DEL)
+ * node->flags |= DELETE;
+ */
+ if(!r->tail || !r->tail->s || !*r->tail->s) {
+ a->next = newarc((Node *)0, r, "", rmatch);
+ a = a->next;
+ } else
+ for(w = r->tail; w; w = w->next){
+ a->next = newarc(applyrules(w->s, cnt), r, "", rmatch);
+ a = a->next;
+ }
+ cnt[r->rule]--;
+ head.n = node;
+ }
+ for(r = metarules; r; r = r->next){
+ if((!r->recipe || !*r->recipe) && (!r->tail || !r->tail->s || !*r->tail->s)) continue; /* no effect; ignore */
+ if ((r->attr&NOVIRT) && a != &head && (a->r->attr&VIR))
+ continue;
+ if(r->attr&REGEXP){
+ stem[0] = 0;
+ patrule = r;
+ memset((char*)rmatch, 0, sizeof(rmatch));
+ if(regexec(r->pat, node->name, rmatch, NREGEXP) == 0)
+ continue;
+ } else {
+ if(!match(node->name, r->target, stem)) continue;
+ }
+ if(cnt[r->rule] >= nreps) continue;
+ cnt[r->rule]++;
+
+/* if(r->attr&VIR)
+ * node->flags |= VIRTUAL;
+ * if(r->attr&NOREC)
+ * node->flags |= NORECIPE;
+ * if(r->attr&DEL)
+ * node->flags |= DELETE;
+ */
+
+ if(!r->tail || !r->tail->s || !*r->tail->s) {
+ a->next = newarc((Node *)0, r, stem, rmatch);
+ a = a->next;
+ } else
+ for(w = r->tail; w; w = w->next){
+ if(r->attr&REGEXP)
+ regsub(w->s, buf, sizeof buf, rmatch, NREGEXP);
+ else
+ subst(stem, w->s, buf);
+ a->next = newarc(applyrules(buf, cnt), r, stem, rmatch);
+ a = a->next;
+ }
+ cnt[r->rule]--;
+ }
+ a->next = node->prereqs;
+ node->prereqs = head.next;
+ return(node);
+}
+
+static void
+togo(Node *node)
+{
+ Arc *la, *a;
+
+ /* delete them now */
+ la = 0;
+ for(a = node->prereqs; a; la = a, a = a->next)
+ if(a->flag&TOGO){
+ if(a == node->prereqs)
+ node->prereqs = a->next;
+ else
+ la->next = a->next, a = la;
+ }
+}
+
+static int
+vacuous(Node *node)
+{
+ Arc *la, *a;
+ int vac = !(node->flags&PROBABLE);
+
+ if(node->flags&READY)
+ return(node->flags&VACUOUS);
+ node->flags |= READY;
+ for(a = node->prereqs; a; a = a->next)
+ if(a->n && vacuous(a->n) && (a->r->attr&META))
+ a->flag |= TOGO;
+ else
+ vac = 0;
+ /* if a rule generated arcs that DON'T go; no others from that rule go */
+ for(a = node->prereqs; a; a = a->next)
+ if((a->flag&TOGO) == 0)
+ for(la = node->prereqs; la; la = la->next)
+ if((la->flag&TOGO) && (la->r == a->r)){
+ la->flag &= ~TOGO;
+ }
+ togo(node);
+ if(vac)
+ node->flags |= VACUOUS;
+ return(vac);
+}
+
+static Node *
+newnode(char *name)
+{
+ register Node *node;
+
+ node = (Node *)Malloc(sizeof(Node));
+ symlook(name, S_NODE, (void *)node);
+ node->name = name;
+ node->time = timeof(name, 0);
+ node->prereqs = 0;
+ node->flags = node->time? PROBABLE : 0;
+ node->next = 0;
+ return(node);
+}
+
+void
+dumpn(char *s, Node *n)
+{
+ char buf[1024];
+ Arc *a;
+
+ sprint(buf, "%s ", (*s == ' ')? s:"");
+ Bprint(&bout, "%s%s@%ld: time=%ld flags=0x%x next=%ld\n",
+ s, n->name, n, n->time, n->flags, n->next);
+ for(a = n->prereqs; a; a = a->next)
+ dumpa(buf, a);
+}
+
+static void
+trace(char *s, Arc *a)
+{
+ fprint(2, "\t%s", s);
+ while(a){
+ fprint(2, " <-(%s:%d)- %s", a->r->file, a->r->line,
+ a->n? a->n->name:"");
+ if(a->n){
+ for(a = a->n->prereqs; a; a = a->next)
+ if(*a->r->recipe) break;
+ } else
+ a = 0;
+ }
+ fprint(2, "\n");
+}
+
+static void
+cyclechk(Node *n)
+{
+ Arc *a;
+
+ if((n->flags&CYCLE) && n->prereqs){
+ fprint(2, "mk: cycle in graph detected at target %s\n", n->name);
+ Exit();
+ }
+ n->flags |= CYCLE;
+ for(a = n->prereqs; a; a = a->next)
+ if(a->n)
+ cyclechk(a->n);
+ n->flags &= ~CYCLE;
+}
+
+static void
+ambiguous(Node *n)
+{
+ Arc *a;
+ Rule *r = 0;
+ Arc *la;
+ int bad = 0;
+
+ la = 0;
+ for(a = n->prereqs; a; a = a->next){
+ if(a->n)
+ ambiguous(a->n);
+ if(*a->r->recipe == 0) continue;
+ if(r == 0)
+ r = a->r, la = a;
+ else{
+ if(r->recipe != a->r->recipe){
+ if((r->attr&META) && !(a->r->attr&META)){
+ la->flag |= TOGO;
+ r = a->r, la = a;
+ } else if(!(r->attr&META) && (a->r->attr&META)){
+ a->flag |= TOGO;
+ continue;
+ }
+ }
+ if(r->recipe != a->r->recipe){
+ if(bad == 0){
+ fprint(2, "mk: ambiguous recipes for %s:\n", n->name);
+ bad = 1;
+ trace(n->name, la);
+ }
+ trace(n->name, a);
+ }
+ }
+ }
+ if(bad)
+ Exit();
+ togo(n);
+}
+
+static void
+attribute(Node *n)
+{
+ register Arc *a;
+
+ for(a = n->prereqs; a; a = a->next){
+ if(a->r->attr&VIR)
+ n->flags |= VIRTUAL;
+ if(a->r->attr&NOREC)
+ n->flags |= NORECIPE;
+ if(a->r->attr&DEL)
+ n->flags |= DELETE;
+ if(a->n)
+ attribute(a->n);
+ }
+ if(n->flags&VIRTUAL)
+ n->time = 0;
+}
diff --git a/src/cmd/mk/lex.c b/src/cmd/mk/lex.c
new file mode 100644
index 00000000..3ee244f1
--- /dev/null
+++ b/src/cmd/mk/lex.c
@@ -0,0 +1,147 @@
+#include "mk.h"
+
+static int bquote(Biobuf*, Bufblock*);
+
+/*
+ * Assemble a line skipping blank lines, comments, and eliding
+ * escaped newlines
+ */
+int
+assline(Biobuf *bp, Bufblock *buf)
+{
+ int c;
+ int lastc;
+
+ buf->current=buf->start;
+ while ((c = nextrune(bp, 1)) >= 0){
+ switch(c)
+ {
+ case '\r': /* consumes CRs for Win95 */
+ continue;
+ case '\n':
+ if (buf->current != buf->start) {
+ insert(buf, 0);
+ return 1;
+ }
+ break; /* skip empty lines */
+ case '\\':
+ case '\'':
+ case '"':
+ rinsert(buf, c);
+ if (escapetoken(bp, buf, 1, c) == 0)
+ Exit();
+ break;
+ case '`':
+ if (bquote(bp, buf) == 0)
+ Exit();
+ break;
+ case '#':
+ lastc = '#';
+ while ((c = Bgetc(bp)) != '\n') {
+ if (c < 0)
+ goto eof;
+ if(c != '\r')
+ lastc = c;
+ }
+ mkinline++;
+ if (lastc == '\\')
+ break; /* propagate escaped newlines??*/
+ if (buf->current != buf->start) {
+ insert(buf, 0);
+ return 1;
+ }
+ break;
+ default:
+ rinsert(buf, c);
+ break;
+ }
+ }
+eof:
+ insert(buf, 0);
+ return *buf->start != 0;
+}
+
+/*
+ * assemble a back-quoted shell command into a buffer
+ */
+static int
+bquote(Biobuf *bp, Bufblock *buf)
+{
+ int c, line, term;
+ int start;
+
+ line = mkinline;
+ while((c = Bgetrune(bp)) == ' ' || c == '\t')
+ ;
+ if(c == '{'){
+ term = '}'; /* rc style */
+ while((c = Bgetrune(bp)) == ' ' || c == '\t')
+ ;
+ } else
+ term = '`'; /* sh style */
+
+ start = buf->current-buf->start;
+ for(;c > 0; c = nextrune(bp, 0)){
+ if(c == term){
+ insert(buf, '\n');
+ insert(buf,0);
+ buf->current = buf->start+start;
+ execinit();
+ execsh(0, buf->current, buf, envy);
+ return 1;
+ }
+ if(c == '\n')
+ break;
+ if(c == '\'' || c == '"' || c == '\\'){
+ insert(buf, c);
+ if(!escapetoken(bp, buf, 1, c))
+ return 0;
+ continue;
+ }
+ rinsert(buf, c);
+ }
+ SYNERR(line);
+ fprint(2, "missing closing %c after `\n", term);
+ return 0;
+}
+
+/*
+ * get next character stripping escaped newlines
+ * the flag specifies whether escaped newlines are to be elided or
+ * replaced with a blank.
+ */
+int
+nextrune(Biobuf *bp, int elide)
+{
+ int c, c2;
+ static int savec;
+
+ if(savec){
+ c = savec;
+ savec = 0;
+ return c;
+ }
+
+ for (;;) {
+ c = Bgetrune(bp);
+ if (c == '\\') {
+ c2 = Bgetrune(bp);
+ if(c2 == '\r'){
+ savec = c2;
+ c2 = Bgetrune(bp);
+ }
+ if (c2 == '\n') {
+ savec = 0;
+ mkinline++;
+ if (elide)
+ continue;
+ return ' ';
+ }
+ Bungetrune(bp);
+ }
+ if (c == '\n')
+ mkinline++;
+ return c;
+ }
+ return 0;
+}
diff --git a/src/cmd/mk/main.c b/src/cmd/mk/main.c
new file mode 100644
index 00000000..267ed73f
--- /dev/null
+++ b/src/cmd/mk/main.c
@@ -0,0 +1,285 @@
+#include "mk.h"
+
+#define MKFILE "mkfile"
+
+int debug;
+Rule *rules, *metarules;
+int nflag = 0;
+int tflag = 0;
+int iflag = 0;
+int kflag = 0;
+int aflag = 0;
+int uflag = 0;
+char *explain = 0;
+Word *target1;
+int nreps = 1;
+Job *jobs;
+Biobuf bout;
+Rule *patrule;
+void badusage(void);
+#ifdef PROF
+short buf[10000];
+#endif
+
+int
+main(int argc, char **argv)
+{
+ Word *w;
+ char *s, *temp;
+ char *files[256], **f = files, **ff;
+ int sflag = 0;
+ int i;
+ int tfd = -1;
+ Biobuf tb;
+ Bufblock *buf;
+ Bufblock *whatif;
+
+ /*
+ * start with a copy of the current environment variables
+ * instead of sharing them
+ */
+
+ Binit(&bout, 1, OWRITE);
+ buf = newbuf();
+ whatif = 0;
+ USED(argc);
+ for(argv++; *argv && (**argv == '-'); argv++)
+ {
+ bufcpy(buf, argv[0], strlen(argv[0]));
+ insert(buf, ' ');
+ switch(argv[0][1])
+ {
+ case 'a':
+ aflag = 1;
+ break;
+ case 'd':
+ if(*(s = &argv[0][2]))
+ while(*s) switch(*s++)
+ {
+ case 'p': debug |= D_PARSE; break;
+ case 'g': debug |= D_GRAPH; break;
+ case 'e': debug |= D_EXEC; break;
+ }
+ else
+ debug = 0xFFFF;
+ break;
+ case 'e':
+ explain = &argv[0][2];
+ break;
+ case 'f':
+ if(*++argv == 0)
+ badusage();
+ *f++ = *argv;
+ bufcpy(buf, argv[0], strlen(argv[0]));
+ insert(buf, ' ');
+ break;
+ case 'i':
+ iflag = 1;
+ break;
+ case 'k':
+ kflag = 1;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 's':
+ sflag = 1;
+ break;
+ case 't':
+ tflag = 1;
+ break;
+ case 'u':
+ uflag = 1;
+ break;
+ case 'w':
+ if(whatif == 0)
+ whatif = newbuf();
+ else
+ insert(whatif, ' ');
+ if(argv[0][2])
+ bufcpy(whatif, &argv[0][2], strlen(&argv[0][2]));
+ else {
+ if(*++argv == 0)
+ badusage();
+ bufcpy(whatif, &argv[0][0], strlen(&argv[0][0]));
+ }
+ break;
+ default:
+ badusage();
+ }
+ }
+#ifdef PROF
+ {
+ extern etext();
+ monitor(main, etext, buf, sizeof buf, 300);
+ }
+#endif
+
+ if(aflag)
+ iflag = 1;
+ usage();
+ syminit();
+ initenv();
+ usage();
+
+ /*
+ assignment args become null strings
+ */
+ temp = 0;
+ for(i = 0; argv[i]; i++) if(utfrune(argv[i], '=')){
+ bufcpy(buf, argv[i], strlen(argv[i]));
+ insert(buf, ' ');
+ if(tfd < 0){
+ temp = maketmp(&tfd);
+ if(temp == 0) {
+ fprint(2, "temp file: %r\n");
+ Exit();
+ }
+ Binit(&tb, tfd, OWRITE);
+ }
+ Bprint(&tb, "%s\n", argv[i]);
+ *argv[i] = 0;
+ }
+ if(tfd >= 0){
+ Bflush(&tb);
+ LSEEK(tfd, 0L, 0);
+ parse("command line args", tfd, 1);
+ remove(temp);
+ }
+
+ if (buf->current != buf->start) {
+ buf->current--;
+ insert(buf, 0);
+ }
+ symlook("MKFLAGS", S_VAR, (void *) stow(buf->start));
+ buf->current = buf->start;
+ for(i = 0; argv[i]; i++){
+ if(*argv[i] == 0) continue;
+ if(i)
+ insert(buf, ' ');
+ bufcpy(buf, argv[i], strlen(argv[i]));
+ }
+ insert(buf, 0);
+ symlook("MKARGS", S_VAR, (void *) stow(buf->start));
+ freebuf(buf);
+
+ if(f == files){
+ if(access(MKFILE, 4) == 0)
+ parse(MKFILE, open(MKFILE, 0), 0);
+ } else
+ for(ff = files; ff < f; ff++)
+ parse(*ff, open(*ff, 0), 0);
+ if(DEBUG(D_PARSE)){
+ dumpw("default targets", target1);
+ dumpr("rules", rules);
+ dumpr("metarules", metarules);
+ dumpv("variables");
+ }
+ if(whatif){
+ insert(whatif, 0);
+ timeinit(whatif->start);
+ freebuf(whatif);
+ }
+ execinit();
+ /* skip assignment args */
+ while(*argv && (**argv == 0))
+ argv++;
+
+ catchnotes();
+ if(*argv == 0){
+ if(target1)
+ for(w = target1; w; w = w->next)
+ mk(w->s);
+ else {
+ fprint(2, "mk: nothing to mk\n");
+ Exit();
+ }
+ } else {
+ if(sflag){
+ for(; *argv; argv++)
+ if(**argv)
+ mk(*argv);
+ } else {
+ Word *head, *tail, *t;
+
+ /* fake a new rule with all the args as prereqs */
+ tail = 0;
+ t = 0;
+ for(; *argv; argv++)
+ if(**argv){
+ if(tail == 0)
+ tail = t = newword(*argv);
+ else {
+ t->next = newword(*argv);
+ t = t->next;
+ }
+ }
+ if(tail->next == 0)
+ mk(tail->s);
+ else {
+ head = newword("command line arguments");
+ addrules(head, tail, strdup(""), VIR, mkinline, 0);
+ mk(head->s);
+ }
+ }
+ }
+ if(uflag)
+ prusage();
+ exits(0);
+}
+
+void
+badusage(void)
+{
+
+ fprint(2, "Usage: mk [-f file] [-n] [-a] [-e] [-t] [-k] [-i] [-d[egp]] [targets ...]\n");
+ Exit();
+}
+
+void *
+Malloc(int n)
+{
+ register void *s;
+
+ s = malloc(n);
+ if(!s) {
+ fprint(2, "mk: cannot alloc %d bytes\n", n);
+ Exit();
+ }
+ return(s);
+}
+
+void *
+Realloc(void *s, int n)
+{
+ if(s)
+ s = realloc(s, n);
+ else
+ s = malloc(n);
+ if(!s) {
+ fprint(2, "mk: cannot alloc %d bytes\n", n);
+ Exit();
+ }
+ return(s);
+}
+
+void
+assert(char *s, int n)
+{
+ if(!n){
+ fprint(2, "mk: Assertion ``%s'' failed.\n", s);
+ Exit();
+ }
+}
+
+void
+regerror(char *s)
+{
+ if(patrule)
+ fprint(2, "mk: %s:%d: regular expression error; %s\n",
+ patrule->file, patrule->line, s);
+ else
+ fprint(2, "mk: %s:%d: regular expression error; %s\n",
+ infile, mkinline, s);
+ Exit();
+}
diff --git a/src/cmd/mk/match.c b/src/cmd/mk/match.c
new file mode 100644
index 00000000..2a96394a
--- /dev/null
+++ b/src/cmd/mk/match.c
@@ -0,0 +1,49 @@
+#include "mk.h"
+
+int
+match(char *name, char *template, char *stem)
+{
+ Rune r;
+ int n;
+
+ while(*name && *template){
+ n = chartorune(&r, template);
+ if (PERCENT(r))
+ break;
+ while (n--)
+ if(*name++ != *template++)
+ return 0;
+ }
+ if(!PERCENT(*template))
+ return 0;
+ n = strlen(name)-strlen(template+1);
+ if (n < 0)
+ return 0;
+ if (strcmp(template+1, name+n))
+ return 0;
+ strncpy(stem, name, n);
+ stem[n] = 0;
+ if(*template == '&')
+ return !charin(stem, "./");
+ return 1;
+}
+
+void
+subst(char *stem, char *template, char *dest)
+{
+ Rune r;
+ char *s;
+ int n;
+
+ while(*template){
+ n = chartorune(&r, template);
+ if (PERCENT(r)) {
+ template += n;
+ for (s = stem; *s; s++)
+ *dest++ = *s;
+ } else
+ while (n--)
+ *dest++ = *template++;
+ }
+ *dest = 0;
+}
diff --git a/src/cmd/mk/mk.1 b/src/cmd/mk/mk.1
new file mode 100644
index 00000000..c58a6dfe
--- /dev/null
+++ b/src/cmd/mk/mk.1
@@ -0,0 +1,665 @@
+.TH MK 1
+.de EX
+.nf
+.ft B
+..
+.de EE
+.fi
+.ft R
+..
+.de LR
+.if t .BR \\$1 \\$2
+.if n .RB ` \\$1 '\\$2
+..
+.de L
+.nh
+.if t .B \\$1
+.if n .RB ` \\$1 '
+..
+.SH NAME
+mk \- maintain (make) related files
+.SH SYNOPSIS
+.B mk
+[
+.B -f
+.I mkfile
+] ...
+[
+.I option ...
+]
+[
+.I target ...
+]
+.SH DESCRIPTION
+.I Mk
+uses the dependency rules specified in
+.I mkfile
+to control the update (usually by compilation) of
+.I targets
+(usually files)
+from the source files upon which they depend.
+The
+.I mkfile
+(default
+.LR mkfile )
+contains a
+.I rule
+for each target that identifies the files and other
+targets upon which it depends and an
+.IR sh (1)
+script, a
+.IR recipe ,
+to update the target.
+The script is run if the target does not exist
+or if it is older than any of the files it depends on.
+.I Mkfile
+may also contain
+.I meta-rules
+that define actions for updating implicit targets.
+If no
+.I target
+is specified, the target of the first rule (not meta-rule) in
+.I mkfile
+is updated.
+.PP
+The environment variable
+.B $NPROC
+determines how many targets may be updated simultaneously;
+Some operating systems, e.g., Plan 9, set
+.B $NPROC
+automatically to the number of CPUs on the current machine.
+.PP
+Options are:
+.TP \w'\fL-d[egp]\ 'u
+.B -a
+Assume all targets to be out of date.
+Thus, everything is updated.
+.PD 0
+.TP
+.BR -d [ egp ]
+Produce debugging output
+.RB ( p
+is for parsing,
+.B g
+for graph building,
+.B e
+for execution).
+.TP
+.B -e
+Explain why each target is made.
+.TP
+.B -i
+Force any missing intermediate targets to be made.
+.TP
+.B -k
+Do as much work as possible in the face of errors.
+.TP
+.B -n
+Print, but do not execute, the commands
+needed to update the targets.
+.TP
+.B -s
+Make the command line arguments sequentially rather than in parallel.
+.TP
+.B -t
+Touch (update the modified date of) file targets, without
+executing any recipes.
+.TP
+.BI -w target1 , target2,...
+Pretend the modify time for each
+.I target
+is the current time; useful in conjunction with
+.B -n
+to learn what updates would be triggered by
+modifying the
+.IR targets .
+.PD
+.SS The \fLmkfile\fP
+A
+.I mkfile
+consists of
+.I assignments
+(described under `Environment') and
+.IR rules .
+A rule contains
+.I targets
+and a
+.IR tail .
+A target is a literal string
+and is normally a file name.
+The tail contains zero or more
+.I prerequisites
+and an optional
+.IR recipe ,
+which is an
+.B shell
+script.
+Each line of the recipe must begin with white space.
+A rule takes the form
+.IP
+.EX
+target: prereq1 prereq2
+ \f2recipe using\fP prereq1, prereq2 \f2to build\fP target
+.EE
+.PP
+When the recipe is executed,
+the first character on every line is elided.
+.PP
+After the colon on the target line, a rule may specify
+.IR attributes ,
+described below.
+.PP
+A
+.I meta-rule
+has a target of the form
+.IB A % B
+where
+.I A
+and
+.I B
+are (possibly empty) strings.
+A meta-rule acts as a rule for any potential target whose
+name matches
+.IB A % B
+with
+.B %
+replaced by an arbitrary string, called the
+.IR stem .
+In interpreting a meta-rule,
+the stem is substituted for all occurrences of
+.B %
+in the prerequisite names.
+In the recipe of a meta-rule, the environment variable
+.B $stem
+contains the string matched by the
+.BR % .
+For example, a meta-rule to compile a C program using
+.IR cc (1)
+might be:
+.IP
+.EX
+%: %.c
+ cc -c $stem.c
+ cc -o $stem $stem.o
+.EE
+.PP
+Meta-rules may contain an ampersand
+.B &
+rather than a percent sign
+.BR % .
+A
+.B %
+matches a maximal length string of any characters;
+an
+.B &
+matches a maximal length string of any characters except period
+or slash.
+.PP
+The text of the
+.I mkfile
+is processed as follows.
+Lines beginning with
+.B <
+followed by a file name are replaced by the contents of the named
+file.
+Lines beginning with
+.B "<|"
+followed by a file name are replaced by the output
+of the execution of the named
+file.
+Blank lines and comments, which run from unquoted
+.B #
+characters to the following newline, are deleted.
+The character sequence backslash-newline is deleted,
+so long lines in
+.I mkfile
+may be folded.
+Non-recipe lines are processed by substituting for
+.BI `{ command }
+the output of the
+.I command
+when run by
+.IR sh .
+References to variables are replaced by the variables' values.
+Special characters may be quoted using single quotes
+.BR \&''
+as in
+.IR sh (1).
+.PP
+Assignments and rules are distinguished by
+the first unquoted occurrence of
+.B :
+(rule)
+or
+.B =
+(assignment).
+.PP
+A later rule may modify or override an existing rule under the
+following conditions:
+.TP
+\-
+If the targets of the rules exactly match and one rule
+contains only a prerequisite clause and no recipe, the
+clause is added to the prerequisites of the other rule.
+If either or both targets are virtual, the recipe is
+always executed.
+.TP
+\-
+If the targets of the rules match exactly and the
+prerequisites do not match and both rules
+contain recipes,
+.I mk
+reports an ``ambiguous recipe'' error.
+.TP
+\-
+If the target and prerequisites of both rules match exactly,
+the second rule overrides the first.
+.SS Environment
+Rules may make use of
+shell
+environment variables.
+A legal reference of the form
+.B $OBJ
+or
+.B ${name}
+is expanded as in
+.IR sh (1).
+A reference of the form
+.BI ${name: A % B = C\fL%\fID\fL}\fR,
+where
+.I A, B, C, D
+are (possibly empty) strings,
+has the value formed by expanding
+.B $name
+and substituting
+.I C
+for
+.I A
+and
+.I D
+for
+.I B
+in each word in
+.B $name
+that matches pattern
+.IB A % B\f1.
+.PP
+Variables can be set by
+assignments of the form
+.I
+ var\fL=\fR[\fIattr\fL=\fR]\fIvalue\fR
+.br
+Blanks in the
+.I value
+break it into words.
+Such variables are exported
+to the environment of
+recipes as they are executed, unless
+.BR U ,
+the only legal attribute
+.IR attr ,
+is present.
+The initial value of a variable is
+taken from (in increasing order of precedence)
+the default values below,
+.I mk's
+environment, the
+.IR mkfiles ,
+and any command line assignment as an argument to
+.IR mk .
+A variable assignment argument overrides the first (but not any subsequent)
+assignment to that variable.
+The variable
+.B MKFLAGS
+contains all the option arguments (arguments starting with
+.L -
+or containing
+.LR = )
+and
+.B MKARGS
+contains all the targets in the call to
+.IR mk .
+.PP
+Dynamic information may be included in the mkfile by using a line of the form
+.IP
+\fR<|\fIcommand\fR \fIargs\fR
+.LP
+This runs the command
+.I command
+with the given arguments
+.I args
+and pipes its standard output to
+.I mk
+to be included as part of the mkfile. For instance, the Inferno kernels
+use this technique
+to run a shell command with an awk script and a configuration
+file as arguments in order for
+the
+.I awk
+script to process the file and output a set of variables and their values.
+.SS Execution
+.PP
+During execution,
+.I mk
+determines which targets must be updated, and in what order,
+to build the
+.I names
+specified on the command line.
+It then runs the associated recipes.
+.PP
+A target is considered up to date if it has no prerequisites or
+if all its prerequisites are up to date and it is newer
+than all its prerequisites.
+Once the recipe for a target has executed, the target is
+considered up to date.
+.PP
+The date stamp
+used to determine if a target is up to date is computed
+differently for different types of targets.
+If a target is
+.I virtual
+(the target of a rule with the
+.B V
+attribute),
+its date stamp is initially zero; when the target is
+updated the date stamp is set to
+the most recent date stamp of its prerequisites.
+Otherwise, if a target does not exist as a file,
+its date stamp is set to the most recent date stamp of its prerequisites,
+or zero if it has no prerequisites.
+Otherwise, the target is the name of a file and
+the target's date stamp is always that file's modification date.
+The date stamp is computed when the target is needed in
+the execution of a rule; it is not a static value.
+.PP
+Nonexistent targets that have prerequisites
+and are themselves prerequisites are treated specially.
+Such a target
+.I t
+is given the date stamp of its most recent prerequisite
+and if this causes all the targets which have
+.I t
+as a prerequisite to be up to date,
+.I t
+is considered up to date.
+Otherwise,
+.I t
+is made in the normal fashion.
+The
+.B -i
+flag overrides this special treatment.
+.PP
+Files may be made in any order that respects
+the preceding restrictions.
+.PP
+A recipe is executed by supplying the recipe as standard input to
+the command
+.BR /bin/sh .
+(Note that unlike
+.IR make ,
+.I mk
+feeds the entire recipe to the shell rather than running each line
+of the recipe separately.)
+The environment is augmented by the following variables:
+.TP 14
+.B $alltarget
+all the targets of this rule.
+.TP
+.B $newprereq
+the prerequisites that caused this rule to execute.
+.TP
+.B $newmember
+the prerequisites that are members of an aggregate
+that caused this rule to execute.
+When the prerequisites of a rule are members of an
+aggregate,
+.B $newprereq
+contains the name of the aggregate and out of date
+members, while
+.B $newmember
+contains only the name of the members.
+.TP
+.B $nproc
+the process slot for this recipe.
+It satisfies
+.RB 0≤ $nproc < $NPROC .
+.TP
+.B $pid
+the process id for the
+.I mk
+executing the recipe.
+.TP
+.B $prereq
+all the prerequisites for this rule.
+.TP
+.B $stem
+if this is a meta-rule,
+.B $stem
+is the string that matched
+.B %
+or
+.BR & .
+Otherwise, it is empty.
+For regular expression meta-rules (see below), the variables
+.LR stem0 ", ...,"
+.L stem9
+are set to the corresponding subexpressions.
+.TP
+.B $target
+the targets for this rule that need to be remade.
+.PP
+These variables are available only during the execution of a recipe,
+not while evaluating the
+.IR mkfile .
+.PP
+Unless the rule has the
+.B Q
+attribute,
+the recipe is printed prior to execution
+with recognizable environment variables expanded.
+Commands returning error status
+cause
+.I mk
+to terminate.
+.PP
+Recipes and backquoted
+.B rc
+commands in places such as assignments
+execute in a copy of
+.I mk's
+environment; changes they make to
+environment variables are not visible from
+.IR mk .
+.PP
+Variable substitution in a rule is done when
+the rule is read; variable substitution in the recipe is done
+when the recipe is executed. For example:
+.IP
+.EX
+bar=a.c
+foo: $bar
+ $CC -o foo $bar
+bar=b.c
+.EE
+.PP
+will compile
+.B b.c
+into
+.BR foo ,
+if
+.B a.c
+is newer than
+.BR foo .
+.SS Aggregates
+Names of the form
+.IR a ( b )
+refer to member
+.I b
+of the aggregate
+.IR a .
+Currently, the only aggregates supported are
+.IR ar (1)
+archives.
+.SS Attributes
+The colon separating the target from the prerequisites
+may be
+immediately followed by
+.I attributes
+and another colon.
+The attributes are:
+.TP
+.B D
+If the recipe exits with a non-null status, the target is deleted.
+.TP
+.B E
+Continue execution if the recipe draws errors.
+.TP
+.B N
+If there is no recipe, the target has its time updated.
+.TP
+.B n
+The rule is a meta-rule that cannot be a target of a virtual rule.
+Only files match the pattern in the target.
+.TP
+.B P
+The characters after the
+.B P
+until the terminating
+.B :
+are taken as a program name.
+It will be invoked as
+.B "sh -c prog 'arg1' 'arg2'"
+and should return a zero exit status
+if and only if arg1 is up to date with respect to arg2.
+Date stamps are still propagated in the normal way.
+.TP
+.B Q
+The recipe is not printed prior to execution.
+.TP
+.B R
+The rule is a meta-rule using regular expressions.
+In the rule,
+.B %
+has no special meaning.
+The target is interpreted as a regular expression as defined in
+.IR regexp (6).
+The prerequisites may contain references
+to subexpressions in form
+.BI \e n\f1,
+as in the substitute command of
+.IR sed (1).
+.TP
+.B U
+The targets are considered to have been updated
+even if the recipe did not do so.
+.TP
+.B V
+The targets of this rule are marked as virtual.
+They are distinct from files of the same name.
+.PD
+.SH EXAMPLES
+A simple mkfile to compile a program:
+.IP
+.EX
+.ta 8n +8n +8n +8n +8n +8n +8n
+</$objtype/mkfile
+
+prog: a.$O b.$O c.$O
+ $LD $LDFLAGS -o $target $prereq
+
+%.$O: %.c
+ $CC $CFLAGS $stem.c
+.EE
+.PP
+Override flag settings in the mkfile:
+.IP
+.EX
+% mk target 'CFLAGS=-S -w'
+.EE
+.PP
+Maintain a library:
+.IP
+.EX
+libc.a(%.$O):N: %.$O
+libc.a: libc.a(abs.$O) libc.a(access.$O) libc.a(alarm.$O) ...
+ ar r libc.a $newmember
+.EE
+.PP
+String expression variables to derive names from a master list:
+.IP
+.EX
+NAMES=alloc arc bquote builtins expand main match mk var word
+OBJ=${NAMES:%=%.$O}
+.EE
+.PP
+Regular expression meta-rules:
+.IP
+.EX
+([^/]*)/(.*)\e.$O:R: \e1/\e2.c
+ cd $stem1; $CC $CFLAGS $stem2.c
+.EE
+.PP
+A correct way to deal with
+.IR yacc (1)
+grammars.
+The file
+.B lex.c
+includes the file
+.B x.tab.h
+rather than
+.B y.tab.h
+in order to reflect changes in content, not just modification time.
+.IP
+.EX
+lex.$O: x.tab.h
+x.tab.h: y.tab.h
+ cmp -s x.tab.h y.tab.h || cp y.tab.h x.tab.h
+y.tab.c y.tab.h: gram.y
+ $YACC -d gram.y
+.EE
+.PP
+The above example could also use the
+.B P
+attribute for the
+.B x.tab.h
+rule:
+.IP
+.EX
+x.tab.h:Pcmp -s: y.tab.h
+ cp y.tab.h x.tab.h
+.EE
+.SH SEE ALSO
+.IR sh (1),
+.IR regexp9 (7)
+.PP
+A. Hume,
+``Mk: a Successor to Make''
+(Tenth Edition Research Unix Manuals).
+.PP
+Andrew G. Hume and Bob Flandrena,
+``Maintaining Files on Plan 9 with Mk''.
+DOCPREFIX/doc/mk.pdf
+.SH HISTORY
+Andrew Hume wrote
+.I mk
+for Tenth Edition Research Unix.
+It was later ported to Plan 9.
+This software is a port of the Plan 9 version back to Unix.
+.SH BUGS
+Identical recipes for regular expression meta-rules only have one target.
+.br
+Seemingly appropriate input like
+.B CFLAGS=-DHZ=60
+is parsed as an erroneous attribute; correct it by inserting
+a space after the first
+.LR = .
+.br
+The recipes printed by
+.I mk
+before being passed to
+.I sh
+for execution are sometimes erroneously expanded
+for printing. Don't trust what's printed; rely
+on what
+.I sh
+does.
diff --git a/src/cmd/mk/mk.c b/src/cmd/mk/mk.c
new file mode 100644
index 00000000..26b8a976
--- /dev/null
+++ b/src/cmd/mk/mk.c
@@ -0,0 +1,226 @@
+#include "mk.h"
+
+int runerrs;
+
+void
+mk(char *target)
+{
+ Node *node;
+ int did = 0;
+
+ nproc(); /* it can be updated dynamically */
+ nrep(); /* it can be updated dynamically */
+ runerrs = 0;
+ node = graph(target);
+ if(DEBUG(D_GRAPH)){
+ dumpn("new target\n", node);
+ Bflush(&bout);
+ }
+ clrmade(node);
+ while(node->flags&NOTMADE){
+ if(work(node, (Node *)0, (Arc *)0))
+ did = 1; /* found something to do */
+ else {
+ if(waitup(1, (int *)0) > 0){
+ if(node->flags&(NOTMADE|BEINGMADE)){
+ assert("must be run errors", runerrs);
+ break; /* nothing more waiting */
+ }
+ }
+ }
+ }
+ if(node->flags&BEINGMADE)
+ waitup(-1, (int *)0);
+ while(jobs)
+ waitup(-2, (int *)0);
+ assert("target didn't get done", runerrs || (node->flags&MADE));
+ if(did == 0)
+ Bprint(&bout, "mk: '%s' is up to date\n", node->name);
+}
+
+void
+clrmade(Node *n)
+{
+ Arc *a;
+
+ n->flags &= ~(CANPRETEND|PRETENDING);
+ if(strchr(n->name, '(') ==0 || n->time)
+ n->flags |= CANPRETEND;
+ MADESET(n, NOTMADE);
+ for(a = n->prereqs; a; a = a->next)
+ if(a->n)
+ clrmade(a->n);
+}
+
+static void
+unpretend(Node *n)
+{
+ MADESET(n, NOTMADE);
+ n->flags &= ~(CANPRETEND|PRETENDING);
+ n->time = 0;
+}
+
+int
+work(Node *node, Node *p, Arc *parc)
+{
+ Arc *a, *ra;
+ int weoutofdate;
+ int ready;
+ int did = 0;
+
+ /*print("work(%s) flags=0x%x time=%ld\n", node->name, node->flags, node->time);*//**/
+ if(node->flags&BEINGMADE)
+ return(did);
+ if((node->flags&MADE) && (node->flags&PRETENDING) && p && outofdate(p, parc, 0)){
+ if(explain)
+ fprint(1, "unpretending %s(%ld) because %s is out of date(%ld)\n",
+ node->name, node->time, p->name, p->time);
+ unpretend(node);
+ }
+ /*
+ have a look if we are pretending in case
+ someone has been unpretended out from underneath us
+ */
+ if(node->flags&MADE){
+ if(node->flags&PRETENDING){
+ node->time = 0;
+ }else
+ return(did);
+ }
+ /* consider no prerequsite case */
+ if(node->prereqs == 0){
+ if(node->time == 0){
+ fprint(2, "mk: don't know how to make '%s'\n", node->name);
+ if(kflag){
+ node->flags |= BEINGMADE;
+ runerrs++;
+ } else
+ Exit();
+ } else
+ MADESET(node, MADE);
+ return(did);
+ }
+ /*
+ now see if we are out of date or what
+ */
+ ready = 1;
+ weoutofdate = aflag;
+ ra = 0;
+ for(a = node->prereqs; a; a = a->next)
+ if(a->n){
+ did = work(a->n, node, a) || did;
+ if(a->n->flags&(NOTMADE|BEINGMADE))
+ ready = 0;
+ if(outofdate(node, a, 0)){
+ weoutofdate = 1;
+ if((ra == 0) || (ra->n == 0)
+ || (ra->n->time < a->n->time))
+ ra = a;
+ }
+ } else {
+ if(node->time == 0){
+ if(ra == 0)
+ ra = a;
+ weoutofdate = 1;
+ }
+ }
+ if(ready == 0) /* can't do anything now */
+ return(did);
+ if(weoutofdate == 0){
+ MADESET(node, MADE);
+ return(did);
+ }
+ /*
+ can we pretend to be made?
+ */
+ if((iflag == 0) && (node->time == 0) && (node->flags&(PRETENDING|CANPRETEND))
+ && p && ra->n && !outofdate(p, ra, 0)){
+ node->flags &= ~CANPRETEND;
+ MADESET(node, MADE);
+ if(explain && ((node->flags&PRETENDING) == 0))
+ fprint(1, "pretending %s has time %ld\n", node->name, node->time);
+ node->flags |= PRETENDING;
+ return(did);
+ }
+ /*
+ node is out of date and we REALLY do have to do something.
+ quickly rescan for pretenders
+ */
+ for(a = node->prereqs; a; a = a->next)
+ if(a->n && (a->n->flags&PRETENDING)){
+ if(explain)
+ Bprint(&bout, "unpretending %s because of %s because of %s\n",
+ a->n->name, node->name, ra->n? ra->n->name : "rule with no prerequisites");
+
+ unpretend(a->n);
+ did = work(a->n, node, a) || did;
+ ready = 0;
+ }
+ if(ready == 0) /* try later unless nothing has happened for -k's sake */
+ return(did || work(node, p, parc));
+ did = dorecipe(node) || did;
+ return(did);
+}
+
+void
+update(int fake, Node *node)
+{
+ Arc *a;
+
+ MADESET(node, fake? BEINGMADE : MADE);
+ if(((node->flags&VIRTUAL) == 0) && (access(node->name, 0) == 0)){
+ node->time = timeof(node->name, 1);
+ node->flags &= ~(CANPRETEND|PRETENDING);
+ for(a = node->prereqs; a; a = a->next)
+ if(a->prog)
+ outofdate(node, a, 1);
+ } else {
+ node->time = 1;
+ for(a = node->prereqs; a; a = a->next)
+ if(a->n && outofdate(node, a, 1))
+ node->time = a->n->time;
+ }
+/* print("----node %s time=%ld flags=0x%x\n", node->name, node->time, node->flags);*//**/
+}
+
+static int
+pcmp(char *prog, char *p, char *q)
+{
+ char buf[3*NAMEBLOCK];
+ int pid;
+
+ Bflush(&bout);
+ sprint(buf, "%s '%s' '%s'\n", prog, p, q);
+ pid = pipecmd(buf, 0, 0);
+ while(waitup(-3, &pid) >= 0)
+ ;
+ return(pid? 2:1);
+}
+
+int
+outofdate(Node *node, Arc *arc, int eval)
+{
+ char buf[3*NAMEBLOCK], *str;
+ Symtab *sym;
+ int ret;
+
+ str = 0;
+ if(arc->prog){
+ sprint(buf, "%s%c%s", node->name, 0377, arc->n->name);
+ sym = symlook(buf, S_OUTOFDATE, 0);
+ if(sym == 0 || eval){
+ if(sym == 0)
+ str = strdup(buf);
+ ret = pcmp(arc->prog, node->name, arc->n->name);
+ if(sym)
+ sym->value = (void *)ret;
+ else
+ symlook(str, S_OUTOFDATE, (void *)ret);
+ } else
+ ret = (int)sym->value;
+ return(ret-1);
+ } else if(strchr(arc->n->name, '(') && arc->n->time == 0) /* missing archive member */
+ return 1;
+ else
+ return node->time < arc->n->time;
+}
diff --git a/src/cmd/mk/mk.h b/src/cmd/mk/mk.h
new file mode 100644
index 00000000..9b3f627d
--- /dev/null
+++ b/src/cmd/mk/mk.h
@@ -0,0 +1,203 @@
+#include <utf.h>
+#include <fmt.h>
+#include <setjmp.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <bio.h>
+#include <regexp9.h>
+#include <time.h>
+
+#define uchar _mkuchar
+#define ushort _mkushort
+#define uint _mkuint
+#define ulong _mkulong
+#define vlong _mkvlong
+#define uvlong _mkuvlong
+
+#define nil ((void*)0)
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+
+#define nelem(x) (sizeof(x)/sizeof((x)[0]))
+
+#define OREAD O_RDONLY
+#define OWRITE O_WRONLY
+#define ORDWR O_RDWR
+#define USED(x) if(x);else
+#define remove unlink
+#define seek lseek
+#define exits(s) exit((s) && ((char*)s)[0] ? 1 : 0)
+#define create(name, mode, perm) creat(name, perm)
+#define ERRMAX 256
+
+#undef assert
+#define assert mkassert
+extern Biobuf bout;
+
+typedef struct Bufblock
+{
+ struct Bufblock *next;
+ char *start;
+ char *end;
+ char *current;
+} Bufblock;
+
+typedef struct Word
+{
+ char *s;
+ struct Word *next;
+} Word;
+
+typedef struct Envy
+{
+ char *name;
+ Word *values;
+} Envy;
+
+extern Envy *envy;
+
+typedef struct Rule
+{
+ char *target; /* one target */
+ Word *tail; /* constituents of targets */
+ char *recipe; /* do it ! */
+ short attr; /* attributes */
+ short line; /* source line */
+ char *file; /* source file */
+ Word *alltargets; /* all the targets */
+ int rule; /* rule number */
+ Reprog *pat; /* reg exp goo */
+ char *prog; /* to use in out of date */
+ struct Rule *chain; /* hashed per target */
+ struct Rule *next;
+} Rule;
+
+extern Rule *rules, *metarules, *patrule;
+
+/* Rule.attr */
+#define META 0x0001
+#define UNUSED 0x0002
+#define UPD 0x0004
+#define QUIET 0x0008
+#define VIR 0x0010
+#define REGEXP 0x0020
+#define NOREC 0x0040
+#define DEL 0x0080
+#define NOVIRT 0x0100
+
+#define NREGEXP 10
+
+typedef struct Arc
+{
+ short flag;
+ struct Node *n;
+ Rule *r;
+ char *stem;
+ char *prog;
+ char *match[NREGEXP];
+ struct Arc *next;
+} Arc;
+
+ /* Arc.flag */
+#define TOGO 1
+
+typedef struct Node
+{
+ char *name;
+ long time;
+ unsigned short flags;
+ Arc *prereqs;
+ struct Node *next; /* list for a rule */
+} Node;
+
+ /* Node.flags */
+#define VIRTUAL 0x0001
+#define CYCLE 0x0002
+#define READY 0x0004
+#define CANPRETEND 0x0008
+#define PRETENDING 0x0010
+#define NOTMADE 0x0020
+#define BEINGMADE 0x0040
+#define MADE 0x0080
+#define MADESET(n,m) n->flags = (n->flags&~(NOTMADE|BEINGMADE|MADE))|(m)
+#define PROBABLE 0x0100
+#define VACUOUS 0x0200
+#define NORECIPE 0x0400
+#define DELETE 0x0800
+#define NOMINUSE 0x1000
+
+typedef struct Job
+{
+ Rule *r; /* master rule for job */
+ Node *n; /* list of node targets */
+ char *stem;
+ char **match;
+ Word *p; /* prerequistes */
+ Word *np; /* new prerequistes */
+ Word *t; /* targets */
+ Word *at; /* all targets */
+ int nproc; /* slot number */
+ struct Job *next;
+} Job;
+extern Job *jobs;
+
+typedef struct Symtab
+{
+ short space;
+ char *name;
+ void *value;
+ struct Symtab *next;
+} Symtab;
+
+enum {
+ S_VAR, /* variable -> value */
+ S_TARGET, /* target -> rule */
+ S_TIME, /* file -> time */
+ S_PID, /* pid -> products */
+ S_NODE, /* target name -> node */
+ S_AGG, /* aggregate -> time */
+ S_BITCH, /* bitched about aggregate not there */
+ S_NOEXPORT, /* var -> noexport */
+ S_OVERRIDE, /* can't override */
+ S_OUTOFDATE, /* n1\377n2 -> 2(outofdate) or 1(not outofdate) */
+ S_MAKEFILE, /* target -> node */
+ S_MAKEVAR, /* dumpable mk variable */
+ S_EXPORTED, /* var -> current exported value */
+ S_WESET, /* variable; we set in the mkfile */
+ S_INTERNAL /* an internal mk variable (e.g., stem, target) */
+};
+
+extern int debug;
+extern int nflag, tflag, iflag, kflag, aflag, mflag;
+extern int mkinline;
+extern char *infile;
+extern int nreps;
+extern char *explain;
+extern char *termchars;
+extern int IWS;
+extern char *shell;
+extern char *shellname;
+extern char *shflags;
+
+#define SYNERR(l) (fprint(2, "mk: %s:%d: syntax error; ", infile, ((l)>=0)?(l):mkinline))
+#define RERR(r) (fprint(2, "mk: %s:%d: rule error; ", (r)->file, (r)->line))
+#define NAMEBLOCK 1000
+#define BIGBLOCK 20000
+
+#define SEP(c) (((c)==' ')||((c)=='\t')||((c)=='\n'))
+#define WORDCHR(r) ((r) > ' ' && !utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", (r)))
+
+#define DEBUG(x) (debug&(x))
+#define D_PARSE 0x01
+#define D_GRAPH 0x02
+#define D_EXEC 0x04
+
+#define LSEEK(f,o,p) seek(f,o,p)
+
+#define PERCENT(ch) (((ch) == '%') || ((ch) == '&'))
+
+#include "fns.h"
diff --git a/src/cmd/mk/mkfile b/src/cmd/mk/mkfile
new file mode 100644
index 00000000..10151356
--- /dev/null
+++ b/src/cmd/mk/mkfile
@@ -0,0 +1,9 @@
+all:V: Makefile Make.FreeBSD-386 Make.Linux-386 Make.HP-UX-9000 Make.OSF1-alpha \
+ Make.SunOS-sun4u Make.SunOS-sun4u-cc Make.SunOS-sun4u-gcc \
+ Make.NetBSD-386 Make.Darwin-PowerMacintosh
+
+Makefile:D: ../libutf/Makefile.TOP Makefile.MID ../libutf/Makefile.CMD ../libutf/Makefile.BOT
+ cat $prereq >$target
+
+Make.%: ../libutf/Make.%
+ cp $prereq $target
diff --git a/src/cmd/mk/mkfile.test b/src/cmd/mk/mkfile.test
new file mode 100644
index 00000000..5c1c0eda
--- /dev/null
+++ b/src/cmd/mk/mkfile.test
@@ -0,0 +1,5 @@
+a: b
+ cp b a
+
+c:V:
+ echo hello world
diff --git a/src/cmd/mk/parse.c b/src/cmd/mk/parse.c
new file mode 100644
index 00000000..b7737ef9
--- /dev/null
+++ b/src/cmd/mk/parse.c
@@ -0,0 +1,307 @@
+#include "mk.h"
+
+char *infile;
+int mkinline;
+static int rhead(char *, Word **, Word **, int *, char **);
+static char *rbody(Biobuf*);
+extern Word *target1;
+
+void
+parse(char *f, int fd, int varoverride)
+{
+ int hline;
+ char *body;
+ Word *head, *tail;
+ int attr, set, pid;
+ char *prog, *p;
+ int newfd;
+ Biobuf in;
+ Bufblock *buf;
+
+ if(fd < 0){
+ fprint(2, "open %s: %r\n", f);
+ Exit();
+ }
+ ipush();
+ infile = strdup(f);
+ mkinline = 1;
+ Binit(&in, fd, OREAD);
+ buf = newbuf();
+ while(assline(&in, buf)){
+ hline = mkinline;
+ switch(rhead(buf->start, &head, &tail, &attr, &prog))
+ {
+ case '<':
+ p = wtos(tail, ' ');
+ if(*p == 0){
+ SYNERR(-1);
+ fprint(2, "missing include file name\n");
+ Exit();
+ }
+ newfd = open(p, OREAD);
+ if(newfd < 0){
+ fprint(2, "warning: skipping missing include file %s: %r\n", p);
+ } else
+ parse(p, newfd, 0);
+ break;
+ case '|':
+ p = wtos(tail, ' ');
+ if(*p == 0){
+ SYNERR(-1);
+ fprint(2, "missing include program name\n");
+ Exit();
+ }
+ execinit();
+ pid=pipecmd(p, envy, &newfd);
+ if(newfd < 0){
+ fprint(2, "warning: skipping missing program file %s: %r\n", p);
+ } else
+ parse(p, newfd, 0);
+ while(waitup(-3, &pid) >= 0)
+ ;
+ if(pid != 0){
+ fprint(2, "bad include program status\n");
+ Exit();
+ }
+ break;
+ case ':':
+ body = rbody(&in);
+ addrules(head, tail, body, attr, hline, prog);
+ break;
+ case '=':
+ if(head->next){
+ SYNERR(-1);
+ fprint(2, "multiple vars on left side of assignment\n");
+ Exit();
+ }
+ if(symlook(head->s, S_OVERRIDE, 0)){
+ set = varoverride;
+ } else {
+ set = 1;
+ if(varoverride)
+ symlook(head->s, S_OVERRIDE, (void *)"");
+ }
+ if(set){
+/*
+char *cp;
+dumpw("tail", tail);
+cp = wtos(tail, ' '); print("assign %s to %s\n", head->s, cp); free(cp);
+*/
+ setvar(head->s, (void *) tail);
+ symlook(head->s, S_WESET, (void *)"");
+ }
+ if(attr)
+ symlook(head->s, S_NOEXPORT, (void *)"");
+ break;
+ default:
+ SYNERR(hline);
+ fprint(2, "expected one of :<=\n");
+ Exit();
+ break;
+ }
+ }
+ close(fd);
+ freebuf(buf);
+ ipop();
+}
+
+void
+addrules(Word *head, Word *tail, char *body, int attr, int hline, char *prog)
+{
+ Word *w;
+
+ assert("addrules args", head && body);
+ /* tuck away first non-meta rule as default target*/
+ if(target1 == 0 && !(attr&REGEXP)){
+ for(w = head; w; w = w->next)
+ if(charin(w->s, "%&"))
+ break;
+ if(w == 0)
+ target1 = wdup(head);
+ }
+ for(w = head; w; w = w->next)
+ addrule(w->s, tail, body, head, attr, hline, prog);
+}
+
+static int
+rhead(char *line, Word **h, Word **t, int *attr, char **prog)
+{
+ char *p;
+ char *pp;
+ int sep;
+ Rune r;
+ int n;
+ Word *w;
+
+ p = charin(line,":=<");
+ if(p == 0)
+ return('?');
+ sep = *p;
+ *p++ = 0;
+ if(sep == '<' && *p == '|'){
+ sep = '|';
+ p++;
+ }
+ *attr = 0;
+ *prog = 0;
+ if(sep == '='){
+ pp = charin(p, termchars); /* termchars is shell-dependent */
+ if (pp && *pp == '=') {
+ while (p != pp) {
+ n = chartorune(&r, p);
+ switch(r)
+ {
+ default:
+ SYNERR(-1);
+ fprint(2, "unknown attribute '%c'\n",*p);
+ Exit();
+ case 'U':
+ *attr = 1;
+ break;
+ }
+ p += n;
+ }
+ p++; /* skip trailing '=' */
+ }
+ }
+ if((sep == ':') && *p && (*p != ' ') && (*p != '\t')){
+ while (*p) {
+ n = chartorune(&r, p);
+ if (r == ':')
+ break;
+ p += n;
+ switch(r)
+ {
+ default:
+ SYNERR(-1);
+ fprint(2, "unknown attribute '%c'\n", p[-1]);
+ Exit();
+ case 'D':
+ *attr |= DEL;
+ break;
+ case 'E':
+ *attr |= NOMINUSE;
+ break;
+ case 'n':
+ *attr |= NOVIRT;
+ break;
+ case 'N':
+ *attr |= NOREC;
+ break;
+ case 'P':
+ pp = utfrune(p, ':');
+ if (pp == 0 || *pp == 0)
+ goto eos;
+ *pp = 0;
+ *prog = strdup(p);
+ *pp = ':';
+ p = pp;
+ break;
+ case 'Q':
+ *attr |= QUIET;
+ break;
+ case 'R':
+ *attr |= REGEXP;
+ break;
+ case 'U':
+ *attr |= UPD;
+ break;
+ case 'V':
+ *attr |= VIR;
+ break;
+ }
+ }
+ if (*p++ != ':') {
+ eos:
+ SYNERR(-1);
+ fprint(2, "missing trailing :\n");
+ Exit();
+ }
+ }
+ *h = w = stow(line);
+ if(*w->s == 0 && sep != '<' && sep != '|') {
+ SYNERR(mkinline-1);
+ fprint(2, "no var on left side of assignment/rule\n");
+ Exit();
+ }
+ *t = stow(p);
+ return(sep);
+}
+
+static char *
+rbody(Biobuf *in)
+{
+ Bufblock *buf;
+ int r, lastr;
+ char *p;
+
+ lastr = '\n';
+ buf = newbuf();
+ for(;;){
+ r = Bgetrune(in);
+ if (r < 0)
+ break;
+ if (lastr == '\n') {
+ if (r == '#')
+ rinsert(buf, r);
+ else if (r != ' ' && r != '\t') {
+ Bungetrune(in);
+ break;
+ }
+ } else
+ rinsert(buf, r);
+ lastr = r;
+ if (r == '\n')
+ mkinline++;
+ }
+ insert(buf, 0);
+ p = strdup(buf->start);
+ freebuf(buf);
+ return p;
+}
+
+struct input
+{
+ char *file;
+ int line;
+ struct input *next;
+};
+static struct input *inputs = 0;
+
+void
+ipush(void)
+{
+ struct input *in, *me;
+
+ me = (struct input *)Malloc(sizeof(*me));
+ me->file = infile;
+ me->line = mkinline;
+ me->next = 0;
+ if(inputs == 0)
+ inputs = me;
+ else {
+ for(in = inputs; in->next; )
+ in = in->next;
+ in->next = me;
+ }
+}
+
+void
+ipop(void)
+{
+ struct input *in, *me;
+
+ assert("pop input list", inputs != 0);
+ if(inputs->next == 0){
+ me = inputs;
+ inputs = 0;
+ } else {
+ for(in = inputs; in->next->next; )
+ in = in->next;
+ me = in->next;
+ in->next = 0;
+ }
+ infile = me->file;
+ mkinline = me->line;
+ free((char *)me);
+}
diff --git a/src/cmd/mk/rc.c b/src/cmd/mk/rc.c
new file mode 100644
index 00000000..657ddf27
--- /dev/null
+++ b/src/cmd/mk/rc.c
@@ -0,0 +1,175 @@
+#include "mk.h"
+
+char *termchars = "'= \t"; /*used in parse.c to isolate assignment attribute*/
+char *shflags = "-I"; /* rc flag to force non-interactive mode */
+int IWS = '\1'; /* inter-word separator in env - not used in plan 9 */
+
+/*
+ * This file contains functions that depend on rc's syntax. Most
+ * of the routines extract strings observing rc's escape conventions
+ */
+
+
+/*
+ * skip a token in single quotes.
+ */
+static char *
+squote(char *cp)
+{
+ Rune r;
+ int n;
+
+ while(*cp){
+ n = chartorune(&r, cp);
+ if(r == '\'') {
+ n += chartorune(&r, cp+n);
+ if(r != '\'')
+ return(cp);
+ }
+ cp += n;
+ }
+ SYNERR(-1); /* should never occur */
+ fprint(2, "missing closing '\n");
+ return 0;
+}
+
+/*
+ * search a string for characters in a pattern set
+ * characters in quotes and variable generators are escaped
+ */
+char *
+charin(char *cp, char *pat)
+{
+ Rune r;
+ int n, vargen;
+
+ vargen = 0;
+ while(*cp){
+ n = chartorune(&r, cp);
+ switch(r){
+ case '\'': /* skip quoted string */
+ cp = squote(cp+1); /* n must = 1 */
+ if(!cp)
+ return 0;
+ break;
+ case '$':
+ if(*(cp+1) == '{')
+ vargen = 1;
+ break;
+ case '}':
+ if(vargen)
+ vargen = 0;
+ else if(utfrune(pat, r))
+ return cp;
+ break;
+ default:
+ if(vargen == 0 && utfrune(pat, r))
+ return cp;
+ break;
+ }
+ cp += n;
+ }
+ if(vargen){
+ SYNERR(-1);
+ fprint(2, "missing closing } in pattern generator\n");
+ }
+ return 0;
+}
+
+/*
+ * extract an escaped token. Possible escape chars are single-quote,
+ * double-quote,and backslash. Only the first is valid for rc. the
+ * others are just inserted into the receiving buffer.
+ */
+char*
+expandquote(char *s, Rune r, Bufblock *b)
+{
+ if (r != '\'') {
+ rinsert(b, r);
+ return s;
+ }
+
+ while(*s){
+ s += chartorune(&r, s);
+ if(r == '\'') {
+ if(*s == '\'')
+ s++;
+ else
+ return s;
+ }
+ rinsert(b, r);
+ }
+ return 0;
+}
+
+/*
+ * Input an escaped token. Possible escape chars are single-quote,
+ * double-quote and backslash. Only the first is a valid escape for
+ * rc; the others are just inserted into the receiving buffer.
+ */
+int
+escapetoken(Biobuf *bp, Bufblock *buf, int preserve, int esc)
+{
+ int c, line;
+
+ if(esc != '\'')
+ return 1;
+
+ line = mkinline;
+ while((c = nextrune(bp, 0)) > 0){
+ if(c == '\''){
+ if(preserve)
+ rinsert(buf, c);
+ c = Bgetrune(bp);
+ if (c < 0)
+ break;
+ if(c != '\''){
+ Bungetrune(bp);
+ return 1;
+ }
+ }
+ rinsert(buf, c);
+ }
+ SYNERR(line); fprint(2, "missing closing %c\n", esc);
+ return 0;
+}
+
+/*
+ * copy a single-quoted string; s points to char after opening quote
+ */
+static char *
+copysingle(char *s, Bufblock *buf)
+{
+ Rune r;
+
+ while(*s){
+ s += chartorune(&r, s);
+ rinsert(buf, r);
+ if(r == '\'')
+ break;
+ }
+ return s;
+}
+/*
+ * check for quoted strings. backquotes are handled here; single quotes above.
+ * s points to char after opening quote, q.
+ */
+char *
+copyq(char *s, Rune q, Bufblock *buf)
+{
+ if(q == '\'') /* copy quoted string */
+ return copysingle(s, buf);
+
+ if(q != '`') /* not quoted */
+ return s;
+
+ while(*s){ /* copy backquoted string */
+ s += chartorune(&q, s);
+ rinsert(buf, q);
+ if(q == '}')
+ break;
+ if(q == '\'')
+ s = copysingle(s, buf); /* copy quoted string */
+ }
+ return s;
+}
diff --git a/src/cmd/mk/recipe.c b/src/cmd/mk/recipe.c
new file mode 100644
index 00000000..144a4904
--- /dev/null
+++ b/src/cmd/mk/recipe.c
@@ -0,0 +1,117 @@
+#include "mk.h"
+
+int
+dorecipe(Node *node)
+{
+ char buf[BIGBLOCK];
+ register Node *n;
+ Rule *r = 0;
+ Arc *a, *aa;
+ Word head, ahead, lp, ln, *w, *ww, *aw;
+ Symtab *s;
+ int did = 0;
+
+ aa = 0;
+ /*
+ pick up the rule
+ */
+ for(a = node->prereqs; a; a = a->next)
+ if(*a->r->recipe)
+ r = (aa = a)->r;
+ /*
+ no recipe? go to buggery!
+ */
+ if(r == 0){
+ if(!(node->flags&VIRTUAL) && !(node->flags&NORECIPE)){
+ fprint(2, "mk: no recipe to make '%s'\n", node->name);
+ Exit();
+ }
+ if(strchr(node->name, '(') && node->time == 0)
+ MADESET(node, MADE);
+ else
+ update(0, node);
+ if(tflag){
+ if(!(node->flags&VIRTUAL))
+ touch(node->name);
+ else if(explain)
+ Bprint(&bout, "no touch of virtual '%s'\n", node->name);
+ }
+ return(did);
+ }
+ /*
+ build the node list
+ */
+ node->next = 0;
+ head.next = 0;
+ ww = &head;
+ ahead.next = 0;
+ aw = &ahead;
+ if(r->attr&REGEXP){
+ ww->next = newword(node->name);
+ aw->next = newword(node->name);
+ } else {
+ for(w = r->alltargets; w; w = w->next){
+ if(r->attr&META)
+ subst(aa->stem, w->s, buf);
+ else
+ strcpy(buf, w->s);
+ aw->next = newword(buf);
+ aw = aw->next;
+ if((s = symlook(buf, S_NODE, 0)) == 0)
+ continue; /* not a node we are interested in */
+ n = (Node *)s->value;
+ if(aflag == 0 && n->time) {
+ for(a = n->prereqs; a; a = a->next)
+ if(a->n && outofdate(n, a, 0))
+ break;
+ if(a == 0)
+ continue;
+ }
+ ww->next = newword(buf);
+ ww = ww->next;
+ if(n == node) continue;
+ n->next = node->next;
+ node->next = n;
+ }
+ }
+ for(n = node; n; n = n->next)
+ if((n->flags&READY) == 0)
+ return(did);
+ /*
+ gather the params for the job
+ */
+ lp.next = ln.next = 0;
+ for(n = node; n; n = n->next){
+ for(a = n->prereqs; a; a = a->next){
+ if(a->n){
+ addw(&lp, a->n->name);
+ if(outofdate(n, a, 0)){
+ addw(&ln, a->n->name);
+ if(explain)
+ fprint(1, "%s(%ld) < %s(%ld)\n",
+ n->name, n->time, a->n->name, a->n->time);
+ }
+ } else {
+ if(explain)
+ fprint(1, "%s has no prerequisites\n",
+ n->name);
+ }
+ }
+ MADESET(n, BEINGMADE);
+ }
+ /*print("lt=%s ln=%s lp=%s\n",wtos(head.next, ' '),wtos(ln.next, ' '),wtos(lp.next, ' '));*//**/
+ run(newjob(r, node, aa->stem, aa->match, lp.next, ln.next, head.next, ahead.next));
+ return(1);
+}
+
+void
+addw(Word *w, char *s)
+{
+ Word *lw;
+
+ for(lw = w; w = w->next; lw = w){
+ if(strcmp(s, w->s) == 0)
+ return;
+ }
+ lw->next = newword(s);
+}
diff --git a/src/cmd/mk/rpm.spec b/src/cmd/mk/rpm.spec
new file mode 100644
index 00000000..be75e6b8
--- /dev/null
+++ b/src/cmd/mk/rpm.spec
@@ -0,0 +1,29 @@
+Summary: Streamlined replacement for make
+Name: mk
+Version: 2.0
+Release: 1
+Group: Development/Utils
+Copyright: Public Domain
+Packager: Russ Cox <rsc@post.harvard.edu>
+Source: http://pdos.lcs.mit.edu/~rsc/software/mk-2.0.tgz
+URL: http://pdos.lcs.mit.edu/~rsc/software/#mk
+Requires: libfmt libbio libregexp9 libutf
+
+%description
+Mk is a streamlined replacement for make, written for
+Tenth Edition Research Unix by Andrew Hume.
+
+http://plan9.bell-labs.com/sys/doc/mk.pdf
+%prep
+%setup
+
+%build
+make
+
+%install
+make install
+
+%files
+/usr/local/doc/mk.pdf
+/usr/local/man/man1/mk.1
+/usr/local/bin/mk
diff --git a/src/cmd/mk/rule.c b/src/cmd/mk/rule.c
new file mode 100644
index 00000000..662f067f
--- /dev/null
+++ b/src/cmd/mk/rule.c
@@ -0,0 +1,107 @@
+#include "mk.h"
+
+static Rule *lr, *lmr;
+static int rcmp(Rule *r, char *target, Word *tail);
+static int nrules = 0;
+
+void
+addrule(char *head, Word *tail, char *body, Word *ahead, int attr, int hline, char *prog)
+{
+ Rule *r;
+ Rule *rr;
+ Symtab *sym;
+ int reuse;
+
+ r = 0;
+ reuse = 0;
+ if(sym = symlook(head, S_TARGET, 0)){
+ for(r = (Rule *)sym->value; r; r = r->chain)
+ if(rcmp(r, head, tail) == 0){
+ reuse = 1;
+ break;
+ }
+ }
+ if(r == 0)
+ r = (Rule *)Malloc(sizeof(Rule));
+ r->target = head;
+ r->tail = tail;
+ r->recipe = body;
+ r->line = hline;
+ r->file = infile;
+ r->attr = attr;
+ r->alltargets = ahead;
+ r->prog = prog;
+ r->rule = nrules++;
+ if(!reuse){
+ rr = (Rule *)symlook(head, S_TARGET, (void *)r)->value;
+ if(rr != r){
+ r->chain = rr->chain;
+ rr->chain = r;
+ } else
+ r->chain = 0;
+ }
+ if(!reuse)
+ r->next = 0;
+ if((attr&REGEXP) || charin(head, "%&")){
+ r->attr |= META;
+ if(reuse)
+ return;
+ if(attr&REGEXP){
+ patrule = r;
+ r->pat = regcomp(head);
+ }
+ if(metarules == 0)
+ metarules = lmr = r;
+ else {
+ lmr->next = r;
+ lmr = r;
+ }
+ } else {
+ if(reuse)
+ return;
+ r->pat = 0;
+ if(rules == 0)
+ rules = lr = r;
+ else {
+ lr->next = r;
+ lr = r;
+ }
+ }
+}
+
+void
+dumpr(char *s, Rule *r)
+{
+ Bprint(&bout, "%s: start=%ld\n", s, r);
+ for(; r; r = r->next){
+ Bprint(&bout, "\tRule %ld: %s[%d] attr=%x next=%ld chain=%ld alltarget='%s'",
+ r, r->file, r->line, r->attr, r->next, r->chain, wtos(r->alltargets, ' '));
+ if(r->prog)
+ Bprint(&bout, " prog='%s'", r->prog);
+ Bprint(&bout, "\n\ttarget=%s: %s\n", r->target, wtos(r->tail, ' '));
+ Bprint(&bout, "\trecipe@%ld='%s'\n", r->recipe, r->recipe);
+ }
+}
+
+static int
+rcmp(Rule *r, char *target, Word *tail)
+{
+ Word *w;
+
+ if(strcmp(r->target, target))
+ return 1;
+ for(w = r->tail; w && tail; w = w->next, tail = tail->next)
+ if(strcmp(w->s, tail->s))
+ return 1;
+ return(w || tail);
+}
+
+char *
+rulecnt(void)
+{
+ char *s;
+
+ s = Malloc(nrules);
+ memset(s, 0, nrules);
+ return(s);
+}
diff --git a/src/cmd/mk/run.c b/src/cmd/mk/run.c
new file mode 100644
index 00000000..3c0c7f3e
--- /dev/null
+++ b/src/cmd/mk/run.c
@@ -0,0 +1,296 @@
+#include "mk.h"
+
+typedef struct Event
+{
+ int pid;
+ Job *job;
+} Event;
+static Event *events;
+static int nevents, nrunning, nproclimit;
+
+typedef struct Process
+{
+ int pid;
+ int status;
+ struct Process *b, *f;
+} Process;
+static Process *phead, *pfree;
+static void sched(void);
+static void pnew(int, int), pdelete(Process *);
+
+int pidslot(int);
+
+void
+run(Job *j)
+{
+ Job *jj;
+
+ if(jobs){
+ for(jj = jobs; jj->next; jj = jj->next)
+ ;
+ jj->next = j;
+ } else
+ jobs = j;
+ j->next = 0;
+ /* this code also in waitup after parse redirect */
+ if(nrunning < nproclimit)
+ sched();
+}
+
+static void
+sched(void)
+{
+ char *flags;
+ Job *j;
+ Bufblock *buf;
+ int slot;
+ Node *n;
+ Envy *e;
+
+ if(jobs == 0){
+ usage();
+ return;
+ }
+ j = jobs;
+ jobs = j->next;
+ if(DEBUG(D_EXEC))
+ fprint(1, "firing up job for target %s\n", wtos(j->t, ' '));
+ slot = nextslot();
+ events[slot].job = j;
+ buf = newbuf();
+ e = buildenv(j, slot);
+ shprint(j->r->recipe, e, buf);
+ if(!tflag && (nflag || !(j->r->attr&QUIET)))
+ Bwrite(&bout, buf->start, (long)strlen(buf->start));
+ freebuf(buf);
+ if(nflag||tflag){
+ for(n = j->n; n; n = n->next){
+ if(tflag){
+ if(!(n->flags&VIRTUAL))
+ touch(n->name);
+ else if(explain)
+ Bprint(&bout, "no touch of virtual '%s'\n", n->name);
+ }
+ n->time = time((long *)0);
+ MADESET(n, MADE);
+ }
+ } else {
+ if(DEBUG(D_EXEC))
+ fprint(1, "recipe='%s'", j->r->recipe);/**/
+ Bflush(&bout);
+ if(j->r->attr&NOMINUSE)
+ flags = 0;
+ else
+ flags = "-e";
+ events[slot].pid = execsh(flags, j->r->recipe, 0, e);
+ usage();
+ nrunning++;
+ if(DEBUG(D_EXEC))
+ fprint(1, "pid for target %s = %d\n", wtos(j->t, ' '), events[slot].pid);
+ }
+}
+
+int
+waitup(int echildok, int *retstatus)
+{
+ Envy *e;
+ int pid;
+ int slot;
+ Symtab *s;
+ Word *w;
+ Job *j;
+ char buf[ERRMAX];
+ Bufblock *bp;
+ int uarg = 0;
+ int done;
+ Node *n;
+ Process *p;
+ extern int runerrs;
+
+ /* first check against the proces slist */
+ if(retstatus)
+ for(p = phead; p; p = p->f)
+ if(p->pid == *retstatus){
+ *retstatus = p->status;
+ pdelete(p);
+ return(-1);
+ }
+again: /* rogue processes */
+ pid = waitfor(buf);
+ if(pid == -1){
+ if(echildok > 0)
+ return(1);
+ else {
+ fprint(2, "mk: (waitup %d): %r\n", echildok);
+ Exit();
+ }
+ }
+ if(DEBUG(D_EXEC))
+ fprint(1, "waitup got pid=%d, status='%s'\n", pid, buf);
+ if(retstatus && pid == *retstatus){
+ *retstatus = buf[0]? 1:0;
+ return(-1);
+ }
+ slot = pidslot(pid);
+ if(slot < 0){
+ if(DEBUG(D_EXEC))
+ fprint(2, "mk: wait returned unexpected process %d\n", pid);
+ pnew(pid, buf[0]? 1:0);
+ goto again;
+ }
+ j = events[slot].job;
+ usage();
+ nrunning--;
+ events[slot].pid = -1;
+ if(buf[0]){
+ e = buildenv(j, slot);
+ bp = newbuf();
+ shprint(j->r->recipe, e, bp);
+ front(bp->start);
+ fprint(2, "mk: %s: exit status=%s", bp->start, buf);
+ freebuf(bp);
+ for(n = j->n, done = 0; n; n = n->next)
+ if(n->flags&DELETE){
+ if(done++ == 0)
+ fprint(2, ", deleting");
+ fprint(2, " '%s'", n->name);
+ delete(n->name);
+ }
+ fprint(2, "\n");
+ if(kflag){
+ runerrs++;
+ uarg = 1;
+ } else {
+ jobs = 0;
+ Exit();
+ }
+ }
+ for(w = j->t; w; w = w->next){
+ if((s = symlook(w->s, S_NODE, 0)) == 0)
+ continue; /* not interested in this node */
+ update(uarg, (Node *)s->value);
+ }
+ if(nrunning < nproclimit)
+ sched();
+ return(0);
+}
+
+void
+nproc(void)
+{
+ Symtab *sym;
+ Word *w;
+
+ if(sym = symlook("NPROC", S_VAR, 0)) {
+ w = (Word *) sym->value;
+ if (w && w->s && w->s[0])
+ nproclimit = atoi(w->s);
+ }
+ if(nproclimit < 1)
+ nproclimit = 1;
+ if(DEBUG(D_EXEC))
+ fprint(1, "nprocs = %d\n", nproclimit);
+ if(nproclimit > nevents){
+ if(nevents)
+ events = (Event *)Realloc((char *)events, nproclimit*sizeof(Event));
+ else
+ events = (Event *)Malloc(nproclimit*sizeof(Event));
+ while(nevents < nproclimit)
+ events[nevents++].pid = 0;
+ }
+}
+
+int
+nextslot(void)
+{
+ int i;
+
+ for(i = 0; i < nproclimit; i++)
+ if(events[i].pid <= 0) return i;
+ assert("out of slots!!", 0);
+ return 0; /* cyntax */
+}
+
+int
+pidslot(int pid)
+{
+ int i;
+
+ for(i = 0; i < nevents; i++)
+ if(events[i].pid == pid) return(i);
+ if(DEBUG(D_EXEC))
+ fprint(2, "mk: wait returned unexpected process %d\n", pid);
+ return(-1);
+}
+
+
+static void
+pnew(int pid, int status)
+{
+ Process *p;
+
+ if(pfree){
+ p = pfree;
+ pfree = p->f;
+ } else
+ p = (Process *)Malloc(sizeof(Process));
+ p->pid = pid;
+ p->status = status;
+ p->f = phead;
+ phead = p;
+ if(p->f)
+ p->f->b = p;
+ p->b = 0;
+}
+
+static void
+pdelete(Process *p)
+{
+ if(p->f)
+ p->f->b = p->b;
+ if(p->b)
+ p->b->f = p->f;
+ else
+ phead = p->f;
+ p->f = pfree;
+ pfree = p;
+}
+
+void
+killchildren(char *msg)
+{
+ Process *p;
+
+ kflag = 1; /* to make sure waitup doesn't exit */
+ jobs = 0; /* make sure no more get scheduled */
+ for(p = phead; p; p = p->f)
+ expunge(p->pid, msg);
+ while(waitup(1, (int *)0) == 0)
+ ;
+ Bprint(&bout, "mk: %s\n", msg);
+ Exit();
+}
+
+static long tslot[1000];
+static long tick;
+
+void
+usage(void)
+{
+ long t;
+
+ time(&t);
+ if(tick)
+ tslot[nrunning] += (t-tick);
+ tick = t;
+}
+
+void
+prusage(void)
+{
+ int i;
+
+ usage();
+ for(i = 0; i <= nevents; i++)
+ fprint(1, "%d: %ld\n", i, tslot[i]);
+}
diff --git a/src/cmd/mk/sh.c b/src/cmd/mk/sh.c
new file mode 100644
index 00000000..524167a5
--- /dev/null
+++ b/src/cmd/mk/sh.c
@@ -0,0 +1,189 @@
+#include "mk.h"
+
+char *termchars = "\"'= \t"; /*used in parse.c to isolate assignment attribute*/
+char *shflags = 0;
+int IWS = ' '; /* inter-word separator in env */
+
+/*
+ * This file contains functions that depend on the shell's syntax. Most
+ * of the routines extract strings observing the shell's escape conventions.
+ */
+
+
+/*
+ * skip a token in quotes.
+ */
+static char *
+squote(char *cp, int c)
+{
+ Rune r;
+ int n;
+
+ while(*cp){
+ n = chartorune(&r, cp);
+ if(r == c)
+ return cp;
+ if(r == '\\')
+ n += chartorune(&r, cp+n);
+ cp += n;
+ }
+ SYNERR(-1); /* should never occur */
+ fprint(2, "missing closing '\n");
+ return 0;
+}
+/*
+ * search a string for unescaped characters in a pattern set
+ */
+char *
+charin(char *cp, char *pat)
+{
+ Rune r;
+ int n, vargen;
+
+ vargen = 0;
+ while(*cp){
+ n = chartorune(&r, cp);
+ switch(r){
+ case '\\': /* skip escaped char */
+ cp += n;
+ n = chartorune(&r, cp);
+ break;
+ case '\'': /* skip quoted string */
+ case '"':
+ cp = squote(cp+1, r); /* n must = 1 */
+ if(!cp)
+ return 0;
+ break;
+ case '$':
+ if(*(cp+1) == '{')
+ vargen = 1;
+ break;
+ case '}':
+ if(vargen)
+ vargen = 0;
+ else if(utfrune(pat, r))
+ return cp;
+ break;
+ default:
+ if(vargen == 0 && utfrune(pat, r))
+ return cp;
+ break;
+ }
+ cp += n;
+ }
+ if(vargen){
+ SYNERR(-1);
+ fprint(2, "missing closing } in pattern generator\n");
+ }
+ return 0;
+}
+
+/*
+ * extract an escaped token. Possible escape chars are single-quote,
+ * double-quote,and backslash.
+ */
+char*
+expandquote(char *s, Rune esc, Bufblock *b)
+{
+ Rune r;
+
+ if (esc == '\\') {
+ s += chartorune(&r, s);
+ rinsert(b, r);
+ return s;
+ }
+
+ while(*s){
+ s += chartorune(&r, s);
+ if(r == esc)
+ return s;
+ if (r == '\\') {
+ rinsert(b, r);
+ s += chartorune(&r, s);
+ }
+ rinsert(b, r);
+ }
+ return 0;
+}
+
+/*
+ * Input an escaped token. Possible escape chars are single-quote,
+ * double-quote and backslash.
+ */
+int
+escapetoken(Biobuf *bp, Bufblock *buf, int preserve, int esc)
+{
+ int c, line;
+
+ if(esc == '\\') {
+ c = Bgetrune(bp);
+ if(c == '\r')
+ c = Bgetrune(bp);
+ if (c == '\n')
+ mkinline++;
+ rinsert(buf, c);
+ return 1;
+ }
+
+ line = mkinline;
+ while((c = nextrune(bp, 0)) >= 0){
+ if(c == esc){
+ if(preserve)
+ rinsert(buf, c);
+ return 1;
+ }
+ if(c == '\\') {
+ rinsert(buf, c);
+ c = Bgetrune(bp);
+ if(c == '\r')
+ c = Bgetrune(bp);
+ if (c < 0)
+ break;
+ if (c == '\n')
+ mkinline++;
+ }
+ rinsert(buf, c);
+ }
+ SYNERR(line); fprint(2, "missing closing %c\n", esc);
+ return 0;
+}
+
+/*
+ * copy a quoted string; s points to char after opening quote
+ */
+static char *
+copysingle(char *s, Rune q, Bufblock *buf)
+{
+ Rune r;
+
+ while(*s){
+ s += chartorune(&r, s);
+ rinsert(buf, r);
+ if(r == q)
+ break;
+ }
+ return s;
+}
+/*
+ * check for quoted strings. backquotes are handled here; single quotes above.
+ * s points to char after opening quote, q.
+ */
+char *
+copyq(char *s, Rune q, Bufblock *buf)
+{
+ if(q == '\'' || q == '"') /* copy quoted string */
+ return copysingle(s, q, buf);
+
+ if(q != '`') /* not quoted */
+ return s;
+
+ while(*s){ /* copy backquoted string */
+ s += chartorune(&q, s);
+ rinsert(buf, q);
+ if(q == '`')
+ break;
+ if(q == '\'' || q == '"')
+ s = copysingle(s, q, buf); /* copy quoted string */
+ }
+ return s;
+}
diff --git a/src/cmd/mk/shprint.c b/src/cmd/mk/shprint.c
new file mode 100644
index 00000000..9e15bcef
--- /dev/null
+++ b/src/cmd/mk/shprint.c
@@ -0,0 +1,123 @@
+#include "mk.h"
+
+static char *vexpand(char*, Envy*, Bufblock*);
+
+static int
+getfields(char *str, char **args, int max, int mflag, char *set)
+{
+ Rune r;
+ int nr, intok, narg;
+
+ if(max <= 0)
+ return 0;
+
+ narg = 0;
+ args[narg] = str;
+ if(!mflag)
+ narg++;
+ intok = 0;
+ for(;; str += nr) {
+ nr = chartorune(&r, str);
+ if(r == 0)
+ break;
+ if(utfrune(set, r)) {
+ if(narg >= max)
+ break;
+ *str = 0;
+ intok = 0;
+ args[narg] = str + nr;
+ if(!mflag)
+ narg++;
+ } else {
+ if(!intok && mflag)
+ narg++;
+ intok = 1;
+ }
+ }
+ return narg;
+}
+
+void
+shprint(char *s, Envy *env, Bufblock *buf)
+{
+ int n;
+ Rune r;
+
+ while(*s) {
+ n = chartorune(&r, s);
+ if (r == '$')
+ s = vexpand(s, env, buf);
+ else {
+ rinsert(buf, r);
+ s += n;
+ s = copyq(s, r, buf); /*handle quoted strings*/
+ }
+ }
+ insert(buf, 0);
+}
+
+static char *
+mygetenv(char *name, Envy *env)
+{
+ if (!env)
+ return 0;
+ if (symlook(name, S_WESET, 0) == 0 && symlook(name, S_INTERNAL, 0) == 0)
+ return 0;
+ /* only resolve internal variables and variables we've set */
+ for(; env->name; env++){
+ if (strcmp(env->name, name) == 0)
+ return wtos(env->values, ' ');
+ }
+ return 0;
+}
+
+static char *
+vexpand(char *w, Envy *env, Bufblock *buf)
+{
+ char *s, carry, *p, *q;
+
+ assert("vexpand no $", *w == '$');
+ p = w+1; /* skip dollar sign */
+ if(*p == '{') {
+ p++;
+ q = utfrune(p, '}');
+ if (!q)
+ q = strchr(p, 0);
+ } else
+ q = shname(p);
+ carry = *q;
+ *q = 0;
+ s = mygetenv(p, env);
+ *q = carry;
+ if (carry == '}')
+ q++;
+ if (s) {
+ bufcpy(buf, s, strlen(s));
+ free(s);
+ } else /* copy name intact*/
+ bufcpy(buf, w, q-w);
+ return(q);
+}
+
+void
+front(char *s)
+{
+ char *t, *q;
+ int i, j;
+ char *flds[512];
+
+ q = strdup(s);
+ i = getfields(q, flds, 512, 0, " \t\n");
+ if(i > 5){
+ flds[4] = flds[i-1];
+ flds[3] = "...";
+ i = 5;
+ }
+ t = s;
+ for(j = 0; j < i; j++){
+ for(s = flds[j]; *s; *t++ = *s++);
+ *t++ = ' ';
+ }
+ *t = 0;
+ free(q);
+}
diff --git a/src/cmd/mk/symtab.c b/src/cmd/mk/symtab.c
new file mode 100644
index 00000000..6f7b8886
--- /dev/null
+++ b/src/cmd/mk/symtab.c
@@ -0,0 +1,95 @@
+#include "mk.h"
+
+#define NHASH 4099
+#define HASHMUL 79L /* this is a good value */
+static Symtab *hash[NHASH];
+
+void
+syminit(void)
+{
+ Symtab **s, *ss;
+
+ for(s = hash; s < &hash[NHASH]; s++){
+ for(ss = *s; ss; ss = ss->next)
+ free((char *)ss);
+ *s = 0;
+ }
+}
+
+Symtab *
+symlook(char *sym, int space, void *install)
+{
+ long h;
+ char *p;
+ Symtab *s;
+
+ for(p = sym, h = space; *p; h += *p++)
+ h *= HASHMUL;
+ if(h < 0)
+ h = ~h;
+ h %= NHASH;
+ for(s = hash[h]; s; s = s->next)
+ if((s->space == space) && (strcmp(s->name, sym) == 0))
+ return(s);
+ if(install == 0)
+ return(0);
+ s = (Symtab *)Malloc(sizeof(Symtab));
+ s->space = space;
+ s->name = sym;
+ s->value = install;
+ s->next = hash[h];
+ hash[h] = s;
+ return(s);
+}
+
+void
+symdel(char *sym, int space)
+{
+ long h;
+ char *p;
+ Symtab *s, *ls;
+
+ /* multiple memory leaks */
+
+ for(p = sym, h = space; *p; h += *p++)
+ h *= HASHMUL;
+ if(h < 0)
+ h = ~h;
+ h %= NHASH;
+ for(s = hash[h], ls = 0; s; ls = s, s = s->next)
+ if((s->space == space) && (strcmp(s->name, sym) == 0)){
+ if(ls)
+ ls->next = s->next;
+ else
+ hash[h] = s->next;
+ free((char *)s);
+ }
+}
+
+void
+symtraverse(int space, void (*fn)(Symtab*))
+{
+ Symtab **s, *ss;
+
+ for(s = hash; s < &hash[NHASH]; s++)
+ for(ss = *s; ss; ss = ss->next)
+ if(ss->space == space)
+ (*fn)(ss);
+}
+
+void
+symstat(void)
+{
+ Symtab **s, *ss;
+ int n;
+ int l[1000];
+
+ memset((char *)l, 0, sizeof(l));
+ for(s = hash; s < &hash[NHASH]; s++){
+ for(ss = *s, n = 0; ss; ss = ss->next)
+ n++;
+ l[n]++;
+ }
+ for(n = 0; n < 1000; n++)
+ if(l[n]) Bprint(&bout, "%ld of length %d\n", l[n], n);
+}
diff --git a/src/cmd/mk/unix.c b/src/cmd/mk/unix.c
new file mode 100644
index 00000000..60222788
--- /dev/null
+++ b/src/cmd/mk/unix.c
@@ -0,0 +1,306 @@
+#include "mk.h"
+#include <sys/wait.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+char *shell = "/bin/sh";
+char *shellname = "sh";
+
+extern char **environ;
+
+static void
+mkperror(char *s)
+{
+ fprint(2, "%s: %r\n", s);
+}
+
+void
+readenv(void)
+{
+ char **p, *s;
+ Word *w;
+
+ for(p = environ; *p; p++){
+ s = shname(*p);
+ if(*s == '=') {
+ *s = 0;
+ w = newword(s+1);
+ } else
+ w = newword("");
+ if (symlook(*p, S_INTERNAL, 0))
+ continue;
+ s = strdup(*p);
+ setvar(s, (void *)w);
+ symlook(s, S_EXPORTED, (void*)"")->value = (void*)"";
+ }
+}
+
+/*
+ * done on child side of fork, so parent's env is not affected
+ * and we don't care about freeing memory because we're going
+ * to exec immediately after this.
+ */
+void
+exportenv(Envy *e)
+{
+ int i;
+ char **p;
+ char buf[4096];
+
+ p = 0;
+ for(i = 0; e->name; e++, i++) {
+ p = (char**) Realloc(p, (i+2)*sizeof(char*));
+ if(e->values)
+ sprint(buf, "%s=%s", e->name, wtos(e->values, IWS));
+ else
+ sprint(buf, "%s=", e->name);
+ p[i] = strdup(buf);
+ }
+ p[i] = 0;
+ environ = p;
+}
+
+int
+waitfor(char *msg)
+{
+ int status;
+ int pid;
+
+ *msg = 0;
+ pid = wait(&status);
+ if(pid > 0) {
+ if(status&0x7f) {
+ if(status&0x80)
+ snprint(msg, ERRMAX, "signal %d, core dumped", status&0x7f);
+ else
+ snprint(msg, ERRMAX, "signal %d", status&0x7f);
+ } else if(status&0xff00)
+ snprint(msg, ERRMAX, "exit(%d)", (status>>8)&0xff);
+ }
+ return pid;
+}
+
+void
+expunge(int pid, char *msg)
+{
+ if(strcmp(msg, "interrupt"))
+ kill(pid, SIGINT);
+ else
+ kill(pid, SIGHUP);
+}
+
+int
+execsh(char *args, char *cmd, Bufblock *buf, Envy *e)
+{
+ char *p;
+ int tot, n, pid, in[2], out[2];
+
+ if(buf && pipe(out) < 0){
+ mkperror("pipe");
+ Exit();
+ }
+ pid = fork();
+ if(pid < 0){
+ mkperror("mk fork");
+ Exit();
+ }
+ if(pid == 0){
+ if(buf)
+ close(out[0]);
+ if(pipe(in) < 0){
+ mkperror("pipe");
+ Exit();
+ }
+ pid = fork();
+ if(pid < 0){
+ mkperror("mk fork");
+ Exit();
+ }
+ if(pid != 0){
+ dup2(in[0], 0);
+ if(buf){
+ dup2(out[1], 1);
+ close(out[1]);
+ }
+ close(in[0]);
+ close(in[1]);
+ if (e)
+ exportenv(e);
+ if(shflags)
+ execl(shell, shellname, shflags, args, 0);
+ else
+ execl(shell, shellname, args, 0);
+ mkperror(shell);
+ _exit(1);
+ }
+ close(out[1]);
+ close(in[0]);
+ if(DEBUG(D_EXEC))
+ fprint(1, "starting: %s\n", cmd);
+ p = cmd+strlen(cmd);
+ while(cmd < p){
+ n = write(in[1], cmd, p-cmd);
+ if(n < 0)
+ break;
+ cmd += n;
+ }
+ close(in[1]);
+ _exit(0);
+ }
+ if(buf){
+ close(out[1]);
+ tot = 0;
+ for(;;){
+ if (buf->current >= buf->end)
+ growbuf(buf);
+ n = read(out[0], buf->current, buf->end-buf->current);
+ if(n <= 0)
+ break;
+ buf->current += n;
+ tot += n;
+ }
+ if (tot && buf->current[-1] == '\n')
+ buf->current--;
+ close(out[0]);
+ }
+ return pid;
+}
+
+int
+pipecmd(char *cmd, Envy *e, int *fd)
+{
+ int pid, pfd[2];
+
+ if(DEBUG(D_EXEC))
+ fprint(1, "pipecmd='%s'\n", cmd);/**/
+
+ if(fd && pipe(pfd) < 0){
+ mkperror("pipe");
+ Exit();
+ }
+ pid = fork();
+ if(pid < 0){
+ mkperror("mk fork");
+ Exit();
+ }
+ if(pid == 0){
+ if(fd){
+ close(pfd[0]);
+ dup2(pfd[1], 1);
+ close(pfd[1]);
+ }
+ if(e)
+ exportenv(e);
+ if(shflags)
+ execl(shell, shellname, shflags, "-c", cmd, 0);
+ else
+ execl(shell, shellname, "-c", cmd, 0);
+ mkperror(shell);
+ _exit(1);
+ }
+ if(fd){
+ close(pfd[1]);
+ *fd = pfd[0];
+ }
+ return pid;
+}
+
+void
+Exit(void)
+{
+ while(wait(0) >= 0)
+ ;
+ exits("error");
+}
+
+static struct
+{
+ int sig;
+ char *msg;
+} sigmsgs[] =
+{
+ SIGALRM, "alarm",
+ SIGFPE, "sys: fp: fptrap",
+ SIGPIPE, "sys: write on closed pipe",
+ SIGILL, "sys: trap: illegal instruction",
+ SIGSEGV, "sys: segmentation violation",
+ 0, 0
+};
+
+static void
+notifyf(int sig)
+{
+ int i;
+
+ for(i = 0; sigmsgs[i].msg; i++)
+ if(sigmsgs[i].sig == sig)
+ killchildren(sigmsgs[i].msg);
+
+ /* should never happen */
+ signal(sig, SIG_DFL);
+ kill(getpid(), sig);
+}
+
+void
+catchnotes()
+{
+ int i;
+
+ for(i = 0; sigmsgs[i].msg; i++)
+ signal(sigmsgs[i].sig, notifyf);
+}
+
+char*
+maketmp(int *pfd)
+{
+ static char temp[] = "/tmp/mkargXXXXXX";
+ static char buf[100];
+ int fd;
+
+ strcpy(buf, temp);
+ fd = mkstemp(buf);
+ if(fd < 0)
+ return 0;
+ *pfd = fd;
+ return buf;
+}
+
+int
+chgtime(char *name)
+{
+ if(access(name, 0) >= 0)
+ return utimes(name, 0);
+ return close(creat(name, 0666));
+}
+
+void
+rcopy(char **to, Resub *match, int n)
+{
+ int c;
+ char *p;
+
+ *to = match->s.sp; /* stem0 matches complete target */
+ for(to++, match++; --n > 0; to++, match++){
+ if(match->s.sp && match->e.ep){
+ p = match->e.ep;
+ c = *p;
+ *p = 0;
+ *to = strdup(match->s.sp);
+ *p = c;
+ }
+ else
+ *to = 0;
+ }
+}
+
+ulong
+mkmtime(char *name)
+{
+ struct stat st;
+
+ if(stat(name, &st) < 0)
+ return 0;
+
+ return st.st_mtime;
+}
diff --git a/src/cmd/mk/var.c b/src/cmd/mk/var.c
new file mode 100644
index 00000000..8429918d
--- /dev/null
+++ b/src/cmd/mk/var.c
@@ -0,0 +1,41 @@
+#include "mk.h"
+
+void
+setvar(char *name, void *value)
+{
+ symlook(name, S_VAR, value)->value = value;
+ symlook(name, S_MAKEVAR, (void*)"");
+}
+
+static void
+print1(Symtab *s)
+{
+ Word *w;
+
+ Bprint(&bout, "\t%s=", s->name);
+ for (w = (Word *) s->value; w; w = w->next)
+ Bprint(&bout, "'%s'", w->s);
+ Bprint(&bout, "\n");
+}
+
+void
+dumpv(char *s)
+{
+ Bprint(&bout, "%s:\n", s);
+ symtraverse(S_VAR, print1);
+}
+
+char *
+shname(char *a)
+{
+ Rune r;
+ int n;
+
+ while (*a) {
+ n = chartorune(&r, a);
+ if (!WORDCHR(r))
+ break;
+ a += n;
+ }
+ return a;
+}
diff --git a/src/cmd/mk/varsub.c b/src/cmd/mk/varsub.c
new file mode 100644
index 00000000..2a9ad987
--- /dev/null
+++ b/src/cmd/mk/varsub.c
@@ -0,0 +1,256 @@
+#include "mk.h"
+
+static Word *subsub(Word*, char*, char*);
+static Word *expandvar(char**);
+static Bufblock *varname(char**);
+static Word *extractpat(char*, char**, char*, char*);
+static int submatch(char*, Word*, Word*, int*, char**);
+static Word *varmatch(char *, char**);
+
+Word *
+varsub(char **s)
+{
+ Bufblock *b;
+ Word *w;
+
+ if(**s == '{') /* either ${name} or ${name: A%B==C%D}*/
+ return expandvar(s);
+
+ b = varname(s);
+ if(b == 0)
+ return 0;
+
+ w = varmatch(b->start, s);
+ freebuf(b);
+ return w;
+}
+
+/*
+ * extract a variable name
+ */
+static Bufblock*
+varname(char **s)
+{
+ Bufblock *b;
+ char *cp;
+ Rune r;
+ int n;
+
+ b = newbuf();
+ cp = *s;
+ for(;;){
+ n = chartorune(&r, cp);
+ if (!WORDCHR(r))
+ break;
+ rinsert(b, r);
+ cp += n;
+ }
+ if (b->current == b->start){
+ SYNERR(-1);
+ fprint(2, "missing variable name <%s>\n", *s);
+ freebuf(b);
+ return 0;
+ }
+ *s = cp;
+ insert(b, 0);
+ return b;
+}
+
+static Word*
+varmatch(char *name, char **s)
+{
+ Word *w;
+ Symtab *sym;
+ char *cp;
+
+ sym = symlook(name, S_VAR, 0);
+ if(sym){
+ /* check for at least one non-NULL value */
+ for (w = (Word*)sym->value; w; w = w->next)
+ if(w->s && *w->s)
+ return wdup(w);
+ }
+ for(cp = *s; *cp == ' ' || *cp == '\t'; cp++) /* skip trailing whitespace */
+ ;
+ *s = cp;
+ return 0;
+}
+
+static Word*
+expandvar(char **s)
+{
+ Word *w;
+ Bufblock *buf;
+ Symtab *sym;
+ char *cp, *begin, *end;
+
+ begin = *s;
+ (*s)++; /* skip the '{' */
+ buf = varname(s);
+ if (buf == 0)
+ return 0;
+ cp = *s;
+ if (*cp == '}') { /* ${name} variant*/
+ (*s)++; /* skip the '}' */
+ w = varmatch(buf->start, s);
+ freebuf(buf);
+ return w;
+ }
+ if (*cp != ':') {
+ SYNERR(-1);
+ fprint(2, "bad variable name <%s>\n", buf->start);
+ freebuf(buf);
+ return 0;
+ }
+ cp++;
+ end = charin(cp , "}");
+ if(end == 0){
+ SYNERR(-1);
+ fprint(2, "missing '}': %s\n", begin);
+ Exit();
+ }
+ *end = 0;
+ *s = end+1;
+
+ sym = symlook(buf->start, S_VAR, 0);
+ if(sym == 0 || sym->value == 0)
+ w = newword(buf->start);
+ else
+ w = subsub((Word*) sym->value, cp, end);
+ freebuf(buf);
+ return w;
+}
+
+static Word*
+extractpat(char *s, char **r, char *term, char *end)
+{
+ int save;
+ char *cp;
+ Word *w;
+
+ cp = charin(s, term);
+ if(cp){
+ *r = cp;
+ if(cp == s)
+ return 0;
+ save = *cp;
+ *cp = 0;
+ w = stow(s);
+ *cp = save;
+ } else {
+ *r = end;
+ w = stow(s);
+ }
+ return w;
+}
+
+static Word*
+subsub(Word *v, char *s, char *end)
+{
+ int nmid;
+ Word *head, *tail, *w, *h;
+ Word *a, *b, *c, *d;
+ Bufblock *buf;
+ char *cp, *enda;
+
+ a = extractpat(s, &cp, "=%&", end);
+ b = c = d = 0;
+ if(PERCENT(*cp))
+ b = extractpat(cp+1, &cp, "=", end);
+ if(*cp == '=')
+ c = extractpat(cp+1, &cp, "&%", end);
+ if(PERCENT(*cp))
+ d = stow(cp+1);
+ else if(*cp)
+ d = stow(cp);
+
+ head = tail = 0;
+ buf = newbuf();
+ for(; v; v = v->next){
+ h = w = 0;
+ if(submatch(v->s, a, b, &nmid, &enda)){
+ /* enda points to end of A match in source;
+ * nmid = number of chars between end of A and start of B
+ */
+ if(c){
+ h = w = wdup(c);
+ while(w->next)
+ w = w->next;
+ }
+ if(PERCENT(*cp) && nmid > 0){
+ if(w){
+ bufcpy(buf, w->s, strlen(w->s));
+ bufcpy(buf, enda, nmid);
+ insert(buf, 0);
+ free(w->s);
+ w->s = strdup(buf->start);
+ } else {
+ bufcpy(buf, enda, nmid);
+ insert(buf, 0);
+ h = w = newword(buf->start);
+ }
+ buf->current = buf->start;
+ }
+ if(d && *d->s){
+ if(w){
+
+ bufcpy(buf, w->s, strlen(w->s));
+ bufcpy(buf, d->s, strlen(d->s));
+ insert(buf, 0);
+ free(w->s);
+ w->s = strdup(buf->start);
+ w->next = wdup(d->next);
+ while(w->next)
+ w = w->next;
+ buf->current = buf->start;
+ } else
+ h = w = wdup(d);
+ }
+ }
+ if(w == 0)
+ h = w = newword(v->s);
+
+ if(head == 0)
+ head = h;
+ else
+ tail->next = h;
+ tail = w;
+ }
+ freebuf(buf);
+ delword(a);
+ delword(b);
+ delword(c);
+ delword(d);
+ return head;
+}
+
+static int
+submatch(char *s, Word *a, Word *b, int *nmid, char **enda)
+{
+ Word *w;
+ int n;
+ char *end;
+
+ n = 0;
+ for(w = a; w; w = w->next){
+ n = strlen(w->s);
+ if(strncmp(s, w->s, n) == 0)
+ break;
+ }
+ if(a && w == 0) /* a == NULL matches everything*/
+ return 0;
+
+ *enda = s+n; /* pointer to end a A part match */
+ *nmid = strlen(s)-n; /* size of remainder of source */
+ end = *enda+*nmid;
+ for(w = b; w; w = w->next){
+ n = strlen(w->s);
+ if(strcmp(w->s, end-n) == 0){
+ *nmid -= n;
+ break;
+ }
+ }
+ if(b && w == 0) /* b == NULL matches everything */
+ return 0;
+ return 1;
+}
diff --git a/src/cmd/mk/word.c b/src/cmd/mk/word.c
new file mode 100644
index 00000000..ac34c47b
--- /dev/null
+++ b/src/cmd/mk/word.c
@@ -0,0 +1,180 @@
+#include "mk.h"
+
+static Word *nextword(char**);
+
+Word*
+newword(char *s)
+{
+ Word *w;
+
+ w = (Word *)Malloc(sizeof(Word));
+ w->s = strdup(s);
+ w->next = 0;
+ return(w);
+}
+
+Word *
+stow(char *s)
+{
+ Word *head, *w, *new;
+
+ w = head = 0;
+ while(*s){
+ new = nextword(&s);
+ if(new == 0)
+ break;
+ if (w)
+ w->next = new;
+ else
+ head = w = new;
+ while(w->next)
+ w = w->next;
+
+ }
+ if (!head)
+ head = newword("");
+ return(head);
+}
+
+char *
+wtos(Word *w, int sep)
+{
+ Bufblock *buf;
+ char *cp;
+
+ buf = newbuf();
+ for(; w; w = w->next){
+ for(cp = w->s; *cp; cp++)
+ insert(buf, *cp);
+ if(w->next)
+ insert(buf, sep);
+ }
+ insert(buf, 0);
+ cp = strdup(buf->start);
+ freebuf(buf);
+ return(cp);
+}
+
+Word*
+wdup(Word *w)
+{
+ Word *v, *new, *base;
+
+ v = base = 0;
+ while(w){
+ new = newword(w->s);
+ if(v)
+ v->next = new;
+ else
+ base = new;
+ v = new;
+ w = w->next;
+ }
+ return base;
+}
+
+void
+delword(Word *w)
+{
+ Word *v;
+
+ while(v = w){
+ w = w->next;
+ if(v->s)
+ free(v->s);
+ free(v);
+ }
+}
+
+/*
+ * break out a word from a string handling quotes, executions,
+ * and variable expansions.
+ */
+static Word*
+nextword(char **s)
+{
+ Bufblock *b;
+ Word *head, *tail, *w;
+ Rune r;
+ char *cp;
+
+ cp = *s;
+ b = newbuf();
+ head = tail = 0;
+ while(*cp == ' ' || *cp == '\t') /* leading white space */
+ cp++;
+ while(*cp){
+ cp += chartorune(&r, cp);
+ switch(r)
+ {
+ case ' ':
+ case '\t':
+ case '\n':
+ goto out;
+ case '\\':
+ case '\'':
+ case '"':
+ cp = expandquote(cp, r, b);
+ if(cp == 0){
+ fprint(2, "missing closing quote: %s\n", *s);
+ Exit();
+ }
+ break;
+ case '$':
+ w = varsub(&cp);
+ if(w == 0)
+ break;
+ if(b->current != b->start){
+ bufcpy(b, w->s, strlen(w->s));
+ insert(b, 0);
+ free(w->s);
+ w->s = strdup(b->start);
+ b->current = b->start;
+ }
+ if(head){
+ bufcpy(b, tail->s, strlen(tail->s));
+ bufcpy(b, w->s, strlen(w->s));
+ insert(b, 0);
+ free(tail->s);
+ tail->s = strdup(b->start);
+ tail->next = w->next;
+ free(w->s);
+ free(w);
+ b->current = b->start;
+ } else
+ tail = head = w;
+ while(tail->next)
+ tail = tail->next;
+ break;
+ default:
+ rinsert(b, r);
+ break;
+ }
+ }
+out:
+ *s = cp;
+ if(b->current != b->start){
+ if(head){
+ cp = b->current;
+ bufcpy(b, tail->s, strlen(tail->s));
+ bufcpy(b, b->start, cp-b->start);
+ insert(b, 0);
+ free(tail->s);
+ tail->s = strdup(cp);
+ } else {
+ insert(b, 0);
+ head = newword(b->start);
+ }
+ }
+ freebuf(b);
+ return head;
+}
+
+void
+dumpw(char *s, Word *w)
+{
+ Bprint(&bout, "%s", s);
+ for(; w; w = w->next)
+ Bprint(&bout, " '%s'", w->s);
+ Bputc(&bout, '\n');
+}
diff --git a/src/cmd/sam/LICENSE b/src/cmd/sam/LICENSE
new file mode 100644
index 00000000..a5d7d87d
--- /dev/null
+++ b/src/cmd/sam/LICENSE
@@ -0,0 +1,258 @@
+The Plan 9 software is provided under the terms of the
+Lucent Public License, Version 1.02, reproduced below,
+with the following exceptions:
+
+1. No right is granted to create derivative works of or
+ to redistribute (other than with the Plan 9 Operating System)
+ the screen imprinter fonts identified in subdirectory
+ /lib/font/bit/lucida and printer fonts (Lucida Sans Unicode, Lucida
+ Sans Italic, Lucida Sans Demibold, Lucida Typewriter, Lucida Sans
+ Typewriter83), identified in subdirectory /sys/lib/postscript/font.
+ These directories contain material copyrights by B&H Inc. and Y&Y Inc.
+
+2. The printer fonts identified in subdirectory /sys/lib/ghostscript/font
+ are subject to the GNU GPL, reproduced in the file /LICENSE.gpl.
+
+3. The ghostscript program in the subdirectory /sys/src/cmd/gs is
+ covered by the Aladdin Free Public License, reproduced in the file
+ /LICENSE.afpl.
+
+===================================================================
+
+Lucent Public License Version 1.02
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
+PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+ a. in the case of Lucent Technologies Inc. ("LUCENT"), the Original
+ Program, and
+ b. in the case of each Contributor,
+
+ i. changes to the Program, and
+ ii. additions to the Program;
+
+ where such changes and/or additions to the Program were added to the
+ Program by such Contributor itself or anyone acting on such
+ Contributor's behalf, and the Contributor explicitly consents, in
+ accordance with Section 3C, to characterization of the changes and/or
+ additions as Contributions.
+
+"Contributor" means LUCENT and any other entity that has Contributed a
+Contribution to the Program.
+
+"Distributor" means a Recipient that distributes the Program,
+modifications to the Program, or any part thereof.
+
+"Licensed Patents" mean patent claims licensable by a Contributor
+which are necessarily infringed by the use or sale of its Contribution
+alone or when combined with the Program.
+
+"Original Program" means the original version of the software
+accompanying this Agreement as released by LUCENT, including source
+code, object code and documentation, if any.
+
+"Program" means the Original Program and Contributions or any part
+thereof
+
+"Recipient" means anyone who receives the Program under this
+Agreement, including all Contributors.
+
+2. GRANT OF RIGHTS
+
+ a. Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free copyright
+ license to reproduce, prepare derivative works of, publicly display,
+ publicly perform, distribute and sublicense the Contribution of such
+ Contributor, if any, and such derivative works, in source code and
+ object code form.
+
+ b. Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free patent
+ license under Licensed Patents to make, use, sell, offer to sell,
+ import and otherwise transfer the Contribution of such Contributor, if
+ any, in source code and object code form. The patent license granted
+ by a Contributor shall also apply to the combination of the
+ Contribution of that Contributor and the Program if, at the time the
+ Contribution is added by the Contributor, such addition of the
+ Contribution causes such combination to be covered by the Licensed
+ Patents. The patent license granted by a Contributor shall not apply
+ to (i) any other combinations which include the Contribution, nor to
+ (ii) Contributions of other Contributors. No hardware per se is
+ licensed hereunder.
+
+ c. Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity. Each
+ Contributor disclaims any liability to Recipient for claims brought by
+ any other entity based on infringement of intellectual property rights
+ or otherwise. As a condition to exercising the rights and licenses
+ granted hereunder, each Recipient hereby assumes sole responsibility
+ to secure any other intellectual property rights needed, if any. For
+ example, if a third party patent license is required to allow
+ Recipient to distribute the Program, it is Recipient's responsibility
+ to acquire that license before distributing the Program.
+
+ d. Each Contributor represents that to its knowledge it has sufficient
+ copyright rights in its Contribution, if any, to grant the copyright
+ license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A. Distributor may choose to distribute the Program in any form under
+this Agreement or under its own license agreement, provided that:
+
+ a. it complies with the terms and conditions of this Agreement;
+
+ b. if the Program is distributed in source code or other tangible
+ form, a copy of this Agreement or Distributor's own license agreement
+ is included with each copy of the Program; and
+
+ c. if distributed under Distributor's own license agreement, such
+ license agreement:
+
+ i. effectively disclaims on behalf of all Contributors all warranties
+ and conditions, express and implied, including warranties or
+ conditions of title and non-infringement, and implied warranties or
+ conditions of merchantability and fitness for a particular purpose;
+ ii. effectively excludes on behalf of all Contributors all liability
+ for damages, including direct, indirect, special, incidental and
+ consequential damages, such as lost profits; and
+ iii. states that any provisions which differ from this Agreement are
+ offered by that Contributor alone and not by any other party.
+
+B. Each Distributor must include the following in a conspicuous
+ location in the Program:
+
+ Copyright (C) 2003, Lucent Technologies Inc. and others. All Rights
+ Reserved.
+
+C. In addition, each Contributor must identify itself as the
+originator of its Contribution in a manner that reasonably allows
+subsequent Recipients to identify the originator of the Contribution.
+Also, each Contributor must agree that the additions and/or changes
+are intended to be a Contribution. Once a Contribution is contributed,
+it may not thereafter be revoked.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain
+responsibilities with respect to end users, business partners and the
+like. While this license is intended to facilitate the commercial use
+of the Program, the Distributor who includes the Program in a
+commercial product offering should do so in a manner which does not
+create potential liability for Contributors. Therefore, if a
+Distributor includes the Program in a commercial product offering,
+such Distributor ("Commercial Distributor") hereby agrees to defend
+and indemnify every Contributor ("Indemnified Contributor") against
+any losses, damages and costs (collectively"Losses") arising from
+claims, lawsuits and other legal actions brought by a third party
+against the Indemnified Contributor to the extent caused by the acts
+or omissions of such Commercial Distributor in connection with its
+distribution of the Program in a commercial product offering. The
+obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement.
+In order to qualify, an Indemnified Contributor must: a) promptly
+notify the Commercial Distributor in writing of such claim, and b)
+allow the Commercial Distributor to control, and cooperate with the
+Commercial Distributor in, the defense and any related settlement
+negotiations. The Indemnified Contributor may participate in any such
+claim at its own expense.
+
+For example, a Distributor might include the Program in a commercial
+product offering, Product X. That Distributor is then a Commercial
+Distributor. If that Commercial Distributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Distributor's responsibility
+alone. Under this section, the Commercial Distributor would have to
+defend claims against the Contributors related to those performance
+claims and warranties, and if a court requires any Contributor to pay
+any damages as a result, the Commercial Distributor must pay those
+damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+PROVIDED ON AN"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
+responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its
+exercise of rights under this Agreement, including but not limited to
+the risks and costs of program errors, compliance with applicable
+laws, damage to or loss of data, programs or equipment, and
+unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), 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 OR
+DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. EXPORT CONTROL
+
+Recipient agrees that Recipient alone is responsible for compliance
+with the United States export administration regulations (and the
+export control laws and regulation of any other countries).
+
+8. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with
+respect to a patent applicable to software (including a cross-claim or
+counterclaim in a lawsuit), then any patent licenses granted by that
+Contributor to such Recipient under this Agreement shall terminate as
+of the date such litigation is filed. In addition, if Recipient
+institutes patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or
+hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and
+survive.
+
+LUCENT may publish new versions (including revisions) of this
+Agreement from time to time. Each new version of the Agreement will be
+given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the
+Agreement under which it was received. In addition, after a new
+version of the Agreement is published, Contributor may elect to
+distribute the Program (including its Contributions) under the new
+version. No one other than LUCENT has the right to modify this
+Agreement. Except as expressly stated in Sections 2(a) and 2(b) above,
+Recipient receives no rights or licenses to the intellectual property
+of any Contributor under this Agreement, whether expressly, by
+implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and
+the intellectual property laws of the United States of America. No
+party to this Agreement will bring a legal action under this Agreement
+more than one year after the cause of action arose. Each party waives
+its rights to a jury trial in any resulting litigation.
+
diff --git a/src/cmd/sam/Makefile b/src/cmd/sam/Makefile
new file mode 100644
index 00000000..f5e91463
--- /dev/null
+++ b/src/cmd/sam/Makefile
@@ -0,0 +1,18 @@
+H=errors.h mesg.h parse.h plumb.h sam.h
+SRC= address.c buff.c cmd.c disk.c error.c file.c io.c\
+ list.c mesg.c moveto.c multi.c unix.c rasp.c regexp.c\
+ sam.c shell.c string.c sys.c util.c xec.c plumb.c
+
+CC=gcc
+PREFIX=$(HOME)
+#PREFIX=/usr/local
+CFLAGS=-I. -I$(PREFIX)/include -O -g
+LDFLAGS=-L$(PREFIX)/lib
+LDLIBS=-l9 -lfmt -lutf
+
+all: sam
+sam: $(SRC) $(H)
+ $(CC) -o $@ $(CFLAGS) $(SRC) $(LDFLAGS) $(LDLIBS)
+clean:
+ rm -f *.o *~
+ rm -f sam
diff --git a/src/cmd/sam/address.c b/src/cmd/sam/address.c
new file mode 100644
index 00000000..85cca170
--- /dev/null
+++ b/src/cmd/sam/address.c
@@ -0,0 +1,240 @@
+#include "sam.h"
+#include "parse.h"
+
+Address addr;
+String lastpat;
+int patset;
+File *menu;
+
+File *matchfile(String*);
+Address charaddr(Posn, Address, int);
+
+Address
+address(Addr *ap, Address a, int sign)
+{
+ File *f = a.f;
+ Address a1, a2;
+
+ do{
+ switch(ap->type){
+ case 'l':
+ case '#':
+ a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
+ break;
+
+ case '.':
+ a = f->dot;
+ break;
+
+ case '$':
+ a.r.p1 = a.r.p2 = f->_.nc;
+ break;
+
+ case '\'':
+ a.r = f->mark;
+ break;
+
+ case '?':
+ sign = -sign;
+ if(sign == 0)
+ sign = -1;
+ /* fall through */
+ case '/':
+ nextmatch(f, ap->are, sign>=0? a.r.p2 : a.r.p1, sign);
+ a.r = sel.p[0];
+ break;
+
+ case '"':
+ a = matchfile(ap->are)->dot;
+ f = a.f;
+ if(f->unread)
+ load(f);
+ break;
+
+ case '*':
+ a.r.p1 = 0, a.r.p2 = f->_.nc;
+ return a;
+
+ case ',':
+ case ';':
+ if(ap->left)
+ a1 = address(ap->left, a, 0);
+ else
+ a1.f = a.f, a1.r.p1 = a1.r.p2 = 0;
+ if(ap->type == ';'){
+ f = a1.f;
+ a = a1;
+ f->dot = a1;
+ }
+ if(ap->next)
+ a2 = address(ap->next, a, 0);
+ else
+ a2.f = a.f, a2.r.p1 = a2.r.p2 = f->_.nc;
+ if(a1.f != a2.f)
+ error(Eorder);
+ a.f = a1.f, a.r.p1 = a1.r.p1, a.r.p2 = a2.r.p2;
+ if(a.r.p2 < a.r.p1)
+ error(Eorder);
+ return a;
+
+ case '+':
+ case '-':
+ sign = 1;
+ if(ap->type == '-')
+ sign = -1;
+ if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
+ a = lineaddr(1L, a, sign);
+ break;
+ default:
+ panic("address");
+ return a;
+ }
+ }while(ap = ap->next); /* assign = */
+ return a;
+}
+
+void
+nextmatch(File *f, String *r, Posn p, int sign)
+{
+ compile(r);
+ if(sign >= 0){
+ if(!execute(f, p, INFINITY))
+ error(Esearch);
+ if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p1==p){
+ if(++p>f->_.nc)
+ p = 0;
+ if(!execute(f, p, INFINITY))
+ panic("address");
+ }
+ }else{
+ if(!bexecute(f, p))
+ error(Esearch);
+ if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p2==p){
+ if(--p<0)
+ p = f->_.nc;
+ if(!bexecute(f, p))
+ panic("address");
+ }
+ }
+}
+
+File *
+matchfile(String *r)
+{
+ File *f;
+ File *match = 0;
+ int i;
+
+ for(i = 0; i<file.nused; i++){
+ f = file.filepptr[i];
+ if(f == cmd)
+ continue;
+ if(filematch(f, r)){
+ if(match)
+ error(Emanyfiles);
+ match = f;
+ }
+ }
+ if(!match)
+ error(Efsearch);
+ return match;
+}
+
+int
+filematch(File *f, String *r)
+{
+ char *c, buf[STRSIZE+100];
+ String *t;
+
+ c = Strtoc(&f->name);
+ sprint(buf, "%c%c%c %s\n", " '"[f->mod],
+ "-+"[f->rasp!=0], " ."[f==curfile], c);
+ free(c);
+ t = tmpcstr(buf);
+ Strduplstr(&genstr, t);
+ freetmpstr(t);
+ /* A little dirty... */
+ if(menu == 0)
+ menu = fileopen();
+ bufreset(menu);
+ bufinsert(menu, 0, genstr.s, genstr.n);
+ compile(r);
+ return execute(menu, 0, menu->_.nc);
+}
+
+Address
+charaddr(Posn l, Address addr, int sign)
+{
+ if(sign == 0)
+ addr.r.p1 = addr.r.p2 = l;
+ else if(sign < 0)
+ addr.r.p2 = addr.r.p1-=l;
+ else if(sign > 0)
+ addr.r.p1 = addr.r.p2+=l;
+ if(addr.r.p1<0 || addr.r.p2>addr.f->_.nc)
+ error(Erange);
+ return addr;
+}
+
+Address
+lineaddr(Posn l, Address addr, int sign)
+{
+ int n;
+ int c;
+ File *f = addr.f;
+ Address a;
+ Posn p;
+
+ a.f = f;
+ if(sign >= 0){
+ if(l == 0){
+ if(sign==0 || addr.r.p2==0){
+ a.r.p1 = a.r.p2 = 0;
+ return a;
+ }
+ a.r.p1 = addr.r.p2;
+ p = addr.r.p2-1;
+ }else{
+ if(sign==0 || addr.r.p2==0){
+ p = (Posn)0;
+ n = 1;
+ }else{
+ p = addr.r.p2-1;
+ n = filereadc(f, p++)=='\n';
+ }
+ while(n < l){
+ if(p >= f->_.nc)
+ error(Erange);
+ if(filereadc(f, p++) == '\n')
+ n++;
+ }
+ a.r.p1 = p;
+ }
+ while(p < f->_.nc && filereadc(f, p++)!='\n')
+ ;
+ a.r.p2 = p;
+ }else{
+ p = addr.r.p1;
+ if(l == 0)
+ a.r.p2 = addr.r.p1;
+ else{
+ for(n = 0; n<l; ){ /* always runs once */
+ if(p == 0){
+ if(++n != l)
+ error(Erange);
+ }else{
+ c = filereadc(f, p-1);
+ if(c != '\n' || ++n != l)
+ p--;
+ }
+ }
+ a.r.p2 = p;
+ if(p > 0)
+ p--;
+ }
+ while(p > 0 && filereadc(f, p-1)!='\n') /* lines start after a newline */
+ p--;
+ a.r.p1 = p;
+ }
+ return a;
+}
diff --git a/src/cmd/sam/buff.c b/src/cmd/sam/buff.c
new file mode 100644
index 00000000..30493c3e
--- /dev/null
+++ b/src/cmd/sam/buff.c
@@ -0,0 +1,302 @@
+#include "sam.h"
+
+enum
+{
+ Slop = 100, /* room to grow with reallocation */
+};
+
+static
+void
+sizecache(Buffer *b, uint n)
+{
+ if(n <= b->cmax)
+ return;
+ b->cmax = n+Slop;
+ b->c = runerealloc(b->c, b->cmax);
+}
+
+static
+void
+addblock(Buffer *b, uint i, uint n)
+{
+ if(i > b->nbl)
+ panic("internal error: addblock");
+
+ b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]);
+ if(i < b->nbl)
+ memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*));
+ b->bl[i] = disknewblock(disk, n);
+ b->nbl++;
+}
+
+
+static
+void
+delblock(Buffer *b, uint i)
+{
+ if(i >= b->nbl)
+ panic("internal error: delblock");
+
+ diskrelease(disk, b->bl[i]);
+ b->nbl--;
+ if(i < b->nbl)
+ memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*));
+ b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]);
+}
+
+/*
+ * Move cache so b->cq <= q0 < b->cq+b->cnc.
+ * If at very end, q0 will fall on end of cache block.
+ */
+
+static
+void
+flush(Buffer *b)
+{
+ if(b->cdirty || b->cnc==0){
+ if(b->cnc == 0)
+ delblock(b, b->cbi);
+ else
+ diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc);
+ b->cdirty = FALSE;
+ }
+}
+
+static
+void
+setcache(Buffer *b, uint q0)
+{
+ Block **blp, *bl;
+ uint i, q;
+
+ if(q0 > b->nc)
+ panic("internal error: setcache");
+ /*
+ * flush and reload if q0 is not in cache.
+ */
+ if(b->nc == 0 || (b->cq<=q0 && q0<b->cq+b->cnc))
+ return;
+ /*
+ * if q0 is at end of file and end of cache, continue to grow this block
+ */
+ if(q0==b->nc && q0==b->cq+b->cnc && b->cnc<=Maxblock)
+ return;
+ flush(b);
+ /* find block */
+ if(q0 < b->cq){
+ q = 0;
+ i = 0;
+ }else{
+ q = b->cq;
+ i = b->cbi;
+ }
+ blp = &b->bl[i];
+ while(q+(*blp)->_.n <= q0 && q+(*blp)->_.n < b->nc){
+ q += (*blp)->_.n;
+ i++;
+ blp++;
+ if(i >= b->nbl)
+ panic("block not found");
+ }
+ bl = *blp;
+ /* remember position */
+ b->cbi = i;
+ b->cq = q;
+ sizecache(b, bl->_.n);
+ b->cnc = bl->_.n;
+ /*read block*/
+ diskread(disk, bl, b->c, b->cnc);
+}
+
+void
+bufinsert(Buffer *b, uint q0, Rune *s, uint n)
+{
+ uint i, m, t, off;
+
+ if(q0 > b->nc)
+ panic("internal error: bufinsert");
+
+ while(n > 0){
+ setcache(b, q0);
+ off = q0-b->cq;
+ if(b->cnc+n <= Maxblock){
+ /* Everything fits in one block. */
+ t = b->cnc+n;
+ m = n;
+ if(b->bl == nil){ /* allocate */
+ if(b->cnc != 0)
+ panic("internal error: bufinsert1 cnc!=0");
+ addblock(b, 0, t);
+ b->cbi = 0;
+ }
+ sizecache(b, t);
+ runemove(b->c+off+m, b->c+off, b->cnc-off);
+ runemove(b->c+off, s, m);
+ b->cnc = t;
+ goto Tail;
+ }
+ /*
+ * We must make a new block. If q0 is at
+ * the very beginning or end of this block,
+ * just make a new block and fill it.
+ */
+ if(q0==b->cq || q0==b->cq+b->cnc){
+ if(b->cdirty)
+ flush(b);
+ m = min(n, Maxblock);
+ if(b->bl == nil){ /* allocate */
+ if(b->cnc != 0)
+ panic("internal error: bufinsert2 cnc!=0");
+ i = 0;
+ }else{
+ i = b->cbi;
+ if(q0 > b->cq)
+ i++;
+ }
+ addblock(b, i, m);
+ sizecache(b, m);
+ runemove(b->c, s, m);
+ b->cq = q0;
+ b->cbi = i;
+ b->cnc = m;
+ goto Tail;
+ }
+ /*
+ * Split the block; cut off the right side and
+ * let go of it.
+ */
+ m = b->cnc-off;
+ if(m > 0){
+ i = b->cbi+1;
+ addblock(b, i, m);
+ diskwrite(disk, &b->bl[i], b->c+off, m);
+ b->cnc -= m;
+ }
+ /*
+ * Now at end of block. Take as much input
+ * as possible and tack it on end of block.
+ */
+ m = min(n, Maxblock-b->cnc);
+ sizecache(b, b->cnc+m);
+ runemove(b->c+b->cnc, s, m);
+ b->cnc += m;
+ Tail:
+ b->nc += m;
+ q0 += m;
+ s += m;
+ n -= m;
+ b->cdirty = TRUE;
+ }
+}
+
+void
+bufdelete(Buffer *b, uint q0, uint q1)
+{
+ uint m, n, off;
+
+ if(!(q0<=q1 && q0<=b->nc && q1<=b->nc))
+ panic("internal error: bufdelete");
+ while(q1 > q0){
+ setcache(b, q0);
+ off = q0-b->cq;
+ if(q1 > b->cq+b->cnc)
+ n = b->cnc - off;
+ else
+ n = q1-q0;
+ m = b->cnc - (off+n);
+ if(m > 0)
+ runemove(b->c+off, b->c+off+n, m);
+ b->cnc -= n;
+ b->cdirty = TRUE;
+ q1 -= n;
+ b->nc -= n;
+ }
+}
+
+uint
+bufload(Buffer *b, uint q0, int fd, int *nulls)
+{
+ char *p;
+ Rune *r;
+ int l, m, n, nb, nr;
+ uint q1;
+
+ if(q0 > b->nc)
+ panic("internal error: bufload");
+ p = malloc((Maxblock+UTFmax+1)*sizeof p[0]);
+ if(p == nil)
+ panic("bufload: malloc failed");
+ r = runemalloc(Maxblock);
+ m = 0;
+ n = 1;
+ q1 = q0;
+ /*
+ * At top of loop, may have m bytes left over from
+ * last pass, possibly representing a partial rune.
+ */
+ while(n > 0){
+ n = read(fd, p+m, Maxblock);
+ if(n < 0){
+ error(Ebufload);
+ break;
+ }
+ m += n;
+ p[m] = 0;
+ l = m;
+ if(n > 0)
+ l -= UTFmax;
+ cvttorunes(p, l, r, &nb, &nr, nulls);
+ memmove(p, p+nb, m-nb);
+ m -= nb;
+ bufinsert(b, q1, r, nr);
+ q1 += nr;
+ }
+ free(p);
+ free(r);
+ return q1-q0;
+}
+
+void
+bufread(Buffer *b, uint q0, Rune *s, uint n)
+{
+ uint m;
+
+ if(!(q0<=b->nc && q0+n<=b->nc))
+ panic("bufread: internal error");
+
+ while(n > 0){
+ setcache(b, q0);
+ m = min(n, b->cnc-(q0-b->cq));
+ runemove(s, b->c+(q0-b->cq), m);
+ q0 += m;
+ s += m;
+ n -= m;
+ }
+}
+
+void
+bufreset(Buffer *b)
+{
+ int i;
+
+ b->nc = 0;
+ b->cnc = 0;
+ b->cq = 0;
+ b->cdirty = 0;
+ b->cbi = 0;
+ /* delete backwards to avoid n² behavior */
+ for(i=b->nbl-1; --i>=0; )
+ delblock(b, i);
+}
+
+void
+bufclose(Buffer *b)
+{
+ bufreset(b);
+ free(b->c);
+ b->c = nil;
+ b->cnc = 0;
+ free(b->bl);
+ b->bl = nil;
+ b->nbl = 0;
+}
diff --git a/src/cmd/sam/cmd.c b/src/cmd/sam/cmd.c
new file mode 100644
index 00000000..8c152f94
--- /dev/null
+++ b/src/cmd/sam/cmd.c
@@ -0,0 +1,594 @@
+#include "sam.h"
+#include "parse.h"
+
+static char linex[]="\n";
+static char wordx[]=" \t\n";
+struct cmdtab cmdtab[]={
+/* cmdc text regexp addr defcmd defaddr count token fn */
+ '\n', 0, 0, 0, 0, aDot, 0, 0, nl_cmd,
+ 'a', 1, 0, 0, 0, aDot, 0, 0, a_cmd,
+ 'b', 0, 0, 0, 0, aNo, 0, linex, b_cmd,
+ 'B', 0, 0, 0, 0, aNo, 0, linex, b_cmd,
+ 'c', 1, 0, 0, 0, aDot, 0, 0, c_cmd,
+ 'd', 0, 0, 0, 0, aDot, 0, 0, d_cmd,
+ 'D', 0, 0, 0, 0, aNo, 0, linex, D_cmd,
+ 'e', 0, 0, 0, 0, aNo, 0, wordx, e_cmd,
+ 'f', 0, 0, 0, 0, aNo, 0, wordx, f_cmd,
+ 'g', 0, 1, 0, 'p', aDot, 0, 0, g_cmd,
+ 'i', 1, 0, 0, 0, aDot, 0, 0, i_cmd,
+ 'k', 0, 0, 0, 0, aDot, 0, 0, k_cmd,
+ 'm', 0, 0, 1, 0, aDot, 0, 0, m_cmd,
+ 'n', 0, 0, 0, 0, aNo, 0, 0, n_cmd,
+ 'p', 0, 0, 0, 0, aDot, 0, 0, p_cmd,
+ 'q', 0, 0, 0, 0, aNo, 0, 0, q_cmd,
+ 'r', 0, 0, 0, 0, aDot, 0, wordx, e_cmd,
+ 's', 0, 1, 0, 0, aDot, 1, 0, s_cmd,
+ 't', 0, 0, 1, 0, aDot, 0, 0, m_cmd,
+ 'u', 0, 0, 0, 0, aNo, 2, 0, u_cmd,
+ 'v', 0, 1, 0, 'p', aDot, 0, 0, g_cmd,
+ 'w', 0, 0, 0, 0, aAll, 0, wordx, w_cmd,
+ 'x', 0, 1, 0, 'p', aDot, 0, 0, x_cmd,
+ 'y', 0, 1, 0, 'p', aDot, 0, 0, x_cmd,
+ 'X', 0, 1, 0, 'f', aNo, 0, 0, X_cmd,
+ 'Y', 0, 1, 0, 'f', aNo, 0, 0, X_cmd,
+ '!', 0, 0, 0, 0, aNo, 0, linex, plan9_cmd,
+ '>', 0, 0, 0, 0, aDot, 0, linex, plan9_cmd,
+ '<', 0, 0, 0, 0, aDot, 0, linex, plan9_cmd,
+ '|', 0, 0, 0, 0, aDot, 0, linex, plan9_cmd,
+ '=', 0, 0, 0, 0, aDot, 0, linex, eq_cmd,
+ 'c'|0x100,0, 0, 0, 0, aNo, 0, wordx, cd_cmd,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+Cmd *parsecmd(int);
+Addr *compoundaddr(void);
+Addr *simpleaddr(void);
+void freecmd(void);
+void okdelim(int);
+
+Rune line[BLOCKSIZE];
+Rune termline[BLOCKSIZE];
+Rune *linep = line;
+Rune *terminp = termline;
+Rune *termoutp = termline;
+List cmdlist;
+List addrlist;
+List relist;
+List stringlist;
+int eof;
+
+void
+resetcmd(void)
+{
+ linep = line;
+ *linep = 0;
+ terminp = termoutp = termline;
+ freecmd();
+}
+
+int
+inputc(void)
+{
+ int n, nbuf;
+ char buf[3];
+ Rune r;
+
+ Again:
+ nbuf = 0;
+ if(downloaded){
+ while(termoutp == terminp){
+ cmdupdate();
+ if(patset)
+ tellpat();
+ while(termlocked > 0){
+ outT0(Hunlock);
+ termlocked--;
+ }
+ if(rcv() == 0)
+ return -1;
+ }
+ r = *termoutp++;
+ if(termoutp == terminp)
+ terminp = termoutp = termline;
+ }else{
+ do{
+ n = read(0, buf+nbuf, 1);
+ if(n <= 0)
+ return -1;
+ nbuf += n;
+ }while(!fullrune(buf, nbuf));
+ chartorune(&r, buf);
+ }
+ if(r == 0){
+ warn(Wnulls);
+ goto Again;
+ }
+ return r;
+}
+
+int
+inputline(void)
+{
+ int i, c;
+
+ linep = line;
+ i = 0;
+ do{
+ if((c = inputc())<=0)
+ return -1;
+ if(i == (sizeof line)/RUNESIZE-1)
+ error(Etoolong);
+ }while((line[i++]=c) != '\n');
+ line[i] = 0;
+ return 1;
+}
+
+int
+getch(void)
+{
+ if(eof)
+ return -1;
+ if(*linep==0 && inputline()<0){
+ eof = TRUE;
+ return -1;
+ }
+ return *linep++;
+}
+
+int
+nextc(void)
+{
+ if(*linep == 0)
+ return -1;
+ return *linep;
+}
+
+void
+ungetch(void)
+{
+ if(--linep < line)
+ panic("ungetch");
+}
+
+Posn
+getnum(int signok)
+{
+ Posn n=0;
+ int c, sign;
+
+ sign = 1;
+ if(signok>1 && nextc()=='-'){
+ sign = -1;
+ getch();
+ }
+ if((c=nextc())<'0' || '9'<c) /* no number defaults to 1 */
+ return sign;
+ while('0'<=(c=getch()) && c<='9')
+ n = n*10 + (c-'0');
+ ungetch();
+ return sign*n;
+}
+
+int
+skipbl(void)
+{
+ int c;
+ do
+ c = getch();
+ while(c==' ' || c=='\t');
+ if(c >= 0)
+ ungetch();
+ return c;
+}
+
+void
+termcommand(void)
+{
+ Posn p;
+
+ for(p=cmdpt; p<cmd->_.nc; p++){
+ if(terminp >= &termline[BLOCKSIZE]){
+ cmdpt = cmd->_.nc;
+ error(Etoolong);
+ }
+ *terminp++ = filereadc(cmd, p);
+ }
+ cmdpt = cmd->_.nc;
+}
+
+void
+cmdloop(void)
+{
+ Cmd *cmdp;
+ File *ocurfile;
+ int loaded;
+
+ for(;;){
+ if(!downloaded && curfile && curfile->unread)
+ load(curfile);
+ if((cmdp = parsecmd(0))==0){
+ if(downloaded){
+ rescue();
+ exits("eof");
+ }
+ break;
+ }
+ ocurfile = curfile;
+ loaded = curfile && !curfile->unread;
+ if(cmdexec(curfile, cmdp) == 0)
+ break;
+ freecmd();
+ cmdupdate();
+ update();
+ if(downloaded && curfile &&
+ (ocurfile!=curfile || (!loaded && !curfile->unread)))
+ outTs(Hcurrent, curfile->tag);
+ /* don't allow type ahead on files that aren't bound */
+ if(downloaded && curfile && curfile->rasp == 0)
+ terminp = termoutp;
+ }
+}
+
+Cmd *
+newcmd(void){
+ Cmd *p;
+
+ p = emalloc(sizeof(Cmd));
+ inslist(&cmdlist, cmdlist.nused, (long)p);
+ return p;
+}
+
+Addr*
+newaddr(void)
+{
+ Addr *p;
+
+ p = emalloc(sizeof(Addr));
+ inslist(&addrlist, addrlist.nused, (long)p);
+ return p;
+}
+
+String*
+newre(void)
+{
+ String *p;
+
+ p = emalloc(sizeof(String));
+ inslist(&relist, relist.nused, (long)p);
+ Strinit(p);
+ return p;
+}
+
+String*
+newstring(void)
+{
+ String *p;
+
+ p = emalloc(sizeof(String));
+ inslist(&stringlist, stringlist.nused, (long)p);
+ Strinit(p);
+ return p;
+}
+
+void
+freecmd(void)
+{
+ int i;
+
+ while(cmdlist.nused > 0)
+ free(cmdlist.ucharpptr[--cmdlist.nused]);
+ while(addrlist.nused > 0)
+ free(addrlist.ucharpptr[--addrlist.nused]);
+ while(relist.nused > 0){
+ i = --relist.nused;
+ Strclose(relist.stringpptr[i]);
+ free(relist.stringpptr[i]);
+ }
+ while(stringlist.nused>0){
+ i = --stringlist.nused;
+ Strclose(stringlist.stringpptr[i]);
+ free(stringlist.stringpptr[i]);
+ }
+}
+
+int
+lookup(int c)
+{
+ int i;
+
+ for(i=0; cmdtab[i].cmdc; i++)
+ if(cmdtab[i].cmdc == c)
+ return i;
+ return -1;
+}
+
+void
+okdelim(int c)
+{
+ if(c=='\\' || ('a'<=c && c<='z')
+ || ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
+ error_c(Edelim, c);
+}
+
+void
+atnl(void)
+{
+ skipbl();
+ if(getch() != '\n')
+ error(Enewline);
+}
+
+void
+getrhs(String *s, int delim, int cmd)
+{
+ int c;
+
+ while((c = getch())>0 && c!=delim && c!='\n'){
+ if(c == '\\'){
+ if((c=getch()) <= 0)
+ error(Ebadrhs);
+ if(c == '\n'){
+ ungetch();
+ c='\\';
+ }else if(c == 'n')
+ c='\n';
+ else if(c!=delim && (cmd=='s' || c!='\\')) /* s does its own */
+ Straddc(s, '\\');
+ }
+ Straddc(s, c);
+ }
+ ungetch(); /* let client read whether delimeter, '\n' or whatever */
+}
+
+String *
+collecttoken(char *end)
+{
+ String *s = newstring();
+ int c;
+
+ while((c=nextc())==' ' || c=='\t')
+ Straddc(s, getch()); /* blanks significant for getname() */
+ while((c=getch())>0 && utfrune(end, c)==0)
+ Straddc(s, c);
+ Straddc(s, 0);
+ if(c != '\n')
+ atnl();
+ return s;
+}
+
+String *
+collecttext(void)
+{
+ String *s = newstring();
+ int begline, i, c, delim;
+
+ if(skipbl()=='\n'){
+ getch();
+ i = 0;
+ do{
+ begline = i;
+ while((c = getch())>0 && c!='\n')
+ i++, Straddc(s, c);
+ i++, Straddc(s, '\n');
+ if(c < 0)
+ goto Return;
+ }while(s->s[begline]!='.' || s->s[begline+1]!='\n');
+ Strdelete(s, s->n-2, s->n);
+ }else{
+ okdelim(delim = getch());
+ getrhs(s, delim, 'a');
+ if(nextc()==delim)
+ getch();
+ atnl();
+ }
+ Return:
+ Straddc(s, 0); /* JUST FOR CMDPRINT() */
+ return s;
+}
+
+Cmd *
+parsecmd(int nest)
+{
+ int i, c;
+ struct cmdtab *ct;
+ Cmd *cp, *ncp;
+ Cmd cmd;
+
+ cmd.next = cmd.ccmd = 0;
+ cmd.re = 0;
+ cmd.flag = cmd.num = 0;
+ cmd.addr = compoundaddr();
+ if(skipbl() == -1)
+ return 0;
+ if((c=getch())==-1)
+ return 0;
+ cmd.cmdc = c;
+ if(cmd.cmdc=='c' && nextc()=='d'){ /* sleazy two-character case */
+ getch(); /* the 'd' */
+ cmd.cmdc='c'|0x100;
+ }
+ i = lookup(cmd.cmdc);
+ if(i >= 0){
+ if(cmd.cmdc == '\n')
+ goto Return; /* let nl_cmd work it all out */
+ ct = &cmdtab[i];
+ if(ct->defaddr==aNo && cmd.addr)
+ error(Enoaddr);
+ if(ct->count)
+ cmd.num = getnum(ct->count);
+ if(ct->regexp){
+ /* x without pattern -> .*\n, indicated by cmd.re==0 */
+ /* X without pattern is all files */
+ if((ct->cmdc!='x' && ct->cmdc!='X') ||
+ ((c = nextc())!=' ' && c!='\t' && c!='\n')){
+ skipbl();
+ if((c = getch())=='\n' || c<0)
+ error(Enopattern);
+ okdelim(c);
+ cmd.re = getregexp(c);
+ if(ct->cmdc == 's'){
+ cmd.ctext = newstring();
+ getrhs(cmd.ctext, c, 's');
+ if(nextc() == c){
+ getch();
+ if(nextc() == 'g')
+ cmd.flag = getch();
+ }
+
+ }
+ }
+ }
+ if(ct->addr && (cmd.caddr=simpleaddr())==0)
+ error(Eaddress);
+ if(ct->defcmd){
+ if(skipbl() == '\n'){
+ getch();
+ cmd.ccmd = newcmd();
+ cmd.ccmd->cmdc = ct->defcmd;
+ }else if((cmd.ccmd = parsecmd(nest))==0)
+ panic("defcmd");
+ }else if(ct->text)
+ cmd.ctext = collecttext();
+ else if(ct->token)
+ cmd.ctext = collecttoken(ct->token);
+ else
+ atnl();
+ }else
+ switch(cmd.cmdc){
+ case '{':
+ cp = 0;
+ do{
+ if(skipbl()=='\n')
+ getch();
+ ncp = parsecmd(nest+1);
+ if(cp)
+ cp->next = ncp;
+ else
+ cmd.ccmd = ncp;
+ }while(cp = ncp);
+ break;
+ case '}':
+ atnl();
+ if(nest==0)
+ error(Enolbrace);
+ return 0;
+ default:
+ error_c(Eunk, cmd.cmdc);
+ }
+ Return:
+ cp = newcmd();
+ *cp = cmd;
+ return cp;
+}
+
+String* /* BUGGERED */
+getregexp(int delim)
+{
+ String *r = newre();
+ int c;
+
+ for(Strzero(&genstr); ; Straddc(&genstr, c))
+ if((c = getch())=='\\'){
+ if(nextc()==delim)
+ c = getch();
+ else if(nextc()=='\\'){
+ Straddc(&genstr, c);
+ c = getch();
+ }
+ }else if(c==delim || c=='\n')
+ break;
+ if(c!=delim && c)
+ ungetch();
+ if(genstr.n > 0){
+ patset = TRUE;
+ Strduplstr(&lastpat, &genstr);
+ Straddc(&lastpat, '\0');
+ }
+ if(lastpat.n <= 1)
+ error(Epattern);
+ Strduplstr(r, &lastpat);
+ return r;
+}
+
+Addr *
+simpleaddr(void)
+{
+ Addr addr;
+ Addr *ap, *nap;
+
+ addr.next = 0;
+ addr.left = 0;
+ switch(skipbl()){
+ case '#':
+ addr.type = getch();
+ addr.num = getnum(1);
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ addr.num = getnum(1);
+ addr.type='l';
+ break;
+ case '/': case '?': case '"':
+ addr.are = getregexp(addr.type = getch());
+ break;
+ case '.':
+ case '$':
+ case '+':
+ case '-':
+ case '\'':
+ addr.type = getch();
+ break;
+ default:
+ return 0;
+ }
+ if(addr.next = simpleaddr())
+ switch(addr.next->type){
+ case '.':
+ case '$':
+ case '\'':
+ if(addr.type!='"')
+ case '"':
+ error(Eaddress);
+ break;
+ case 'l':
+ case '#':
+ if(addr.type=='"')
+ break;
+ /* fall through */
+ case '/':
+ case '?':
+ if(addr.type!='+' && addr.type!='-'){
+ /* insert the missing '+' */
+ nap = newaddr();
+ nap->type='+';
+ nap->next = addr.next;
+ addr.next = nap;
+ }
+ break;
+ case '+':
+ case '-':
+ break;
+ default:
+ panic("simpleaddr");
+ }
+ ap = newaddr();
+ *ap = addr;
+ return ap;
+}
+
+Addr *
+compoundaddr(void)
+{
+ Addr addr;
+ Addr *ap, *next;
+
+ addr.left = simpleaddr();
+ if((addr.type = skipbl())!=',' && addr.type!=';')
+ return addr.left;
+ getch();
+ next = addr.next = compoundaddr();
+ if(next && (next->type==',' || next->type==';') && next->left==0)
+ error(Eaddress);
+ ap = newaddr();
+ *ap = addr;
+ return ap;
+}
diff --git a/src/cmd/sam/disk.c b/src/cmd/sam/disk.c
new file mode 100644
index 00000000..83b2553d
--- /dev/null
+++ b/src/cmd/sam/disk.c
@@ -0,0 +1,122 @@
+#include "sam.h"
+
+static Block *blist;
+
+#if 0
+static int
+tempdisk(void)
+{
+ char buf[128];
+ int i, fd;
+
+ snprint(buf, sizeof buf, "/tmp/X%d.%.4ssam", getpid(), getuser());
+ for(i='A'; i<='Z'; i++){
+ buf[5] = i;
+ if(access(buf, AEXIST) == 0)
+ continue;
+ fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600);
+ if(fd >= 0)
+ return fd;
+ }
+ return -1;
+}
+#else
+extern int tempdisk(void);
+#endif
+
+Disk*
+diskinit()
+{
+ Disk *d;
+
+ d = emalloc(sizeof(Disk));
+ d->fd = tempdisk();
+ if(d->fd < 0){
+ fprint(2, "sam: can't create temp file: %r\n");
+ exits("diskinit");
+ }
+ return d;
+}
+
+static
+uint
+ntosize(uint n, uint *ip)
+{
+ uint size;
+
+ if(n > Maxblock)
+ panic("internal error: ntosize");
+ size = n;
+ if(size & (Blockincr-1))
+ size += Blockincr - (size & (Blockincr-1));
+ /* last bucket holds blocks of exactly Maxblock */
+ if(ip)
+ *ip = size/Blockincr;
+ return size * sizeof(Rune);
+}
+
+Block*
+disknewblock(Disk *d, uint n)
+{
+ uint i, j, size;
+ Block *b;
+
+ size = ntosize(n, &i);
+ b = d->free[i];
+ if(b)
+ d->free[i] = b->_.next;
+ else{
+ /* allocate in chunks to reduce malloc overhead */
+ if(blist == nil){
+ blist = emalloc(100*sizeof(Block));
+ for(j=0; j<100-1; j++)
+ blist[j]._.next = &blist[j+1];
+ }
+ b = blist;
+ blist = b->_.next;
+ b->addr = d->addr;
+ d->addr += size;
+ }
+ b->_.n = n;
+ return b;
+}
+
+void
+diskrelease(Disk *d, Block *b)
+{
+ uint i;
+
+ ntosize(b->_.n, &i);
+ b->_.next = d->free[i];
+ d->free[i] = b;
+}
+
+void
+diskwrite(Disk *d, Block **bp, Rune *r, uint n)
+{
+ int size, nsize;
+ Block *b;
+
+ b = *bp;
+ size = ntosize(b->_.n, nil);
+ nsize = ntosize(n, nil);
+ if(size != nsize){
+ diskrelease(d, b);
+ b = disknewblock(d, n);
+ *bp = b;
+ }
+ if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
+ panic("write error to temp file");
+ b->_.n = n;
+}
+
+void
+diskread(Disk *d, Block *b, Rune *r, uint n)
+{
+ if(n > b->_.n)
+ panic("internal error: diskread");
+
+ ntosize(b->_.n, nil); /* called only for sanity check on Maxblock */
+ if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
+ panic("read error from temp file");
+}
diff --git a/src/cmd/sam/error.c b/src/cmd/sam/error.c
new file mode 100644
index 00000000..d19b9621
--- /dev/null
+++ b/src/cmd/sam/error.c
@@ -0,0 +1,144 @@
+#include "sam.h"
+
+static char *emsg[]={
+ /* error_s */
+ "can't open",
+ "can't create",
+ "not in menu:",
+ "changes to",
+ "I/O error:",
+ "can't write while changing:",
+ /* error_c */
+ "unknown command",
+ "no operand for",
+ "bad delimiter",
+ /* error */
+ "can't fork",
+ "interrupt",
+ "address",
+ "search",
+ "pattern",
+ "newline expected",
+ "blank expected",
+ "pattern expected",
+ "can't nest X or Y",
+ "unmatched `}'",
+ "command takes no address",
+ "addresses overlap",
+ "substitution",
+ "& match too long",
+ "bad \\ in rhs",
+ "address range",
+ "changes not in sequence",
+ "addresses out of order",
+ "no file name",
+ "unmatched `('",
+ "unmatched `)'",
+ "malformed `[]'",
+ "malformed regexp",
+ "reg. exp. list overflow",
+ "plan 9 command",
+ "can't pipe",
+ "no current file",
+ "string too long",
+ "changed files",
+ "empty string",
+ "file search",
+ "non-unique match for \"\"",
+ "tag match too long",
+ "too many subexpressions",
+ "temporary file too large",
+ "file is append-only",
+ "no destination for plumb message",
+ "internal read error in buffer load",
+};
+static char *wmsg[]={
+ /* warn_s */
+ "duplicate file name",
+ "no such file",
+ "write might change good version of",
+ /* warn_S */
+ "files might be aliased",
+ /* warn */
+ "null characters elided",
+ "can't run pwd",
+ "last char not newline",
+ "exit status not 0",
+};
+
+void
+error(Err s)
+{
+ char buf[512];
+
+ sprint(buf, "?%s", emsg[s]);
+ hiccough(buf);
+}
+
+void
+error_s(Err s, char *a)
+{
+ char buf[512];
+
+ sprint(buf, "?%s \"%s\"", emsg[s], a);
+ hiccough(buf);
+}
+
+void
+error_r(Err s, char *a)
+{
+ char buf[512];
+
+ sprint(buf, "?%s \"%s\": %r", emsg[s], a);
+ hiccough(buf);
+}
+
+void
+error_c(Err s, int c)
+{
+ char buf[512];
+
+ sprint(buf, "?%s `%C'", emsg[s], c);
+ hiccough(buf);
+}
+
+void
+warn(Warn s)
+{
+ dprint("?warning: %s\n", wmsg[s]);
+}
+
+void
+warn_S(Warn s, String *a)
+{
+ print_s(wmsg[s], a);
+}
+
+void
+warn_SS(Warn s, String *a, String *b)
+{
+ print_ss(wmsg[s], a, b);
+}
+
+void
+warn_s(Warn s, char *a)
+{
+ dprint("?warning: %s `%s'\n", wmsg[s], a);
+}
+
+void
+termwrite(char *s)
+{
+ String *p;
+
+ if(downloaded){
+ p = tmpcstr(s);
+ if(cmd)
+ loginsert(cmd, cmdpt, p->s, p->n);
+ else
+ Strinsert(&cmdstr, p, cmdstr.n);
+ cmdptadv += p->n;
+ free(p);
+ }else
+ Write(2, s, strlen(s));
+}
diff --git a/src/cmd/sam/errors.h b/src/cmd/sam/errors.h
new file mode 100644
index 00000000..7bf46ea1
--- /dev/null
+++ b/src/cmd/sam/errors.h
@@ -0,0 +1,65 @@
+typedef enum Err{
+ /* error_s */
+ Eopen,
+ Ecreate,
+ Emenu,
+ Emodified,
+ Eio,
+ Ewseq,
+ /* error_c */
+ Eunk,
+ Emissop,
+ Edelim,
+ /* error */
+ Efork,
+ Eintr,
+ Eaddress,
+ Esearch,
+ Epattern,
+ Enewline,
+ Eblank,
+ Enopattern,
+ EnestXY,
+ Enolbrace,
+ Enoaddr,
+ Eoverlap,
+ Enosub,
+ Elongrhs,
+ Ebadrhs,
+ Erange,
+ Esequence,
+ Eorder,
+ Enoname,
+ Eleftpar,
+ Erightpar,
+ Ebadclass,
+ Ebadregexp,
+ Eoverflow,
+ Enocmd,
+ Epipe,
+ Enofile,
+ Etoolong,
+ Echanges,
+ Eempty,
+ Efsearch,
+ Emanyfiles,
+ Elongtag,
+ Esubexp,
+ Etmpovfl,
+ Eappend,
+ Ecantplumb,
+ Ebufload,
+}Err;
+typedef enum Warn{
+ /* warn_s */
+ Wdupname,
+ Wfile,
+ Wdate,
+ /* warn_ss */
+ Wdupfile,
+ /* warn */
+ Wnulls,
+ Wpwd,
+ Wnotnewline,
+ Wbadstatus,
+}Warn;
diff --git a/src/cmd/sam/file.c b/src/cmd/sam/file.c
new file mode 100644
index 00000000..0428379d
--- /dev/null
+++ b/src/cmd/sam/file.c
@@ -0,0 +1,631 @@
+#include "sam.h"
+
+/*
+ * Structure of Undo list:
+ * The Undo structure follows any associated data, so the list
+ * can be read backwards: read the structure, then read whatever
+ * data is associated (insert string, file name) and precedes it.
+ * The structure includes the previous value of the modify bit
+ * and a sequence number; successive Undo structures with the
+ * same sequence number represent simultaneous changes.
+ */
+
+typedef struct Undo Undo;
+typedef struct Merge Merge;
+
+struct Undo
+{
+ short type; /* Delete, Insert, Filename, Dot, Mark */
+ short mod; /* modify bit */
+ uint seq; /* sequence number */
+ uint p0; /* location of change (unused in f) */
+ uint n; /* # runes in string or file name */
+};
+
+struct Merge
+{
+ File *f;
+ uint seq; /* of logged change */
+ uint p0; /* location of change (unused in f) */
+ uint n; /* # runes to delete */
+ uint nbuf; /* # runes to insert */
+ Rune buf[RBUFSIZE];
+};
+
+enum
+{
+ Maxmerge = 50,
+ Undosize = sizeof(Undo)/sizeof(Rune),
+};
+
+static Merge merge;
+
+File*
+fileopen(void)
+{
+ File *f;
+
+ f = emalloc(sizeof(File));
+ f->dot.f = f;
+ f->ndot.f = f;
+ f->seq = 0;
+ f->mod = FALSE;
+ f->unread = TRUE;
+ Strinit0(&f->name);
+ return f;
+}
+
+int
+fileisdirty(File *f)
+{
+ return f->seq != f->cleanseq;
+}
+
+static void
+wrinsert(Buffer *delta, int seq, int mod, uint p0, Rune *s, uint ns)
+{
+ Undo u;
+
+ u.type = Insert;
+ u.mod = mod;
+ u.seq = seq;
+ u.p0 = p0;
+ u.n = ns;
+ bufinsert(delta, delta->nc, s, ns);
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+static void
+wrdelete(Buffer *delta, int seq, int mod, uint p0, uint p1)
+{
+ Undo u;
+
+ u.type = Delete;
+ u.mod = mod;
+ u.seq = seq;
+ u.p0 = p0;
+ u.n = p1 - p0;
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+void
+flushmerge(void)
+{
+ File *f;
+
+ f = merge.f;
+ if(f == nil)
+ return;
+ if(merge.seq != f->seq)
+ panic("flushmerge seq mismatch");
+ if(merge.n != 0)
+ wrdelete(&f->epsilon, f->seq, TRUE, merge.p0, merge.p0+merge.n);
+ if(merge.nbuf != 0)
+ wrinsert(&f->epsilon, f->seq, TRUE, merge.p0+merge.n, merge.buf, merge.nbuf);
+ merge.f = nil;
+ merge.n = 0;
+ merge.nbuf = 0;
+}
+
+void
+mergeextend(File *f, uint p0)
+{
+ uint mp0n;
+
+ mp0n = merge.p0+merge.n;
+ if(mp0n != p0){
+ bufread(f, mp0n, merge.buf+merge.nbuf, p0-mp0n);
+ merge.nbuf += p0-mp0n;
+ merge.n = p0-merge.p0;
+ }
+}
+
+/*
+ * like fileundelete, but get the data from arguments
+ */
+void
+loginsert(File *f, uint p0, Rune *s, uint ns)
+{
+ if(f->rescuing)
+ return;
+ if(ns == 0)
+ return;
+ if(ns<0 || ns>STRSIZE)
+ panic("loginsert");
+ if(f->seq < seq)
+ filemark(f);
+ if(p0 < f->hiposn)
+ error(Esequence);
+
+ if(merge.f != f
+ || p0-(merge.p0+merge.n)>Maxmerge /* too far */
+ || merge.nbuf+((p0+ns)-(merge.p0+merge.n))>RBUFSIZE) /* too long */
+ flushmerge();
+
+ if(ns>=RBUFSIZE){
+ if(!(merge.n == 0 && merge.nbuf == 0 && merge.f == nil))
+ panic("loginsert bad merge state");
+ wrinsert(&f->epsilon, f->seq, TRUE, p0, s, ns);
+ }else{
+ if(merge.f != f){
+ merge.f = f;
+ merge.p0 = p0;
+ merge.seq = f->seq;
+ }
+ mergeextend(f, p0);
+
+ /* append string to merge */
+ runemove(merge.buf+merge.nbuf, s, ns);
+ merge.nbuf += ns;
+ }
+
+ f->hiposn = p0;
+ if(!f->unread && !f->mod)
+ state(f, Dirty);
+}
+
+void
+logdelete(File *f, uint p0, uint p1)
+{
+ if(f->rescuing)
+ return;
+ if(p0 == p1)
+ return;
+ if(f->seq < seq)
+ filemark(f);
+ if(p0 < f->hiposn)
+ error(Esequence);
+
+ if(merge.f != f
+ || p0-(merge.p0+merge.n)>Maxmerge /* too far */
+ || merge.nbuf+(p0-(merge.p0+merge.n))>RBUFSIZE){ /* too long */
+ flushmerge();
+ merge.f = f;
+ merge.p0 = p0;
+ merge.seq = f->seq;
+ }
+
+ mergeextend(f, p0);
+
+ /* add to deletion */
+ merge.n = p1-merge.p0;
+
+ f->hiposn = p1;
+ if(!f->unread && !f->mod)
+ state(f, Dirty);
+}
+
+/*
+ * like fileunsetname, but get the data from arguments
+ */
+void
+logsetname(File *f, String *s)
+{
+ Undo u;
+ Buffer *delta;
+
+ if(f->rescuing)
+ return;
+
+ if(f->unread){ /* This is setting initial file name */
+ filesetname(f, s);
+ return;
+ }
+
+ if(f->seq < seq)
+ filemark(f);
+
+ /* undo a file name change by restoring old name */
+ delta = &f->epsilon;
+ u.type = Filename;
+ u.mod = TRUE;
+ u.seq = f->seq;
+ u.p0 = 0; /* unused */
+ u.n = s->n;
+ if(s->n)
+ bufinsert(delta, delta->nc, s->s, s->n);
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+ if(!f->unread && !f->mod)
+ state(f, Dirty);
+}
+
+#ifdef NOTEXT
+File*
+fileaddtext(File *f, Text *t)
+{
+ if(f == nil){
+ f = emalloc(sizeof(File));
+ f->unread = TRUE;
+ }
+ f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*));
+ f->text[f->ntext++] = t;
+ f->curtext = t;
+ return f;
+}
+
+void
+filedeltext(File *f, Text *t)
+{
+ int i;
+
+ for(i=0; i<f->ntext; i++)
+ if(f->text[i] == t)
+ goto Found;
+ panic("can't find text in filedeltext");
+
+ Found:
+ f->ntext--;
+ if(f->ntext == 0){
+ fileclose(f);
+ return;
+ }
+ memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*));
+ if(f->curtext == t)
+ f->curtext = f->text[0];
+}
+#endif
+
+void
+fileinsert(File *f, uint p0, Rune *s, uint ns)
+{
+ if(p0 > f->_.nc)
+ panic("internal error: fileinsert");
+ if(f->seq > 0)
+ fileuninsert(f, &f->delta, p0, ns);
+ bufinsert(f, p0, s, ns);
+ if(ns)
+ f->mod = TRUE;
+}
+
+void
+fileuninsert(File *f, Buffer *delta, uint p0, uint ns)
+{
+ Undo u;
+
+ /* undo an insertion by deleting */
+ u.type = Delete;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = p0;
+ u.n = ns;
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+void
+filedelete(File *f, uint p0, uint p1)
+{
+ if(!(p0<=p1 && p0<=f->_.nc && p1<=f->_.nc))
+ panic("internal error: filedelete");
+ if(f->seq > 0)
+ fileundelete(f, &f->delta, p0, p1);
+ bufdelete(f, p0, p1);
+ if(p1 > p0)
+ f->mod = TRUE;
+}
+
+void
+fileundelete(File *f, Buffer *delta, uint p0, uint p1)
+{
+ Undo u;
+ Rune *buf;
+ uint i, n;
+
+ /* undo a deletion by inserting */
+ u.type = Insert;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = p0;
+ u.n = p1-p0;
+ buf = fbufalloc();
+ for(i=p0; i<p1; i+=n){
+ n = p1 - i;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(f, i, buf, n);
+ bufinsert(delta, delta->nc, buf, n);
+ }
+ fbuffree(buf);
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+
+}
+
+int
+filereadc(File *f, uint q)
+{
+ Rune r;
+
+ if(q >= f->_.nc)
+ return -1;
+ bufread(f, q, &r, 1);
+ return r;
+}
+
+void
+filesetname(File *f, String *s)
+{
+ if(!f->unread) /* This is setting initial file name */
+ fileunsetname(f, &f->delta);
+ Strduplstr(&f->name, s);
+ sortname(f);
+ f->unread = TRUE;
+}
+
+void
+fileunsetname(File *f, Buffer *delta)
+{
+ String s;
+ Undo u;
+
+ /* undo a file name change by restoring old name */
+ u.type = Filename;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = 0; /* unused */
+ Strinit(&s);
+ Strduplstr(&s, &f->name);
+ fullname(&s);
+ u.n = s.n;
+ if(s.n)
+ bufinsert(delta, delta->nc, s.s, s.n);
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+ Strclose(&s);
+}
+
+void
+fileunsetdot(File *f, Buffer *delta, Range dot)
+{
+ Undo u;
+
+ u.type = Dot;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = dot.p1;
+ u.n = dot.p2 - dot.p1;
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+void
+fileunsetmark(File *f, Buffer *delta, Range mark)
+{
+ Undo u;
+
+ u.type = Mark;
+ u.mod = f->mod;
+ u.seq = f->seq;
+ u.p0 = mark.p1;
+ u.n = mark.p2 - mark.p1;
+ bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
+}
+
+uint
+fileload(File *f, uint p0, int fd, int *nulls)
+{
+ if(f->seq > 0)
+ panic("undo in file.load unimplemented");
+ return bufload(f, p0, fd, nulls);
+}
+
+int
+fileupdate(File *f, int notrans, int toterm)
+{
+ uint p1, p2;
+ int mod;
+
+ if(f->rescuing)
+ return FALSE;
+
+ flushmerge();
+
+ /*
+ * fix the modification bit
+ * subtle point: don't save it away in the log.
+ *
+ * if another change is made, the correct f->mod
+ * state is saved in the undo log by filemark
+ * when setting the dot and mark.
+ *
+ * if the change is undone, the correct state is
+ * saved from f in the fileun... routines.
+ */
+ mod = f->mod;
+ f->mod = f->prevmod;
+ if(f == cmd)
+ notrans = TRUE;
+ else{
+ fileunsetdot(f, &f->delta, f->prevdot);
+ fileunsetmark(f, &f->delta, f->prevmark);
+ }
+ f->dot = f->ndot;
+ fileundo(f, FALSE, !notrans, &p1, &p2, toterm);
+ f->mod = mod;
+
+ if(f->delta.nc == 0)
+ f->seq = 0;
+
+ if(f == cmd)
+ return FALSE;
+
+ if(f->mod){
+ f->closeok = 0;
+ quitok = 0;
+ }else
+ f->closeok = 1;
+ return TRUE;
+}
+
+long
+prevseq(Buffer *b)
+{
+ Undo u;
+ uint up;
+
+ up = b->nc;
+ if(up == 0)
+ return 0;
+ up -= Undosize;
+ bufread(b, up, (Rune*)&u, Undosize);
+ return u.seq;
+}
+
+long
+undoseq(File *f, int isundo)
+{
+ if(isundo)
+ return f->seq;
+
+ return prevseq(&f->epsilon);
+}
+
+void
+fileundo(File *f, int isundo, int canredo, uint *q0p, uint *q1p, int flag)
+{
+ Undo u;
+ Rune *buf;
+ uint i, n, up;
+ uint stop;
+ Buffer *delta, *epsilon;
+
+ if(isundo){
+ /* undo; reverse delta onto epsilon, seq decreases */
+ delta = &f->delta;
+ epsilon = &f->epsilon;
+ stop = f->seq;
+ }else{
+ /* redo; reverse epsilon onto delta, seq increases */
+ delta = &f->epsilon;
+ epsilon = &f->delta;
+ stop = 0; /* don't know yet */
+ }
+
+ raspstart(f);
+ while(delta->nc > 0){
+ up = delta->nc-Undosize;
+ bufread(delta, up, (Rune*)&u, Undosize);
+ if(isundo){
+ if(u.seq < stop){
+ f->seq = u.seq;
+ raspdone(f, flag);
+ return;
+ }
+ }else{
+ if(stop == 0)
+ stop = u.seq;
+ if(u.seq > stop){
+ raspdone(f, flag);
+ return;
+ }
+ }
+ switch(u.type){
+ default:
+ panic("undo unknown u.type");
+ break;
+
+ case Delete:
+ f->seq = u.seq;
+ if(canredo)
+ fileundelete(f, epsilon, u.p0, u.p0+u.n);
+ f->mod = u.mod;
+ bufdelete(f, u.p0, u.p0+u.n);
+ raspdelete(f, u.p0, u.p0+u.n, flag);
+ *q0p = u.p0;
+ *q1p = u.p0;
+ break;
+
+ case Insert:
+ f->seq = u.seq;
+ if(canredo)
+ fileuninsert(f, epsilon, u.p0, u.n);
+ f->mod = u.mod;
+ up -= u.n;
+ buf = fbufalloc();
+ for(i=0; i<u.n; i+=n){
+ n = u.n - i;
+ if(n > RBUFSIZE)
+ n = RBUFSIZE;
+ bufread(delta, up+i, buf, n);
+ bufinsert(f, u.p0+i, buf, n);
+ raspinsert(f, u.p0+i, buf, n, flag);
+ }
+ fbuffree(buf);
+ *q0p = u.p0;
+ *q1p = u.p0+u.n;
+ break;
+
+ case Filename:
+ f->seq = u.seq;
+ if(canredo)
+ fileunsetname(f, epsilon);
+ f->mod = u.mod;
+ up -= u.n;
+
+ Strinsure(&f->name, u.n+1);
+ bufread(delta, up, f->name.s, u.n);
+ f->name.s[u.n] = 0;
+ f->name.n = u.n;
+ fixname(&f->name);
+ sortname(f);
+ break;
+ case Dot:
+ f->seq = u.seq;
+ if(canredo)
+ fileunsetdot(f, epsilon, f->dot.r);
+ f->mod = u.mod;
+ f->dot.r.p1 = u.p0;
+ f->dot.r.p2 = u.p0 + u.n;
+ break;
+ case Mark:
+ f->seq = u.seq;
+ if(canredo)
+ fileunsetmark(f, epsilon, f->mark);
+ f->mod = u.mod;
+ f->mark.p1 = u.p0;
+ f->mark.p2 = u.p0 + u.n;
+ break;
+ }
+ bufdelete(delta, up, delta->nc);
+ }
+ if(isundo)
+ f->seq = 0;
+ raspdone(f, flag);
+}
+
+void
+filereset(File *f)
+{
+ bufreset(&f->delta);
+ bufreset(&f->epsilon);
+ f->seq = 0;
+}
+
+void
+fileclose(File *f)
+{
+ Strclose(&f->name);
+ bufclose(f);
+ bufclose(&f->delta);
+ bufclose(&f->epsilon);
+ if(f->rasp)
+ listfree(f->rasp);
+ free(f);
+}
+
+void
+filemark(File *f)
+{
+
+ if(f->unread)
+ return;
+ if(f->epsilon.nc)
+ bufdelete(&f->epsilon, 0, f->epsilon.nc);
+
+ if(f != cmd){
+ f->prevdot = f->dot.r;
+ f->prevmark = f->mark;
+ f->prevseq = f->seq;
+ f->prevmod = f->mod;
+ }
+
+ f->ndot = f->dot;
+ f->seq = seq;
+ f->hiposn = 0;
+}
diff --git a/src/cmd/sam/io.c b/src/cmd/sam/io.c
new file mode 100644
index 00000000..236090a0
--- /dev/null
+++ b/src/cmd/sam/io.c
@@ -0,0 +1,262 @@
+#include "sam.h"
+
+#define NSYSFILE 3
+#define NOFILE 128
+
+void
+checkqid(File *f)
+{
+ int i, w;
+ File *g;
+
+ w = whichmenu(f);
+ for(i=1; i<file.nused; i++){
+ g = file.filepptr[i];
+ if(w == i)
+ continue;
+ if(f->dev==g->dev && f->qidpath==g->qidpath)
+ warn_SS(Wdupfile, &f->name, &g->name);
+ }
+}
+
+void
+writef(File *f)
+{
+ Posn n;
+ char *name;
+ int i, samename, newfile;
+ ulong dev;
+ uvlong qid;
+ long mtime, appendonly, length;
+
+ newfile = 0;
+ samename = Strcmp(&genstr, &f->name) == 0;
+ name = Strtoc(&f->name);
+ i = statfile(name, &dev, &qid, &mtime, 0, 0);
+ if(i == -1)
+ newfile++;
+ else if(samename &&
+ (f->dev!=dev || f->qidpath!=qid || f->mtime<mtime)){
+ f->dev = dev;
+ f->qidpath = qid;
+ f->mtime = mtime;
+ warn_S(Wdate, &genstr);
+ return;
+ }
+ if(genc)
+ free(genc);
+ genc = Strtoc(&genstr);
+ if((io=create(genc, 1, 0666L)) < 0)
+ error_r(Ecreate, genc);
+ dprint("%s: ", genc);
+ if(statfd(io, 0, 0, 0, &length, &appendonly) > 0 && appendonly && length>0)
+ error(Eappend);
+ n = writeio(f);
+ if(f->name.s[0]==0 || samename){
+ if(addr.r.p1==0 && addr.r.p2==f->_.nc)
+ f->cleanseq = f->seq;
+ state(f, f->cleanseq==f->seq? Clean : Dirty);
+ }
+ if(newfile)
+ dprint("(new file) ");
+ if(addr.r.p2>0 && filereadc(f, addr.r.p2-1)!='\n')
+ warn(Wnotnewline);
+ closeio(n);
+ if(f->name.s[0]==0 || samename){
+ if(statfile(name, &dev, &qid, &mtime, 0, 0) > 0){
+ f->dev = dev;
+ f->qidpath = qid;
+ f->mtime = mtime;
+ checkqid(f);
+ }
+ }
+}
+
+Posn
+readio(File *f, int *nulls, int setdate, int toterm)
+{
+ int n, b, w;
+ Rune *r;
+ Posn nt;
+ Posn p = addr.r.p2;
+ ulong dev;
+ uvlong qid;
+ long mtime;
+ char buf[BLOCKSIZE+1], *s;
+
+ *nulls = FALSE;
+ b = 0;
+ if(f->unread){
+ nt = bufload(f, 0, io, nulls);
+ if(toterm)
+ raspload(f);
+ }else
+ for(nt = 0; (n = read(io, buf+b, BLOCKSIZE-b))>0; nt+=(r-genbuf)){
+ n += b;
+ b = 0;
+ r = genbuf;
+ s = buf;
+ while(n > 0){
+ if((*r = *(uchar*)s) < Runeself){
+ if(*r)
+ r++;
+ else
+ *nulls = TRUE;
+ --n;
+ s++;
+ continue;
+ }
+ if(fullrune(s, n)){
+ w = chartorune(r, s);
+ if(*r)
+ r++;
+ else
+ *nulls = TRUE;
+ n -= w;
+ s += w;
+ continue;
+ }
+ b = n;
+ memmove(buf, s, b);
+ break;
+ }
+ loginsert(f, p, genbuf, r-genbuf);
+ }
+ if(b)
+ *nulls = TRUE;
+ if(*nulls)
+ warn(Wnulls);
+ if(setdate){
+ if(statfd(io, &dev, &qid, &mtime, 0, 0) > 0){
+ f->dev = dev;
+ f->qidpath = qid;
+ f->mtime = mtime;
+ checkqid(f);
+ }
+ }
+ return nt;
+}
+
+Posn
+writeio(File *f)
+{
+ int m, n;
+ Posn p = addr.r.p1;
+ char *c;
+
+ while(p < addr.r.p2){
+ if(addr.r.p2-p>BLOCKSIZE)
+ n = BLOCKSIZE;
+ else
+ n = addr.r.p2-p;
+ bufread(f, p, genbuf, n);
+ c = Strtoc(tmprstr(genbuf, n));
+ m = strlen(c);
+ if(Write(io, c, m) != m){
+ free(c);
+ if(p > 0)
+ p += n;
+ break;
+ }
+ free(c);
+ p += n;
+ }
+ return p-addr.r.p1;
+}
+void
+closeio(Posn p)
+{
+ close(io);
+ io = 0;
+ if(p >= 0)
+ dprint("#%lud\n", p);
+}
+
+int remotefd0 = 0;
+int remotefd1 = 1;
+
+void
+bootterm(char *machine, char **argv, char **end)
+{
+ int ph2t[2], pt2h[2];
+
+ if(machine){
+ dup(remotefd0, 0);
+ dup(remotefd1, 1);
+ close(remotefd0);
+ close(remotefd1);
+ argv[0] = "samterm";
+ *end = 0;
+ exec(samterm, argv);
+ fprint(2, "can't exec: ");
+ perror(samterm);
+ _exits("damn");
+ }
+ if(pipe(ph2t)==-1 || pipe(pt2h)==-1)
+ panic("pipe");
+ switch(fork()){
+ case 0:
+ dup(ph2t[0], 0);
+ dup(pt2h[1], 1);
+ close(ph2t[0]);
+ close(ph2t[1]);
+ close(pt2h[0]);
+ close(pt2h[1]);
+ argv[0] = "samterm";
+ *end = 0;
+ exec(samterm, argv);
+ fprint(2, "can't exec: ");
+ perror(samterm);
+ _exits("damn");
+ case -1:
+ panic("can't fork samterm");
+ }
+ dup(pt2h[0], 0);
+ dup(ph2t[1], 1);
+ close(ph2t[0]);
+ close(ph2t[1]);
+ close(pt2h[0]);
+ close(pt2h[1]);
+}
+
+void
+connectto(char *machine)
+{
+ int p1[2], p2[2];
+
+ if(pipe(p1)<0 || pipe(p2)<0){
+ dprint("can't pipe\n");
+ exits("pipe");
+ }
+ remotefd0 = p1[0];
+ remotefd1 = p2[1];
+ switch(fork()){
+ case 0:
+ dup(p2[0], 0);
+ dup(p1[1], 1);
+ close(p1[0]);
+ close(p1[1]);
+ close(p2[0]);
+ close(p2[1]);
+ execl(RXPATH, RX, machine, rsamname, "-R", (char*)0);
+ dprint("can't exec %s\n", RXPATH);
+ exits("exec");
+
+ case -1:
+ dprint("can't fork\n");
+ exits("fork");
+ }
+ close(p1[1]);
+ close(p2[0]);
+}
+
+void
+startup(char *machine, int Rflag, char **argv, char **end)
+{
+ if(machine)
+ connectto(machine);
+ if(!Rflag)
+ bootterm(machine, argv, end);
+ downloaded = 1;
+ outTs(Hversion, VERSION);
+}
diff --git a/src/cmd/sam/list.c b/src/cmd/sam/list.c
new file mode 100644
index 00000000..a8105425
--- /dev/null
+++ b/src/cmd/sam/list.c
@@ -0,0 +1,47 @@
+#include "sam.h"
+
+/*
+ * Check that list has room for one more element.
+ */
+void
+growlist(List *l)
+{
+ if(l->listptr==0 || l->nalloc==0){
+ l->nalloc = INCR;
+ l->listptr = emalloc(INCR*sizeof(long));
+ l->nused = 0;
+ }else if(l->nused == l->nalloc){
+ l->listptr = erealloc(l->listptr, (l->nalloc+INCR)*sizeof(long));
+ memset((void*)(l->longptr+l->nalloc), 0, INCR*sizeof(long));
+ l->nalloc += INCR;
+ }
+}
+
+/*
+ * Remove the ith element from the list
+ */
+void
+dellist(List *l, int i)
+{
+ memmove(&l->longptr[i], &l->longptr[i+1], (l->nused-(i+1))*sizeof(long));
+ l->nused--;
+}
+
+/*
+ * Add a new element, whose position is i, to the list
+ */
+void
+inslist(List *l, int i, long val)
+{
+ growlist(l);
+ memmove(&l->longptr[i+1], &l->longptr[i], (l->nused-i)*sizeof(long));
+ l->longptr[i] = val;
+ l->nused++;
+}
+
+void
+listfree(List *l)
+{
+ free(l->listptr);
+ free(l);
+}
diff --git a/src/cmd/sam/mesg.c b/src/cmd/sam/mesg.c
new file mode 100644
index 00000000..189c11ac
--- /dev/null
+++ b/src/cmd/sam/mesg.c
@@ -0,0 +1,821 @@
+#include "sam.h"
+
+Header h;
+uchar indata[DATASIZE];
+uchar outdata[2*DATASIZE+3]; /* room for overflow message */
+uchar *inp;
+uchar *outp;
+uchar *outmsg = outdata;
+Posn cmdpt;
+Posn cmdptadv;
+Buffer snarfbuf;
+int waitack;
+int noflush;
+int tversion;
+
+long inlong(void);
+long invlong(void);
+int inshort(void);
+int inmesg(Tmesg);
+void setgenstr(File*, Posn, Posn);
+
+#ifdef DEBUG
+char *hname[] = {
+ [Hversion] "Hversion",
+ [Hbindname] "Hbindname",
+ [Hcurrent] "Hcurrent",
+ [Hnewname] "Hnewname",
+ [Hmovname] "Hmovname",
+ [Hgrow] "Hgrow",
+ [Hcheck0] "Hcheck0",
+ [Hcheck] "Hcheck",
+ [Hunlock] "Hunlock",
+ [Hdata] "Hdata",
+ [Horigin] "Horigin",
+ [Hunlockfile] "Hunlockfile",
+ [Hsetdot] "Hsetdot",
+ [Hgrowdata] "Hgrowdata",
+ [Hmoveto] "Hmoveto",
+ [Hclean] "Hclean",
+ [Hdirty] "Hdirty",
+ [Hcut] "Hcut",
+ [Hsetpat] "Hsetpat",
+ [Hdelname] "Hdelname",
+ [Hclose] "Hclose",
+ [Hsetsnarf] "Hsetsnarf",
+ [Hsnarflen] "Hsnarflen",
+ [Hack] "Hack",
+ [Hexit] "Hexit",
+ [Hplumb] "Hplumb",
+};
+
+char *tname[] = {
+ [Tversion] "Tversion",
+ [Tstartcmdfile] "Tstartcmdfile",
+ [Tcheck] "Tcheck",
+ [Trequest] "Trequest",
+ [Torigin] "Torigin",
+ [Tstartfile] "Tstartfile",
+ [Tworkfile] "Tworkfile",
+ [Ttype] "Ttype",
+ [Tcut] "Tcut",
+ [Tpaste] "Tpaste",
+ [Tsnarf] "Tsnarf",
+ [Tstartnewfile] "Tstartnewfile",
+ [Twrite] "Twrite",
+ [Tclose] "Tclose",
+ [Tlook] "Tlook",
+ [Tsearch] "Tsearch",
+ [Tsend] "Tsend",
+ [Tdclick] "Tdclick",
+ [Tstartsnarf] "Tstartsnarf",
+ [Tsetsnarf] "Tsetsnarf",
+ [Tack] "Tack",
+ [Texit] "Texit",
+ [Tplumb] "Tplumb",
+};
+
+void
+journal(int out, char *s)
+{
+ static int fd = 0;
+
+ if(fd <= 0)
+ fd = create("/tmp/sam.out", 1, 0666L);
+ fprint(fd, "%s%s\n", out? "out: " : "in: ", s);
+}
+
+void
+journaln(int out, long n)
+{
+ char buf[32];
+
+ sprint(buf, "%ld", n);
+ journal(out, buf);
+}
+#else
+#define journal(a, b)
+#define journaln(a, b)
+#endif
+
+int
+rcvchar(void){
+ static uchar buf[64];
+ static i, nleft = 0;
+
+ if(nleft <= 0){
+ nleft = read(0, (char *)buf, sizeof buf);
+ if(nleft <= 0)
+ return -1;
+ i = 0;
+ }
+ --nleft;
+ return buf[i++];
+}
+
+int
+rcv(void){
+ int c;
+ static state = 0;
+ static count = 0;
+ static i = 0;
+
+ while((c=rcvchar()) != -1)
+ switch(state){
+ case 0:
+ h.type = c;
+ state++;
+ break;
+
+ case 1:
+ h.count0 = c;
+ state++;
+ break;
+
+ case 2:
+ h.count1 = c;
+ count = h.count0|(h.count1<<8);
+ i = 0;
+ if(count > DATASIZE)
+ panic("count>DATASIZE");
+ if(count == 0)
+ goto zerocount;
+ state++;
+ break;
+
+ case 3:
+ indata[i++] = c;
+ if(i == count){
+ zerocount:
+ indata[i] = 0;
+ state = count = 0;
+ return inmesg(h.type);
+ }
+ break;
+ }
+ return 0;
+}
+
+File *
+whichfile(int tag)
+{
+ int i;
+
+ for(i = 0; i<file.nused; i++)
+ if(file.filepptr[i]->tag==tag)
+ return file.filepptr[i];
+ hiccough((char *)0);
+ return 0;
+}
+
+int
+inmesg(Tmesg type)
+{
+ Rune buf[1025];
+ char cbuf[64];
+ int i, m;
+ short s;
+ long l, l1;
+ File *f;
+ Posn p0, p1, p;
+ Range r;
+ String *str;
+ char *c, *wdir;
+ Rune *rp;
+ Plumbmsg *pm;
+
+ if(type > TMAX)
+ panic("inmesg");
+
+ journal(0, tname[type]);
+
+ inp = indata;
+ switch(type){
+ case -1:
+ panic("rcv error");
+
+ default:
+ fprint(2, "unknown type %d\n", type);
+ panic("rcv unknown");
+
+ case Tversion:
+ tversion = inshort();
+ journaln(0, tversion);
+ break;
+
+ case Tstartcmdfile:
+ l = invlong(); /* for 64-bit pointers */
+ journaln(0, l);
+ Strdupl(&genstr, samname);
+ cmd = newfile();
+ cmd->unread = 0;
+ outTsv(Hbindname, cmd->tag, l);
+ outTs(Hcurrent, cmd->tag);
+ logsetname(cmd, &genstr);
+ cmd->rasp = emalloc(sizeof(List));
+ cmd->mod = 0;
+ if(cmdstr.n){
+ loginsert(cmd, 0L, cmdstr.s, cmdstr.n);
+ Strdelete(&cmdstr, 0L, (Posn)cmdstr.n);
+ }
+ fileupdate(cmd, FALSE, TRUE);
+ outT0(Hunlock);
+ break;
+
+ case Tcheck:
+ /* go through whichfile to check the tag */
+ outTs(Hcheck, whichfile(inshort())->tag);
+ break;
+
+ case Trequest:
+ f = whichfile(inshort());
+ p0 = inlong();
+ p1 = p0+inshort();
+ journaln(0, p0);
+ journaln(0, p1-p0);
+ if(f->unread)
+ panic("Trequest: unread");
+ if(p1>f->_.nc)
+ p1 = f->_.nc;
+ if(p0>f->_.nc) /* can happen e.g. scrolling during command */
+ p0 = f->_.nc;
+ if(p0 == p1){
+ i = 0;
+ r.p1 = r.p2 = p0;
+ }else{
+ r = rdata(f->rasp, p0, p1-p0);
+ i = r.p2-r.p1;
+ bufread(f, r.p1, buf, i);
+ }
+ buf[i]=0;
+ outTslS(Hdata, f->tag, r.p1, tmprstr(buf, i+1));
+ break;
+
+ case Torigin:
+ s = inshort();
+ l = inlong();
+ l1 = inlong();
+ journaln(0, l1);
+ lookorigin(whichfile(s), l, l1);
+ break;
+
+ case Tstartfile:
+ termlocked++;
+ f = whichfile(inshort());
+ if(!f->rasp) /* this might be a duplicate message */
+ f->rasp = emalloc(sizeof(List));
+ current(f);
+ outTsv(Hbindname, f->tag, invlong()); /* for 64-bit pointers */
+ outTs(Hcurrent, f->tag);
+ journaln(0, f->tag);
+ if(f->unread)
+ load(f);
+ else{
+ if(f->_.nc>0){
+ rgrow(f->rasp, 0L, f->_.nc);
+ outTsll(Hgrow, f->tag, 0L, f->_.nc);
+ }
+ outTs(Hcheck0, f->tag);
+ moveto(f, f->dot.r);
+ }
+ break;
+
+ case Tworkfile:
+ i = inshort();
+ f = whichfile(i);
+ current(f);
+ f->dot.r.p1 = inlong();
+ f->dot.r.p2 = inlong();
+ f->tdot = f->dot.r;
+ journaln(0, i);
+ journaln(0, f->dot.r.p1);
+ journaln(0, f->dot.r.p2);
+ break;
+
+ case Ttype:
+ f = whichfile(inshort());
+ p0 = inlong();
+ journaln(0, p0);
+ journal(0, (char*)inp);
+ str = tmpcstr((char*)inp);
+ i = str->n;
+ loginsert(f, p0, str->s, str->n);
+ if(fileupdate(f, FALSE, FALSE))
+ seq++;
+ if(f==cmd && p0==f->_.nc-i && i>0 && str->s[i-1]=='\n'){
+ freetmpstr(str);
+ termlocked++;
+ termcommand();
+ }else
+ freetmpstr(str);
+ f->dot.r.p1 = f->dot.r.p2 = p0+i; /* terminal knows this already */
+ f->tdot = f->dot.r;
+ break;
+
+ case Tcut:
+ f = whichfile(inshort());
+ p0 = inlong();
+ p1 = inlong();
+ journaln(0, p0);
+ journaln(0, p1);
+ logdelete(f, p0, p1);
+ if(fileupdate(f, FALSE, FALSE))
+ seq++;
+ f->dot.r.p1 = f->dot.r.p2 = p0;
+ f->tdot = f->dot.r; /* terminal knows the value of dot already */
+ break;
+
+ case Tpaste:
+ f = whichfile(inshort());
+ p0 = inlong();
+ journaln(0, p0);
+ for(l=0; l<snarfbuf.nc; l+=m){
+ m = snarfbuf.nc-l;
+ if(m>BLOCKSIZE)
+ m = BLOCKSIZE;
+ bufread(&snarfbuf, l, genbuf, m);
+ loginsert(f, p0, tmprstr(genbuf, m)->s, m);
+ }
+ if(fileupdate(f, FALSE, TRUE))
+ seq++;
+ f->dot.r.p1 = p0;
+ f->dot.r.p2 = p0+snarfbuf.nc;
+ f->tdot.p1 = -1; /* force telldot to tell (arguably a BUG) */
+ telldot(f);
+ outTs(Hunlockfile, f->tag);
+ break;
+
+ case Tsnarf:
+ i = inshort();
+ p0 = inlong();
+ p1 = inlong();
+ snarf(whichfile(i), p0, p1, &snarfbuf, 0);
+ break;
+
+ case Tstartnewfile:
+ l = invlong();
+ Strdupl(&genstr, empty);
+ f = newfile();
+ f->rasp = emalloc(sizeof(List));
+ outTsv(Hbindname, f->tag, l);
+ logsetname(f, &genstr);
+ outTs(Hcurrent, f->tag);
+ current(f);
+ load(f);
+ break;
+
+ case Twrite:
+ termlocked++;
+ i = inshort();
+ journaln(0, i);
+ f = whichfile(i);
+ addr.r.p1 = 0;
+ addr.r.p2 = f->_.nc;
+ if(f->name.s[0] == 0)
+ error(Enoname);
+ Strduplstr(&genstr, &f->name);
+ writef(f);
+ break;
+
+ case Tclose:
+ termlocked++;
+ i = inshort();
+ journaln(0, i);
+ f = whichfile(i);
+ current(f);
+ trytoclose(f);
+ /* if trytoclose fails, will error out */
+ delete(f);
+ break;
+
+ case Tlook:
+ f = whichfile(inshort());
+ termlocked++;
+ p0 = inlong();
+ p1 = inlong();
+ journaln(0, p0);
+ journaln(0, p1);
+ setgenstr(f, p0, p1);
+ for(l = 0; l<genstr.n; l++){
+ i = genstr.s[l];
+ if(utfrune(".*+?(|)\\[]^$", i))
+ Strinsert(&genstr, tmpcstr("\\"), l++);
+ }
+ Straddc(&genstr, '\0');
+ nextmatch(f, &genstr, p1, 1);
+ moveto(f, sel.p[0]);
+ break;
+
+ case Tsearch:
+ termlocked++;
+ if(curfile == 0)
+ error(Enofile);
+ if(lastpat.s[0] == 0)
+ panic("Tsearch");
+ nextmatch(curfile, &lastpat, curfile->dot.r.p2, 1);
+ moveto(curfile, sel.p[0]);
+ break;
+
+ case Tsend:
+ termlocked++;
+ inshort(); /* ignored */
+ p0 = inlong();
+ p1 = inlong();
+ setgenstr(cmd, p0, p1);
+ bufreset(&snarfbuf);
+ bufinsert(&snarfbuf, (Posn)0, genstr.s, genstr.n);
+ outTl(Hsnarflen, genstr.n);
+ if(genstr.s[genstr.n-1] != '\n')
+ Straddc(&genstr, '\n');
+ loginsert(cmd, cmd->_.nc, genstr.s, genstr.n);
+ fileupdate(cmd, FALSE, TRUE);
+ cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->_.nc;
+ telldot(cmd);
+ termcommand();
+ break;
+
+ case Tdclick:
+ f = whichfile(inshort());
+ p1 = inlong();
+ doubleclick(f, p1);
+ f->tdot.p1 = f->tdot.p2 = p1;
+ telldot(f);
+ outTs(Hunlockfile, f->tag);
+ break;
+
+ case Tstartsnarf:
+ if (snarfbuf.nc <= 0) { /* nothing to export */
+ outTs(Hsetsnarf, 0);
+ break;
+ }
+ c = 0;
+ i = 0;
+ m = snarfbuf.nc;
+ if(m > SNARFSIZE) {
+ m = SNARFSIZE;
+ dprint("?warning: snarf buffer truncated\n");
+ }
+ rp = malloc(m*sizeof(Rune));
+ if(rp){
+ bufread(&snarfbuf, 0, rp, m);
+ c = Strtoc(tmprstr(rp, m));
+ free(rp);
+ i = strlen(c);
+ }
+ outTs(Hsetsnarf, i);
+ if(c){
+ Write(1, c, i);
+ free(c);
+ } else
+ dprint("snarf buffer too long\n");
+ break;
+
+ case Tsetsnarf:
+ m = inshort();
+ if(m > SNARFSIZE)
+ error(Etoolong);
+ c = malloc(m+1);
+ if(c){
+ for(i=0; i<m; i++)
+ c[i] = rcvchar();
+ c[m] = 0;
+ str = tmpcstr(c);
+ free(c);
+ bufreset(&snarfbuf);
+ bufinsert(&snarfbuf, (Posn)0, str->s, str->n);
+ freetmpstr(str);
+ outT0(Hunlock);
+ }
+ break;
+
+ case Tack:
+ waitack = 0;
+ break;
+
+ case Tplumb:
+ f = whichfile(inshort());
+ p0 = inlong();
+ p1 = inlong();
+ pm = emalloc(sizeof(Plumbmsg));
+ pm->src = strdup("sam");
+ pm->dst = 0;
+ /* construct current directory */
+ c = Strtoc(&f->name);
+ if(c[0] == '/')
+ pm->wdir = c;
+ else{
+ wdir = emalloc(1024);
+ getwd(wdir, 1024);
+ pm->wdir = emalloc(1024);
+ snprint(pm->wdir, 1024, "%s/%s", wdir, c);
+ cleanname(pm->wdir);
+ free(wdir);
+ free(c);
+ }
+ c = strrchr(pm->wdir, '/');
+ if(c)
+ *c = '\0';
+ pm->type = strdup("text");
+ if(p1 > p0)
+ pm->attr = nil;
+ else{
+ p = p0;
+ while(p0>0 && (i=filereadc(f, p0 - 1))!=' ' && i!='\t' && i!='\n')
+ p0--;
+ while(p1<f->_.nc && (i=filereadc(f, p1))!=' ' && i!='\t' && i!='\n')
+ p1++;
+ sprint(cbuf, "click=%ld", p-p0);
+ pm->attr = plumbunpackattr(cbuf);
+ }
+ if(p0==p1 || p1-p0>=BLOCKSIZE){
+ plumbfree(pm);
+ break;
+ }
+ setgenstr(f, p0, p1);
+ pm->data = Strtoc(&genstr);
+ pm->ndata = strlen(pm->data);
+ c = plumbpack(pm, &i);
+ if(c != 0){
+ outTs(Hplumb, i);
+ Write(1, c, i);
+ free(c);
+ }
+ plumbfree(pm);
+ break;
+
+ case Texit:
+ exits(0);
+ }
+ return TRUE;
+}
+
+void
+snarf(File *f, Posn p1, Posn p2, Buffer *buf, int emptyok)
+{
+ Posn l;
+ int i;
+
+ if(!emptyok && p1==p2)
+ return;
+ bufreset(buf);
+ /* Stage through genbuf to avoid compaction problems (vestigial) */
+ if(p2 > f->_.nc){
+ fprint(2, "bad snarf addr p1=%ld p2=%ld f->_.nc=%d\n", p1, p2, f->_.nc); /*ZZZ should never happen, can remove */
+ p2 = f->_.nc;
+ }
+ for(l=p1; l<p2; l+=i){
+ i = p2-l>BLOCKSIZE? BLOCKSIZE : p2-l;
+ bufread(f, l, genbuf, i);
+ bufinsert(buf, buf->nc, tmprstr(genbuf, i)->s, i);
+ }
+}
+
+int
+inshort(void)
+{
+ ushort n;
+
+ n = inp[0] | (inp[1]<<8);
+ inp += 2;
+ return n;
+}
+
+long
+inlong(void)
+{
+ ulong n;
+
+ n = inp[0] | (inp[1]<<8) | (inp[2]<<16) | (inp[3]<<24);
+ inp += 4;
+ return n;
+}
+
+long
+invlong(void)
+{
+ ulong n;
+
+ n = (inp[7]<<24) | (inp[6]<<16) | (inp[5]<<8) | inp[4];
+ n = (n<<16) | (inp[3]<<8) | inp[2];
+ n = (n<<16) | (inp[1]<<8) | inp[0];
+ inp += 8;
+ return n;
+}
+
+void
+setgenstr(File *f, Posn p0, Posn p1)
+{
+ if(p0 != p1){
+ if(p1-p0 >= TBLOCKSIZE)
+ error(Etoolong);
+ Strinsure(&genstr, p1-p0);
+ bufread(f, p0, genbuf, p1-p0);
+ memmove(genstr.s, genbuf, RUNESIZE*(p1-p0));
+ genstr.n = p1-p0;
+ }else{
+ if(snarfbuf.nc == 0)
+ error(Eempty);
+ if(snarfbuf.nc > TBLOCKSIZE)
+ error(Etoolong);
+ bufread(&snarfbuf, (Posn)0, genbuf, snarfbuf.nc);
+ Strinsure(&genstr, snarfbuf.nc);
+ memmove(genstr.s, genbuf, RUNESIZE*snarfbuf.nc);
+ genstr.n = snarfbuf.nc;
+ }
+}
+
+void
+outT0(Hmesg type)
+{
+ outstart(type);
+ outsend();
+}
+
+void
+outTl(Hmesg type, long l)
+{
+ outstart(type);
+ outlong(l);
+ outsend();
+}
+
+void
+outTs(Hmesg type, int s)
+{
+ outstart(type);
+ journaln(1, s);
+ outshort(s);
+ outsend();
+}
+
+void
+outS(String *s)
+{
+ char *c;
+ int i;
+
+ c = Strtoc(s);
+ i = strlen(c);
+ outcopy(i, c);
+ if(i > 99)
+ c[99] = 0;
+ journaln(1, i);
+ journal(1, c);
+ free(c);
+}
+
+void
+outTsS(Hmesg type, int s1, String *s)
+{
+ outstart(type);
+ outshort(s1);
+ outS(s);
+ outsend();
+}
+
+void
+outTslS(Hmesg type, int s1, Posn l1, String *s)
+{
+ outstart(type);
+ outshort(s1);
+ journaln(1, s1);
+ outlong(l1);
+ journaln(1, l1);
+ outS(s);
+ outsend();
+}
+
+void
+outTS(Hmesg type, String *s)
+{
+ outstart(type);
+ outS(s);
+ outsend();
+}
+
+void
+outTsllS(Hmesg type, int s1, Posn l1, Posn l2, String *s)
+{
+ outstart(type);
+ outshort(s1);
+ outlong(l1);
+ outlong(l2);
+ journaln(1, l1);
+ journaln(1, l2);
+ outS(s);
+ outsend();
+}
+
+void
+outTsll(Hmesg type, int s, Posn l1, Posn l2)
+{
+ outstart(type);
+ outshort(s);
+ outlong(l1);
+ outlong(l2);
+ journaln(1, l1);
+ journaln(1, l2);
+ outsend();
+}
+
+void
+outTsl(Hmesg type, int s, Posn l)
+{
+ outstart(type);
+ outshort(s);
+ outlong(l);
+ journaln(1, l);
+ outsend();
+}
+
+void
+outTsv(Hmesg type, int s, Posn l)
+{
+ outstart(type);
+ outshort(s);
+ outvlong((void*)l);
+ journaln(1, l);
+ outsend();
+}
+
+void
+outstart(Hmesg type)
+{
+ journal(1, hname[type]);
+ outmsg[0] = type;
+ outp = outmsg+3;
+}
+
+void
+outcopy(int count, void *data)
+{
+ memmove(outp, data, count);
+ outp += count;
+}
+
+void
+outshort(int s)
+{
+ *outp++ = s;
+ *outp++ = s>>8;
+}
+
+void
+outlong(long l)
+{
+ *outp++ = l;
+ *outp++ = l>>8;
+ *outp++ = l>>16;
+ *outp++ = l>>24;
+}
+
+void
+outvlong(void *v)
+{
+ int i;
+ ulong l;
+
+ l = (ulong) v;
+ for(i = 0; i < 8; i++, l >>= 8)
+ *outp++ = l;
+}
+
+void
+outsend(void)
+{
+ int outcount;
+
+ outcount = outp-outmsg;
+ outcount -= 3;
+ outmsg[1] = outcount;
+ outmsg[2] = outcount>>8;
+ outmsg = outp;
+ if(!noflush){
+ outcount = outmsg-outdata;
+ if (write(1, (char*) outdata, outcount) != outcount)
+ rescue();
+ outmsg = outdata;
+ return;
+ }
+ if(outmsg < outdata+DATASIZE)
+ return;
+ outflush();
+}
+
+void
+outflush(void)
+{
+ if(outmsg == outdata)
+ return;
+ noflush = 0;
+ outT0(Hack);
+ waitack = 1;
+ do
+ if(rcv() == 0){
+ rescue();
+ exits("eof");
+ }
+ while(waitack);
+ outmsg = outdata;
+ noflush = 1;
+}
diff --git a/src/cmd/sam/mesg.h b/src/cmd/sam/mesg.h
new file mode 100644
index 00000000..b88bf148
--- /dev/null
+++ b/src/cmd/sam/mesg.h
@@ -0,0 +1,131 @@
+/* VERSION 1 introduces plumbing
+ 2 increases SNARFSIZE from 4096 to 32000
+ */
+#define VERSION 2
+
+#define TBLOCKSIZE 512 /* largest piece of text sent to terminal */
+#define DATASIZE (UTFmax*TBLOCKSIZE+30) /* ... including protocol header stuff */
+#define SNARFSIZE 32000 /* maximum length of exchanged snarf buffer, must fit in 15 bits */
+/*
+ * Messages originating at the terminal
+ */
+typedef enum Tmesg
+{
+ Tversion, /* version */
+ Tstartcmdfile, /* terminal just opened command frame */
+ Tcheck, /* ask host to poke with Hcheck */
+ Trequest, /* request data to fill a hole */
+ Torigin, /* gimme an Horigin near here */
+ Tstartfile, /* terminal just opened a file's frame */
+ Tworkfile, /* set file to which commands apply */
+ Ttype, /* add some characters, but terminal already knows */
+ Tcut,
+ Tpaste,
+ Tsnarf,
+ Tstartnewfile, /* terminal just opened a new frame */
+ Twrite, /* write file */
+ Tclose, /* terminal requests file close; check mod. status */
+ Tlook, /* search for literal current text */
+ Tsearch, /* search for last regular expression */
+ Tsend, /* pretend he typed stuff */
+ Tdclick, /* double click */
+ Tstartsnarf, /* initiate snarf buffer exchange */
+ Tsetsnarf, /* remember string in snarf buffer */
+ Tack, /* acknowledge Hack */
+ Texit, /* exit */
+ Tplumb, /* send plumb message */
+ TMAX,
+}Tmesg;
+/*
+ * Messages originating at the host
+ */
+typedef enum Hmesg
+{
+ Hversion, /* version */
+ Hbindname, /* attach name[0] to text in terminal */
+ Hcurrent, /* make named file the typing file */
+ Hnewname, /* create "" name in menu */
+ Hmovname, /* move file name in menu */
+ Hgrow, /* insert space in rasp */
+ Hcheck0, /* see below */
+ Hcheck, /* ask terminal to check whether it needs more data */
+ Hunlock, /* command is finished; user can do things */
+ Hdata, /* store this data in previously allocated space */
+ Horigin, /* set origin of file/frame in terminal */
+ Hunlockfile, /* unlock file in terminal */
+ Hsetdot, /* set dot in terminal */
+ Hgrowdata, /* Hgrow + Hdata folded together */
+ Hmoveto, /* scrolling, context search, etc. */
+ Hclean, /* named file is now 'clean' */
+ Hdirty, /* named file is now 'dirty' */
+ Hcut, /* remove space from rasp */
+ Hsetpat, /* set remembered regular expression */
+ Hdelname, /* delete file name from menu */
+ Hclose, /* close file and remove from menu */
+ Hsetsnarf, /* remember string in snarf buffer */
+ Hsnarflen, /* report length of implicit snarf */
+ Hack, /* request acknowledgement */
+ Hexit,
+ Hplumb, /* return plumb message to terminal */
+ HMAX,
+}Hmesg;
+typedef struct Header{
+ uchar type; /* one of the above */
+ uchar count0; /* low bits of data size */
+ uchar count1; /* high bits of data size */
+ uchar data[1]; /* variable size */
+}Header;
+
+/*
+ * File transfer protocol schematic, a la Holzmann
+ * #define N 6
+ *
+ * chan h = [4] of { mtype };
+ * chan t = [4] of { mtype };
+ *
+ * mtype = { Hgrow, Hdata,
+ * Hcheck, Hcheck0,
+ * Trequest, Tcheck,
+ * };
+ *
+ * active proctype host()
+ * { byte n;
+ *
+ * do
+ * :: n < N -> n++; t!Hgrow
+ * :: n == N -> n++; t!Hcheck0
+ *
+ * :: h?Trequest -> t!Hdata
+ * :: h?Tcheck -> t!Hcheck
+ * od
+ * }
+ *
+ * active proctype term()
+ * {
+ * do
+ * :: t?Hgrow -> h!Trequest
+ * :: t?Hdata -> skip
+ * :: t?Hcheck0 -> h!Tcheck
+ * :: t?Hcheck ->
+ * if
+ * :: h!Trequest -> progress: h!Tcheck
+ * :: break
+ * fi
+ * od;
+ * printf("term exits\n")
+ * }
+ *
+ * From: gerard@research.bell-labs.com
+ * Date: Tue Jul 17 13:47:23 EDT 2001
+ * To: rob@research.bell-labs.com
+ *
+ * spin -c (or -a) spec
+ * pcc -DNP -o pan pan.c
+ * pan -l
+ *
+ * proves that there are no non-progress cycles
+ * (infinite executions *not* passing through
+ * the statement marked with a label starting
+ * with the prefix "progress")
+ *
+ */
diff --git a/src/cmd/sam/mkfile b/src/cmd/sam/mkfile
new file mode 100644
index 00000000..cb604976
--- /dev/null
+++ b/src/cmd/sam/mkfile
@@ -0,0 +1,40 @@
+</$objtype/mkfile
+
+TARG=sam
+OFILES=sam.$O\
+ address.$O\
+ buff.$O\
+ cmd.$O\
+ disk.$O\
+ error.$O\
+ file.$O\
+ io.$O\
+ list.$O\
+ mesg.$O\
+ moveto.$O\
+ multi.$O\
+ plan9.$O\
+ rasp.$O\
+ regexp.$O\
+ shell.$O\
+ string.$O\
+ sys.$O\
+ util.$O\
+ xec.$O\
+
+HFILES=sam.h\
+ errors.h\
+ mesg.h\
+
+BIN=/$objtype/bin
+</sys/src/cmd/mkone
+
+address.$O cmd.$O parse.$O xec.$O unix.$O: parse.h
+
+safeinstall: $O.out
+ mv $BIN/$TARG $BIN/o$TARG
+ cp $prereq $BIN/$TARG
+
+safeinstallall:V:
+ for (objtype in $CPUS)
+ mk safeinstall
diff --git a/src/cmd/sam/moveto.c b/src/cmd/sam/moveto.c
new file mode 100644
index 00000000..a84578c7
--- /dev/null
+++ b/src/cmd/sam/moveto.c
@@ -0,0 +1,173 @@
+#include "sam.h"
+
+void
+moveto(File *f, Range r)
+{
+ Posn p1 = r.p1, p2 = r.p2;
+
+ f->dot.r.p1 = p1;
+ f->dot.r.p2 = p2;
+ if(f->rasp){
+ telldot(f);
+ outTsl(Hmoveto, f->tag, f->dot.r.p1);
+ }
+}
+
+void
+telldot(File *f)
+{
+ if(f->rasp == 0)
+ panic("telldot");
+ if(f->dot.r.p1==f->tdot.p1 && f->dot.r.p2==f->tdot.p2)
+ return;
+ outTsll(Hsetdot, f->tag, f->dot.r.p1, f->dot.r.p2);
+ f->tdot = f->dot.r;
+}
+
+void
+tellpat(void)
+{
+ outTS(Hsetpat, &lastpat);
+ patset = FALSE;
+}
+
+#define CHARSHIFT 128
+
+void
+lookorigin(File *f, Posn p0, Posn ls)
+{
+ int nl, nc, c;
+ Posn p, oldp0;
+
+ if(p0 > f->_.nc)
+ p0 = f->_.nc;
+ oldp0 = p0;
+ p = p0;
+ for(nl=nc=c=0; c!=-1 && nl<ls && nc<ls*CHARSHIFT; nc++)
+ if((c=filereadc(f, --p)) == '\n'){
+ nl++;
+ oldp0 = p0-nc;
+ }
+ if(c == -1)
+ p0 = 0;
+ else if(nl==0){
+ if(p0>=CHARSHIFT/2)
+ p0-=CHARSHIFT/2;
+ else
+ p0 = 0;
+ }else
+ p0 = oldp0;
+ outTsl(Horigin, f->tag, p0);
+}
+
+int
+alnum(int c)
+{
+ /*
+ * Hard to get absolutely right. Use what we know about ASCII
+ * and assume anything above the Latin control characters is
+ * potentially an alphanumeric.
+ */
+ if(c<=' ')
+ return 0;
+ if(0x7F<=c && c<=0xA0)
+ return 0;
+ if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
+ return 0;
+ return 1;
+}
+
+int
+clickmatch(File *f, int cl, int cr, int dir, Posn *p)
+{
+ int c;
+ int nest = 1;
+
+ for(;;){
+ if(dir > 0){
+ if(*p >= f->_.nc)
+ break;
+ c = filereadc(f, (*p)++);
+ }else{
+ if(*p == 0)
+ break;
+ c = filereadc(f, --(*p));
+ }
+ if(c == cr){
+ if(--nest==0)
+ return 1;
+ }else if(c == cl)
+ nest++;
+ }
+ return cl=='\n' && nest==1;
+}
+
+Rune*
+strrune(Rune *s, Rune c)
+{
+ Rune c1;
+
+ if(c == 0) {
+ while(*s++)
+ ;
+ return s-1;
+ }
+
+ while(c1 = *s++)
+ if(c1 == c)
+ return s-1;
+ return 0;
+}
+
+void
+doubleclick(File *f, Posn p1)
+{
+ int c, i;
+ Rune *r, *l;
+ Posn p;
+
+ if(p1 > f->_.nc)
+ return;
+ f->dot.r.p1 = f->dot.r.p2 = p1;
+ for(i=0; left[i]; i++){
+ l = left[i];
+ r = right[i];
+ /* try left match */
+ p = p1;
+ if(p1 == 0)
+ c = '\n';
+ else
+ c = filereadc(f, p - 1);
+ if(strrune(l, c)){
+ if(clickmatch(f, c, r[strrune(l, c)-l], 1, &p)){
+ f->dot.r.p1 = p1;
+ f->dot.r.p2 = p-(c!='\n');
+ }
+ return;
+ }
+ /* try right match */
+ p = p1;
+ if(p1 == f->_.nc)
+ c = '\n';
+ else
+ c = filereadc(f, p);
+ if(strrune(r, c)){
+ if(clickmatch(f, c, l[strrune(r, c)-r], -1, &p)){
+ f->dot.r.p1 = p;
+ if(c!='\n' || p!=0 || filereadc(f, 0)=='\n')
+ f->dot.r.p1++;
+ f->dot.r.p2 = p1+(p1<f->_.nc && c=='\n');
+ }
+ return;
+ }
+ }
+ /* try filling out word to right */
+ p = p1;
+ while(p < f->_.nc && alnum(filereadc(f, p++)))
+ f->dot.r.p2++;
+ /* try filling out word to left */
+ p = p1;
+ while(--p >= 0 && alnum(filereadc(f, p)))
+ f->dot.r.p1--;
+}
+
diff --git a/src/cmd/sam/multi.c b/src/cmd/sam/multi.c
new file mode 100644
index 00000000..3df0dbee
--- /dev/null
+++ b/src/cmd/sam/multi.c
@@ -0,0 +1,123 @@
+#include "sam.h"
+
+List file;
+ushort tag;
+
+File *
+newfile(void)
+{
+ File *f;
+
+ f = fileopen();
+ inslist(&file, 0, (long)f);
+ f->tag = tag++;
+ if(downloaded)
+ outTs(Hnewname, f->tag);
+ /* already sorted; file name is "" */
+ return f;
+}
+
+int
+whichmenu(File *f)
+{
+ int i;
+
+ for(i=0; i<file.nused; i++)
+ if(file.filepptr[i]==f)
+ return i;
+ return -1;
+}
+
+void
+delfile(File *f)
+{
+ int w = whichmenu(f);
+
+ if(w < 0) /* e.g. x/./D */
+ return;
+ if(downloaded)
+ outTs(Hdelname, f->tag);
+ dellist(&file, w);
+ fileclose(f);
+}
+
+void
+fullname(String *name)
+{
+ if(name->n > 0 && name->s[0]!='/' && name->s[0]!=0)
+ Strinsert(name, &curwd, (Posn)0);
+}
+
+void
+fixname(String *name)
+{
+ String *t;
+ char *s;
+
+ fullname(name);
+ s = Strtoc(name);
+ if(strlen(s) > 0)
+ s = cleanname(s);
+ t = tmpcstr(s);
+ Strduplstr(name, t);
+ free(s);
+ freetmpstr(t);
+
+ if(Strispre(&curwd, name))
+ Strdelete(name, 0, curwd.n);
+}
+
+void
+sortname(File *f)
+{
+ int i, cmp, w;
+ int dupwarned;
+
+ w = whichmenu(f);
+ dupwarned = FALSE;
+ dellist(&file, w);
+ if(f == cmd)
+ i = 0;
+ else{
+ for(i=0; i<file.nused; i++){
+ cmp = Strcmp(&f->name, &file.filepptr[i]->name);
+ if(cmp==0 && !dupwarned){
+ dupwarned = TRUE;
+ warn_S(Wdupname, &f->name);
+ }else if(cmp<0 && (i>0 || cmd==0))
+ break;
+ }
+ }
+ inslist(&file, i, (long)f);
+ if(downloaded)
+ outTsS(Hmovname, f->tag, &f->name);
+}
+
+void
+state(File *f, int cleandirty)
+{
+ if(f == cmd)
+ return;
+ f->unread = FALSE;
+ if(downloaded && whichmenu(f)>=0){ /* else flist or menu */
+ if(f->mod && cleandirty!=Dirty)
+ outTs(Hclean, f->tag);
+ else if(!f->mod && cleandirty==Dirty)
+ outTs(Hdirty, f->tag);
+ }
+ if(cleandirty == Clean)
+ f->mod = FALSE;
+ else
+ f->mod = TRUE;
+}
+
+File *
+lookfile(String *s)
+{
+ int i;
+
+ for(i=0; i<file.nused; i++)
+ if(Strcmp(&file.filepptr[i]->name, s) == 0)
+ return file.filepptr[i];
+ return 0;
+}
diff --git a/src/cmd/sam/parse.h b/src/cmd/sam/parse.h
new file mode 100644
index 00000000..12f293f2
--- /dev/null
+++ b/src/cmd/sam/parse.h
@@ -0,0 +1,68 @@
+typedef struct Addr Addr;
+typedef struct Cmd Cmd;
+struct Addr
+{
+ char type; /* # (char addr), l (line addr), / ? . $ + - , ; */
+ union{
+ String *re;
+ Addr *aleft; /* left side of , and ; */
+ } g;
+ Posn num;
+ Addr *next; /* or right side of , and ; */
+};
+
+#define are g.re
+#define left g.aleft
+
+struct Cmd
+{
+ Addr *addr; /* address (range of text) */
+ String *re; /* regular expression for e.g. 'x' */
+ union{
+ Cmd *cmd; /* target of x, g, {, etc. */
+ String *text; /* text of a, c, i; rhs of s */
+ Addr *addr; /* address for m, t */
+ } g;
+ Cmd *next; /* pointer to next element in {} */
+ short num;
+ ushort flag; /* whatever */
+ ushort cmdc; /* command character; 'x' etc. */
+};
+
+#define ccmd g.cmd
+#define ctext g.text
+#define caddr g.addr
+
+extern struct cmdtab{
+ ushort cmdc; /* command character */
+ uchar text; /* takes a textual argument? */
+ uchar regexp; /* takes a regular expression? */
+ uchar addr; /* takes an address (m or t)? */
+ uchar defcmd; /* default command; 0==>none */
+ uchar defaddr; /* default address */
+ uchar count; /* takes a count e.g. s2/// */
+ char *token; /* takes text terminated by one of these */
+ int (*fn)(File*, Cmd*); /* function to call with parse tree */
+}cmdtab[];
+
+enum Defaddr{ /* default addresses */
+ aNo,
+ aDot,
+ aAll,
+};
+
+int nl_cmd(File*, Cmd*), a_cmd(File*, Cmd*), b_cmd(File*, Cmd*);
+int c_cmd(File*, Cmd*), cd_cmd(File*, Cmd*), d_cmd(File*, Cmd*);
+int D_cmd(File*, Cmd*), e_cmd(File*, Cmd*);
+int f_cmd(File*, Cmd*), g_cmd(File*, Cmd*), i_cmd(File*, Cmd*);
+int k_cmd(File*, Cmd*), m_cmd(File*, Cmd*), n_cmd(File*, Cmd*);
+int p_cmd(File*, Cmd*), q_cmd(File*, Cmd*);
+int s_cmd(File*, Cmd*), u_cmd(File*, Cmd*), w_cmd(File*, Cmd*);
+int x_cmd(File*, Cmd*), X_cmd(File*, Cmd*), plan9_cmd(File*, Cmd*);
+int eq_cmd(File*, Cmd*);
+
+
+String *getregexp(int);
+Addr *newaddr(void);
+Address address(Addr*, Address, int);
+int cmdexec(File*, Cmd*);
diff --git a/src/cmd/sam/plan9.c b/src/cmd/sam/plan9.c
new file mode 100644
index 00000000..6c3a60e9
--- /dev/null
+++ b/src/cmd/sam/plan9.c
@@ -0,0 +1,185 @@
+#include "sam.h"
+
+Rune samname[] = L"~~sam~~";
+
+Rune *left[]= {
+ L"{[(<«",
+ L"\n",
+ L"'\"`",
+ 0
+};
+Rune *right[]= {
+ L"}])>»",
+ L"\n",
+ L"'\"`",
+ 0
+};
+
+char RSAM[] = "sam";
+char SAMTERM[] = "/bin/aux/samterm";
+char HOME[] = "home";
+char TMPDIR[] = "/tmp";
+char SH[] = "rc";
+char SHPATH[] = "/bin/rc";
+char RX[] = "rx";
+char RXPATH[] = "/bin/rx";
+char SAMSAVECMD[] = "/bin/rc\n/sys/lib/samsave";
+
+void
+dprint(char *z, ...)
+{
+ char buf[BLOCKSIZE];
+ va_list arg;
+
+ va_start(arg, z);
+ vseprint(buf, &buf[BLOCKSIZE], z, arg);
+ va_end(arg);
+ termwrite(buf);
+}
+
+void
+print_ss(char *s, String *a, String *b)
+{
+ dprint("?warning: %s: `%.*S' and `%.*S'\n", s, a->n, a->s, b->n, b->s);
+}
+
+void
+print_s(char *s, String *a)
+{
+ dprint("?warning: %s `%.*S'\n", s, a->n, a->s);
+}
+
+char*
+getuser(void)
+{
+ static char user[64];
+ int fd;
+
+ if(user[0] == 0){
+ fd = open("/dev/user", 0);
+ if(fd<0 || read(fd, user, sizeof user-1)<=0)
+ strcpy(user, "none");
+ close(fd);
+ }
+ return user;
+}
+
+int
+statfile(char *name, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
+{
+ Dir *dirb;
+
+ dirb = dirstat(name);
+ if(dirb == nil)
+ return -1;
+ if(dev)
+ *dev = dirb->type|(dirb->dev<<16);
+ if(id)
+ *id = dirb->qid.path;
+ if(time)
+ *time = dirb->mtime;
+ if(length)
+ *length = dirb->length;
+ if(appendonly)
+ *appendonly = dirb->mode & DMAPPEND;
+ free(dirb);
+ return 1;
+}
+
+int
+statfd(int fd, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
+{
+ Dir *dirb;
+
+ dirb = dirfstat(fd);
+ if(dirb == nil)
+ return -1;
+ if(dev)
+ *dev = dirb->type|(dirb->dev<<16);
+ if(id)
+ *id = dirb->qid.path;
+ if(time)
+ *time = dirb->mtime;
+ if(length)
+ *length = dirb->length;
+ if(appendonly)
+ *appendonly = dirb->mode & DMAPPEND;
+ free(dirb);
+ return 1;
+}
+
+void
+notifyf(void *a, char *s)
+{
+ USED(a);
+ if(bpipeok && strcmp(s, "sys: write on closed pipe") == 0)
+ noted(NCONT);
+ if(strcmp(s, "interrupt") == 0)
+ noted(NCONT);
+ panicking = 1;
+ rescue();
+ noted(NDFLT);
+}
+
+int
+newtmp(int num)
+{
+ int i, fd;
+ static char tempnam[30];
+
+ i = getpid();
+ do
+ snprint(tempnam, sizeof tempnam, "%s/%d%.4s%dsam", TMPDIR, num, getuser(), i++);
+ while(access(tempnam, 0) == 0);
+ fd = create(tempnam, ORDWR|OCEXEC|ORCLOSE, 0000);
+ if(fd < 0){
+ remove(tempnam);
+ fd = create(tempnam, ORDWR|OCEXEC|ORCLOSE, 0000);
+ }
+ return fd;
+}
+
+int
+waitfor(int pid)
+{
+ int msg;
+ Waitmsg *w;
+
+ while((w = wait()) != nil){
+ if(w->pid != pid){
+ free(w);
+ continue;
+ }
+ msg = (w->msg[0] != '\0');
+ free(w);
+ return msg;
+ }
+ return -1;
+}
+
+void
+samerr(char *buf)
+{
+ sprint(buf, "%s/sam.err", TMPDIR);
+}
+
+void*
+emalloc(ulong n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == 0)
+ panic("malloc fails");
+ memset(p, 0, n);
+ return p;
+}
+
+void*
+erealloc(void *p, ulong n)
+{
+ p = realloc(p, n);
+ if(p == 0)
+ panic("realloc fails");
+ return p;
+}
diff --git a/src/cmd/sam/plumb.c b/src/cmd/sam/plumb.c
new file mode 100644
index 00000000..d8db33d4
--- /dev/null
+++ b/src/cmd/sam/plumb.c
@@ -0,0 +1,9 @@
+#include <u.h>
+#include "plumb.h"
+
+/* XXX - Can we do better than this? */
+char *cleanname(char *s) { return s; }
+char *plumbunpackattr(char *cbuf) { return 0; }
+char *plumbpack(Plumbmsg *pm, int *i) { return 0; }
+int plumbfree(Plumbmsg *pm) { return 0; }
+
diff --git a/src/cmd/sam/rasp.c b/src/cmd/sam/rasp.c
new file mode 100644
index 00000000..45ac8206
--- /dev/null
+++ b/src/cmd/sam/rasp.c
@@ -0,0 +1,325 @@
+#include "sam.h"
+/*
+ * GROWDATASIZE must be big enough that all errors go out as Hgrowdata's,
+ * so they will be scrolled into visibility in the ~~sam~~ window (yuck!).
+ */
+#define GROWDATASIZE 50 /* if size is > this, send data with grow */
+
+void rcut(List*, Posn, Posn);
+int rterm(List*, Posn);
+void rgrow(List*, Posn, Posn);
+
+static Posn growpos;
+static Posn grown;
+static Posn shrinkpos;
+static Posn shrunk;
+
+/*
+ * rasp routines inform the terminal of changes to the file.
+ *
+ * a rasp is a list of spans within the file, and an indication
+ * of whether the terminal knows about the span.
+ *
+ * optimize by coalescing multiple updates to the same span
+ * if it is not known by the terminal.
+ *
+ * other possible optimizations: flush terminal's rasp by cut everything,
+ * insert everything if rasp gets too large.
+ */
+
+/*
+ * only called for initial load of file
+ */
+void
+raspload(File *f)
+{
+ if(f->rasp == nil)
+ return;
+ grown = f->_.nc;
+ growpos = 0;
+ if(f->_.nc)
+ rgrow(f->rasp, 0, f->_.nc);
+ raspdone(f, 1);
+}
+
+void
+raspstart(File *f)
+{
+ if(f->rasp == nil)
+ return;
+ grown = 0;
+ shrunk = 0;
+ noflush = 1;
+}
+
+void
+raspdone(File *f, int toterm)
+{
+ if(f->dot.r.p1 > f->_.nc)
+ f->dot.r.p1 = f->_.nc;
+ if(f->dot.r.p2 > f->_.nc)
+ f->dot.r.p2 = f->_.nc;
+ if(f->mark.p1 > f->_.nc)
+ f->mark.p1 = f->_.nc;
+ if(f->mark.p2 > f->_.nc)
+ f->mark.p2 = f->_.nc;
+ if(f->rasp == nil)
+ return;
+ if(grown)
+ outTsll(Hgrow, f->tag, growpos, grown);
+ else if(shrunk)
+ outTsll(Hcut, f->tag, shrinkpos, shrunk);
+ if(toterm)
+ outTs(Hcheck0, f->tag);
+ outflush();
+ noflush = 0;
+ if(f == cmd){
+ cmdpt += cmdptadv;
+ cmdptadv = 0;
+ }
+}
+
+void
+raspdelete(File *f, uint p1, uint p2, int toterm)
+{
+ long n;
+
+ n = p2 - p1;
+ if(n == 0)
+ return;
+
+ if(p2 <= f->dot.r.p1){
+ f->dot.r.p1 -= n;
+ f->dot.r.p2 -= n;
+ }
+ if(p2 <= f->mark.p1){
+ f->mark.p1 -= n;
+ f->mark.p2 -= n;
+ }
+
+ if(f->rasp == nil)
+ return;
+
+ if(f==cmd && p1<cmdpt){
+ if(p2 <= cmdpt)
+ cmdpt -= n;
+ else
+ cmdpt = p1;
+ }
+ if(toterm){
+ if(grown){
+ outTsll(Hgrow, f->tag, growpos, grown);
+ grown = 0;
+ }else if(shrunk && shrinkpos!=p1 && shrinkpos!=p2){
+ outTsll(Hcut, f->tag, shrinkpos, shrunk);
+ shrunk = 0;
+ }
+ if(!shrunk || shrinkpos==p2)
+ shrinkpos = p1;
+ shrunk += n;
+ }
+ rcut(f->rasp, p1, p2);
+}
+
+void
+raspinsert(File *f, uint p1, Rune *buf, uint n, int toterm)
+{
+ Range r;
+
+ if(n == 0)
+ return;
+
+ if(p1 < f->dot.r.p1){
+ f->dot.r.p1 += n;
+ f->dot.r.p2 += n;
+ }
+ if(p1 < f->mark.p1){
+ f->mark.p1 += n;
+ f->mark.p2 += n;
+ }
+
+
+ if(f->rasp == nil)
+ return;
+ if(f==cmd && p1<cmdpt)
+ cmdpt += n;
+ if(toterm){
+ if(shrunk){
+ outTsll(Hcut, f->tag, shrinkpos, shrunk);
+ shrunk = 0;
+ }
+ if(n>GROWDATASIZE || !rterm(f->rasp, p1)){
+ rgrow(f->rasp, p1, n);
+ if(grown && growpos+grown!=p1 && growpos!=p1){
+ outTsll(Hgrow, f->tag, growpos, grown);
+ grown = 0;
+ }
+ if(!grown)
+ growpos = p1;
+ grown += n;
+ }else{
+ if(grown){
+ outTsll(Hgrow, f->tag, growpos, grown);
+ grown = 0;
+ }
+ rgrow(f->rasp, p1, n);
+ r = rdata(f->rasp, p1, n);
+ if(r.p1!=p1 || r.p2!=p1+n)
+ panic("rdata in toterminal");
+ outTsllS(Hgrowdata, f->tag, p1, n, tmprstr(buf, n));
+ }
+ }else{
+ rgrow(f->rasp, p1, n);
+ r = rdata(f->rasp, p1, n);
+ if(r.p1!=p1 || r.p2!=p1+n)
+ panic("rdata in toterminal");
+ }
+}
+
+#define M 0x80000000L
+#define P(i) r->longptr[i]
+#define T(i) (P(i)&M) /* in terminal */
+#define L(i) (P(i)&~M) /* length of this piece */
+
+void
+rcut(List *r, Posn p1, Posn p2)
+{
+ Posn p, x;
+ int i;
+
+ if(p1 == p2)
+ panic("rcut 0");
+ for(p=0,i=0; i<r->nused && p+L(i)<=p1; p+=L(i++))
+ ;
+ if(i == r->nused)
+ panic("rcut 1");
+ if(p < p1){ /* chop this piece */
+ if(p+L(i) < p2){
+ x = p1-p;
+ p += L(i);
+ }else{
+ x = L(i)-(p2-p1);
+ p = p2;
+ }
+ if(T(i))
+ P(i) = x|M;
+ else
+ P(i) = x;
+ i++;
+ }
+ while(i<r->nused && p+L(i)<=p2){
+ p += L(i);
+ dellist(r, i);
+ }
+ if(p < p2){
+ if(i == r->nused)
+ panic("rcut 2");
+ x = L(i)-(p2-p);
+ if(T(i))
+ P(i) = x|M;
+ else
+ P(i) = x;
+ }
+ /* can we merge i and i-1 ? */
+ if(i>0 && i<r->nused && T(i-1)==T(i)){
+ x = L(i-1)+L(i);
+ dellist(r, i--);
+ if(T(i))
+ P(i)=x|M;
+ else
+ P(i)=x;
+ }
+}
+
+void
+rgrow(List *r, Posn p1, Posn n)
+{
+ Posn p;
+ int i;
+
+ if(n == 0)
+ panic("rgrow 0");
+ for(p=0,i=0; i<r->nused && p+L(i)<=p1; p+=L(i++))
+ ;
+ if(i == r->nused){ /* stick on end of file */
+ if(p!=p1)
+ panic("rgrow 1");
+ if(i>0 && !T(i-1))
+ P(i-1)+=n;
+ else
+ inslist(r, i, n);
+ }else if(!T(i)) /* goes in this empty piece */
+ P(i)+=n;
+ else if(p==p1 && i>0 && !T(i-1)) /* special case; simplifies life */
+ P(i-1)+=n;
+ else if(p==p1)
+ inslist(r, i, n);
+ else{ /* must break piece in terminal */
+ inslist(r, i+1, (L(i)-(p1-p))|M);
+ inslist(r, i+1, n);
+ P(i) = (p1-p)|M;
+ }
+}
+
+int
+rterm(List *r, Posn p1)
+{
+ Posn p;
+ int i;
+
+ for(p = 0,i = 0; i<r->nused && p+L(i)<=p1; p+=L(i++))
+ ;
+ if(i==r->nused && (i==0 || !T(i-1)))
+ return 0;
+ return T(i);
+}
+
+Range
+rdata(List *r, Posn p1, Posn n)
+{
+ Posn p;
+ int i;
+ Range rg;
+
+ if(n==0)
+ panic("rdata 0");
+ for(p = 0,i = 0; i<r->nused && p+L(i)<=p1; p+=L(i++))
+ ;
+ if(i==r->nused)
+ panic("rdata 1");
+ if(T(i)){
+ n-=L(i)-(p1-p);
+ if(n<=0){
+ rg.p1 = rg.p2 = p1;
+ return rg;
+ }
+ p+=L(i++);
+ p1 = p;
+ }
+ if(T(i) || i==r->nused)
+ panic("rdata 2");
+ if(p+L(i)<p1+n)
+ n = L(i)-(p1-p);
+ rg.p1 = p1;
+ rg.p2 = p1+n;
+ if(p!=p1){
+ inslist(r, i+1, L(i)-(p1-p));
+ P(i)=p1-p;
+ i++;
+ }
+ if(L(i)!=n){
+ inslist(r, i+1, L(i)-n);
+ P(i)=n;
+ }
+ P(i)|=M;
+ /* now i is set; can we merge? */
+ if(i<r->nused-1 && T(i+1)){
+ P(i)=(n+=L(i+1))|M;
+ dellist(r, i+1);
+ }
+ if(i>0 && T(i-1)){
+ P(i)=(n+L(i-1))|M;
+ dellist(r, i-1);
+ }
+ return rg;
+}
diff --git a/src/cmd/sam/regexp.c b/src/cmd/sam/regexp.c
new file mode 100644
index 00000000..dee4377d
--- /dev/null
+++ b/src/cmd/sam/regexp.c
@@ -0,0 +1,801 @@
+#include "sam.h"
+
+Rangeset sel;
+String lastregexp;
+/*
+ * Machine Information
+ */
+typedef struct Inst Inst;
+
+struct Inst
+{
+ long type; /* < 0x10000 ==> literal, otherwise action */
+ union {
+ int rsid;
+ int rsubid;
+ int class;
+ struct Inst *rother;
+ struct Inst *rright;
+ } r;
+ union{
+ struct Inst *lleft;
+ struct Inst *lnext;
+ } l;
+};
+#define sid r.rsid
+#define subid r.rsubid
+#define rclass r.class
+#define other r.rother
+#define right r.rright
+#define left l.lleft
+#define next l.lnext
+
+#define NPROG 1024
+Inst program[NPROG];
+Inst *progp;
+Inst *startinst; /* First inst. of program; might not be program[0] */
+Inst *bstartinst; /* same for backwards machine */
+
+typedef struct Ilist Ilist;
+struct Ilist
+{
+ Inst *inst; /* Instruction of the thread */
+ Rangeset se;
+ Posn startp; /* first char of match */
+};
+
+#define NLIST 128
+
+Ilist *tl, *nl; /* This list, next list */
+Ilist list[2][NLIST];
+static Rangeset sempty;
+
+/*
+ * Actions and Tokens
+ *
+ * 0x100xx are operators, value == precedence
+ * 0x200xx are tokens, i.e. operands for operators
+ */
+#define OPERATOR 0x10000 /* Bitmask of all operators */
+#define START 0x10000 /* Start, used for marker on stack */
+#define RBRA 0x10001 /* Right bracket, ) */
+#define LBRA 0x10002 /* Left bracket, ( */
+#define OR 0x10003 /* Alternation, | */
+#define CAT 0x10004 /* Concatentation, implicit operator */
+#define STAR 0x10005 /* Closure, * */
+#define PLUS 0x10006 /* a+ == aa* */
+#define QUEST 0x10007 /* a? == a|nothing, i.e. 0 or 1 a's */
+#define ANY 0x20000 /* Any character but newline, . */
+#define NOP 0x20001 /* No operation, internal use only */
+#define BOL 0x20002 /* Beginning of line, ^ */
+#define EOL 0x20003 /* End of line, $ */
+#define CCLASS 0x20004 /* Character class, [] */
+#define NCCLASS 0x20005 /* Negated character class, [^] */
+#define END 0x20077 /* Terminate: match found */
+
+#define ISATOR 0x10000
+#define ISAND 0x20000
+
+/*
+ * Parser Information
+ */
+typedef struct Node Node;
+struct Node
+{
+ Inst *first;
+ Inst *last;
+};
+
+#define NSTACK 20
+Node andstack[NSTACK];
+Node *andp;
+int atorstack[NSTACK];
+int *atorp;
+int lastwasand; /* Last token was operand */
+int cursubid;
+int subidstack[NSTACK];
+int *subidp;
+int backwards;
+int nbra;
+Rune *exprp; /* pointer to next character in source expression */
+#define DCLASS 10 /* allocation increment */
+int nclass; /* number active */
+int Nclass; /* high water mark */
+Rune **class;
+int negateclass;
+
+void addinst(Ilist *l, Inst *inst, Rangeset *sep);
+void newmatch(Rangeset*);
+void bnewmatch(Rangeset*);
+void pushand(Inst*, Inst*);
+void pushator(int);
+Node *popand(int);
+int popator(void);
+void startlex(Rune*);
+int lex(void);
+void operator(int);
+void operand(int);
+void evaluntil(int);
+void optimize(Inst*);
+void bldcclass(void);
+
+void
+regerror(Err e)
+{
+ Strzero(&lastregexp);
+ error(e);
+}
+
+void
+regerror_c(Err e, int c)
+{
+ Strzero(&lastregexp);
+ error_c(e, c);
+}
+
+Inst *
+newinst(int t)
+{
+ if(progp >= &program[NPROG])
+ regerror(Etoolong);
+ progp->type = t;
+ progp->left = 0;
+ progp->right = 0;
+ return progp++;
+}
+
+Inst *
+realcompile(Rune *s)
+{
+ int token;
+
+ startlex(s);
+ atorp = atorstack;
+ andp = andstack;
+ subidp = subidstack;
+ cursubid = 0;
+ lastwasand = FALSE;
+ /* Start with a low priority operator to prime parser */
+ pushator(START-1);
+ while((token=lex()) != END){
+ if((token&ISATOR) == OPERATOR)
+ operator(token);
+ else
+ operand(token);
+ }
+ /* Close with a low priority operator */
+ evaluntil(START);
+ /* Force END */
+ operand(END);
+ evaluntil(START);
+ if(nbra)
+ regerror(Eleftpar);
+ --andp; /* points to first and only operand */
+ return andp->first;
+}
+
+void
+compile(String *s)
+{
+ int i;
+ Inst *oprogp;
+
+ if(Strcmp(s, &lastregexp)==0)
+ return;
+ for(i=0; i<nclass; i++)
+ free(class[i]);
+ nclass = 0;
+ progp = program;
+ backwards = FALSE;
+ startinst = realcompile(s->s);
+ optimize(program);
+ oprogp = progp;
+ backwards = TRUE;
+ bstartinst = realcompile(s->s);
+ optimize(oprogp);
+ Strduplstr(&lastregexp, s);
+}
+
+void
+operand(int t)
+{
+ Inst *i;
+ if(lastwasand)
+ operator(CAT); /* catenate is implicit */
+ i = newinst(t);
+ if(t == CCLASS){
+ if(negateclass)
+ i->type = NCCLASS; /* UGH */
+ i->rclass = nclass-1; /* UGH */
+ }
+ pushand(i, i);
+ lastwasand = TRUE;
+}
+
+void
+operator(int t)
+{
+ if(t==RBRA && --nbra<0)
+ regerror(Erightpar);
+ if(t==LBRA){
+/*
+ * if(++cursubid >= NSUBEXP)
+ * regerror(Esubexp);
+ */
+ cursubid++; /* silently ignored */
+ nbra++;
+ if(lastwasand)
+ operator(CAT);
+ }else
+ evaluntil(t);
+ if(t!=RBRA)
+ pushator(t);
+ lastwasand = FALSE;
+ if(t==STAR || t==QUEST || t==PLUS || t==RBRA)
+ lastwasand = TRUE; /* these look like operands */
+}
+
+void
+cant(char *s)
+{
+ char buf[100];
+
+ sprint(buf, "regexp: can't happen: %s", s);
+ panic(buf);
+}
+
+void
+pushand(Inst *f, Inst *l)
+{
+ if(andp >= &andstack[NSTACK])
+ cant("operand stack overflow");
+ andp->first = f;
+ andp->last = l;
+ andp++;
+}
+
+void
+pushator(int t)
+{
+ if(atorp >= &atorstack[NSTACK])
+ cant("operator stack overflow");
+ *atorp++=t;
+ if(cursubid >= NSUBEXP)
+ *subidp++= -1;
+ else
+ *subidp++=cursubid;
+}
+
+Node *
+popand(int op)
+{
+ if(andp <= &andstack[0])
+ if(op)
+ regerror_c(Emissop, op);
+ else
+ regerror(Ebadregexp);
+ return --andp;
+}
+
+int
+popator(void)
+{
+ if(atorp <= &atorstack[0])
+ cant("operator stack underflow");
+ --subidp;
+ return *--atorp;
+}
+
+void
+evaluntil(int pri)
+{
+ Node *op1, *op2, *t;
+ Inst *inst1, *inst2;
+
+ while(pri==RBRA || atorp[-1]>=pri){
+ switch(popator()){
+ case LBRA:
+ op1 = popand('(');
+ inst2 = newinst(RBRA);
+ inst2->subid = *subidp;
+ op1->last->next = inst2;
+ inst1 = newinst(LBRA);
+ inst1->subid = *subidp;
+ inst1->next = op1->first;
+ pushand(inst1, inst2);
+ return; /* must have been RBRA */
+ default:
+ panic("unknown regexp operator");
+ break;
+ case OR:
+ op2 = popand('|');
+ op1 = popand('|');
+ inst2 = newinst(NOP);
+ op2->last->next = inst2;
+ op1->last->next = inst2;
+ inst1 = newinst(OR);
+ inst1->right = op1->first;
+ inst1->left = op2->first;
+ pushand(inst1, inst2);
+ break;
+ case CAT:
+ op2 = popand(0);
+ op1 = popand(0);
+ if(backwards && op2->first->type!=END)
+ t = op1, op1 = op2, op2 = t;
+ op1->last->next = op2->first;
+ pushand(op1->first, op2->last);
+ break;
+ case STAR:
+ op2 = popand('*');
+ inst1 = newinst(OR);
+ op2->last->next = inst1;
+ inst1->right = op2->first;
+ pushand(inst1, inst1);
+ break;
+ case PLUS:
+ op2 = popand('+');
+ inst1 = newinst(OR);
+ op2->last->next = inst1;
+ inst1->right = op2->first;
+ pushand(op2->first, inst1);
+ break;
+ case QUEST:
+ op2 = popand('?');
+ inst1 = newinst(OR);
+ inst2 = newinst(NOP);
+ inst1->left = inst2;
+ inst1->right = op2->first;
+ op2->last->next = inst2;
+ pushand(inst1, inst2);
+ break;
+ }
+ }
+}
+
+
+void
+optimize(Inst *start)
+{
+ Inst *inst, *target;
+
+ for(inst=start; inst->type!=END; inst++){
+ target = inst->next;
+ while(target->type == NOP)
+ target = target->next;
+ inst->next = target;
+ }
+}
+
+#ifdef DEBUG
+void
+dumpstack(void){
+ Node *stk;
+ int *ip;
+
+ dprint("operators\n");
+ for(ip = atorstack; ip<atorp; ip++)
+ dprint("0%o\n", *ip);
+ dprint("operands\n");
+ for(stk = andstack; stk<andp; stk++)
+ dprint("0%o\t0%o\n", stk->first->type, stk->last->type);
+}
+void
+dump(void){
+ Inst *l;
+
+ l = program;
+ do{
+ dprint("%d:\t0%o\t%d\t%d\n", l-program, l->type,
+ l->left-program, l->right-program);
+ }while(l++->type);
+}
+#endif
+
+void
+startlex(Rune *s)
+{
+ exprp = s;
+ nbra = 0;
+}
+
+
+int
+lex(void){
+ int c= *exprp++;
+
+ switch(c){
+ case '\\':
+ if(*exprp)
+ if((c= *exprp++)=='n')
+ c='\n';
+ break;
+ case 0:
+ c = END;
+ --exprp; /* In case we come here again */
+ break;
+ case '*':
+ c = STAR;
+ break;
+ case '?':
+ c = QUEST;
+ break;
+ case '+':
+ c = PLUS;
+ break;
+ case '|':
+ c = OR;
+ break;
+ case '.':
+ c = ANY;
+ break;
+ case '(':
+ c = LBRA;
+ break;
+ case ')':
+ c = RBRA;
+ break;
+ case '^':
+ c = BOL;
+ break;
+ case '$':
+ c = EOL;
+ break;
+ case '[':
+ c = CCLASS;
+ bldcclass();
+ break;
+ }
+ return c;
+}
+
+long
+nextrec(void){
+ if(exprp[0]==0 || (exprp[0]=='\\' && exprp[1]==0))
+ regerror(Ebadclass);
+ if(exprp[0] == '\\'){
+ exprp++;
+ if(*exprp=='n'){
+ exprp++;
+ return '\n';
+ }
+ return *exprp++|0x10000;
+ }
+ return *exprp++;
+}
+
+void
+bldcclass(void)
+{
+ long c1, c2, n, na;
+ Rune *classp;
+
+ classp = emalloc(DCLASS*RUNESIZE);
+ n = 0;
+ na = DCLASS;
+ /* we have already seen the '[' */
+ if(*exprp == '^'){
+ classp[n++] = '\n'; /* don't match newline in negate case */
+ negateclass = TRUE;
+ exprp++;
+ }else
+ negateclass = FALSE;
+ while((c1 = nextrec()) != ']'){
+ if(c1 == '-'){
+ Error:
+ free(classp);
+ regerror(Ebadclass);
+ }
+ if(n+4 >= na){ /* 3 runes plus NUL */
+ na += DCLASS;
+ classp = erealloc(classp, na*RUNESIZE);
+ }
+ if(*exprp == '-'){
+ exprp++; /* eat '-' */
+ if((c2 = nextrec()) == ']')
+ goto Error;
+ classp[n+0] = 0xFFFF;
+ classp[n+1] = c1;
+ classp[n+2] = c2;
+ n += 3;
+ }else
+ classp[n++] = c1;
+ }
+ classp[n] = 0;
+ if(nclass == Nclass){
+ Nclass += DCLASS;
+ class = erealloc(class, Nclass*sizeof(Rune*));
+ }
+ class[nclass++] = classp;
+}
+
+int
+classmatch(int classno, int c, int negate)
+{
+ Rune *p;
+
+ p = class[classno];
+ while(*p){
+ if(*p == 0xFFFF){
+ if(p[1]<=c && c<=p[2])
+ return !negate;
+ p += 3;
+ }else if(*p++ == c)
+ return !negate;
+ }
+ return negate;
+}
+
+/*
+ * Note optimization in addinst:
+ * *l must be pending when addinst called; if *l has been looked
+ * at already, the optimization is a bug.
+ */
+void
+addinst(Ilist *l, Inst *inst, Rangeset *sep)
+{
+ Ilist *p;
+
+ for(p = l; p->inst; p++){
+ if(p->inst==inst){
+ if((sep)->p[0].p1 < p->se.p[0].p1)
+ p->se= *sep; /* this would be bug */
+ return; /* It's already there */
+ }
+ }
+ p->inst = inst;
+ p->se= *sep;
+ (p+1)->inst = 0;
+}
+
+int
+execute(File *f, Posn startp, Posn eof)
+{
+ int flag = 0;
+ Inst *inst;
+ Ilist *tlp;
+ Posn p = startp;
+ int nnl = 0, ntl;
+ int c;
+ int wrapped = 0;
+ int startchar = startinst->type<OPERATOR? startinst->type : 0;
+
+ list[0][0].inst = list[1][0].inst = 0;
+ sel.p[0].p1 = -1;
+ /* Execute machine once for each character */
+ for(;;p++){
+ doloop:
+ c = filereadc(f, p);
+ if(p>=eof || c<0){
+ switch(wrapped++){
+ case 0: /* let loop run one more click */
+ case 2:
+ break;
+ case 1: /* expired; wrap to beginning */
+ if(sel.p[0].p1>=0 || eof!=INFINITY)
+ goto Return;
+ list[0][0].inst = list[1][0].inst = 0;
+ p = 0;
+ goto doloop;
+ default:
+ goto Return;
+ }
+ }else if(((wrapped && p>=startp) || sel.p[0].p1>0) && nnl==0)
+ break;
+ /* fast check for first char */
+ if(startchar && nnl==0 && c!=startchar)
+ continue;
+ tl = list[flag];
+ nl = list[flag^=1];
+ nl->inst = 0;
+ ntl = nnl;
+ nnl = 0;
+ if(sel.p[0].p1<0 && (!wrapped || p<startp || startp==eof)){
+ /* Add first instruction to this list */
+ if(++ntl >= NLIST)
+ Overflow:
+ error(Eoverflow);
+ sempty.p[0].p1 = p;
+ addinst(tl, startinst, &sempty);
+ }
+ /* Execute machine until this list is empty */
+ for(tlp = tl; inst = tlp->inst; tlp++){ /* assignment = */
+ Switchstmt:
+ switch(inst->type){
+ default: /* regular character */
+ if(inst->type==c){
+ Addinst:
+ if(++nnl >= NLIST)
+ goto Overflow;
+ addinst(nl, inst->next, &tlp->se);
+ }
+ break;
+ case LBRA:
+ if(inst->subid>=0)
+ tlp->se.p[inst->subid].p1 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case RBRA:
+ if(inst->subid>=0)
+ tlp->se.p[inst->subid].p2 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case ANY:
+ if(c!='\n')
+ goto Addinst;
+ break;
+ case BOL:
+ if(p==0 || filereadc(f, p - 1)=='\n'){
+ Step:
+ inst = inst->next;
+ goto Switchstmt;
+ }
+ break;
+ case EOL:
+ if(c == '\n')
+ goto Step;
+ break;
+ case CCLASS:
+ if(c>=0 && classmatch(inst->rclass, c, 0))
+ goto Addinst;
+ break;
+ case NCCLASS:
+ if(c>=0 && classmatch(inst->rclass, c, 1))
+ goto Addinst;
+ break;
+ case OR:
+ /* evaluate right choice later */
+ if(++ntl >= NLIST)
+ goto Overflow;
+ addinst(tlp, inst->right, &tlp->se);
+ /* efficiency: advance and re-evaluate */
+ inst = inst->left;
+ goto Switchstmt;
+ case END: /* Match! */
+ tlp->se.p[0].p2 = p;
+ newmatch(&tlp->se);
+ break;
+ }
+ }
+ }
+ Return:
+ return sel.p[0].p1>=0;
+}
+
+void
+newmatch(Rangeset *sp)
+{
+ int i;
+
+ if(sel.p[0].p1<0 || sp->p[0].p1<sel.p[0].p1 ||
+ (sp->p[0].p1==sel.p[0].p1 && sp->p[0].p2>sel.p[0].p2))
+ for(i = 0; i<NSUBEXP; i++)
+ sel.p[i] = sp->p[i];
+}
+
+int
+bexecute(File *f, Posn startp)
+{
+ int flag = 0;
+ Inst *inst;
+ Ilist *tlp;
+ Posn p = startp;
+ int nnl = 0, ntl;
+ int c;
+ int wrapped = 0;
+ int startchar = bstartinst->type<OPERATOR? bstartinst->type : 0;
+
+ list[0][0].inst = list[1][0].inst = 0;
+ sel.p[0].p1= -1;
+ /* Execute machine once for each character, including terminal NUL */
+ for(;;--p){
+ doloop:
+ if((c = filereadc(f, p - 1))==-1){
+ switch(wrapped++){
+ case 0: /* let loop run one more click */
+ case 2:
+ break;
+ case 1: /* expired; wrap to end */
+ if(sel.p[0].p1>=0)
+ case 3:
+ goto Return;
+ list[0][0].inst = list[1][0].inst = 0;
+ p = f->_.nc;
+ goto doloop;
+ default:
+ goto Return;
+ }
+ }else if(((wrapped && p<=startp) || sel.p[0].p1>0) && nnl==0)
+ break;
+ /* fast check for first char */
+ if(startchar && nnl==0 && c!=startchar)
+ continue;
+ tl = list[flag];
+ nl = list[flag^=1];
+ nl->inst = 0;
+ ntl = nnl;
+ nnl = 0;
+ if(sel.p[0].p1<0 && (!wrapped || p>startp)){
+ /* Add first instruction to this list */
+ if(++ntl >= NLIST)
+ Overflow:
+ error(Eoverflow);
+ /* the minus is so the optimizations in addinst work */
+ sempty.p[0].p1 = -p;
+ addinst(tl, bstartinst, &sempty);
+ }
+ /* Execute machine until this list is empty */
+ for(tlp = tl; inst = tlp->inst; tlp++){ /* assignment = */
+ Switchstmt:
+ switch(inst->type){
+ default: /* regular character */
+ if(inst->type == c){
+ Addinst:
+ if(++nnl >= NLIST)
+ goto Overflow;
+ addinst(nl, inst->next, &tlp->se);
+ }
+ break;
+ case LBRA:
+ if(inst->subid>=0)
+ tlp->se.p[inst->subid].p1 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case RBRA:
+ if(inst->subid >= 0)
+ tlp->se.p[inst->subid].p2 = p;
+ inst = inst->next;
+ goto Switchstmt;
+ case ANY:
+ if(c != '\n')
+ goto Addinst;
+ break;
+ case BOL:
+ if(c=='\n' || p==0){
+ Step:
+ inst = inst->next;
+ goto Switchstmt;
+ }
+ break;
+ case EOL:
+ if(p==f->_.nc || filereadc(f, p)=='\n')
+ goto Step;
+ break;
+ case CCLASS:
+ if(c>=0 && classmatch(inst->rclass, c, 0))
+ goto Addinst;
+ break;
+ case NCCLASS:
+ if(c>=0 && classmatch(inst->rclass, c, 1))
+ goto Addinst;
+ break;
+ case OR:
+ /* evaluate right choice later */
+ if(++ntl >= NLIST)
+ goto Overflow;
+ addinst(tlp, inst->right, &tlp->se);
+ /* efficiency: advance and re-evaluate */
+ inst = inst->left;
+ goto Switchstmt;
+ case END: /* Match! */
+ tlp->se.p[0].p1 = -tlp->se.p[0].p1; /* minus sign */
+ tlp->se.p[0].p2 = p;
+ bnewmatch(&tlp->se);
+ break;
+ }
+ }
+ }
+ Return:
+ return sel.p[0].p1>=0;
+}
+
+void
+bnewmatch(Rangeset *sp)
+{
+ int i;
+ if(sel.p[0].p1<0 || sp->p[0].p1>sel.p[0].p2 || (sp->p[0].p1==sel.p[0].p2 && sp->p[0].p2<sel.p[0].p1))
+ for(i = 0; i<NSUBEXP; i++){ /* note the reversal; p1<=p2 */
+ sel.p[i].p1 = sp->p[i].p2;
+ sel.p[i].p2 = sp->p[i].p1;
+ }
+}
diff --git a/src/cmd/sam/sam b/src/cmd/sam/sam
new file mode 100755
index 00000000..733b555c
--- /dev/null
+++ b/src/cmd/sam/sam
Binary files differ
diff --git a/src/cmd/sam/sam.c b/src/cmd/sam/sam.c
new file mode 100644
index 00000000..81ccaf78
--- /dev/null
+++ b/src/cmd/sam/sam.c
@@ -0,0 +1,739 @@
+#include "sam.h"
+
+Rune genbuf[BLOCKSIZE];
+int io;
+int panicking;
+int rescuing;
+String genstr;
+String rhs;
+String curwd;
+String cmdstr;
+Rune empty[] = { 0 };
+char *genc;
+File *curfile;
+File *flist;
+File *cmd;
+jmp_buf mainloop;
+List tempfile;
+int quitok = TRUE;
+int downloaded;
+int dflag;
+int Rflag;
+char *machine;
+char *home;
+int bpipeok;
+int termlocked;
+char *samterm = SAMTERM;
+char *rsamname = RSAM;
+File *lastfile;
+Disk *disk;
+long seq;
+
+Rune baddir[] = { '<', 'b', 'a', 'd', 'd', 'i', 'r', '>', '\n'};
+
+void usage(void);
+
+int main(int argc, char *argv[])
+{
+ int i;
+ String *t;
+ char **ap, **arg;
+
+ { // libfmt-2.0 uses %lu where we need %lud
+ extern int __flagfmt(Fmt*);
+ fmtinstall('u', __flagfmt);
+ }
+
+ arg = argv++;
+ ap = argv;
+ while(argc>1 && argv[0] && argv[0][0]=='-'){
+ switch(argv[0][1]){
+ case 'd':
+ dflag++;
+ break;
+
+ case 'r':
+ --argc, argv++;
+ if(argc == 1)
+ usage();
+ machine = *argv;
+ break;
+
+ case 'R':
+ Rflag++;
+ break;
+
+ case 't':
+ --argc, argv++;
+ if(argc == 1)
+ usage();
+ samterm = *argv;
+ break;
+
+ case 's':
+ --argc, argv++;
+ if(argc == 1)
+ usage();
+ rsamname = *argv;
+ break;
+
+ case 'x': /* x11 option - strip the x */
+ strcpy(*argv+1, *argv+2);
+ *ap++ = *argv++;
+ *ap++ = *argv;
+ argc--;
+ break;
+
+ default:
+ dprint("sam: unknown flag %c\n", argv[0][1]);
+ exits("usage");
+ }
+ --argc, argv++;
+ }
+ Strinit(&cmdstr);
+ Strinit0(&lastpat);
+ Strinit0(&lastregexp);
+ Strinit0(&genstr);
+ Strinit0(&rhs);
+ Strinit0(&curwd);
+ tempfile.listptr = emalloc(1); /* so it can be freed later */
+ Strinit0(&plan9cmd);
+ home = getenv(HOME);
+ disk = diskinit();
+ if(home == 0)
+ home = "/";
+ if(!dflag)
+ startup(machine, Rflag, arg, ap);
+ notify(notifyf);
+ getcurwd();
+ if(argc>1){
+ for(i=0; i<argc-1; i++){
+ if(!setjmp(mainloop)){
+ t = tmpcstr(argv[i]);
+ Straddc(t, '\0');
+ Strduplstr(&genstr, t);
+ freetmpstr(t);
+ fixname(&genstr);
+ logsetname(newfile(), &genstr);
+ }
+ }
+ }else if(!downloaded)
+ newfile();
+ seq++;
+ if(file.nused)
+ current(file.filepptr[0]);
+ setjmp(mainloop);
+ cmdloop();
+ trytoquit(); /* if we already q'ed, quitok will be TRUE */
+ exits(0);
+}
+
+void
+usage(void)
+{
+ dprint("usage: sam [-d] [-t samterm] [-s sam name] -r machine\n");
+ exits("usage");
+}
+
+void
+rescue(void)
+{
+ int i, nblank = 0;
+ File *f;
+ char *c;
+ char buf[256];
+
+ if(rescuing++)
+ return;
+ io = -1;
+ for(i=0; i<file.nused; i++){
+ f = file.filepptr[i];
+ if(f==cmd || f->_.nc==0 || !fileisdirty(f))
+ continue;
+ if(io == -1){
+ sprint(buf, "%s/sam.save", home);
+ io = create(buf, 1, 0777);
+ if(io<0)
+ return;
+ }
+ if(f->name.s[0]){
+ c = Strtoc(&f->name);
+ strncpy(buf, c, sizeof buf-1);
+ buf[sizeof buf-1] = 0;
+ free(c);
+ }else
+ sprint(buf, "nameless.%d", nblank++);
+ fprint(io, "#!%s '%s' $* <<'---%s'\n", SAMSAVECMD, buf, buf);
+ addr.r.p1 = 0, addr.r.p2 = f->_.nc;
+ writeio(f);
+ fprint(io, "\n---%s\n", (char *)buf);
+ }
+}
+
+void
+panic(char *s)
+{
+ int wasd;
+
+ if(!panicking++ && !setjmp(mainloop)){
+ wasd = downloaded;
+ downloaded = 0;
+ dprint("sam: panic: %s: %r\n", s);
+ if(wasd)
+ fprint(2, "sam: panic: %s: %r\n", s);
+ rescue();
+ abort();
+ }
+}
+
+void
+hiccough(char *s)
+{
+ File *f;
+ int i;
+
+ if(rescuing)
+ exits("rescue");
+ if(s)
+ dprint("%s\n", s);
+ resetcmd();
+ resetxec();
+ resetsys();
+ if(io > 0)
+ close(io);
+
+ /*
+ * back out any logged changes & restore old sequences
+ */
+ for(i=0; i<file.nused; i++){
+ f = file.filepptr[i];
+ if(f==cmd)
+ continue;
+ if(f->seq==seq){
+ bufdelete(&f->epsilon, 0, f->epsilon.nc);
+ f->seq = f->prevseq;
+ f->dot.r = f->prevdot;
+ f->mark = f->prevmark;
+ state(f, f->prevmod ? Dirty: Clean);
+ }
+ }
+
+ update();
+ if (curfile) {
+ if (curfile->unread)
+ curfile->unread = FALSE;
+ else if (downloaded)
+ outTs(Hcurrent, curfile->tag);
+ }
+ longjmp(mainloop, 1);
+}
+
+void
+intr(void)
+{
+ error(Eintr);
+}
+
+void
+trytoclose(File *f)
+{
+ char *t;
+ char buf[256];
+
+ if(f == cmd) /* possible? */
+ return;
+ if(f->deleted)
+ return;
+ if(fileisdirty(f) && !f->closeok){
+ f->closeok = TRUE;
+ if(f->name.s[0]){
+ t = Strtoc(&f->name);
+ strncpy(buf, t, sizeof buf-1);
+ free(t);
+ }else
+ strcpy(buf, "nameless file");
+ error_s(Emodified, buf);
+ }
+ f->deleted = TRUE;
+}
+
+void
+trytoquit(void)
+{
+ int c;
+ File *f;
+
+ if(!quitok){
+ for(c = 0; c<file.nused; c++){
+ f = file.filepptr[c];
+ if(f!=cmd && fileisdirty(f)){
+ quitok = TRUE;
+ eof = FALSE;
+ error(Echanges);
+ }
+ }
+ }
+}
+
+void
+load(File *f)
+{
+ Address saveaddr;
+
+ Strduplstr(&genstr, &f->name);
+ filename(f);
+ if(f->name.s[0]){
+ saveaddr = addr;
+ edit(f, 'I');
+ addr = saveaddr;
+ }else{
+ f->unread = 0;
+ f->cleanseq = f->seq;
+ }
+
+ fileupdate(f, TRUE, TRUE);
+}
+
+void
+cmdupdate(void)
+{
+ if(cmd && cmd->seq!=0){
+ fileupdate(cmd, FALSE, downloaded);
+ cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->_.nc;
+ telldot(cmd);
+ }
+}
+
+void
+delete(File *f)
+{
+ if(downloaded && f->rasp)
+ outTs(Hclose, f->tag);
+ delfile(f);
+ if(f == curfile)
+ current(0);
+}
+
+void
+update(void)
+{
+ int i, anymod;
+ File *f;
+
+ settempfile();
+ for(anymod = i=0; i<tempfile.nused; i++){
+ f = tempfile.filepptr[i];
+ if(f==cmd) /* cmd gets done in main() */
+ continue;
+ if(f->deleted) {
+ delete(f);
+ continue;
+ }
+ if(f->seq==seq && fileupdate(f, FALSE, downloaded))
+ anymod++;
+ if(f->rasp)
+ telldot(f);
+ }
+ if(anymod)
+ seq++;
+}
+
+File *
+current(File *f)
+{
+ return curfile = f;
+}
+
+void
+edit(File *f, int cmd)
+{
+ int empty = TRUE;
+ Posn p;
+ int nulls;
+
+ if(cmd == 'r')
+ logdelete(f, addr.r.p1, addr.r.p2);
+ if(cmd=='e' || cmd=='I'){
+ logdelete(f, (Posn)0, f->_.nc);
+ addr.r.p2 = f->_.nc;
+ }else if(f->_.nc!=0 || (f->name.s[0] && Strcmp(&genstr, &f->name)!=0))
+ empty = FALSE;
+ if((io = open(genc, OREAD))<0) {
+ if (curfile && curfile->unread)
+ curfile->unread = FALSE;
+ error_r(Eopen, genc);
+ }
+ p = readio(f, &nulls, empty, TRUE);
+ closeio((cmd=='e' || cmd=='I')? -1 : p);
+ if(cmd == 'r')
+ f->ndot.r.p1 = addr.r.p2, f->ndot.r.p2 = addr.r.p2+p;
+ else
+ f->ndot.r.p1 = f->ndot.r.p2 = 0;
+ f->closeok = empty;
+ if (quitok)
+ quitok = empty;
+ else
+ quitok = FALSE;
+ state(f, empty && !nulls? Clean : Dirty);
+ if(empty && !nulls)
+ f->cleanseq = f->seq;
+ if(cmd == 'e')
+ filename(f);
+}
+
+int
+getname(File *f, String *s, int save)
+{
+ int c, i;
+
+ Strzero(&genstr);
+ if(genc){
+ free(genc);
+ genc = 0;
+ }
+ if(s==0 || (c = s->s[0])==0){ /* no name provided */
+ if(f)
+ Strduplstr(&genstr, &f->name);
+ goto Return;
+ }
+ if(c!=' ' && c!='\t')
+ error(Eblank);
+ for(i=0; (c=s->s[i])==' ' || c=='\t'; i++)
+ ;
+ while(s->s[i] > ' ')
+ Straddc(&genstr, s->s[i++]);
+ if(s->s[i])
+ error(Enewline);
+ fixname(&genstr);
+ if(f && (save || f->name.s[0]==0)){
+ logsetname(f, &genstr);
+ if(Strcmp(&f->name, &genstr)){
+ quitok = f->closeok = FALSE;
+ f->qidpath = 0;
+ f->mtime = 0;
+ state(f, Dirty); /* if it's 'e', fix later */
+ }
+ }
+ Return:
+ genc = Strtoc(&genstr);
+ i = genstr.n;
+ if(i && genstr.s[i-1]==0)
+ i--;
+ return i; /* strlen(name) */
+}
+
+void
+filename(File *f)
+{
+ if(genc)
+ free(genc);
+ genc = Strtoc(&genstr);
+ dprint("%c%c%c %s\n", " '"[f->mod],
+ "-+"[f->rasp!=0], " ."[f==curfile], genc);
+}
+
+void
+undostep(File *f, int isundo)
+{
+ uint p1, p2;
+ int mod;
+
+ mod = f->mod;
+ fileundo(f, isundo, 1, &p1, &p2, TRUE);
+ f->ndot = f->dot;
+ if(f->mod){
+ f->closeok = 0;
+ quitok = 0;
+ }else
+ f->closeok = 1;
+
+ if(f->mod != mod){
+ f->mod = mod;
+ if(mod)
+ mod = Clean;
+ else
+ mod = Dirty;
+ state(f, mod);
+ }
+}
+
+int
+undo(int isundo)
+{
+ File *f;
+ int i;
+ Mod max;
+
+ max = undoseq(curfile, isundo);
+ if(max == 0)
+ return 0;
+ settempfile();
+ for(i = 0; i<tempfile.nused; i++){
+ f = tempfile.filepptr[i];
+ if(f!=cmd && undoseq(f, isundo)==max)
+ undostep(f, isundo);
+ }
+ return 1;
+}
+
+int
+readcmd(String *s)
+{
+ int retcode;
+
+ if(flist != 0)
+ fileclose(flist);
+ flist = fileopen();
+
+ addr.r.p1 = 0, addr.r.p2 = flist->_.nc;
+ retcode = plan9(flist, '<', s, FALSE);
+ fileupdate(flist, FALSE, FALSE);
+ flist->seq = 0;
+ if (flist->_.nc > BLOCKSIZE)
+ error(Etoolong);
+ Strzero(&genstr);
+ Strinsure(&genstr, flist->_.nc);
+ bufread(flist, (Posn)0, genbuf, flist->_.nc);
+ memmove(genstr.s, genbuf, flist->_.nc*RUNESIZE);
+ genstr.n = flist->_.nc;
+ Straddc(&genstr, '\0');
+ return retcode;
+}
+
+void
+getcurwd(void)
+{
+ String *t;
+ char buf[256];
+
+ buf[0] = 0;
+ getwd(buf, sizeof(buf));
+ t = tmpcstr(buf);
+ Strduplstr(&curwd, t);
+ freetmpstr(t);
+ if(curwd.n == 0)
+ warn(Wpwd);
+ else if(curwd.s[curwd.n-1] != '/')
+ Straddc(&curwd, '/');
+}
+
+void
+cd(String *str)
+{
+ int i, fd;
+ char *s;
+ File *f;
+ String owd;
+
+ getcurwd();
+ if(getname((File *)0, str, FALSE))
+ s = genc;
+ else
+ s = home;
+ if(chdir(s))
+ syserror("chdir");
+ fd = open("/dev/wdir", OWRITE);
+ if(fd > 0)
+ write(fd, s, strlen(s));
+ dprint("!\n");
+ Strinit(&owd);
+ Strduplstr(&owd, &curwd);
+ getcurwd();
+ settempfile();
+ for(i=0; i<tempfile.nused; i++){
+ f = tempfile.filepptr[i];
+ if(f!=cmd && f->name.s[0]!='/' && f->name.s[0]!=0){
+ Strinsert(&f->name, &owd, (Posn)0);
+ fixname(&f->name);
+ sortname(f);
+ }else if(f != cmd && Strispre(&curwd, &f->name)){
+ fixname(&f->name);
+ sortname(f);
+ }
+ }
+ Strclose(&owd);
+}
+
+int
+loadflist(String *s)
+{
+ int c, i;
+
+ c = s->s[0];
+ for(i = 0; s->s[i]==' ' || s->s[i]=='\t'; i++)
+ ;
+ if((c==' ' || c=='\t') && s->s[i]!='\n'){
+ if(s->s[i]=='<'){
+ Strdelete(s, 0L, (long)i+1);
+ readcmd(s);
+ }else{
+ Strzero(&genstr);
+ while((c = s->s[i++]) && c!='\n')
+ Straddc(&genstr, c);
+ Straddc(&genstr, '\0');
+ }
+ }else{
+ if(c != '\n')
+ error(Eblank);
+ Strdupl(&genstr, empty);
+ }
+ if(genc)
+ free(genc);
+ genc = Strtoc(&genstr);
+ return genstr.s[0];
+}
+
+File *
+readflist(int readall, int delete)
+{
+ Posn i;
+ int c;
+ File *f;
+ String t;
+
+ Strinit(&t);
+ for(i=0,f=0; f==0 || readall || delete; i++){ /* ++ skips blank */
+ Strdelete(&genstr, (Posn)0, i);
+ for(i=0; (c = genstr.s[i])==' ' || c=='\t' || c=='\n'; i++)
+ ;
+ if(i >= genstr.n)
+ break;
+ Strdelete(&genstr, (Posn)0, i);
+ for(i=0; (c=genstr.s[i]) && c!=' ' && c!='\t' && c!='\n'; i++)
+ ;
+
+ if(i == 0)
+ break;
+ genstr.s[i] = 0;
+ Strduplstr(&t, tmprstr(genstr.s, i+1));
+ fixname(&t);
+ f = lookfile(&t);
+ if(delete){
+ if(f == 0)
+ warn_S(Wfile, &t);
+ else
+ trytoclose(f);
+ }else if(f==0 && readall)
+ logsetname(f = newfile(), &t);
+ }
+ Strclose(&t);
+ return f;
+}
+
+File *
+tofile(String *s)
+{
+ File *f;
+
+ if(s->s[0] != ' ')
+ error(Eblank);
+ if(loadflist(s) == 0){
+ f = lookfile(&genstr); /* empty string ==> nameless file */
+ if(f == 0)
+ error_s(Emenu, genc);
+ }else if((f=readflist(FALSE, FALSE)) == 0)
+ error_s(Emenu, genc);
+ return current(f);
+}
+
+File *
+getfile(String *s)
+{
+ File *f;
+
+ if(loadflist(s) == 0)
+ logsetname(f = newfile(), &genstr);
+ else if((f=readflist(TRUE, FALSE)) == 0)
+ error(Eblank);
+ return current(f);
+}
+
+void
+closefiles(File *f, String *s)
+{
+ if(s->s[0] == 0){
+ if(f == 0)
+ error(Enofile);
+ trytoclose(f);
+ return;
+ }
+ if(s->s[0] != ' ')
+ error(Eblank);
+ if(loadflist(s) == 0)
+ error(Enewline);
+ readflist(FALSE, TRUE);
+}
+
+void
+copy(File *f, Address addr2)
+{
+ Posn p;
+ int ni;
+ for(p=addr.r.p1; p<addr.r.p2; p+=ni){
+ ni = addr.r.p2-p;
+ if(ni > BLOCKSIZE)
+ ni = BLOCKSIZE;
+ bufread(f, p, genbuf, ni);
+ loginsert(addr2.f, addr2.r.p2, tmprstr(genbuf, ni)->s, ni);
+ }
+ addr2.f->ndot.r.p2 = addr2.r.p2+(f->dot.r.p2-f->dot.r.p1);
+ addr2.f->ndot.r.p1 = addr2.r.p2;
+}
+
+void
+move(File *f, Address addr2)
+{
+ if(addr.r.p2 <= addr2.r.p2){
+ logdelete(f, addr.r.p1, addr.r.p2);
+ copy(f, addr2);
+ }else if(addr.r.p1 >= addr2.r.p2){
+ copy(f, addr2);
+ logdelete(f, addr.r.p1, addr.r.p2);
+ }else
+ error(Eoverlap);
+}
+
+Posn
+nlcount(File *f, Posn p0, Posn p1)
+{
+ Posn nl = 0;
+
+ while(p0 < p1)
+ if(filereadc(f, p0++)=='\n')
+ nl++;
+ return nl;
+}
+
+void
+printposn(File *f, int charsonly)
+{
+ Posn l1, l2;
+
+ if(!charsonly){
+ l1 = 1+nlcount(f, (Posn)0, addr.r.p1);
+ l2 = l1+nlcount(f, addr.r.p1, addr.r.p2);
+ /* check if addr ends with '\n' */
+ if(addr.r.p2>0 && addr.r.p2>addr.r.p1 && filereadc(f, addr.r.p2-1)=='\n')
+ --l2;
+ dprint("%lud", l1);
+ if(l2 != l1)
+ dprint(",%lud", l2);
+ dprint("; ");
+ }
+ dprint("#%lud", addr.r.p1);
+ if(addr.r.p2 != addr.r.p1)
+ dprint(",#%lud", addr.r.p2);
+ dprint("\n");
+}
+
+void
+settempfile(void)
+{
+ if(tempfile.nalloc < file.nused){
+ free(tempfile.listptr);
+ tempfile.listptr = emalloc(sizeof(*tempfile.filepptr)*file.nused);
+ tempfile.nalloc = file.nused;
+ }
+ tempfile.nused = file.nused;
+ memmove(&tempfile.filepptr[0], &file.filepptr[0], file.nused*sizeof(File*));
+}
diff --git a/src/cmd/sam/sam.h b/src/cmd/sam/sam.h
new file mode 100644
index 00000000..6a2708c1
--- /dev/null
+++ b/src/cmd/sam/sam.h
@@ -0,0 +1,407 @@
+#include <u.h>
+#include <libc.h>
+#include <plumb.h>
+#include "errors.h"
+
+/*
+ * BLOCKSIZE is relatively small to keep memory consumption down.
+ */
+
+#define BLOCKSIZE 2048
+#define RUNESIZE sizeof(Rune)
+#define NDISC 5
+#define NBUFFILES 3+2*NDISC /* plan 9+undo+snarf+NDISC*(transcript+buf) */
+#define NSUBEXP 10
+
+#define TRUE 1
+#define FALSE 0
+
+#define INFINITY 0x7FFFFFFFL
+#define INCR 25
+#define STRSIZE (2*BLOCKSIZE)
+
+typedef long Posn; /* file position or address */
+typedef ushort Mod; /* modification number */
+
+typedef struct Address Address;
+typedef struct Block Block;
+typedef struct Buffer Buffer;
+typedef struct Disk Disk;
+typedef struct Discdesc Discdesc;
+typedef struct File File;
+typedef struct List List;
+typedef struct Range Range;
+typedef struct Rangeset Rangeset;
+typedef struct String String;
+
+enum State
+{
+ Clean = ' ',
+ Dirty = '\'',
+ Unread = '-',
+};
+
+struct Range
+{
+ Posn p1, p2;
+};
+
+struct Rangeset
+{
+ Range p[NSUBEXP];
+};
+
+struct Address
+{
+ Range r;
+ File *f;
+};
+
+struct String
+{
+ short n;
+ short size;
+ Rune *s;
+};
+
+struct List /* code depends on a long being able to hold a pointer */
+{
+ int nalloc;
+ int nused;
+ union{
+ void *listp;
+ Block *blkp;
+ long *longp;
+ uchar* *ucharp;
+ String* *stringp;
+ File* *filep;
+ long listv;
+ }g;
+};
+
+#define listptr g.listp
+#define blkptr g.blkp
+#define longptr g.longp
+#define ucharpptr g.ucharp
+#define stringpptr g.stringp
+#define filepptr g.filep
+#define listval g.listv
+
+enum
+{
+ Blockincr = 256,
+ Maxblock = 8*1024,
+
+ BUFSIZE = Maxblock, /* size from fbufalloc() */
+ RBUFSIZE = BUFSIZE/sizeof(Rune),
+};
+
+
+enum
+{
+ Null = '-',
+ Delete = 'd',
+ Insert = 'i',
+ Filename = 'f',
+ Dot = 'D',
+ Mark = 'm',
+};
+
+struct Block
+{
+ uint addr; /* disk address in bytes */
+ union
+ {
+ uint n; /* number of used runes in block */
+ Block *next; /* pointer to next in free list */
+ } _;
+};
+
+struct Disk
+{
+ int fd;
+ uint addr; /* length of temp file */
+ Block *free[Maxblock/Blockincr+1];
+};
+
+Disk* diskinit(void);
+Block* disknewblock(Disk*, uint);
+void diskrelease(Disk*, Block*);
+void diskread(Disk*, Block*, Rune*, uint);
+void diskwrite(Disk*, Block**, Rune*, uint);
+
+struct Buffer
+{
+ uint nc;
+ Rune *c; /* cache */
+ uint cnc; /* bytes in cache */
+ uint cmax; /* size of allocated cache */
+ uint cq; /* position of cache */
+ int cdirty; /* cache needs to be written */
+ uint cbi; /* index of cache Block */
+ Block **bl; /* array of blocks */
+ uint nbl; /* number of blocks */
+};
+void bufinsert(Buffer*, uint, Rune*, uint);
+void bufdelete(Buffer*, uint, uint);
+uint bufload(Buffer*, uint, int, int*);
+void bufread(Buffer*, uint, Rune*, uint);
+void bufclose(Buffer*);
+void bufreset(Buffer*);
+
+struct File
+{
+ Buffer _; /* the data */
+ Buffer delta; /* transcript of changes */
+ Buffer epsilon; /* inversion of delta for redo */
+ String name; /* name of associated file */
+ uvlong qidpath; /* of file when read */
+ uint mtime; /* of file when read */
+ int dev; /* of file when read */
+ int unread; /* file has not been read from disk */
+
+ long seq; /* if seq==0, File acts like Buffer */
+ long cleanseq; /* f->seq at last read/write of file */
+ int mod; /* file appears modified in menu */
+ char rescuing; /* sam exiting; this file unusable */
+
+// Text *curtext; /* most recently used associated text */
+// Text **text; /* list of associated texts */
+// int ntext;
+// int dumpid; /* used in dumping zeroxed windows */
+
+ Posn hiposn; /* highest address touched this Mod */
+ Address dot; /* current position */
+ Address ndot; /* new current position after update */
+ Range tdot; /* what terminal thinks is current range */
+ Range mark; /* tagged spot in text (don't confuse with Mark) */
+ List *rasp; /* map of what terminal's got */
+ short tag; /* for communicating with terminal */
+ char closeok; /* ok to close file? */
+ char deleted; /* delete at completion of command */
+ Range prevdot; /* state before start of change */
+ Range prevmark;
+ long prevseq;
+ int prevmod;
+};
+//File* fileaddtext(File*, Text*);
+void fileclose(File*);
+void filedelete(File*, uint, uint);
+//void filedeltext(File*, Text*);
+void fileinsert(File*, uint, Rune*, uint);
+uint fileload(File*, uint, int, int*);
+void filemark(File*);
+void filereset(File*);
+void filesetname(File*, String*);
+void fileundelete(File*, Buffer*, uint, uint);
+void fileuninsert(File*, Buffer*, uint, uint);
+void fileunsetname(File*, Buffer*);
+void fileundo(File*, int, int, uint*, uint*, int);
+int fileupdate(File*, int, int);
+
+int filereadc(File*, uint);
+File *fileopen(void);
+void loginsert(File*, uint, Rune*, uint);
+void logdelete(File*, uint, uint);
+void logsetname(File*, String*);
+int fileisdirty(File*);
+long undoseq(File*, int);
+long prevseq(Buffer*);
+
+void raspload(File*);
+void raspstart(File*);
+void raspdelete(File*, uint, uint, int);
+void raspinsert(File*, uint, Rune*, uint, int);
+void raspdone(File*, int);
+
+/*
+ * acme fns
+ */
+void* fbufalloc(void);
+void fbuffree(void*);
+uint min(uint, uint);
+void cvttorunes(char*, int, Rune*, int*, int*, int*);
+
+#define runemalloc(a) (Rune*)emalloc((a)*sizeof(Rune))
+#define runerealloc(a, b) (Rune*)realloc((a), (b)*sizeof(Rune))
+#define runemove(a, b, c) memmove((a), (b), (c)*sizeof(Rune))
+
+int alnum(int);
+int Read(int, void*, int);
+void Seek(int, long, int);
+int plan9(File*, int, String*, int);
+int Write(int, void*, int);
+int bexecute(File*, Posn);
+void cd(String*);
+void closefiles(File*, String*);
+void closeio(Posn);
+void cmdloop(void);
+void cmdupdate(void);
+void compile(String*);
+void copy(File*, Address);
+File *current(File*);
+void delete(File*);
+void delfile(File*);
+void dellist(List*, int);
+void doubleclick(File*, Posn);
+void dprint(char*, ...);
+void edit(File*, int);
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+void error(Err);
+void error_c(Err, int);
+void error_r(Err, char*);
+void error_s(Err, char*);
+int execute(File*, Posn, Posn);
+int filematch(File*, String*);
+void filename(File*);
+void fixname(String*);
+void fullname(String*);
+void getcurwd(void);
+File *getfile(String*);
+int getname(File*, String*, int);
+long getnum(int);
+void hiccough(char*);
+void inslist(List*, int, long);
+Address lineaddr(Posn, Address, int);
+void listfree(List*);
+void load(File*);
+File *lookfile(String*);
+void lookorigin(File*, Posn, Posn);
+int lookup(int);
+void move(File*, Address);
+void moveto(File*, Range);
+File *newfile(void);
+void nextmatch(File*, String*, Posn, int);
+int newtmp(int);
+void notifyf(void*, char*);
+void panic(char*);
+void printposn(File*, int);
+void print_ss(char*, String*, String*);
+void print_s(char*, String*);
+int rcv(void);
+Range rdata(List*, Posn, Posn);
+Posn readio(File*, int*, int, int);
+void rescue(void);
+void resetcmd(void);
+void resetsys(void);
+void resetxec(void);
+void rgrow(List*, Posn, Posn);
+void samerr(char*);
+void settempfile(void);
+int skipbl(void);
+void snarf(File*, Posn, Posn, Buffer*, int);
+void sortname(File*);
+void startup(char*, int, char**, char**);
+void state(File*, int);
+int statfd(int, ulong*, uvlong*, long*, long*, long*);
+int statfile(char*, ulong*, uvlong*, long*, long*, long*);
+void Straddc(String*, int);
+void Strclose(String*);
+int Strcmp(String*, String*);
+void Strdelete(String*, Posn, Posn);
+void Strdupl(String*, Rune*);
+void Strduplstr(String*, String*);
+void Strinit(String*);
+void Strinit0(String*);
+void Strinsert(String*, String*, Posn);
+void Strinsure(String*, ulong);
+int Strispre(String*, String*);
+void Strzero(String*);
+int Strlen(Rune*);
+char *Strtoc(String*);
+void syserror(char*);
+void telldot(File*);
+void tellpat(void);
+String *tmpcstr(char*);
+String *tmprstr(Rune*, int);
+void freetmpstr(String*);
+void termcommand(void);
+void termwrite(char*);
+File *tofile(String*);
+void trytoclose(File*);
+void trytoquit(void);
+int undo(int);
+void update(void);
+int waitfor(int);
+void warn(Warn);
+void warn_s(Warn, char*);
+void warn_SS(Warn, String*, String*);
+void warn_S(Warn, String*);
+int whichmenu(File*);
+void writef(File*);
+Posn writeio(File*);
+Discdesc *Dstart(void);
+
+extern Rune samname[]; /* compiler dependent */
+extern Rune *left[];
+extern Rune *right[];
+
+extern char RSAM[]; /* system dependent */
+extern char SAMTERM[];
+extern char HOME[];
+extern char TMPDIR[];
+extern char SH[];
+extern char SHPATH[];
+extern char RX[];
+extern char RXPATH[];
+extern char SAMSAVECMD[];
+
+/*
+ * acme globals
+ */
+extern long seq;
+extern Disk *disk;
+
+extern char *rsamname; /* globals */
+extern char *samterm;
+extern Rune genbuf[];
+extern char *genc;
+extern int io;
+extern int patset;
+extern int quitok;
+extern Address addr;
+extern Buffer snarfbuf;
+extern Buffer plan9buf;
+extern List file;
+extern List tempfile;
+extern File *cmd;
+extern File *curfile;
+extern File *lastfile;
+extern Mod modnum;
+extern Posn cmdpt;
+extern Posn cmdptadv;
+extern Rangeset sel;
+extern String curwd;
+extern String cmdstr;
+extern String genstr;
+extern String lastpat;
+extern String lastregexp;
+extern String plan9cmd;
+extern int downloaded;
+extern int eof;
+extern int bpipeok;
+extern int panicking;
+extern Rune empty[];
+extern int termlocked;
+extern int noflush;
+
+#include "mesg.h"
+
+void outTs(Hmesg, int);
+void outT0(Hmesg);
+void outTl(Hmesg, long);
+void outTslS(Hmesg, int, long, String*);
+void outTS(Hmesg, String*);
+void outTsS(Hmesg, int, String*);
+void outTsllS(Hmesg, int, long, long, String*);
+void outTsll(Hmesg, int, long, long);
+void outTsl(Hmesg, int, long);
+void outTsv(Hmesg, int, long);
+void outstart(Hmesg);
+void outcopy(int, void*);
+void outshort(int);
+void outlong(long);
+void outvlong(void*);
+void outsend(void);
+void outflush(void);
diff --git a/src/cmd/sam/shell.c b/src/cmd/sam/shell.c
new file mode 100644
index 00000000..2cac31bc
--- /dev/null
+++ b/src/cmd/sam/shell.c
@@ -0,0 +1,152 @@
+#include "sam.h"
+#include "parse.h"
+
+extern jmp_buf mainloop;
+
+char errfile[64];
+String plan9cmd; /* null terminated */
+Buffer plan9buf;
+void checkerrs(void);
+
+int
+plan9(File *f, int type, String *s, int nest)
+{
+ long l;
+ int m;
+ int pid, fd;
+ int retcode;
+ int pipe1[2], pipe2[2];
+
+ if(s->s[0]==0 && plan9cmd.s[0]==0)
+ error(Enocmd);
+ else if(s->s[0])
+ Strduplstr(&plan9cmd, s);
+ if(downloaded){
+ samerr(errfile);
+ remove(errfile);
+ }
+ if(type!='!' && pipe(pipe1)==-1)
+ error(Epipe);
+ if(type=='|')
+ snarf(f, addr.r.p1, addr.r.p2, &plan9buf, 1);
+ if((pid=fork()) == 0){
+ if(downloaded){ /* also put nasty fd's into errfile */
+ fd = create(errfile, 1, 0666L);
+ if(fd < 0)
+ fd = create("/dev/null", 1, 0666L);
+ dup(fd, 2);
+ close(fd);
+ /* 2 now points at err file */
+ if(type == '>')
+ dup(2, 1);
+ else if(type=='!'){
+ dup(2, 1);
+ fd = open("/dev/null", 0);
+ dup(fd, 0);
+ close(fd);
+ }
+ }
+ if(type != '!') {
+ if(type=='<' || type=='|')
+ dup(pipe1[1], 1);
+ else if(type == '>')
+ dup(pipe1[0], 0);
+ close(pipe1[0]);
+ close(pipe1[1]);
+ }
+ if(type == '|'){
+ if(pipe(pipe2) == -1)
+ exits("pipe");
+ if((pid = fork())==0){
+ /*
+ * It's ok if we get SIGPIPE here
+ */
+ close(pipe2[0]);
+ io = pipe2[1];
+ if(retcode=!setjmp(mainloop)){ /* assignment = */
+ char *c;
+ for(l = 0; l<plan9buf.nc; l+=m){
+ m = plan9buf.nc-l;
+ if(m>BLOCKSIZE-1)
+ m = BLOCKSIZE-1;
+ bufread(&plan9buf, l, genbuf, m);
+ genbuf[m] = 0;
+ c = Strtoc(tmprstr(genbuf, m+1));
+ Write(pipe2[1], c, strlen(c));
+ free(c);
+ }
+ }
+ exits(retcode? "error" : 0);
+ }
+ if(pid==-1){
+ fprint(2, "Can't fork?!\n");
+ exits("fork");
+ }
+ dup(pipe2[0], 0);
+ close(pipe2[0]);
+ close(pipe2[1]);
+ }
+ if(type=='<'){
+ close(0); /* so it won't read from terminal */
+ open("/dev/null", 0);
+ }
+ execl(SHPATH, SH, "-c", Strtoc(&plan9cmd), (char *)0);
+ exits("exec");
+ }
+ if(pid == -1)
+ error(Efork);
+ if(type=='<' || type=='|'){
+ int nulls;
+ if(downloaded && addr.r.p1 != addr.r.p2)
+ outTl(Hsnarflen, addr.r.p2-addr.r.p1);
+ snarf(f, addr.r.p1, addr.r.p2, &snarfbuf, 0);
+ logdelete(f, addr.r.p1, addr.r.p2);
+ close(pipe1[1]);
+ io = pipe1[0];
+ f->tdot.p1 = -1;
+ f->ndot.r.p2 = addr.r.p2+readio(f, &nulls, 0, FALSE);
+ f->ndot.r.p1 = addr.r.p2;
+ closeio((Posn)-1);
+ }else if(type=='>'){
+ close(pipe1[0]);
+ io = pipe1[1];
+ bpipeok = 1;
+ writeio(f);
+ bpipeok = 0;
+ closeio((Posn)-1);
+ }
+ retcode = waitfor(pid);
+ if(type=='|' || type=='<')
+ if(retcode!=0)
+ warn(Wbadstatus);
+ if(downloaded)
+ checkerrs();
+ if(!nest)
+ dprint("!\n");
+ return retcode;
+}
+
+void
+checkerrs(void)
+{
+ char buf[256];
+ int f, n, nl;
+ char *p;
+ long l;
+
+ if(statfile(errfile, 0, 0, 0, &l, 0) > 0 && l != 0){
+ if((f=open((char *)errfile, 0)) != -1){
+ if((n=read(f, buf, sizeof buf-1)) > 0){
+ for(nl=0,p=buf; nl<3 && p<&buf[n]; p++)
+ if(*p=='\n')
+ nl++;
+ *p = 0;
+ dprint("%s", buf);
+ if(p-buf < l-1)
+ dprint("(sam: more in %s)\n", errfile);
+ }
+ close(f);
+ }
+ }else
+ remove((char *)errfile);
+}
diff --git a/src/cmd/sam/unix.c b/src/cmd/sam/unix.c
new file mode 100644
index 00000000..cc15db44
--- /dev/null
+++ b/src/cmd/sam/unix.c
@@ -0,0 +1,272 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <pwd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "sam.h"
+
+Rune samname[] = { '~', '~', 's', 'a', 'm', '~', '~', 0 };
+
+static Rune l1[] = { '{', '[', '(', '<', 0253, 0};
+static Rune l2[] = { '\n', 0};
+static Rune l3[] = { '\'', '"', '`', 0};
+Rune *left[]= { l1, l2, l3, 0};
+
+static Rune r1[] = {'}', ']', ')', '>', 0273, 0};
+static Rune r2[] = {'\n', 0};
+static Rune r3[] = {'\'', '"', '`', 0};
+Rune *right[]= { r1, r2, r3, 0};
+
+#ifndef SAMTERMNAME
+#define SAMTERMNAME "/usr/local/bin/samterm"
+#endif
+#ifndef TMPDIRNAME
+#define TMPDIRNAME "/tmp"
+#endif
+#ifndef SHNAME
+#define SHNAME "rc"
+#endif
+#ifndef SHPATHNAME
+#define SHPATHNAME "/bin/rc"
+#endif
+#ifndef RXNAME
+#define RXNAME "ssh"
+#endif
+#ifndef RXPATHNAME
+#define RXPATHNAME "/usr/local/bin/ssh"
+#endif
+#ifndef SAMSAVECMDNAME
+#define SAMSAVECMDNAME "/bin/rc\n/usr/local/bin/samsave"
+#endif
+
+char RSAM[] = "sam";
+char SAMTERM[] = SAMTERMNAME;
+char HOME[] = "HOME";
+char TMPDIR[] = TMPDIRNAME;
+char SH[] = SHNAME;
+char SHPATH[] = SHPATHNAME;
+char RX[] = RXNAME;
+char RXPATH[] = RXPATHNAME;
+char SAMSAVECMD[] = SAMSAVECMDNAME;
+
+
+void
+dprint(char *z, ...)
+{
+ char buf[BLOCKSIZE];
+ va_list arg;
+
+ va_start(arg, z);
+ vseprint(buf, &buf[BLOCKSIZE], z, arg);
+ va_end(arg);
+ termwrite(buf);
+}
+
+void
+print_ss(char *s, String *a, String *b)
+{
+ dprint("?warning: %s: `%.*S' and `%.*S'\n", s, a->n, a->s, b->n, b->s);
+}
+
+void
+print_s(char *s, String *a)
+{
+ dprint("?warning: %s `%.*S'\n", s, a->n, a->s);
+}
+
+char*
+getuser(void)
+{
+ static char user[64];
+ if(user[0] == 0){
+ struct passwd *pw = getpwuid(getuid());
+ strcpy(user, pw ? pw->pw_name : "nobody");
+ }
+ return user;
+}
+
+int
+statfile(char *name, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
+{
+ struct stat dirb;
+
+ if (stat(name, &dirb) == -1)
+ return -1;
+ if (dev)
+ *dev = dirb.st_dev;
+ if (id)
+ *id = dirb.st_ino;
+ if (time)
+ *time = dirb.st_mtime;
+ if (length)
+ *length = dirb.st_size;
+ if(appendonly)
+ *appendonly = 0;
+ return 1;
+}
+
+int
+statfd(int fd, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
+{
+ struct stat dirb;
+
+ if (fstat(fd, &dirb) == -1)
+ return -1;
+ if (dev)
+ *dev = dirb.st_dev;
+ if (id)
+ *id = dirb.st_ino;
+ if (time)
+ *time = dirb.st_mtime;
+ if (length)
+ *length = dirb.st_size;
+ if(appendonly)
+ *appendonly = 0;
+ return 1;
+}
+
+void
+hup(int sig)
+{
+ panicking = 1; // ???
+ rescue();
+ exit(1);
+}
+
+int
+notify (void(*f)(void *, char *))
+{
+ signal(SIGINT, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN); // XXX - bpipeok?
+ signal(SIGHUP, hup);
+ return 1;
+}
+
+void
+notifyf(void *a, char *b) /* never called; hup is instead */
+{
+}
+
+static int
+temp_file(char *buf, int bufsize)
+{
+ char *tmp;
+ int n, fd;
+
+ tmp = getenv("TMPDIR");
+ if (!tmp)
+ tmp = TMPDIR;
+
+ n = snprint(buf, bufsize, "%s/sam.%d.XXXXXXX", tmp, getuid());
+ if (bufsize <= n)
+ return -1;
+ if ((fd = mkstemp(buf)) < 0) /* SES - linux sometimes uses mode 0666 */
+ return -1;
+ if (fcntl(fd, F_SETFD, fcntl(fd,F_GETFD,0) | FD_CLOEXEC) < 0)
+ return -1;
+ return fd;
+}
+
+int
+tempdisk(void)
+{
+ char buf[4096];
+ int fd = temp_file(buf, sizeof buf);
+ if (fd >= 0)
+ remove(buf);
+ return fd;
+}
+
+#undef wait
+int
+waitfor(int pid)
+{
+ int wm;
+ int rpid;
+
+ do; while((rpid = wait(&wm)) != pid && rpid != -1);
+ return (WEXITSTATUS(wm));
+}
+
+void
+samerr(char *buf)
+{
+ sprint(buf, "%s/sam.%s.err", TMPDIR, getuser());
+}
+
+void*
+emalloc(ulong n)
+{
+ void *p;
+
+ p = malloc(n);
+ if(p == 0)
+ panic("malloc fails");
+ memset(p, 0, n);
+ return p;
+}
+
+void*
+erealloc(void *p, ulong n)
+{
+ p = realloc(p, n);
+ if(p == 0)
+ panic("realloc fails");
+ return p;
+}
+
+#if 0
+char *
+strdup(const char *s)
+{
+ return strcpy(emalloc(strlen(s)), s);
+}
+#endif
+
+/*
+void exits(const char *s)
+{
+ if (s) fprint(2, "exit: %s\n", s);
+ exit(s != 0);
+}
+
+void
+_exits(const char *s)
+{
+ if (s) fprint(2, "exit: %s\n", s);
+ _exit(s != 0);
+}
+
+int errstr(char *buf, int size)
+{
+ extern int errno;
+
+ snprint(buf, size, "%s", strerror(errno));
+ return 1;
+}
+*/
+
+int create(char *name, int omode, int perm)
+{
+ int mode;
+ int fd;
+
+ if (omode & OWRITE) mode = O_WRONLY;
+ else if (omode & OREAD) mode = O_RDONLY;
+ else mode = O_RDWR;
+
+ if ((fd = open(name, mode|O_CREAT|O_TRUNC, perm)) < 0)
+ return fd;
+
+ if (omode & OCEXEC)
+ fcntl(fd, F_SETFD, fcntl(fd,F_GETFD,0) | FD_CLOEXEC);
+
+ /* SES - not exactly right, but hopefully good enough. */
+ if (omode & ORCLOSE)
+ remove(name);
+
+ return fd;
+}
diff --git a/src/cmd/sam/xec.c b/src/cmd/sam/xec.c
new file mode 100644
index 00000000..42acab0e
--- /dev/null
+++ b/src/cmd/sam/xec.c
@@ -0,0 +1,508 @@
+#include "sam.h"
+#include "parse.h"
+
+int Glooping;
+int nest;
+
+int append(File*, Cmd*, Posn);
+int display(File*);
+void looper(File*, Cmd*, int);
+void filelooper(Cmd*, int);
+void linelooper(File*, Cmd*);
+
+void
+resetxec(void)
+{
+ Glooping = nest = 0;
+}
+
+int
+cmdexec(File *f, Cmd *cp)
+{
+ int i;
+ Addr *ap;
+ Address a;
+
+ if(f && f->unread)
+ load(f);
+ if(f==0 && (cp->addr==0 || cp->addr->type!='"') &&
+ !utfrune("bBnqUXY!", cp->cmdc) &&
+ cp->cmdc!=('c'|0x100) && !(cp->cmdc=='D' && cp->ctext))
+ error(Enofile);
+ i = lookup(cp->cmdc);
+ if(i >= 0 && cmdtab[i].defaddr != aNo){
+ if((ap=cp->addr)==0 && cp->cmdc!='\n'){
+ cp->addr = ap = newaddr();
+ ap->type = '.';
+ if(cmdtab[i].defaddr == aAll)
+ ap->type = '*';
+ }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
+ ap->next = newaddr();
+ ap->next->type = '.';
+ if(cmdtab[i].defaddr == aAll)
+ ap->next->type = '*';
+ }
+ if(cp->addr){ /* may be false for '\n' (only) */
+ static Address none = {0,0,0};
+ if(f)
+ addr = address(ap, f->dot, 0);
+ else /* a " */
+ addr = address(ap, none, 0);
+ f = addr.f;
+ }
+ }
+ current(f);
+ switch(cp->cmdc){
+ case '{':
+ a = cp->addr? address(cp->addr, f->dot, 0): f->dot;
+ for(cp = cp->ccmd; cp; cp = cp->next){
+ a.f->dot = a;
+ cmdexec(a.f, cp);
+ }
+ break;
+ default:
+ i=(*cmdtab[i].fn)(f, cp);
+ return i;
+ }
+ return 1;
+}
+
+
+int
+a_cmd(File *f, Cmd *cp)
+{
+ return append(f, cp, addr.r.p2);
+}
+
+int
+b_cmd(File *f, Cmd *cp)
+{
+ USED(f);
+ f = cp->cmdc=='b'? tofile(cp->ctext) : getfile(cp->ctext);
+ if(f->unread)
+ load(f);
+ else if(nest == 0)
+ filename(f);
+ return TRUE;
+}
+
+int
+c_cmd(File *f, Cmd *cp)
+{
+ logdelete(f, addr.r.p1, addr.r.p2);
+ f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p2;
+ return append(f, cp, addr.r.p2);
+}
+
+int
+d_cmd(File *f, Cmd *cp)
+{
+ USED(cp);
+ logdelete(f, addr.r.p1, addr.r.p2);
+ f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p1;
+ return TRUE;
+}
+
+int
+D_cmd(File *f, Cmd *cp)
+{
+ closefiles(f, cp->ctext);
+ return TRUE;
+}
+
+int
+e_cmd(File *f, Cmd *cp)
+{
+ if(getname(f, cp->ctext, cp->cmdc=='e')==0)
+ error(Enoname);
+ edit(f, cp->cmdc);
+ return TRUE;
+}
+
+int
+f_cmd(File *f, Cmd *cp)
+{
+ getname(f, cp->ctext, TRUE);
+ filename(f);
+ return TRUE;
+}
+
+int
+g_cmd(File *f, Cmd *cp)
+{
+ if(f!=addr.f)panic("g_cmd f!=addr.f");
+ compile(cp->re);
+ if(execute(f, addr.r.p1, addr.r.p2) ^ cp->cmdc=='v'){
+ f->dot = addr;
+ return cmdexec(f, cp->ccmd);
+ }
+ return TRUE;
+}
+
+int
+i_cmd(File *f, Cmd *cp)
+{
+ return append(f, cp, addr.r.p1);
+}
+
+int
+k_cmd(File *f, Cmd *cp)
+{
+ USED(cp);
+ f->mark = addr.r;
+ return TRUE;
+}
+
+int
+m_cmd(File *f, Cmd *cp)
+{
+ Address addr2;
+
+ addr2 = address(cp->caddr, f->dot, 0);
+ if(cp->cmdc=='m')
+ move(f, addr2);
+ else
+ copy(f, addr2);
+ return TRUE;
+}
+
+int
+n_cmd(File *f, Cmd *cp)
+{
+ int i;
+ USED(f);
+ USED(cp);
+ for(i = 0; i<file.nused; i++){
+ if(file.filepptr[i] == cmd)
+ continue;
+ f = file.filepptr[i];
+ Strduplstr(&genstr, &f->name);
+ filename(f);
+ }
+ return TRUE;
+}
+
+int
+p_cmd(File *f, Cmd *cp)
+{
+ USED(cp);
+ return display(f);
+}
+
+int
+q_cmd(File *f, Cmd *cp)
+{
+ USED(cp);
+ USED(f);
+ trytoquit();
+ if(downloaded){
+ outT0(Hexit);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int
+s_cmd(File *f, Cmd *cp)
+{
+ int i, j, c, n;
+ Posn p1, op, didsub = 0, delta = 0;
+
+ n = cp->num;
+ op= -1;
+ compile(cp->re);
+ for(p1 = addr.r.p1; p1<=addr.r.p2 && execute(f, p1, addr.r.p2); ){
+ if(sel.p[0].p1==sel.p[0].p2){ /* empty match? */
+ if(sel.p[0].p1==op){
+ p1++;
+ continue;
+ }
+ p1 = sel.p[0].p2+1;
+ }else
+ p1 = sel.p[0].p2;
+ op = sel.p[0].p2;
+ if(--n>0)
+ continue;
+ Strzero(&genstr);
+ for(i = 0; i<cp->ctext->n; i++)
+ if((c = cp->ctext->s[i])=='\\' && i<cp->ctext->n-1){
+ c = cp->ctext->s[++i];
+ if('1'<=c && c<='9') {
+ j = c-'0';
+ if(sel.p[j].p2-sel.p[j].p1>BLOCKSIZE)
+ error(Elongtag);
+ bufread(f, sel.p[j].p1, genbuf, sel.p[j].p2-sel.p[j].p1);
+ Strinsert(&genstr, tmprstr(genbuf, (sel.p[j].p2-sel.p[j].p1)), genstr.n);
+ }else
+ Straddc(&genstr, c);
+ }else if(c!='&')
+ Straddc(&genstr, c);
+ else{
+ if(sel.p[0].p2-sel.p[0].p1>BLOCKSIZE)
+ error(Elongrhs);
+ bufread(f, sel.p[0].p1, genbuf, sel.p[0].p2-sel.p[0].p1);
+ Strinsert(&genstr,
+ tmprstr(genbuf, (int)(sel.p[0].p2-sel.p[0].p1)),
+ genstr.n);
+ }
+ if(sel.p[0].p1!=sel.p[0].p2){
+ logdelete(f, sel.p[0].p1, sel.p[0].p2);
+ delta-=sel.p[0].p2-sel.p[0].p1;
+ }
+ if(genstr.n){
+ loginsert(f, sel.p[0].p2, genstr.s, genstr.n);
+ delta+=genstr.n;
+ }
+ didsub = 1;
+ if(!cp->flag)
+ break;
+ }
+ if(!didsub && nest==0)
+ error(Enosub);
+ f->ndot.r.p1 = addr.r.p1, f->ndot.r.p2 = addr.r.p2+delta;
+ return TRUE;
+}
+
+int
+u_cmd(File *f, Cmd *cp)
+{
+ int n;
+
+ USED(f);
+ USED(cp);
+ n = cp->num;
+ if(n >= 0)
+ while(n-- && undo(TRUE))
+ ;
+ else
+ while(n++ && undo(FALSE))
+ ;
+ return TRUE;
+}
+
+int
+w_cmd(File *f, Cmd *cp)
+{
+ int fseq;
+
+ fseq = f->seq;
+ if(getname(f, cp->ctext, FALSE)==0)
+ error(Enoname);
+ if(fseq == seq)
+ error_s(Ewseq, genc);
+ writef(f);
+ return TRUE;
+}
+
+int
+x_cmd(File *f, Cmd *cp)
+{
+ if(cp->re)
+ looper(f, cp, cp->cmdc=='x');
+ else
+ linelooper(f, cp);
+ return TRUE;
+}
+
+int
+X_cmd(File *f, Cmd *cp)
+{
+ USED(f);
+ filelooper(cp, cp->cmdc=='X');
+ return TRUE;
+}
+
+int
+plan9_cmd(File *f, Cmd *cp)
+{
+ plan9(f, cp->cmdc, cp->ctext, nest);
+ return TRUE;
+}
+
+int
+eq_cmd(File *f, Cmd *cp)
+{
+ int charsonly;
+
+ switch(cp->ctext->n){
+ case 1:
+ charsonly = FALSE;
+ break;
+ case 2:
+ if(cp->ctext->s[0]=='#'){
+ charsonly = TRUE;
+ break;
+ }
+ default:
+ SET(charsonly);
+ error(Enewline);
+ }
+ printposn(f, charsonly);
+ return TRUE;
+}
+
+int
+nl_cmd(File *f, Cmd *cp)
+{
+ Address a;
+
+ if(cp->addr == 0){
+ /* First put it on newline boundaries */
+ addr = lineaddr((Posn)0, f->dot, -1);
+ a = lineaddr((Posn)0, f->dot, 1);
+ addr.r.p2 = a.r.p2;
+ if(addr.r.p1==f->dot.r.p1 && addr.r.p2==f->dot.r.p2)
+ addr = lineaddr((Posn)1, f->dot, 1);
+ display(f);
+ }else if(downloaded)
+ moveto(f, addr.r);
+ else
+ display(f);
+ return TRUE;
+}
+
+int
+cd_cmd(File *f, Cmd *cp)
+{
+ USED(f);
+ cd(cp->ctext);
+ return TRUE;
+}
+
+int
+append(File *f, Cmd *cp, Posn p)
+{
+ if(cp->ctext->n>0 && cp->ctext->s[cp->ctext->n-1]==0)
+ --cp->ctext->n;
+ if(cp->ctext->n>0)
+ loginsert(f, p, cp->ctext->s, cp->ctext->n);
+ f->ndot.r.p1 = p;
+ f->ndot.r.p2 = p+cp->ctext->n;
+ return TRUE;
+}
+
+int
+display(File *f)
+{
+ Posn p1, p2;
+ int np;
+ char *c;
+
+ p1 = addr.r.p1;
+ p2 = addr.r.p2;
+ if(p2 > f->_.nc){
+ fprint(2, "bad display addr p1=%ld p2=%ld f->_.nc=%d\n", p1, p2, f->_.nc); /*ZZZ should never happen, can remove */
+ p2 = f->_.nc;
+ }
+ while(p1 < p2){
+ np = p2-p1;
+ if(np>BLOCKSIZE-1)
+ np = BLOCKSIZE-1;
+ bufread(f, p1, genbuf, np);
+ genbuf[np] = 0;
+ c = Strtoc(tmprstr(genbuf, np+1));
+ if(downloaded)
+ termwrite(c);
+ else
+ Write(1, c, strlen(c));
+ free(c);
+ p1 += np;
+ }
+ f->dot = addr;
+ return TRUE;
+}
+
+void
+looper(File *f, Cmd *cp, int xy)
+{
+ Posn p, op;
+ Range r;
+
+ r = addr.r;
+ op= xy? -1 : r.p1;
+ nest++;
+ compile(cp->re);
+ for(p = r.p1; p<=r.p2; ){
+ if(!execute(f, p, r.p2)){ /* no match, but y should still run */
+ if(xy || op>r.p2)
+ break;
+ f->dot.r.p1 = op, f->dot.r.p2 = r.p2;
+ p = r.p2+1; /* exit next loop */
+ }else{
+ if(sel.p[0].p1==sel.p[0].p2){ /* empty match? */
+ if(sel.p[0].p1==op){
+ p++;
+ continue;
+ }
+ p = sel.p[0].p2+1;
+ }else
+ p = sel.p[0].p2;
+ if(xy)
+ f->dot.r = sel.p[0];
+ else
+ f->dot.r.p1 = op, f->dot.r.p2 = sel.p[0].p1;
+ }
+ op = sel.p[0].p2;
+ cmdexec(f, cp->ccmd);
+ compile(cp->re);
+ }
+ --nest;
+}
+
+void
+linelooper(File *f, Cmd *cp)
+{
+ Posn p;
+ Range r, linesel;
+ Address a, a3;
+
+ nest++;
+ r = addr.r;
+ a3.f = f;
+ a3.r.p1 = a3.r.p2 = r.p1;
+ for(p = r.p1; p<r.p2; p = a3.r.p2){
+ a3.r.p1 = a3.r.p2;
+/*pjw if(p!=r.p1 || (linesel = lineaddr((Posn)0, a3, 1)).r.p2==p)*/
+ if(p!=r.p1 || (a = lineaddr((Posn)0, a3, 1), linesel = a.r, linesel.p2==p)){
+ a = lineaddr((Posn)1, a3, 1);
+ linesel = a.r;
+ }
+ if(linesel.p1 >= r.p2)
+ break;
+ if(linesel.p2 >= r.p2)
+ linesel.p2 = r.p2;
+ if(linesel.p2 > linesel.p1)
+ if(linesel.p1>=a3.r.p2 && linesel.p2>a3.r.p2){
+ f->dot.r = linesel;
+ cmdexec(f, cp->ccmd);
+ a3.r = linesel;
+ continue;
+ }
+ break;
+ }
+ --nest;
+}
+
+void
+filelooper(Cmd *cp, int XY)
+{
+ File *f, *cur;
+ int i;
+
+ if(Glooping++)
+ error(EnestXY);
+ nest++;
+ settempfile();
+ cur = curfile;
+ for(i = 0; i<tempfile.nused; i++){
+ f = tempfile.filepptr[i];
+ if(f==cmd)
+ continue;
+ if(cp->re==0 || filematch(f, cp->re)==XY)
+ cmdexec(f, cp->ccmd);
+ }
+ if(cur && whichmenu(cur)>=0) /* check that cur is still a file */
+ current(cur);
+ --Glooping;
+ --nest;
+}