From 76193d7cb0457807b2f0b95f909ab5de19480cd7 Mon Sep 17 00:00:00 2001 From: rsc Date: Tue, 30 Sep 2003 17:47:42 +0000 Subject: Initial revision --- src/cmd/mk/LICENSE | 258 +++++++++++++ src/cmd/mk/Make.FreeBSD-386 | 7 + src/cmd/mk/Make.HP-UX-9000 | 6 + src/cmd/mk/Make.Linux-386 | 7 + src/cmd/mk/Make.OSF1-alpha | 6 + src/cmd/mk/Make.SunOS-sun4u | 2 + src/cmd/mk/Make.SunOS-sun4u-cc | 6 + src/cmd/mk/Make.SunOS-sun4u-gcc | 6 + src/cmd/mk/Makefile | 117 ++++++ src/cmd/mk/Makefile.MID | 45 +++ src/cmd/mk/arc.c | 52 +++ src/cmd/mk/archive.c | 180 +++++++++ src/cmd/mk/bundle.ports | 46 +++ src/cmd/mk/env.c | 149 ++++++++ src/cmd/mk/file.c | 90 +++++ src/cmd/mk/fns.h | 84 ++++ src/cmd/mk/graph.c | 279 ++++++++++++++ src/cmd/mk/lex.c | 147 +++++++ src/cmd/mk/main.c | 285 ++++++++++++++ src/cmd/mk/match.c | 49 +++ src/cmd/mk/mk.1 | 665 ++++++++++++++++++++++++++++++++ src/cmd/mk/mk.c | 226 +++++++++++ src/cmd/mk/mk.h | 203 ++++++++++ src/cmd/mk/mkfile | 9 + src/cmd/mk/mkfile.test | 5 + src/cmd/mk/parse.c | 307 +++++++++++++++ src/cmd/mk/rc.c | 175 +++++++++ src/cmd/mk/recipe.c | 117 ++++++ src/cmd/mk/rpm.spec | 29 ++ src/cmd/mk/rule.c | 107 ++++++ src/cmd/mk/run.c | 296 +++++++++++++++ src/cmd/mk/sh.c | 189 +++++++++ src/cmd/mk/shprint.c | 123 ++++++ src/cmd/mk/symtab.c | 95 +++++ src/cmd/mk/unix.c | 306 +++++++++++++++ src/cmd/mk/var.c | 41 ++ src/cmd/mk/varsub.c | 256 +++++++++++++ src/cmd/mk/word.c | 180 +++++++++ src/cmd/sam/LICENSE | 258 +++++++++++++ src/cmd/sam/Makefile | 18 + src/cmd/sam/address.c | 240 ++++++++++++ src/cmd/sam/buff.c | 302 +++++++++++++++ src/cmd/sam/cmd.c | 594 +++++++++++++++++++++++++++++ src/cmd/sam/disk.c | 122 ++++++ src/cmd/sam/error.c | 144 +++++++ src/cmd/sam/errors.h | 65 ++++ src/cmd/sam/file.c | 631 ++++++++++++++++++++++++++++++ src/cmd/sam/io.c | 262 +++++++++++++ src/cmd/sam/list.c | 47 +++ src/cmd/sam/mesg.c | 821 ++++++++++++++++++++++++++++++++++++++++ src/cmd/sam/mesg.h | 131 +++++++ src/cmd/sam/mkfile | 40 ++ src/cmd/sam/moveto.c | 173 +++++++++ src/cmd/sam/multi.c | 123 ++++++ src/cmd/sam/parse.h | 68 ++++ src/cmd/sam/plan9.c | 185 +++++++++ src/cmd/sam/plumb.c | 9 + src/cmd/sam/rasp.c | 325 ++++++++++++++++ src/cmd/sam/regexp.c | 801 +++++++++++++++++++++++++++++++++++++++ src/cmd/sam/sam | Bin 0 -> 267893 bytes src/cmd/sam/sam.c | 739 ++++++++++++++++++++++++++++++++++++ src/cmd/sam/sam.h | 407 ++++++++++++++++++++ src/cmd/sam/shell.c | 152 ++++++++ src/cmd/sam/unix.c | 272 +++++++++++++ src/cmd/sam/xec.c | 508 +++++++++++++++++++++++++ 65 files changed, 12587 insertions(+) create mode 100644 src/cmd/mk/LICENSE create mode 100644 src/cmd/mk/Make.FreeBSD-386 create mode 100644 src/cmd/mk/Make.HP-UX-9000 create mode 100644 src/cmd/mk/Make.Linux-386 create mode 100644 src/cmd/mk/Make.OSF1-alpha create mode 100644 src/cmd/mk/Make.SunOS-sun4u create mode 100644 src/cmd/mk/Make.SunOS-sun4u-cc create mode 100644 src/cmd/mk/Make.SunOS-sun4u-gcc create mode 100644 src/cmd/mk/Makefile create mode 100644 src/cmd/mk/Makefile.MID create mode 100644 src/cmd/mk/arc.c create mode 100644 src/cmd/mk/archive.c create mode 100644 src/cmd/mk/bundle.ports create mode 100644 src/cmd/mk/env.c create mode 100644 src/cmd/mk/file.c create mode 100644 src/cmd/mk/fns.h create mode 100644 src/cmd/mk/graph.c create mode 100644 src/cmd/mk/lex.c create mode 100644 src/cmd/mk/main.c create mode 100644 src/cmd/mk/match.c create mode 100644 src/cmd/mk/mk.1 create mode 100644 src/cmd/mk/mk.c create mode 100644 src/cmd/mk/mk.h create mode 100644 src/cmd/mk/mkfile create mode 100644 src/cmd/mk/mkfile.test create mode 100644 src/cmd/mk/parse.c create mode 100644 src/cmd/mk/rc.c create mode 100644 src/cmd/mk/recipe.c create mode 100644 src/cmd/mk/rpm.spec create mode 100644 src/cmd/mk/rule.c create mode 100644 src/cmd/mk/run.c create mode 100644 src/cmd/mk/sh.c create mode 100644 src/cmd/mk/shprint.c create mode 100644 src/cmd/mk/symtab.c create mode 100644 src/cmd/mk/unix.c create mode 100644 src/cmd/mk/var.c create mode 100644 src/cmd/mk/varsub.c create mode 100644 src/cmd/mk/word.c create mode 100644 src/cmd/sam/LICENSE create mode 100644 src/cmd/sam/Makefile create mode 100644 src/cmd/sam/address.c create mode 100644 src/cmd/sam/buff.c create mode 100644 src/cmd/sam/cmd.c create mode 100644 src/cmd/sam/disk.c create mode 100644 src/cmd/sam/error.c create mode 100644 src/cmd/sam/errors.h create mode 100644 src/cmd/sam/file.c create mode 100644 src/cmd/sam/io.c create mode 100644 src/cmd/sam/list.c create mode 100644 src/cmd/sam/mesg.c create mode 100644 src/cmd/sam/mesg.h create mode 100644 src/cmd/sam/mkfile create mode 100644 src/cmd/sam/moveto.c create mode 100644 src/cmd/sam/multi.c create mode 100644 src/cmd/sam/parse.h create mode 100644 src/cmd/sam/plan9.c create mode 100644 src/cmd/sam/plumb.c create mode 100644 src/cmd/sam/rasp.c create mode 100644 src/cmd/sam/regexp.c create mode 100755 src/cmd/sam/sam create mode 100644 src/cmd/sam/sam.c create mode 100644 src/cmd/sam/sam.h create mode 100644 src/cmd/sam/shell.c create mode 100644 src/cmd/sam/unix.c create mode 100644 src/cmd/sam/xec.c (limited to 'src/cmd') 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 "!\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 + +post-patch: + ${REINPLACE_CMD} -e 's,$$(PREFIX),${PREFIX},g' ${WRKSRC}/Makefile + +.include +--- 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®EXP) + 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®EXP) && 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®EXP){ + 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®EXP) + 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 +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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®EXP)){ + 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®EXP){ + 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 +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®EXP) || charin(head, "%&")){ + r->attr |= META; + if(reuse) + return; + if(attr®EXP){ + 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 +#include +#include +#include + +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; iname); + 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 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 && q0cq+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'= 0) + ungetch(); + return c; +} + +void +termcommand(void) +{ + Posn p; + + for(p=cmdpt; p_.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; intext; 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 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 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; idev==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->mtimedev = 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; itag==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; lBLOCKSIZE) + 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; ldot.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; is, 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_.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; lBLOCKSIZE? 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 @@ +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=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_.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; itag); + 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; iname, &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; iname, 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 +#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 && p1tag, 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 && p1tag, 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; inused && 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(inused && 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 && inused && 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; inused && 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; inused && 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; inused && 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)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; is); + 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; ipfirst->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->typetype : 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= 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].p1p[0].p1==sel.p[0].p1 && sp->p[0].p2>sel.p[0].p2)) + for(i = 0; ip[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->typetype : 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].p2p[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 Binary files /dev/null and b/src/cmd/sam/sam 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_.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; iseq==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; cname); + 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; ideleted) { + 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_.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; iname.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 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 +#include +#include +#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; lBLOCKSIZE-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 +#include +#include +#include +#include +#include +#include + +#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; iname); + 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; ictext->n; i++) + if((c = cp->ctext->s[i])=='\\' && ictext->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) + 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; ire==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; +} -- cgit v1.2.3