# # initially generated by c2l # implement Mk; include "draw.m"; Mk: module { init: fn(nil: ref Draw->Context, argl: list of string); }; include "sys.m"; sys: Sys; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; include "libc0.m"; libc0: Libc0; include "regex.m"; regex: Regex; include "ar.m"; ARMAG, SARMAG, ARFMAG, SARNAME, ar_hdr, SAR_HDR: import Ar; include "daytime.m"; daytime: Daytime; include "sh.m"; init(nil: ref Draw->Context, argl: list of string) { sys = load Sys Sys->PATH; bufio = load Bufio Bufio->PATH; libc0 = load Libc0 Libc0->PATH; regex = load Regex Regex->PATH; daytime = load Daytime Daytime->PATH; sys->pctl(Sys->FORKNS, nil); main(len argl, libc0->ls2aab(argl)); } NAMELEN: con 28; ERRLEN: con 64; PNPROC, PNGROUP : con iota; # function pointer enum for symtraverse ECOPY, PRINT1: con iota; Bufblock: adt{ next: cyclic ref Bufblock; start: array of byte; end: int; current: int; }; Word: adt{ s: array of byte; next: cyclic ref Word; }; Envy: adt{ name: array of byte; values: ref Word; }; Resub: adt{ sp: array of byte; ep: array of byte; }; Rule: adt{ target: array of byte; # one target tail: ref Word; # constituents of targets recipe: array of byte; # do it ! attr: int; # attributes line: int; # source line file: array of byte; # source file alltargets: ref Word; # all the targets rule: int; # rule number pat: Regex->Re; # reg exp goo prog: array of byte; # to use in out of date chain: cyclic ref Rule; # hashed per target next: cyclic ref Rule; }; # Rule.attr META, SEQ, UPD, QUIET, VIR, REGEXP, NOREC, DEL, NOVIRT: con 1< 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_BULKED # we have bulked this dir , S_WESET # variable; we set in the mkfile # an internal mk variable (e.g., stem, target) , S_INTERNAL: con iota; NAMEBLOCK: con 1000; BIGBLOCK: con 20000; D_PARSE, D_GRAPH, D_EXEC: con 1<FD = sys->fildes(-1); tb: ref Iobuf; buf, whatif: ref Bufblock; # # * start with a copy of the current environment variables # * instead of sharing them # bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); buf = newbuf(); whatif = nil; if(argc) ; for(argv = argv[1: ]; argv[0] != nil && argv[0][0] == byte '-'; argv = argv[1: ]){ bufcpy(buf, argv[0], libc0->strlen(argv[0])); insert(buf, ' '); case(int argv[0][1]){ 'a' => aflag = 1; 'd' => if(int (s = argv[0][2: ])[0]) while(int s[0]){ case(int s[0]){ 'p' => debug |= D_PARSE; 'g' => debug |= D_GRAPH; 'e' => debug |= D_EXEC; } s = s[1: ]; } else debug = 16rffff; 'e' => explain = argv[0][2: ]; 'f' => argv = argv[1: ]; if(argv[0] == nil) badusage(); f[0] = argv[0]; f = f[1: ]; bufcpy(buf, argv[0], libc0->strlen(argv[0])); insert(buf, ' '); 'i' => iflag = 1; 'k' => kflag = 1; 'n' => nflag = 1; 's' => sflag = 1; 't' => tflag = 1; 'u' => uflag = 1; 'w' => if(whatif == nil) whatif = newbuf(); else insert(whatif, ' '); if(int argv[0][2]) bufcpy(whatif, argv[0][2: ], libc0->strlen(argv[0][2: ])); else{ argv = argv[1: ]; if(argv[0] == nil) badusage(); bufcpy(whatif, argv[0][0: ], libc0->strlen(argv[0][0: ])); } * => badusage(); } } if(aflag) iflag = 1; usage(); syminit(); initenv(); initbind(); openwait(); usage(); # # assignment args become null strings # temp = nil; for(i = 0; argv[i] != nil; i++) if(libc0->strchr(argv[i], '=') != nil){ bufcpy(buf, argv[i], libc0->strlen(argv[i])); insert(buf, ' '); if(tfd == nil){ temp = maketmp(); if(temp == nil){ perrors("temp file"); Exit(); } sys->create(libc0->ab2s(temp), Sys->OWRITE, 8r600); if((tfd = sys->open(libc0->ab2s(temp), 2)) == nil){ perror(temp); Exit(); } tb = bufio->fopen(tfd, Sys->OWRITE); } tb.puts(sys->sprint("%s\n", libc0->ab2s(argv[i]))); argv[i][0] = byte 0; } if(tfd != nil){ tb.flush(); sys->seek(tfd, big 0, 0); parse(libc0->s2ab("command line args"), tfd, 1); sys->remove(libc0->ab2s(temp)); } if(buf.current != 0){ buf.current--; insert(buf, 0); } symlookw(libc0->s2ab("MKFLAGS"), S_VAR, stow(buf.start)); buf.current = 0; for(i = 0; argv[i] != nil; i++){ if(argv[i][0] == byte 0) continue; if(i) insert(buf, ' '); bufcpy(buf, argv[i], libc0->strlen(argv[i])); } insert(buf, 0); symlookw(libc0->s2ab("MKARGS"), S_VAR, stow(buf.start)); freebuf(buf); if(f == files){ if(access(libc0->s2ab(MKFILE), Sys->OREAD) == 0) parse(libc0->s2ab(MKFILE), sys->open(MKFILE, 0), 0); } else for(ff = 0; ff < len files && files[ff] != nil; ff++) parse(files[ff], sys->open(libc0->ab2s(files[ff]), 0), 0); if(debug&D_PARSE){ dumpw(libc0->s2ab("default targets"), target1); dumpr(libc0->s2ab("rules"), rules); dumpr(libc0->s2ab("metarules"), metarules); dumpv(libc0->s2ab("variables")); } if(whatif != nil){ insert(whatif, 0); timeinit(whatif.start); freebuf(whatif); } execinit(); # skip assignment args while(argv[0] != nil && argv[0][0] == byte 0) argv = argv[1: ]; catchnotes(); if(argv[0] == nil){ if(target1 != nil) for(w = target1; w != nil; w = w.next) mk(w.s); else{ sys->fprint(sys->fildes(2), "mk: nothing to mk\n"); Exit(); } } else{ if(sflag){ for(; argv[0] != nil; argv = argv[1: ]) if(int argv[0][0]) mk(argv[0]); } else{ head, tail, t: ref Word; # fake a new rule with all the args as prereqs tail = nil; t = nil; for(; argv[0] != nil; argv = argv[1: ]) if(int argv[0][0]){ if(tail == nil) tail = t = newword(argv[0]); else{ t.next = newword(argv[0]); t = t.next; } } if(tail.next == nil) mk(tail.s); else{ head = newword(libc0->s2ab("command line arguments")); addrules(head, tail, libc0->strdup(libc0->s2ab("")), VIR, mkinline, nil); mk(head.s); } } } if(uflag) prusage(); bout.flush(); exit; } badusage() { sys->fprint(sys->fildes(2), "Usage: mk [-f file] [-n] [-a] [-e] [-t] [-k] [-i] [-d[egp]] [targets ...]\n"); Exit(); } assert(s: array of byte, n: int) { if(!n){ sys->fprint(sys->fildes(2), "mk: Assertion ``%s'' failed.\n", libc0->ab2s(s)); Exit(); } } regerror(s: array of byte) { if(patrule != nil) sys->fprint(sys->fildes(2), "mk: %s:%d: regular expression error; %s\n", libc0->ab2s(patrule.file), patrule.line, libc0->ab2s(s)); else sys->fprint(sys->fildes(2), "mk: %s:%d: regular expression error; %s\n", libc0->ab2s(infile), mkinline, libc0->ab2s(s)); Exit(); } perror(s: array of byte) { perrors(libc0->ab2s(s)); } perrors(s: string) { sys->fprint(sys->fildes(2), "mk: %s: %r\n", s); } access(s: array of byte, mode: int): int { fd := sys->open(libc0->ab2s(s), mode); if (fd == nil) return -1; fd = nil; return 0; } stob(buf: array of byte, s: string) { b := libc0->s2ab(s); libc0->strncpy(buf, b, len buf); } mktemp(t: array of byte) { x := libc0->strchr(t, 'X'); if(x == nil) return; pid := libc0->s2ab(string sys->pctl(0, nil)); for(i := 'a'; i <= 'z'; i++){ x[0] = byte i; x = x[1: ]; libc0->strncpy(x, pid, libc0->strlen(x)); (ok, nil) := sys->stat(libc0->ab2s(t)); if(ok >= 0) continue; } } postnote(t: int, pid: int, note: array of byte) { if(pid == 0) return; fd := sys->open("#p/" + string pid + "/ctl", Sys->OWRITE); if(fd == nil) return; s := libc0->ab2s(note); if(t == PNGROUP) s += "grp"; sys->fprint(fd, "%s", s); fd = nil; } map(s: array of byte, n: int): int { i := j := 0; ls := libc0->strlen(s); while(i < ls){ if(j == n) return i; (nil, l, nil) := sys->byte2char(s, i); i += l; j++; } return -1; } regadd(s: array of byte, m: array of (int, int), rm: array of Resub, n: int) { k := len m; for(i := 0; i < n; i++) rm[i].sp = rm[i].ep= nil; for(i = 0; i < k && i < n; i++){ (a, b) := m[i]; if(a >= 0 && b >= 0){ a = map(s, a); b = map(s, b); if(a >= 0 && b >= 0){ rm[i].sp = s[a: ]; rm[i].ep = s[b: ]; } } } } scopy(d: array of byte, j: int, m: array of Resub, k: int, n: int): int { if(k >= n) return 0; sp := m[k].sp; ep := m[k].ep; if(sp == nil || ep == nil) return 0; c := ep[0]; ep[0] = byte 0; libc0->strcpy(d[j: ], sp); ep[0] = c; return libc0->strlen(sp)-libc0->strlen(ep); } regsub(s: array of byte, d: array of byte, m: array of Resub, n: int) { # libc0->strncpy(d, s, libc0->strlen(d)); ls := libc0->strlen(s); j := 0; for(i := 0; i < ls; i++){ case(int s[i]){ '\\' => if(i+1 < ls && s[i+1] >= byte '0' && s[i+1] <= byte '9'){ k := int s[++i]-'0'; j += scopy(d, j, m, k, n); } else d[j++] = byte '\\'; '&' => j += scopy(d, j, m, 0, n); * => d[j++] = s[i]; } } d[j] = byte 0; } wpid := -1; wfd : ref Sys->FD; wprocs := 0; openwait() { pid := sys->pctl(0, nil); w := sys->sprint("#p/%d/wait", pid); fd := sys->open(w, Sys->OREAD); if(fd == nil){ perrors("fd == nil in wait"); return; } wpid = pid; wfd = fd; } addwait() { if(wpid == sys->pctl(0, nil)) wprocs++; } wait(): (int, array of byte) { n: int; if(wpid != -1 && wpid != sys->pctl(0, nil)){ perrors(sys->sprint("wait: pid %d != pid %d", wpid, sys->pctl(0, nil))); return (-1, nil); } if(wprocs == 0) return (-1, nil); buf := array[Sys->WAITLEN] of byte; status := ""; for(;;){ if((n = sys->read(wfd, buf, len buf))<0) perrors("bad read in wait"); status = string buf[0:n]; break; } s := ""; if(status[len status - 1] != ':') s = status; wprocs--; return (int status, libc0->s2ab(s)); } abort() { exit; } execl(sh: string, name: string, a1: string, a2: string, a3: string, a4: string) { # sys->print("execl %s : %s %s %s %s %s\n", sh, name, a1, a2, a3, a4); c := load Command sh; if(c == nil){ sys->fprint(sys->fildes(2), "x %s: %r\n", sh); return; } argl: list of string; if(a4 != nil) argl = a4 :: argl; if(a3 != nil) argl = a3 :: argl; if(a2 != nil) argl = a2 :: argl; if(a1 != nil) argl = a1 :: argl; # argl = "-x" :: argl; argl = name :: argl; # argl := list of { name, a1, a2, a3, a4 }; if(debug&D_EXEC) sys->fprint(sys->fildes(1), "executing %s with args (%s, %s, %s, %s, %s)\n", sh, name, a1, a2, a3, a4); c->init(nil, argl); } getuser(): string { fd := sys->open("/dev/user", sys->OREAD); if(fd == nil) return ""; buf := array[128] of byte; n := sys->read(fd, buf, len buf); if(n < 0) return ""; return string buf[0: n]; } initbind() { f := sys->sprint("/usr/%s/lib/mkbinds", getuser()); b := bufio->open(f, Bufio->OREAD); if(b == nil) b = bufio->open("/lib/mk/binds", Bufio->OREAD); if(b == nil) return; while((s := b.gets('\n')) != nil){ m := len s; if(s[m-1] == '\n') s = s[0: m-1]; (n, l) := sys->tokenize(s, " \t"); if(n == 2) sys->bind(hd l, hd tl l, Sys->MREPL); } } # # mk # runerrs: int; mk(target: array of byte) { node: ref Node; did: int = 0; nproc(); # it can be updated dynamically nrep(); # it can be updated dynamically runerrs = 0; node = graph(target); if(debug&D_GRAPH){ dumpn(libc0->s2ab("new target\n"), node); bout.flush(); } clrmade(node); while(node.flags&NOTMADE){ if(work(node, nil, nil)) did = 1; # found something to do else{ if(waitup(1, nil) > 0){ if(node.flags&(NOTMADE|BEINGMADE)){ assert(libc0->s2ab("must be run errors"), runerrs); break; # nothing more waiting } } } } if(node.flags&BEINGMADE) waitup(-1, nil); while(jobs != nil) waitup(-2, nil); assert(libc0->s2ab("target didn't get done"), runerrs || node.flags&MADE); if(did == 0) bout.puts(sys->sprint("mk: '%s' is up to date\n", libc0->ab2s(node.name))); } clrmade(n: ref Node) { a: ref Arc; n.flags &= ~(CANPRETEND|PRETENDING); if(libc0->strchr(n.name, '(') == nil || n.time) n.flags |= CANPRETEND; n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|NOTMADE; for(a = n.prereqs; a != nil; a = a.next) if(a.n != nil) clrmade(a.n); } unpretend(n: ref Node) { n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|NOTMADE; n.flags &= ~(CANPRETEND|PRETENDING); n.time = 0; } work(node: ref Node, p: ref Node, parc: ref Arc): int { a, ra: ref Arc; weoutofdate, ready: int; did: int = 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 != nil && outofdate(p, parc, 0)){ if(explain != nil) sys->fprint(sys->fildes(1), "unpretending %s(%d) because %s is out of date(%d)\n", libc0->ab2s(node.name), node.time, libc0->ab2s(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 == nil){ if(node.time == 0){ sys->fprint(sys->fildes(2), "mk: don't know how to make '%s'\n", libc0->ab2s(node.name)); if(kflag){ node.flags |= BEINGMADE; runerrs++; } else Exit(); } else node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE; return did; } # # now see if we are out of date or what # ready = 1; weoutofdate = aflag; ra = nil; for(a = node.prereqs; a != nil; a = a.next) if(a.n != nil){ did = work(a.n, node, a) || did; if(a.n.flags&(NOTMADE|BEINGMADE)) ready = 0; if(outofdate(node, a, 0)){ weoutofdate = 1; if(ra == nil || ra.n == nil || ra.n.time < a.n.time) ra = a; } } else{ if(node.time == 0){ if(ra == nil) ra = a; weoutofdate = 1; } } if(ready == 0) # can't do anything now return did; if(weoutofdate == 0){ node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE; return did; } # # can we pretend to be made? # if(iflag == 0 && node.time == 0 && node.flags&(PRETENDING|CANPRETEND) && p != nil && ra.n != nil && !outofdate(p, ra, 0)){ node.flags &= ~CANPRETEND; node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE; if(explain != nil && (node.flags&PRETENDING) == 0) sys->fprint(sys->fildes(1), "pretending %s has time %d\n", libc0->ab2s(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 != nil; a = a.next) if(a.n != nil && a.n.flags&PRETENDING){ if(explain != nil) if(ra.n != nil) bout.puts(sys->sprint("unpretending %s because of %s because of %s\n", libc0->ab2s(a.n.name), libc0->ab2s(node.name), libc0->ab2s(ra.n.name))); else bout.puts(sys->sprint("unpretending %s because of %s because of %s\n", libc0->ab2s(a.n.name), libc0->ab2s(node.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; } update(fake: int, node: ref Node) { a: ref Arc; if(fake) node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|BEINGMADE; else node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|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 != nil; a = a.next) if(a.prog != nil) outofdate(node, a, 1); } else{ node.time = 1; for(a = node.prereqs; a != nil; a = a.next) if(a.n != nil && outofdate(node, a, 1)) node.time = a.n.time; } # print("----node %s time=%ld flags=0x%x\n", node->name, node->time, node->flags);/* } pcmp(prog: array of byte, p: array of byte, q: array of byte): int { buf := array[3*NAMEBLOCK] of byte; pid: int; bout.flush(); stob(buf, sys->sprint("%s '%s' '%s'\n", libc0->ab2s(prog), libc0->ab2s(p), libc0->ab2s(q))); pid = pipecmd(buf, nil, nil); apid := array[1] of int; apid[0] = pid; while(waitup(-3, apid) >= 0) ; pid = apid[0]; if(pid) return 2; else return 1; } outofdate(node: ref Node, arc: ref Arc, eval: int): int { buf := array[3*NAMEBLOCK] of byte; str: array of byte; sym: ref Symtab; ret: int; str = nil; if(arc.prog != nil){ stob(buf, sys->sprint("%s%c%s", libc0->ab2s(node.name), 8r377, libc0->ab2s(arc.n.name))); sym = symlooki(buf, S_OUTOFDATE, 0); if(sym == nil || eval){ if(sym == nil) str = libc0->strdup(buf); ret = pcmp(arc.prog, node.name, arc.n.name); if(sym != nil) sym.ivalue = ret; else symlooki(str, S_OUTOFDATE, ret); } else ret = int sym.ivalue; return ret-1; } else if(libc0->strchr(arc.n.name, '(') != nil && arc.n.time == 0) # missing archive member return 1; else return node.time < arc.n.time; } # # recipe # dorecipe(node: ref Node): int { buf := array[BIGBLOCK] of byte; n: ref Node; r: ref Rule = nil; a, aa: ref Arc; head := ref Word; ahead := ref Word; lp := ref Word; ln := ref Word; w, ww, aw: ref Word; s: ref Symtab; did: int = 0; aa = nil; # # pick up the rule # for(a = node.prereqs; a != nil; a = a.next) if(int a.r.recipe[0]) r = (aa = a).r; # # no recipe? go to buggery! # if(r == nil){ if(!(node.flags&VIRTUAL) && !(node.flags&NORECIPE)){ sys->fprint(sys->fildes(2), "mk: no recipe to make '%s'\n", libc0->ab2s(node.name)); Exit(); } if(libc0->strchr(node.name, '(') != nil && node.time == 0) node.flags = node.flags&~(NOTMADE|BEINGMADE|MADE)|MADE; else update(0, node); if(tflag){ if(!(node.flags&VIRTUAL)) touch(node.name); else if(explain != nil) bout.puts(sys->sprint("no touch of virtual '%s'\n", libc0->ab2s(node.name))); } return did; } # # build the node list # node.next = nil; head.next = nil; ww = head; ahead.next = nil; aw = ahead; if(r.attr®EXP){ ww.next = newword(node.name); aw.next = newword(node.name); } else{ for(w = r.alltargets; w != nil; w = w.next){ if(r.attr&META) subst(aa.stem, w.s, buf); else libc0->strcpy(buf, w.s); aw.next = newword(buf); aw = aw.next; if((s = symlooki(buf, S_NODE, 0)) == nil) continue; # not a node we are interested in n = s.nvalue; if(aflag == 0 && n.time){ for(a = n.prereqs; a != nil; a = a.next) if(a.n != nil && outofdate(n, a, 0)) break; if(a == nil) continue; } ww.next = newword(buf); ww = ww.next; if(n == node) continue; n.next = node.next; node.next = n; } } for(n = node; n != nil; n = n.next) if((n.flags&READY) == 0) return did; # # gather the params for the job # lp.next = ln.next = nil; for(n = node; n != nil; n = n.next){ for(a = n.prereqs; a != nil; a = a.next){ if(a.n != nil){ addw(lp, a.n.name); if(outofdate(n, a, 0)){ addw(ln, a.n.name); if(explain != nil) sys->fprint(sys->fildes(1), "%s(%d) < %s(%d)\n", libc0->ab2s(n.name), n.time, libc0->ab2s(a.n.name), a.n.time); } } else{ if(explain != nil) sys->fprint(sys->fildes(1), "%s has no prerequisites\n", libc0->ab2s(n.name)); } } n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|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; } addw(w: ref Word, s: array of byte) { lw: ref Word; for(lw = w; (w = w.next) != nil; lw = w){ if(libc0->strcmp(s, w.s) == 0) return; } lw.next = newword(s); } # # rule # lr, lmr: ref Rule; nrules: int = 0; addrule(head: array of byte, tail: ref Word, body: array of byte, ahead: ref Word, attr: int, hline: int, prog: array of byte) { r, rr: ref Rule; sym: ref Symtab; reuse: int; r = nil; reuse = 0; if((sym = symlooki(head, S_TARGET, 0)) != nil){ for(r = sym.rvalue; r != nil; r = r.chain) if(rcmp(r, head, tail) == 0){ reuse = 1; break; } } if(r == nil) r = ref 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 = symlookr(head, S_TARGET, r).rvalue; if(rr != r){ r.chain = rr.chain; rr.chain = r; } else r.chain = nil; } if(!reuse) r.next = nil; if(attr®EXP || charin(head, libc0->s2ab("%&")) != nil){ r.attr |= META; if(reuse) return; if(attr®EXP){ patrule = r; e := ""; (r.pat, e) = regex->compile(libc0->ab2s(head), 1); if(e != nil) perrors(sys->sprint("%s: %s", libc0->ab2s(head), e)); } if(metarules == nil) metarules = lmr = r; else{ lmr.next = r; lmr = r; } } else{ if(reuse) return; r.pat = nil; if(rules == nil) rules = lr = r; else{ lr.next = r; lr = r; } } } dumpr(s: array of byte, r: ref Rule) { bout.puts(sys->sprint("%s: start=%x\n", libc0->ab2s(s), r)); for(; r != nil; r = r.next){ bout.puts(sys->sprint("\tRule %x: %s[%d] attr=%x next=%x chain=%x alltarget='%s'", r, libc0->ab2s(r.file), r.line, r.attr, r.next, r.chain, wtostr(r.alltargets, ' '))); if(r.prog != nil) bout.puts(sys->sprint(" prog='%s'", libc0->ab2s(r.prog))); bout.puts(sys->sprint("\n\ttarget=%s: %s\n", libc0->ab2s(r.target), wtostr(r.tail, ' '))); bout.puts(sys->sprint("\trecipe@%x='%s'\n", r.recipe, libc0->ab2s(r.recipe))); } } rcmp(r: ref Rule, target: array of byte, tail: ref Word): int { w: ref Word; if(libc0->strcmp(r.target, target)) return 1; for(w = r.tail; w != nil && tail != nil; (w, tail) = (w.next, tail.next)) if(libc0->strcmp(w.s, tail.s)) return 1; return w != nil || tail != nil; } rulecnt(): array of byte { s: array of byte; s = array[nrules] of byte; for(i := 0; i < nrules; i++) s[i] = byte 0; return s; } # # graph # graph(target: array of byte): ref Node { node: ref Node; cnt: array of byte; cnt = rulecnt(); node = applyrules(target, cnt); cnt = nil; cyclechk(node); node.flags |= PROBABLE; # make sure it doesn't get deleted vacuous(node); ambiguous(node); attribute(node); return node; } applyrules(target: array of byte, cnt: array of byte): ref Node { sym: ref Symtab; node: ref Node; r: ref Rule; head := ref Arc; a: ref Arc = head; w: ref Word; stem := array[NAMEBLOCK] of byte; buf := array[NAMEBLOCK] of byte; rmatch := array[NREGEXP] of Resub; # print("applyrules(%lux='%s')\n", target, target);/* sym = symlooki(target, S_NODE, 0); if(sym != nil) return sym.nvalue; target = libc0->strdup(target); node = newnode(target); head.n = nil; head.next = nil; sym = symlooki(target, S_TARGET, 0); for(i := 0; i < NREGEXP; i++) rmatch[i].sp = rmatch[i].ep = nil; if(sym != nil) tmp_1 := sym.rvalue; else tmp_1 = nil; for(r = tmp_1; r != nil; r = r.chain){ if(r.attr&META) continue; if(libc0->strcmp(target, r.target)) continue; if((r.recipe == nil || !int r.recipe[0]) && (r.tail == nil || r.tail.s == nil || !int r.tail.s[0])) # no effect; ignore continue; if(int 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 == nil || r.tail.s == nil || !int r.tail.s[0]){ a.next = newarc(nil, r, libc0->s2ab(""), rmatch); a = a.next; } else for(w = r.tail; w != nil; w = w.next){ a.next = newarc(applyrules(w.s, cnt), r, libc0->s2ab(""), rmatch); a = a.next; } cnt[r.rule]--; head.n = node; } for(r = metarules; r != nil; r = r.next){ if((r.recipe == nil || !int r.recipe[0]) && (r.tail == nil || r.tail.s == nil || !int r.tail.s[0])) # no effect; ignore continue; if(r.attr&NOVIRT && a != head && a.r.attr&VIR) continue; if(r.attr®EXP){ stem[0] = byte 0; patrule = r; for(i = 0; i < NREGEXP; i++) rmatch[i].sp = rmatch[i].ep = nil; m := regex->execute(r.pat, libc0->ab2s(node.name)); if(m == nil) continue; regadd(node.name, m, rmatch, NREGEXP); } else{ if(!match(node.name, r.target, stem)) continue; } if(int 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 == nil || r.tail.s == nil || !int r.tail.s[0]){ a.next = newarc(nil, r, stem, rmatch); a = a.next; } else for(w = r.tail; w != nil; w = w.next){ if(r.attr®EXP) regsub(w.s, 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; } togo(node: ref Node) { la, a: ref Arc; # delete them now la = nil; for(a = node.prereqs; a != nil; (la, a) = (a, a.next)) if(a.flag&TOGO){ if(a == node.prereqs) node.prereqs = a.next; else (la.next, a) = (a.next, la); } } vacuous(node: ref Node): int { la, a: ref Arc; vac: int = !(node.flags&PROBABLE); if(node.flags&READY) return node.flags&VACUOUS; node.flags |= READY; for(a = node.prereqs; a != nil; a = a.next) if(a.n != nil && 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 != nil; a = a.next) if((a.flag&TOGO) == 0) for(la = node.prereqs; la != nil; la = la.next) if(la.flag&TOGO && la.r == a.r){ la.flag &= ~TOGO; } togo(node); if(vac) node.flags |= VACUOUS; return vac; } newnode(name: array of byte): ref Node { node: ref Node; node = ref Node; symlookn(name, S_NODE, node); node.name = name; node.time = timeof(name, 0); node.prereqs = nil; if(node.time) node.flags = PROBABLE; else node.flags = 0; node.next = nil; return node; } dumpn(s: array of byte, n: ref Node) { buf := array[1024] of byte; a: ref Arc; if(s[0] == byte ' ') stob(buf, sys->sprint("%s ", libc0->ab2s(s))); else stob(buf, sys->sprint("%s ", "")); bout.puts(sys->sprint("%s%s@%x: time=%d flags=0x%x next=%x\n", libc0->ab2s(s), libc0->ab2s(n.name), n, n.time, n.flags, n.next)); for(a = n.prereqs; a != nil; a = a.next) dumpa(buf, a); } trace(s: array of byte, a: ref Arc) { sys->fprint(sys->fildes(2), "\t%s", libc0->ab2s(s)); while(a != nil){ if(a.n != nil) sys->fprint(sys->fildes(2), " <-(%s:%d)- %s", libc0->ab2s(a.r.file), a.r.line, libc0->ab2s(a.n.name)); else sys->fprint(sys->fildes(2), " <-(%s:%d)- %s", libc0->ab2s(a.r.file), a.r.line, ""); if(a.n != nil){ for(a = a.n.prereqs; a != nil; a = a.next) if(int a.r.recipe[0]) break; } else a = nil; } sys->fprint(sys->fildes(2), "\n"); } cyclechk(n: ref Node) { a: ref Arc; if(n.flags&CYCLE && n.prereqs != nil){ sys->fprint(sys->fildes(2), "mk: cycle in graph detected at target %s\n", libc0->ab2s(n.name)); Exit(); } n.flags |= CYCLE; for(a = n.prereqs; a != nil; a = a.next) if(a.n != nil) cyclechk(a.n); n.flags &= ~CYCLE; } ambiguous(n: ref Node) { a: ref Arc; r: ref Rule = nil; la: ref Arc; bad: int = 0; la = nil; for(a = n.prereqs; a != nil; a = a.next){ if(a.n != nil) ambiguous(a.n); if(a.r.recipe[0] == byte 0) continue; if(r == nil) (r, la) = (a.r, a); else{ if(r.recipe != a.r.recipe){ if(r.attr&META && !(a.r.attr&META)){ la.flag |= TOGO; (r, la) = (a.r, a); } else if(!(r.attr&META) && a.r.attr&META){ a.flag |= TOGO; continue; } } if(r.recipe != a.r.recipe){ if(bad == 0){ sys->fprint(sys->fildes(2), "mk: ambiguous recipes for %s:\n", libc0->ab2s(n.name)); bad = 1; trace(n.name, la); } trace(n.name, a); } } } if(bad) Exit(); togo(n); } attribute(n: ref Node) { a: ref Arc; for(a = n.prereqs; a != nil; 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 != nil) attribute(a.n); } if(n.flags&VIRTUAL) n.time = 0; } # # arc # newarc(n: ref Node, r: ref Rule, stem: array of byte, match: array of Resub): ref Arc { a: ref Arc; a = ref Arc; a.n = n; a.r = r; a.stem = libc0->strdup(stem); a.match = array[NREGEXP] of array of byte; rcopy(a.match, match, NREGEXP); a.next = nil; a.flag = 0; a.prog = r.prog; return a; } dumpa(s: array of byte, a: ref Arc) { buf := array[1024] of byte; bout.puts(sys->sprint("%sArc@%x: n=%x r=%x flag=0x%x stem='%s'", libc0->ab2s(s), a, a.n, a.r, a.flag, libc0->ab2s(a.stem))); if(a.prog != nil) bout.puts(sys->sprint(" prog='%s'", libc0->ab2s(a.prog))); bout.puts("\n"); if(a.n != nil){ if(s[0] == byte ' ') stob(buf, sys->sprint("%s ", libc0->ab2s(s))); else stob(buf, sys->sprint("%s ", "")); dumpn(buf, a.n); } } nrep() { sym: ref Symtab; w: ref Word; sym = symlooki(libc0->s2ab("NREP"), S_VAR, 0); if(sym != nil){ w = sym.wvalue; if(w != nil && w.s != nil && int w.s[0]) nreps = int string w.s; } if(nreps < 1) nreps = 1; if(debug&D_GRAPH) bout.puts(sys->sprint("nreps = %d\n", nreps)); } # # job # newjob(r: ref Rule, nlist: ref Node, stem: array of byte, match: array of array of byte, pre: ref Word, npre: ref Word, tar: ref Word, atar: ref Word): ref Job { j: ref Job; j = ref Job; j.r = r; j.n = nlist; j.stem = stem; j.match = match; j.p = pre; j.np = npre; j.t = tar; j.at = atar; j.nproc = -1; j.next = nil; return j; } dumpj(s: array of byte, j: ref Job, all: int) { bout.puts(sys->sprint("%s\n", libc0->ab2s(s))); while(j != nil){ bout.puts(sys->sprint("job@%x: r=%x n=%x stem='%s' nproc=%d\n", j, j.r, j.n, libc0->ab2s(j.stem), j.nproc)); bout.puts(sys->sprint("\ttarget='%s' alltarget='%s' prereq='%s' nprereq='%s'\n", wtostr(j.t, ' '), wtostr(j.at, ' '), wtostr(j.p, ' '), wtostr(j.np, ' '))); if(all) j = j.next; else j = nil; } } # # run # Event: adt{ pid: int; job: ref Job; }; events: array of Event; nevents, nrunning, nproclimit: int; Process: adt{ pid: int; status: int; b: cyclic ref Process; f: cyclic ref Process; }; phead, pfree: ref Process; run(j: ref Job) { jj: ref Job; if(jobs != nil){ for(jj = jobs; jj.next != nil; jj = jj.next) ; jj.next = j; } else jobs = j; j.next = nil; # this code also in waitup after parse redirect if(nrunning < nproclimit) sched(); } sched() { flags: array of byte; j: ref Job; buf: ref Bufblock; slot: int; n: ref Node; e: array of Envy; if(jobs == nil){ usage(); return; } j = jobs; jobs = j.next; if(debug&D_EXEC) sys->fprint(sys->fildes(1), "firing up job for target %s\n", libc0->ab2s(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))) bout.write(buf.start, libc0->strlen(buf.start)); freebuf(buf); if(nflag || tflag){ bout.flush(); for(n = j.n; n != nil; n = n.next){ if(tflag){ if(!(n.flags&VIRTUAL)) touch(n.name); else if(explain != nil) bout.puts(sys->sprint("no touch of virtual '%s'\n", libc0->ab2s(n.name))); } n.time = daytime->now(); n.flags = n.flags&~(NOTMADE|BEINGMADE|MADE)|MADE; } } else{ if(debug&D_EXEC) sys->fprint(sys->fildes(1), "recipe='%s'", libc0->ab2s(j.r.recipe)); # bout.flush(); if(j.r.attr&NOMINUSE) flags = nil; else flags = libc0->s2ab("-e"); events[slot].pid = execsh(flags, j.r.recipe, nil, e); usage(); nrunning++; if(debug&D_EXEC) sys->fprint(sys->fildes(1), "pid for target %s = %d\n", libc0->ab2s(wtos(j.t, ' ')), events[slot].pid); } } waitup(echildok: int, retstatus: array of int): int { e: array of Envy; pid, slot: int; s: ref Symtab; w: ref Word; j: ref Job; buf := array[ERRLEN] of byte; bp: ref Bufblock; uarg: int = 0; done: int; n: ref Node; p: ref Process; runerrs: int; # first check against the proces slist if(retstatus != nil) for(p = phead; p != nil; p = p.f) if(p.pid == retstatus[0]){ retstatus[0] = p.status; pdelete(p); return -1; } # rogue processes for(;;){ pid = waitfor(buf); if(pid == -1){ if(echildok > 0) return 1; else{ sys->fprint(sys->fildes(2), "mk: (waitup %d) ", echildok); perrors("mk wait"); Exit(); } } if(debug&D_EXEC) sys->fprint(sys->fildes(1), "waitup got pid=%d, status='%s'\n", pid, libc0->ab2s(buf)); if(retstatus != nil && pid == retstatus[0]){ if(int buf[0]) retstatus[0] = 1; else retstatus[0] = 0; return -1; } slot = pidslot(pid); if(slot < 0){ if(debug&D_EXEC) sys->fprint(sys->fildes(2), "mk: wait returned unexpected process %d\n", pid); if(int buf[0]) pnew(pid, 1); else pnew(pid, 0); continue; } break; } j = events[slot].job; usage(); nrunning--; events[slot].pid = -1; if(int buf[0]){ e = buildenv(j, slot); bp = newbuf(); shprint(j.r.recipe, e, bp); front(bp.start); sys->fprint(sys->fildes(2), "mk: %s: exit status=%s", libc0->ab2s(bp.start), libc0->ab2s(buf)); freebuf(bp); for((n, done) = (j.n, 0); n != nil; n = n.next) if(n.flags&DELETE){ if(done++ == 0) sys->fprint(sys->fildes(2), ", deleting"); sys->fprint(sys->fildes(2), " '%s'", libc0->ab2s(n.name)); delete(n.name); } sys->fprint(sys->fildes(2), "\n"); if(kflag){ runerrs++; uarg = 1; } else{ jobs = nil; Exit(); } } for(w = j.t; w != nil; w = w.next){ if((s = symlooki(w.s, S_NODE, 0)) == nil) continue; # not interested in this node update(uarg, s.nvalue); } if(nrunning < nproclimit) sched(); return 0; } nproc() { sym: ref Symtab; w: ref Word; if((sym = symlooki(libc0->s2ab("NPROC"), S_VAR, 0)) != nil){ w = sym.wvalue; if(w != nil && w.s != nil && int w.s[0]) nproclimit = int string w.s; } if(1 || nproclimit < 1) nproclimit = 1; if(debug&D_EXEC) sys->fprint(sys->fildes(1), "nprocs = %d\n", nproclimit); if(nproclimit > nevents){ if(nevents){ olen := len events; ne := array[nproclimit] of Event; if(olen) ne[0: ] = events[0: olen]; events = ne; } else events = array[nproclimit] of Event; while(nevents < nproclimit) events[nevents++].pid = 0; } } nextslot(): int { i: int; for(i = 0; i < nproclimit; i++) if(events[i].pid <= 0) return i; assert(libc0->s2ab("out of slots!!"), 0); return 0; # cyntax } pidslot(pid: int): int { i: int; for(i = 0; i < nevents; i++) if(events[i].pid == pid) return i; if(debug&D_EXEC) sys->fprint(sys->fildes(2), "mk: wait returned unexpected process %d\n", pid); return -1; } pnew(pid: int, status: int) { p: ref Process; if(pfree != nil){ p = pfree; pfree = p.f; } else p = ref Process; p.pid = pid; p.status = status; p.f = phead; phead = p; if(p.f != nil) p.f.b = p; p.b = nil; } pdelete(p: ref Process) { if(p.f != nil) p.f.b = p.b; if(p.b != nil) p.b.f = p.f; else phead = p.f; p.f = pfree; pfree = p; } killchildren(msg: array of byte) { p: ref Process; kflag = 1; # to make sure waitup doesn't exit jobs = nil; # make sure no more get scheduled for(p = phead; p != nil; p = p.f) expunge(p.pid, msg); while(waitup(1, nil) == 0) ; bout.puts(sys->sprint("mk: %s\n", libc0->ab2s(msg))); Exit(); } tslot := array[1000] of int; tick: int; usage() { t: int; t = daytime->now(); if(tick) tslot[nrunning] += t-tick; tick = t; } prusage() { i: int; usage(); for(i = 0; i <= nevents; i++) sys->fprint(sys->fildes(1), "%d: %d\n", i, tslot[i]); } # # file # # table-driven version in bootes dump of 12/31/96 timeof(name: array of byte, force: int): int { if(libc0->strchr(name, '(') != nil) return atimeof(force, name); # archive if(force) return mtime(name); return filetime(name); } touch(name: array of byte) { bout.puts(sys->sprint("touch(%s)\n", libc0->ab2s(name))); if(nflag) return; if(libc0->strchr(name, '(') != nil) atouch(name); # archive else if(chgtime(name) < 0){ perror(name); Exit(); } } delete(name: array of byte) { if(libc0->strchr(name, '(') == nil){ # file if(sys->remove(libc0->ab2s(name)) < 0) perror(name); } else sys->fprint(sys->fildes(2), "hoon off; mk can'tdelete archive members\n"); } timeinit(s: array of byte) { t: int; cp: array of byte; r: int; c, n: int; t = daytime->now(); while(int s[0]){ cp = s; do{ (r, n, nil) = sys->byte2char(s, 0); if(r == ' ' || r == ',' || r == '\n') break; s = s[n: ]; }while(int s[0]); c = int s[0]; s[0] = byte 0; symlooki(libc0->strdup(cp), S_TIME, t).ivalue = t; if(c){ s[0] = byte c; s = s[1: ]; } while(int s[0]){ (r, n, nil) = sys->byte2char(s, 0); if(r != ' ' && r != ',' && r != '\n') break; s = s[n: ]; } } } # # parse # infile: array of byte; mkinline: int; parse(f: array of byte, fd: ref Sys->FD, varoverride: int) { hline, v: int; body: array of byte; head, tail: ref Word; attr, set, pid: int; prog, p: array of byte; newfd: ref Sys->FD; in: ref Iobuf; buf: ref Bufblock; if(fd == nil){ perror(f); Exit(); } ipush(); infile = libc0->strdup(f); mkinline = 1; in = bufio->fopen(fd, Sys->OREAD); buf = newbuf(); while(assline(in, buf)){ hline = mkinline; (v, head, tail, attr, prog) = rhead(buf.start); case(v){ '<' => p = wtos(tail, ' '); if(p[0] == byte 0){ if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "missing include file name\n"); Exit(); } newfd = sys->open(libc0->ab2s(p), Sys->OREAD); if(newfd == nil){ sys->fprint(sys->fildes(2), "warning: skipping missing include file: "); perror(p); } else parse(p, newfd, 0); '|' => p = wtos(tail, ' '); if(p[0] == byte 0){ if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "missing include program name\n"); Exit(); } execinit(); anewfd := array[1] of ref Sys->FD; anewfd[0] = newfd; pid = pipecmd(p, envy, anewfd); newfd = anewfd[0]; if(newfd == nil){ sys->fprint(sys->fildes(2), "warning: skipping missing program file: "); perror(p); } else parse(p, newfd, 0); apid := array[1] of int; apid[0] = pid; while(waitup(-3, apid) >= 0) ; pid = apid[0]; if(pid != 0){ sys->fprint(sys->fildes(2), "bad include program status\n"); Exit(); } ':' => body = rbody(in); addrules(head, tail, body, attr, hline, prog); '=' => if(head.next != nil){ if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "multiple vars on left side of assignment\n"); Exit(); } if(symlooki(head.s, S_OVERRIDE, 0) != nil){ set = varoverride; } else{ set = 1; if(varoverride) symlooks(head.s, S_OVERRIDE, libc0->s2ab("")); } if(set){ # # char *cp; # dumpw("tail", tail); # cp = wtos(tail, ' '); print("assign %s to %s\n", head->s, cp); free(cp); # setvar(head.s, tail); symlooks(head.s, S_WESET, libc0->s2ab("")); } if(attr) symlooks(head.s, S_NOEXPORT, libc0->s2ab("")); * => if(hline >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), hline); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "expected one of :<=\n"); Exit(); } } fd = nil; freebuf(buf); ipop(); } addrules(head: ref Word, tail: ref Word, body: array of byte, attr: int, hline: int, prog: array of byte) { w: ref Word; assert(libc0->s2ab("addrules args"), head != nil && body != nil); # tuck away first non-meta rule as default target if(target1 == nil && !(attr®EXP)){ for(w = head; w != nil; w = w.next) if(charin(w.s, libc0->s2ab("%&")) != nil) break; if(w == nil) target1 = wdup(head); } for(w = head; w != nil; w = w.next) addrule(w.s, tail, body, head, attr, hline, prog); } rhead(line: array of byte): (int, ref Word, ref Word, int, array of byte) { h, t: ref Word; attr: int; prog: array of byte; p, pp: array of byte; sep: int; r: int; n: int; w: ref Word; p = charin(line, libc0->s2ab(":=<")); if(p == nil) return ('?', nil, nil, 0, nil); sep = int p[0]; p[0] = byte 0; p = p[1: ]; if(sep == '<' && p[0] == byte '|'){ sep = '|'; p = p[1: ]; } attr = 0; prog = nil; if(sep == '='){ pp = charin(p, termchars); # termchars is shell-dependent if(pp != nil && pp[0] == byte '='){ while(p != pp){ (r, n, nil) = sys->byte2char(p, 0); case(r){ * => if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "unknown attribute '%c'\n", int p[0]); Exit(); 'U' => attr = 1; } p = p[n: ]; } p = p[1: ]; # skip trailing '=' } } if(sep == ':' && int p[0] && p[0] != byte ' ' && p[0] != byte '\t'){ while(int p[0]){ (r, n, nil) = sys->byte2char(p, 0); if(r == ':') break; ea := p[n-1]; p = p[n: ]; case(r){ * => if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "unknown attribute '%c'\n", int ea); Exit(); 'D' => attr |= DEL; 'E' => attr |= NOMINUSE; 'n' => attr |= NOVIRT; 'N' => attr |= NOREC; 'P' => pp = libc0->strchr(p, ':'); if(pp == nil || pp[0] == byte 0){ if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "missing trailing :\n"); Exit(); } pp[0] = byte 0; prog = libc0->strdup(p); pp[0] = byte ':'; p = pp; 'Q' => attr |= QUIET; 'R' => attr |= REGEXP; 'U' => attr |= UPD; 'V' => attr |= VIR; } } if(p[0] != byte ':'){ if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "missing trailing :\n"); Exit(); } p = p[1: ]; } h = w = stow(line); if(w.s[0] == byte 0 && sep != '<' && sep != '|'){ if(mkinline-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline-1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "no var on left side of assignment/rule\n"); Exit(); } t = stow(p); return (sep, h, t, attr, prog); } rbody(in: ref Iobuf): array of byte { buf: ref Bufblock; r, lastr: int; p: array of byte; lastr = '\n'; buf = newbuf(); for(;;){ r = in.getc(); if(r < 0) break; if(lastr == '\n'){ if(r == '#') rinsert(buf, r); else if(r != ' ' && r != '\t'){ in.ungetc(); break; } } else rinsert(buf, r); lastr = r; if(r == '\n') mkinline++; } insert(buf, 0); p = libc0->strdup(buf.start); freebuf(buf); return p; } input: adt{ file: array of byte; line: int; next: cyclic ref input; }; inputs: ref input = nil; ipush() { in, me: ref input; me = ref input; me.file = infile; me.line = mkinline; me.next = nil; if(inputs == nil) inputs = me; else{ for(in = inputs; in.next != nil;) in = in.next; in.next = me; } } ipop() { in, me: ref input; assert(libc0->s2ab("pop input list"), inputs != nil); if(inputs.next == nil){ me = inputs; inputs = nil; } else{ for(in = inputs; in.next.next != nil;) in = in.next; me = in.next; in.next = nil; } infile = me.file; mkinline = me.line; me = nil; } # # lex # # # * Assemble a line skipping blank lines, comments, and eliding # * escaped newlines # assline(bp: ref Iobuf, buf: ref Bufblock): int { c, lastc: int; buf.current = 0; while((c = nextrune(bp, 1)) >= 0){ case(c){ '\r' => # consumes CRs for Win95 continue; '\n' => if(buf.current != 0){ insert(buf, 0); return 1; } # skip empty lines '\\' or '\'' or '"' => rinsert(buf, c); if(escapetoken(bp, buf, 1, c) == 0) Exit(); '`' => if(bquote(bp, buf) == 0) Exit(); '#' => lastc = '#'; while((c = bp.getb()) != '\n'){ if(c < 0){ insert(buf, 0); return buf.start[0] != byte 0; } if(c != '\r') lastc = c; } mkinline++; if(lastc == '\\') break; # propagate escaped newlines?? if(buf.current != 0){ insert(buf, 0); return 1; } * => rinsert(buf, c); } } insert(buf, 0); return buf.start[0] != byte 0; } # # * assemble a back-quoted shell command into a buffer # bquote(bp: ref Iobuf, buf: ref Bufblock): int { c, line, term, start: int; line = mkinline; while((c = bp.getc()) == ' ' || c == '\t') ; if(c == '{'){ term = '}'; # rc style while((c = bp.getc()) == ' ' || c == '\t') ; } else term = '`'; # sh style start = buf.current; for(; c > 0; c = nextrune(bp, 0)){ if(c == term){ insert(buf, '\n'); insert(buf, 0); buf.current = start; execinit(); execsh(nil, buf.start[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); } if(line >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), line); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(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. # savec: int; nextrune(bp: ref Iobuf, elide: int): int { c, c2: int; if(savec){ c = savec; savec = 0; return c; } for(;;){ c = bp.getc(); if(c == '\\'){ c2 = bp.getc(); if(c2 == '\r'){ savec = c2; c2 = bp.getc(); } if(c2 == '\n'){ savec = 0; mkinline++; if(elide) continue; return ' '; } bp.ungetc(); } if(c == '\n') mkinline++; return c; } return 0; } # # symtab # NHASH: con 4099; HASHMUL: con 79; hash := array[NHASH] of ref Symtab; syminit() { s: ref Symtab; ss, ns: ref Symtab; for(i := 0; i < NHASH; i++){ s = hash[i]; for(ss = s; ss != nil; ss = ns){ ns = s.next; ss = nil; } hash[i] = nil; } } symval(sym: ref Symtab): int { return sym.svalue != nil || sym.ivalue != 0 || sym.nvalue != nil || sym.rvalue != nil || sym.wvalue != nil; } symlooks(sym: array of byte, space: int, s: array of byte): ref Symtab { return symlook(sym, space, s != nil, s, 0, nil, nil, nil); } symlooki(sym: array of byte, space: int, i: int): ref Symtab { return symlook(sym, space, i != 0, nil, i, nil, nil, nil); } symlookn(sym: array of byte, space: int, n: ref Node): ref Symtab { return symlook(sym, space, n != nil, nil, 0, n, nil, nil); } symlookr(sym: array of byte, space: int, r: ref Rule): ref Symtab { return symlook(sym, space, r != nil, nil, 0, nil, r, nil); } symlookw(sym: array of byte, space: int, w: ref Word): ref Symtab { return symlook(sym, space, w != nil, nil, 0, nil, nil, w); } symlook(sym: array of byte, space: int, install: int, sv: array of byte, iv: int, nv: ref Node, rv: ref Rule, wv: ref Word): ref Symtab { h: int; p: array of byte; s: ref Symtab; for((p, h) = (sym, space); int p[0]; ){ h *= HASHMUL; h += int p[0]; p = p[1: ]; } if(h < 0) h = ~h; h %= NHASH; for(s = hash[h]; s != nil; s = s.next) if(s.space == space && libc0->strcmp(s.name, sym) == 0) return s; if(install == 0) return nil; s = ref Symtab; s.space = space; s.name = sym; s.svalue = sv; s.ivalue = iv; s.nvalue = nv; s.rvalue = rv; s.wvalue = wv; s.next = hash[h]; hash[h] = s; return s; } symdel(sym: array of byte, space: int) { h: int; p: array of byte; s, ls: ref Symtab; # multiple memory leaks for((p, h) = (sym, space); int p[0]; ){ h *= HASHMUL; h += int p[0]; p = p[1: ]; } if(h < 0) h = ~h; h %= NHASH; for((s, ls) = (hash[h], nil); s != nil; (ls, s) = (s, s.next)) if(s.space == space && libc0->strcmp(s.name, sym) == 0){ if(ls != nil) ls.next = s.next; else hash[h] = s.next; s = nil; } } symtraverse(space: int, fnx: int) { s: ref Symtab; ss: ref Symtab; for(i := 0; i < NHASH; i++){ s = hash[i]; for(ss = s; ss != nil; ss = ss.next) if(ss.space == space){ if(fnx == ECOPY) ecopy(ss); else if(fnx == PRINT1) print1(ss); } } } symstat() { s: ref Symtab; ss: ref Symtab; n: int; l := array[1000] of int; for(i := 0; i < 1000; i++) l[i] = 0; for(i = 0; i < NHASH; i++){ s = hash[i]; for((ss, n) = (s, 0); ss != nil; ss = ss.next) n++; l[n]++; } for(n = 0; n < 1000; n++) if(l[n]) bout.puts(sys->sprint("%d of length %d\n", l[n], n)); } # # varsub # varsub(s: array of byte): (ref Word, array of byte) { b: ref Bufblock; w: ref Word; if(s[0] == byte '{') # either ${name} or ${name: A%B==C%D} return expandvar(s); (b, s) = varname(s); if(b == nil) return (nil, s); (w, s) = varmatch(b.start, s); freebuf(b); return (w, s); } # # * extract a variable name # varname(s: array of byte): (ref Bufblock, array of byte) { b: ref Bufblock; cp: array of byte; r: int; n: int; b = newbuf(); cp = s; for(;;){ (r, n, nil) = sys->byte2char(cp, 0); if(!(r > ' ' && libc0->strchr(libc0->s2ab("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~"), r) == nil)) break; rinsert(b, r); cp = cp[n: ]; } if(b.current == 0){ if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "missing variable name <%s>\n", libc0->ab2s(s)); freebuf(b); return (nil, s); } s = cp; insert(b, 0); return (b, s); } varmatch(name: array of byte, s: array of byte): (ref Word, array of byte) { w: ref Word; sym: ref Symtab; cp: array of byte; sym = symlooki(name, S_VAR, 0); if(sym != nil){ # check for at least one non-NULL value for(w = sym.wvalue; w != nil; w = w.next) if(w.s != nil && int w.s[0]) return (wdup(w), s); } for(cp = s; cp[0] == byte ' ' || cp[0] == byte '\t'; cp = cp[1: ]) # skip trailing whitespace ; s = cp; return (nil, s); } expandvar(s: array of byte): (ref Word, array of byte) { w: ref Word; buf: ref Bufblock; sym: ref Symtab; cp, begin, end: array of byte; begin = s; s = s[1: ]; # skip the '{' (buf, s) = varname(s); if(buf == nil) return (nil, s); cp = s; if(cp[0] == byte '}'){ # ${name} variant s[0]++; # skip the '}' (w, s) = varmatch(buf.start, s); freebuf(buf); return (w, s); } if(cp[0] != byte ':'){ if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "bad variable name <%s>\n", libc0->ab2s(buf.start)); freebuf(buf); return (nil, s); } cp = cp[1: ]; end = charin(cp, libc0->s2ab("}")); if(end == nil){ if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "missing '}': %s\n", libc0->ab2s(begin)); Exit(); } end[0] = byte 0; s = end[1: ]; sym = symlooki(buf.start, S_VAR, 0); if(sym == nil || !symval(sym)) w = newword(buf.start); else w = subsub(sym.wvalue, cp, end); freebuf(buf); return (w, s); } extractpat(s: array of byte, r: array of byte, term: array of byte, end: array of byte): (ref Word, array of byte) { save: int; cp: array of byte; w: ref Word; cp = charin(s, term); if(cp != nil){ r = cp; if(cp == s) return (nil, r); save = int cp[0]; cp[0] = byte 0; w = stow(s); cp[0] = byte save; } else{ r = end; w = stow(s); } return (w, r); } subsub(v: ref Word, s: array of byte, end: array of byte): ref Word { nmid, ok: int; head, tail, w, h, a, b, c, d: ref Word; buf: ref Bufblock; cp, enda: array of byte; (a, cp) = extractpat(s, cp, libc0->s2ab("=%&"), end); b = c = d = nil; if(cp[0] == byte '%' || cp[0] == byte '&') (b, cp) = extractpat(cp[1: ], cp, libc0->s2ab("="), end); if(cp[0] == byte '=') (c, cp) = extractpat(cp[1: ], cp, libc0->s2ab("&%"), end); if(cp[0] == byte '%' || cp[0] == byte '&') d = stow(cp[1: ]); else if(int cp[0]) d = stow(cp); head = tail = nil; buf = newbuf(); for(; v != nil; v = v.next){ h = w = nil; (ok, nmid, enda) = submatch(v.s, a, b, nmid, enda); if(ok){ # enda points to end of A match in source; # * nmid = number of chars between end of A and start of B # if(c != nil){ h = w = wdup(c); while(w.next != nil) w = w.next; } if((cp[0] == byte '%' || cp[0] == byte '&') && nmid > 0){ if(w != nil){ bufcpy(buf, w.s, libc0->strlen(w.s)); bufcpy(buf, enda, nmid); insert(buf, 0); w.s = nil; w.s = libc0->strdup(buf.start); } else{ bufcpy(buf, enda, nmid); insert(buf, 0); h = w = newword(buf.start); } buf.current = 0; } if(d != nil && int d.s[0]){ if(w != nil){ bufcpy(buf, w.s, libc0->strlen(w.s)); bufcpy(buf, d.s, libc0->strlen(d.s)); insert(buf, 0); w.s = nil; w.s = libc0->strdup(buf.start); w.next = wdup(d.next); while(w.next != nil) w = w.next; buf.current = 0; } else h = w = wdup(d); } } if(w == nil) h = w = newword(v.s); if(head == nil) head = h; else tail.next = h; tail = w; } freebuf(buf); delword(a); delword(b); delword(c); delword(d); return head; } submatch(s: array of byte, a: ref Word, b: ref Word, nmid: int, enda: array of byte): (int, int, array of byte) { w: ref Word; n: int; end: array of byte; n = 0; for(w = a; w != nil; w = w.next){ n = libc0->strlen(w.s); if(libc0->strncmp(s, w.s, n) == 0) break; } if(a != nil && w == nil) # a == NULL matches everything return (0, nmid, enda); enda = s[n: ]; # pointer to end a A part match nmid = libc0->strlen(s)-n; # size of remainder of source end = enda[nmid: ]; onmid := nmid; for(w = b; w != nil; w = w.next){ n = libc0->strlen(w.s); if(libc0->strcmp(w.s, enda[onmid-n: ]) == 0){ # end-n nmid -= n; break; } } if(b != nil && w == nil) # b == NULL matches everything return (0, nmid, enda); return (1, nmid, enda); } # # var # setvar(name: array of byte, value: ref Word) { # s := libc0->ab2s(name); # if(s == "ROOT" || s == "OBJTYPE"){ # if(s[0] == 'R') # v := ""; # else # v = "386"; # value.s = libc0->strdup(libc0->s2ab(v)); # } symlookw(name, S_VAR, value).wvalue = value; symlooks(name, S_MAKEVAR, libc0->s2ab("")); } print1(s: ref Symtab) { w: ref Word; bout.puts(sys->sprint("\t%s=", libc0->ab2s(s.name))); for(w = s.wvalue; w != nil; w = w.next) bout.puts(sys->sprint("'%s'", libc0->ab2s(w.s))); bout.puts(sys->sprint("\n")); } dumpv(s: array of byte) { bout.puts(sys->sprint("%s:\n", libc0->ab2s(s))); symtraverse(S_VAR, PRINT1); } shname(a: array of byte): array of byte { r: int; n: int; while(int a[0]){ (r, n, nil) = sys->byte2char(a, 0); if(!(r > ' ' && libc0->strchr(libc0->s2ab("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~"), r) == nil)) break; a = a[n: ]; } return a; } # # word # newword(s: array of byte): ref Word { w: ref Word; w = ref Word; w.s = libc0->strdup(s); w.next = nil; return w; } stow(s: array of byte): ref Word { head, w, new: ref Word; w = head = nil; while(int s[0]){ (new, s) = nextword(s); if(new == nil) break; if(w != nil) w.next = new; else head = w = new; while(w.next != nil) w = w.next; } if(head == nil) head = newword(libc0->s2ab("")); return head; } wtos(w: ref Word, sep: int): array of byte { buf: ref Bufblock; cp: array of byte; buf = newbuf(); for(; w != nil; w = w.next){ for(cp = w.s; int cp[0]; cp = cp[1: ]) insert(buf, int cp[0]); if(w.next != nil) insert(buf, sep); } insert(buf, 0); cp = libc0->strdup(buf.start); freebuf(buf); return cp; } wtostr(w: ref Word, sep: int): string { return libc0->ab2s(wtos(w, sep)); } wdup(w: ref Word): ref Word { v, new, base: ref Word; v = base = nil; while(w != nil){ new = newword(w.s); if(v != nil) v.next = new; else base = new; v = new; w = w.next; } return base; } delword(w: ref Word) { v: ref Word; while((v = w) != nil){ w = w.next; if(v.s != nil) v.s = nil; v = nil; } } # # * break out a word from a string handling quotes, executions, # * and variable expansions. # nextword(s: array of byte): (ref Word, array of byte) { b: ref Bufblock; head, tail, w: ref Word; r, n: int; cp: array of byte; cp = s; b = newbuf(); head = tail = nil; while(cp[0] == byte ' ' || cp[0] == byte '\t') # leading white space cp = cp[1: ]; loop := 1; while(loop && int cp[0]){ (r, n, nil) = sys->byte2char(cp, 0); cp = cp[n: ]; case(r){ ' ' or '\t' or '\n' => loop = 0; '\\' or '\'' or '"' => cp = expandquote(cp, r, b); if(cp == nil){ sys->fprint(sys->fildes(2), "missing closing quote: %s\n", libc0->ab2s(s)); Exit(); } '$' => (w, cp) = varsub(cp); if(w == nil) break; if(b.current != 0){ bufcpy(b, w.s, libc0->strlen(w.s)); insert(b, 0); w.s = nil; w.s = libc0->strdup(b.start); b.current = 0; } if(head != nil){ bufcpy(b, tail.s, libc0->strlen(tail.s)); bufcpy(b, w.s, libc0->strlen(w.s)); insert(b, 0); tail.s = nil; tail.s = libc0->strdup(b.start); tail.next = w.next; w.s = nil; w = nil; b.current = 0; } else tail = head = w; while(tail.next != nil) tail = tail.next; * => rinsert(b, r); } } s = cp; if(b.current != 0){ if(head != nil){ oc := b.current; cp = b.start[b.current: ]; bufcpy(b, tail.s, libc0->strlen(tail.s)); bufcpy(b, b.start, oc); insert(b, 0); tail.s = nil; tail.s = libc0->strdup(cp); } else{ insert(b, 0); head = newword(b.start); } } freebuf(b); return (head, s); } dumpw(s: array of byte, w: ref Word) { bout.puts(sys->sprint("%s", libc0->ab2s(s))); for(; w != nil; w = w.next) bout.puts(sys->sprint(" '%s'", libc0->ab2s(w.s))); bout.putb(byte '\n'); } # # match # match(name: array of byte, template: array of byte, stem: array of byte): int { r: int; n: int; while(int name[0] && int template[0]){ (r, n, nil) = sys->byte2char(template, 0); if(r == '%' || r == '&') break; while(n--) if(name[0] != template[0]) return 0; name = name[1: ]; template = template[1: ]; } if(!(template[0] == byte '%' || template[0] == byte '&')) return 0; n = libc0->strlen(name)-libc0->strlen(template[1: ]); if(n < 0 || libc0->strcmp(template[1: ], name[n: ])) return 0; libc0->strncpy(stem, name, n); stem[n] = byte 0; if(template[0] == byte '&') return charin(stem, libc0->s2ab("./")) == nil; return 1; } subst(stem: array of byte, template: array of byte, dest: array of byte) { r: int; s: array of byte; n: int; while(int template[0]){ (r, n, nil) = sys->byte2char(template, 0); if(r == '%' || r == '&'){ template = template[n: ]; for(s = stem; int s[0]; s = s[1: ]){ dest[0] = s[0]; dest = dest[1: ]; } } else while(n--){ dest[0] = template[0]; dest = dest[1: ]; template = template[1: ]; } } dest[0] = byte 0; } # # os # shell := "/dis/sh.dis"; shellname := "sh"; pcopy(a: array of ref Sys->FD): array of ref Sys->FD { b := array[2] of ref Sys->FD; b[0: ] = a[0: 2]; return b; } readenv() { p: array of byte; envf, f: ref Sys->FD; e := array[20] of Sys->Dir; nam := array[NAMELEN+5] of byte; i, n, lenx: int; w: ref Word; sys->pctl(Sys->FORKENV, nil); # use copy of the current environment variables if(sys->open("/env/autoload", Sys->OREAD) == nil){ fd := sys->create("/env/autoload", Sys->OWRITE, 8r666); if(fd != nil) sys->fprint(fd, "std"); } envf = sys->open("/env", Sys->OREAD); if(envf == nil) return; for(;;){ (n, e) = sys->dirread(envf); if(n <= 0) break; for(i = 0; i < n; i++){ lenx = int e[i].length; # don't import funny names, NULL values, # * or internal mk variables # if(lenx <= 0 || shname(libc0->s2ab(e[i].name))[0] != byte '\0') continue; if(symlooki(libc0->s2ab(e[i].name), S_INTERNAL, 0) != nil) continue; stob(nam, sys->sprint("/env/%s", e[i].name)); f = sys->open(libc0->ab2s(nam), Sys->OREAD); if(f == nil) continue; p = array[lenx+1] of byte; if(sys->read(f, p, lenx) != lenx){ perror(nam); f = nil; continue; } f = nil; if(p[lenx-1] == byte 0) lenx--; else p[lenx] = byte 0; w = encodenulls(p, lenx); p = nil; p = libc0->strdup(libc0->s2ab(e[i].name)); setvar(p, w); symlooks(p, S_EXPORTED, libc0->s2ab("")).svalue = libc0->s2ab(""); } } envf = nil; } # break string of values into words at 01's or nulls encodenulls(s: array of byte, n: int): ref Word { w, head: ref Word; cp: array of byte; head = w = nil; while(n-- > 0){ for(cp = s; int cp[0] && cp[0] != byte '\u0001'; cp = cp[1: ]) n--; cp[0] = byte 0; if(w != nil){ w.next = newword(s); w = w.next; } else head = w = newword(s); s = cp[1: ]; } if(head == nil) head = newword(libc0->s2ab("")); return head; } # as well as 01's, change blanks to nulls, so that rc will # * treat the words as separate arguments # exportenv(e: array of Envy) { f: ref Sys->FD; n, hasvalue: int; w: ref Word; sy: ref Symtab; nam := array[NAMELEN+5] of byte; for(i := 0; e[i].name != nil; i++){ sy = symlooki(e[i].name, S_VAR, 0); if(e[i].values == nil || e[i].values.s == nil || e[i].values.s[0] == byte 0) hasvalue = 0; else hasvalue = 1; if(sy == nil && !hasvalue) # non-existant null symbol continue; stob(nam, sys->sprint("/env/%s", libc0->ab2s(e[i].name))); if(sy != nil && !hasvalue){ # Remove from environment # we could remove it from the symbol table # * too, but we're in the child copy, and it # * would still remain in the parent's table. # sys->remove(libc0->ab2s(nam)); delword(e[i].values); e[i].values = nil; # memory leak continue; } f = sys->create(libc0->ab2s(nam), Sys->OWRITE, 8r666); if(f == nil){ sys->fprint(sys->fildes(2), "can't create %s, f=%d\n", libc0->ab2s(nam), f.fd); perror(nam); continue; } for(w = e[i].values; w != nil; w = w.next){ n = libc0->strlen(w.s); if(n){ if(sys->write(f, w.s, n) != n) perror(nam); if(w.next != nil && sys->write(f, libc0->s2ab(" "), 1) != 1) perror(nam); } } f = nil; } } dirtime(dir: array of byte, path: array of byte) { i: int; fd: ref Sys->FD; n: int; t: int; db := array[32] of Sys->Dir; buf := array[4096] of byte; fd = sys->open(libc0->ab2s(dir), Sys->OREAD); if(fd != nil){ for(;;){ (n, db) = sys->dirread(fd); if(n <= 0) break; for(i = 0; i < n; i++){ t = db[i].mtime; if(t == 0) # zero mode file continue; stob(buf, sys->sprint("%s%s", libc0->ab2s(path), db[i].name)); if(symlooki(buf, S_TIME, 0) != nil) continue; symlooki(libc0->strdup(buf), S_TIME, t).ivalue = t; } } fd = nil; } } waitfor(msg: array of byte): int { wm: array of byte; pid: int; (pid, wm) = wait(); if(pid > 0) libc0->strncpy(msg, wm, ERRLEN); return pid; } expunge(pid: int, msg: array of byte) { postnote(PNPROC, pid, msg); } sub(cmd: array of byte, env: array of Envy): array of byte { buf := newbuf(); shprint(cmd, env, buf); return buf.start; } fork1(c1: chan of int, args: array of byte, cmd: array of byte, buf: ref Bufblock, e: array of Envy, in: array of ref Sys->FD, out: array of ref Sys->FD) { pid: int; c1<- = sys->pctl(Sys->FORKFD|Sys->FORKENV, nil); { if(buf != nil) out[0] = nil; if(sys->pipe(in) < 0){ perrors("pipe"); Exit(); } c2 := chan of int; spawn fork2(c2, cmd, pcopy(in), pcopy(out)); pid = <- c2; addwait(); { sys->dup(in[0].fd, 0); if(buf != nil){ sys->dup(out[1].fd, 1); out[1] = nil; } in[0] = nil; in[1] = nil; if(e != nil) exportenv(e); argss := libc0->ab2s(args); sys->pctl(Sys->NEWFD, 0 :: 1 :: 2 :: nil); if(shflags != nil) execl(shell, shellname, shflags, argss, nil, nil); else execl(shell, shellname, argss, nil, nil, nil); exit; # perror(shell); # exits("exec"); } } } fork2(c2: chan of int, cmd: array of byte, in: array of ref Sys->FD, out: array of ref Sys->FD) { n, p: int; c2<- = sys->pctl(Sys->FORKFD, nil); { out[1] = nil; in[0] = nil; p = libc0->strlen(cmd); c := 0; while(c < p){ # cmd < p if(debug&D_EXEC) sys->fprint(sys->fildes(1), "writing '%s' to shell\n", libc0->ab2s(cmd[0: p-c])); n = sys->write(in[1], cmd, p-c); # p-cmd if(n < 0) break; cmd = cmd[n: ]; c += n; } in[1] = nil; exit; # exits(nil); } } execsh(args: array of byte, cmd: array of byte, buf: ref Bufblock, e: array of Envy): int { tot, n, pid: int; in := array[2] of ref Sys->FD; out := array[2] of ref Sys->FD; cmd = sub(cmd, e); if(buf != nil && sys->pipe(out) < 0){ perrors("pipe"); Exit(); } c1 := chan of int; spawn fork1(c1, args, cmd, buf, e, in, pcopy(out)); pid = <-c1; addwait(); if(buf != nil){ out[1] = nil; tot = 0; for(;;){ if(buf.current >= buf.end) growbuf(buf); n = sys->read(out[0], buf.start[buf.current: ], buf.end-buf.current); if(n <= 0) break; buf.current += n; tot += n; } if(tot && buf.start[buf.current-1] == byte '\n') buf.current--; out[0] = nil; } return pid; } fork3(c3: chan of int, cmd: array of byte, e: array of Envy, fd: array of ref Sys->FD, pfd: array of ref Sys->FD) { c3<- = sys->pctl(Sys->FORKFD|Sys->FORKENV, nil); { if(fd != nil){ pfd[0] = nil; sys->dup(pfd[1].fd, 1); pfd[1] = nil; } if(e != nil) exportenv(e); cmds := libc0->ab2s(cmd); if(shflags != nil) execl(shell, shellname, shflags, "-c", cmds, nil); else execl(shell, shellname, "-c", cmds, nil, nil); exit; # perror(shell); # exits("exec"); } } pipecmd(cmd: array of byte, e: array of Envy, fd: array of ref Sys->FD): int { pid: int; pfd := array[2] of ref Sys->FD; cmd = sub(cmd, e); if(debug&D_EXEC) sys->fprint(sys->fildes(1), "pipecmd='%s'", libc0->ab2s(cmd)); # if(fd != nil && sys->pipe(pfd) < 0){ perrors("pipe"); Exit(); } c3 := chan of int; spawn fork3(c3, cmd, e, fd, pcopy(pfd)); pid = <- c3; addwait(); if(fd != nil){ pfd[1] = nil; fd[0] = pfd[0]; } return pid; } Exit() { while(wait().t0 >= 0) ; bout.flush(); exit; } nnote: int; notifyf(a: array of byte, msg: array of byte): int { if(a != nil) ; if(++nnote > 100){ # until andrew fixes his program sys->fprint(sys->fildes(2), "mk: too many notes\n"); # notify(nil); abort(); } if(libc0->strcmp(msg, libc0->s2ab("interrupt")) != 0 && libc0->strcmp(msg, libc0->s2ab("hangup")) != 0) return 0; killchildren(msg); return -1; } catchnotes() { # atnotify(notifyf, 1); } temp := array[] of { byte '/', byte 't', byte 'm', byte 'p', byte '/', byte 'm', byte 'k', byte 'a', byte 'r', byte 'g', byte 'X', byte 'X', byte 'X', byte 'X', byte 'X', byte 'X', byte '\0' }; maketmp(): array of byte { t := libc0->strdup(temp); mktemp(t); return t; } chgtime(name: array of byte): int { (ok, nil) := sys->stat(libc0->ab2s(name)); if(ok >= 0){ sbuf := sys->nulldir; sbuf.mtime = daytime->now(); return sys->wstat(libc0->ab2s(name), sbuf); } fd := sys->create(libc0->ab2s(name), Sys->OWRITE, 8r666); if(fd == nil) return -1; fd = nil; return 0; } rcopy(tox: array of array of byte, match: array of Resub, n: int) { c: int; p: array of byte; i := 0; tox[0] = match[0].sp; # stem0 matches complete target for(i++; --n > 0; i++){ if(match[i].sp != nil && match[i].ep != nil){ p = match[i].ep; c = int p[0]; p[0] = byte 0; tox[i] = libc0->strdup(match[i].sp); p[0] = byte c; } else tox[i] = nil; } } mkdirstat(name: array of byte): (int, Sys->Dir) { return sys->stat(libc0->ab2s(name)); } membername(s: array of byte, fd: ref Sys->FD, sz: int): array of byte { if(fd == nil) ; if(sz) ; return s; } # # sh # termchars := array[] of { byte '\'', byte '=', byte ' ', byte '\t', byte '\0' }; # used in parse.c to isolate assignment attribute shflags := ""; # rc flag to force non-interactive mode - was -l IWS: int = '\u0001'; # 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. # squote(cp: array of byte): array of byte { r: int; n, nn: int; while(int cp[0]){ (r, n, nil) = sys->byte2char(cp, 0); if(r == '\''){ (r, nn, nil) = sys->byte2char(cp[n: ], 0); n += nn; if(r != '\'') return cp; } cp = cp[n: ]; } if(-1 >= 0) # should never occur sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "missing closing '\n"); return nil; } # # * search a string for characters in a pattern set # * characters in quotes and variable generators are escaped # charin(cp: array of byte, pat: array of byte): array of byte { r: int; n, vargen: int; vargen = 0; while(int cp[0]){ (r, n, nil) = sys->byte2char(cp, 0); case(r){ '\'' => # skip quoted string cp = squote(cp[1: ]); # n must = 1 if(cp == nil) return nil; '$' => if((cp[1: ])[0] == byte '{') vargen = 1; '}' => if(vargen) vargen = 0; else if(libc0->strchr(pat, r) != nil) return cp; * => if(vargen == 0 && libc0->strchr(pat, r) != nil) return cp; } cp = cp[n: ]; } if(vargen){ if(-1 >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), -1); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "missing closing } in pattern generator\n"); } return nil; } # # * 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. # expandquote(s: array of byte, r: int, b: ref Bufblock): array of byte { n: int; if(r != '\''){ rinsert(b, r); return s; } while(int s[0]){ (r, n, nil) = sys->byte2char(s, 0); s = s[n: ]; if(r == '\''){ if(s[0] == byte '\'') s = s[1: ]; else return s; } rinsert(b, r); } return nil; } # # * 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. # escapetoken(bp: ref Iobuf, buf: ref Bufblock, preserve: int, esc: int): int { c, line: int; if(esc != '\'') return 1; line = mkinline; while((c = nextrune(bp, 0)) > 0){ if(c == '\''){ if(preserve) rinsert(buf, c); c = bp.getc(); if(c < 0) break; if(c != '\''){ bp.ungetc(); return 1; } } rinsert(buf, c); } if(line >= 0) sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), line); else sys->fprint(sys->fildes(2), "mk: %s:%d: syntax error; ", libc0->ab2s(infile), mkinline); sys->fprint(sys->fildes(2), "missing closing %c\n", esc); return 0; } # # * copy a single-quoted string; s points to char after opening quote # copysingle(s: array of byte, buf: ref Bufblock): array of byte { r, n: int; while(int s[0]){ (r, n, nil) = sys->byte2char(s, 0); s = s[n: ]; 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. # copyq(s: array of byte, q: int, buf: ref Bufblock): array of byte { n: int; if(q == '\'') # copy quoted string return copysingle(s, buf); if(q != '`') # not quoted return s; while(int s[0]){ # copy backquoted string (q, n, nil) = sys->byte2char(s, 0); s = s[n: ]; rinsert(buf, q); if(q == '}') break; if(q == '\'') s = copysingle(s, buf); # copy quoted string } return s; } # # shprint # shprint(s: array of byte, env: array of Envy, buf: ref Bufblock) { n: int; r: int; while(int s[0]){ (r, n, nil) = sys->byte2char(s, 0); if(r == '$') s = vexpand(s, env, buf); else{ rinsert(buf, r); s = s[n: ]; s = copyq(s, r, buf); # handle quoted strings } } insert(buf, 0); } mygetenv(name: array of byte, env: array of Envy): array of byte { if(env == nil) return nil; if(symlooki(name, S_WESET, 0) == nil && symlooki(name, S_INTERNAL, 0) == nil) return nil; # only resolve internal variables and variables we've set for(e := 0; env[e].name != nil; e++){ if(libc0->strcmp(env[e].name, name) == 0) return wtos(env[e].values, ' '); } return nil; } vexpand(w: array of byte, env: array of Envy, buf: ref Bufblock): array of byte { s: array of byte; carry: byte; p, q: array of byte; assert(libc0->s2ab("vexpand no $"), w[0] == byte '$'); p = w[1: ]; # skip dollar sign if(p[0] == byte '{'){ p = p[1: ]; q = libc0->strchr(p, '}'); if(q == nil) q = libc0->strchr(p, 0); } else q = shname(p); carry = q[0]; q[0] = byte 0; s = mygetenv(p, env); q[0] = carry; if(carry == byte '}') q = q[1: ]; if(s != nil){ bufcpy(buf, s, libc0->strlen(s)); s = nil; } else # copy name intact bufcpy(buf, w, libc0->strlen(w)-libc0->strlen(q)); # q-w return q; } front(s: array of byte) { t, q: array of byte; i, j: int; # flds := array[512] of array of byte; fields: list of string; q = libc0->strdup(s); (i, fields) = sys->tokenize(libc0->ab2s(q), " \t\n"); flds := array[len fields] of array of byte; for(j = 0; j < len flds; j++){ flds[j] = libc0->s2ab(hd fields); fields = tl fields; } if(i > 5){ flds[4] = flds[i-1]; flds[3] = libc0->s2ab("..."); i = 5; } t = s; for(j = 0; j < i; j++){ for(s = flds[j]; int s[0]; ){ t[0] = s[0]; s = s[1: ]; t = t[1: ]; } t[0] = byte ' '; t = t[1: ]; } t[0] = byte 0; q = nil; } # # env # ENVQUANTA: con 10; envy: array of Envy; nextv: int; myenv: array of array of byte; initenv() { p: int; myenv = array[19] of { libc0->s2ab("target"), libc0->s2ab("stem"), libc0->s2ab("prereq"), libc0->s2ab("pid"), libc0->s2ab("nproc"), libc0->s2ab("newprereq"), libc0->s2ab("alltarget"), libc0->s2ab("newmember"), libc0->s2ab("stem0"), # must be in order from here libc0->s2ab("stem1"), libc0->s2ab("stem2"), libc0->s2ab("stem3"), libc0->s2ab("stem4"), libc0->s2ab("stem5"), libc0->s2ab("stem6"), libc0->s2ab("stem7"), libc0->s2ab("stem8"), libc0->s2ab("stem9"), array of byte nil, }; for(p = 0; myenv[p] != nil; p++) symlooks(myenv[p], S_INTERNAL, libc0->s2ab("")); readenv(); # o.s. dependent } envsize: int; envinsert(name: array of byte, value: ref Word) { if(nextv >= envsize){ envsize += ENVQUANTA; es := len envy; ne := array[envsize] of Envy; if(es) ne[0: ] = envy[0: es]; envy = ne; } envy[nextv].name = name; envy[nextv++].values = value; } envupd(name: array of byte, value: ref Word) { e: int; for(e = 0; envy[e].name != nil; e++) if(libc0->strcmp(name, envy[e].name) == 0){ delword(envy[e].values); envy[e].values = value; return; } envy[e].name = name; envy[e].values = value; envinsert(nil, nil); } ecopy(s: ref Symtab) { p: int; if(symlooki(s.name, S_NOEXPORT, 0) != nil) return; for(p = 0; myenv[p] != nil; p++) if(libc0->strcmp(myenv[p], s.name) == 0) return; envinsert(s.name, s.wvalue); } execinit() { p: int; nextv = 0; for(p = 0; myenv[p] != nil; p++) envinsert(myenv[p], stow(libc0->s2ab(""))); symtraverse(S_VAR, ECOPY); envinsert(nil, nil); } buildenv(j: ref Job, slot: int): array of Envy { p: int; cp, qp: array of byte; w, v: ref Word; l: ref Word; i: int; buf := array[256] of byte; envupd(libc0->s2ab("target"), wdup(j.t)); if(j.r.attr®EXP) envupd(libc0->s2ab("stem"), newword(libc0->s2ab(""))); else envupd(libc0->s2ab("stem"), newword(j.stem)); envupd(libc0->s2ab("prereq"), wdup(j.p)); stob(buf, sys->sprint("%d", sys->pctl(0, nil))); envupd(libc0->s2ab("pid"), newword(buf)); stob(buf, sys->sprint("%d", slot)); envupd(libc0->s2ab("nproc"), newword(buf)); envupd(libc0->s2ab("newprereq"), wdup(j.np)); envupd(libc0->s2ab("alltarget"), wdup(j.at)); l = ref Word; l.next = v = w = wdup(j.np); while(w != nil){ cp = libc0->strchr(w.s, '('); if(cp != nil){ cp = cp[1: ]; qp = libc0->strchr(cp, ')'); if(qp != nil){ qp[0] = byte 0; libc0->strcpy(w.s, cp); l.next = w; l = w; w = w.next; continue; } } l.next = w.next; w.s = nil; w = nil; w = l.next; } v = l.next; envupd(libc0->s2ab("newmember"), v); # update stem0 -> stem9 for(p = 0; myenv[p] != nil; p++) if(libc0->strcmp(myenv[p], libc0->s2ab("stem0")) == 0) break; for(i = 0; myenv[p] != nil; i++){ if(j.r.attr®EXP && j.match[i] != nil) envupd(myenv[p], newword(j.match[i])); else envupd(myenv[p], newword(libc0->s2ab(""))); p++; } return envy; } # # dir # bulkmtime(dir: array of byte) { buf := array[4096] of byte; ss, s: array of byte; db: Sys->Dir; ok: int; if(dir != nil){ s = dir; if(libc0->strcmp(dir, libc0->s2ab("/")) == 0) libc0->strcpy(buf, dir); else stob(buf, sys->sprint("%s/", libc0->ab2s(dir))); (ok, db) = mkdirstat(dir); if(ok >= 0 && (db.qid.qtype&Sys->QTDIR) == 0){ # bugger off sys->fprint(sys->fildes(2), "mk: %s is not a directory path=%ux\n", libc0->ab2s(dir), int db.qid.path); Exit(); } } else{ s = libc0->s2ab("."); buf[0] = byte 0; } if(symlooki(s, S_BULKED, 0) != nil) return; ss = libc0->strdup(s); symlooks(ss, S_BULKED, ss); dirtime(s, buf); } mtime(name: array of byte): int { sbuf: Sys->Dir; s, ss: array of byte; carry: byte; ok: int; s = libc0->strrchr(name, '/'); if(s == name) s = s[1: ]; if(s != nil){ ss = name; carry = s[0]; s[0] = byte 0; } else{ ss = nil; carry = byte 0; } bulkmtime(ss); if(int carry) s[0] = carry; (ok, sbuf) = mkdirstat(name); if(ok < 0) return 0; return sbuf.mtime; } filetime(name: array of byte): int { sym: ref Symtab; sym = symlooki(name, S_TIME, 0); if(sym != nil) return sym.ivalue; # uggh return mtime(name); } # # archive # dolong: int; atimeof(force: int, name: array of byte): int { sym: ref Symtab; t: int; archive, member: array of byte; buf := array[512] of byte; (archive, member) = split(name); if(archive == nil) Exit(); t = mtime(archive); sym = symlooki(archive, S_AGG, 0); if(sym != nil){ if(force || t > sym.ivalue){ atimes(archive); sym.ivalue = t; } } else{ atimes(archive); # mark the aggegate as having been done symlooks(libc0->strdup(archive), S_AGG, libc0->s2ab("")).ivalue = t; } # truncate long member name to sizeof of name field in archive header if(dolong) stob(buf, sys->sprint("%s(%s)", libc0->ab2s(archive), libc0->ab2s(member))); else stob(buf, sys->sprint("%s(%.*s)", libc0->ab2s(archive), SARNAME, libc0->ab2s(member))); sym = symlooki(buf, S_TIME, 0); if(sym != nil) return sym.ivalue; # uggh return 0; } atouch(name: array of byte) { archive, member: array of byte; fd: ref Sys->FD; i: int; # h: ar_hdr; t: int; (archive, member) = split(name); if(archive == nil) Exit(); fd = sys->open(libc0->ab2s(archive), Sys->ORDWR); if(fd == nil){ fd = sys->create(libc0->ab2s(archive), Sys->OWRITE, 8r666); if(fd == nil){ perror(archive); Exit(); } sys->write(fd, libc0->s2ab(ARMAG), SARMAG); } if(symlooki(name, S_TIME, 0) != nil){ # hoon off and change it in situ sys->seek(fd, big SARMAG, 0); buf := array[SAR_HDR] of byte; while(sys->read(fd, buf, SAR_HDR) == SAR_HDR){ name = buf[0: SARNAME]; for(i = SARNAME-1; i > 0 && name[i] == byte ' '; i--) ; name[i+1] = byte 0; if(libc0->strcmp(member, name) == 0){ t = SARNAME-SAR_HDR; # ughgghh sys->seek(fd, big t, 1); sys->fprint(fd, "%-12d", daytime->now()); break; } t = int string buf[48: 58]; if(t&8r1) t++; sys->seek(fd, big t, 1); } } fd = nil; } atimes(ar: array of byte) { # h: ar_hdr; t: int; fd: ref Sys->FD; i: int; buf := array[BIGBLOCK] of byte; n: array of byte; name := array[SARNAME+1] of byte; fd = sys->open(libc0->ab2s(ar), Sys->OREAD); if(fd == nil) return; if(sys->read(fd, buf, SARMAG) != SARMAG){ fd = nil; return; } b := array[SAR_HDR] of byte; while(sys->read(fd, b, SAR_HDR) == SAR_HDR){ t = int string b[16: 28]; if(t == 0) # as it sometimes happens; thanks ken t = 1; hname := b[0: SARNAME]; libc0->strncpy(name, hname, SARNAME); for(i = SARNAME-1; i > 0 && name[i] == byte ' '; i--) ; if(name[i] == byte '/') # system V bug i--; name[i+1] = byte 0; n = membername(name, fd, int string b[48: 58]); if(n == nil){ dolong = 1; continue; } stob(buf, sys->sprint("%s(%s)", libc0->ab2s(ar), libc0->ab2s(n))); symlooki(libc0->strdup(buf), S_TIME, t).ivalue = t; t = int string b[48: 58]; if(t&8r1) t++; sys->seek(fd, big t, 1); } fd = nil; } typex(file: array of byte): int { fd: ref Sys->FD; buf := array[SARMAG] of byte; fd = sys->open(libc0->ab2s(file), Sys->OREAD); if(fd == nil){ if(symlooki(file, S_BITCH, 0) == nil){ bout.puts(sys->sprint("%s doesn't exist: assuming it will be an archive\n", libc0->ab2s(file))); symlooks(file, S_BITCH, file); } return 1; } if(sys->read(fd, buf, SARMAG) != SARMAG){ fd = nil; return 0; } fd = nil; return !libc0->strncmp(libc0->s2ab(ARMAG), buf, SARMAG); } split(name: array of byte): (array of byte, array of byte) { member: array of byte; p, q: array of byte; p = libc0->strdup(name); q = libc0->strchr(p, '('); if(q != nil){ q[0] = byte 0; q = q[1: ]; member = q; q = libc0->strchr(q, ')'); if(q != nil) q[0] = byte 0; if(typex(p)) return (p, member); p = nil; sys->fprint(sys->fildes(2), "mk: '%s' is not an archive\n", libc0->ab2s(name)); } return (nil, member); } # # bufblock # freelist: ref Bufblock; QUANTA: con 4096; newbuf(): ref Bufblock { p: ref Bufblock; if(freelist != nil){ p = freelist; freelist = freelist.next; } else{ p = ref Bufblock; p.start = array[QUANTA*1] of byte; p.end = QUANTA; } p.current = 0; p.start[0] = byte 0; p.next = nil; return p; } freebuf(p: ref Bufblock) { p.next = freelist; freelist = p; } growbuf(p: ref Bufblock) { n: int; f: ref Bufblock; cp: array of byte; n = p.end+QUANTA; # search the free list for a big buffer for(f = freelist; f != nil; f = f.next){ if(f.end >= n){ f.start[0: ] = p.start[0: p.end]; cp = f.start; f.start = p.start; p.start = cp; cpi := f.end; f.end = p.end; p.end = cpi; f.current = 0; break; } } if(f == nil){ # not found - grow it nps := array[n] of byte; for(i := 0; i < p.end; i++) nps[i] = p.start[i]; p.start = nps; p.end = n; } p.current = n-QUANTA; } bufcpy(buf: ref Bufblock, cp: array of byte, n: int) { i := 0; while(n--) insert(buf, int cp[i++]); } insert(buf: ref Bufblock, c: int) { if(buf.current >= buf.end) growbuf(buf); buf.start[buf.current++] = byte c; } rinsert(buf: ref Bufblock, r: int) { n: int; b := array[Sys->UTFmax] of byte; n = sys->char2byte(r, b, 0); if(buf.current+n > buf.end) growbuf(buf); buf.start[buf.current: ] = b[0: n]; buf.current += n; }