diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/cmd')
401 files changed, 142101 insertions, 0 deletions
diff --git a/appl/cmd/9660srv.b b/appl/cmd/9660srv.b new file mode 100644 index 00000000..17fa053b --- /dev/null +++ b/appl/cmd/9660srv.b @@ -0,0 +1,1504 @@ +implement ISO9660; + +include "sys.m"; + sys: Sys; + Dir, Qid, QTDIR, QTFILE, DMDIR: import sys; + +include "draw.m"; + +include "daytime.m"; + daytime: Daytime; + +include "string.m"; + str: String; + +include "styx.m"; + styx: Styx; + Rmsg, Tmsg: import styx; + +include "arg.m"; + +ISO9660: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Sectorsize: con 2048; +Maxname: con 256; + +Enonexist: con "file does not exist"; +Eperm: con "permission denied"; +Enofile: con "no file system specified"; +Eauth: con "authentication failed"; +Ebadfid: con "invalid fid"; +Efidinuse: con "fid already in use"; +Enotdir: con "not a directory"; +Esyntax: con "file name syntax"; + +devname: string; + +chatty := 0; +showstyx := 0; +progname := "9660srv"; +stderr: ref Sys->FD; +noplan9 := 0; +nojoliet := 0; +norock := 0; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: %s [-rabc] [-9JR] [-s] cd_device dir\n", progname); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + stderr = sys->fildes(2); + + if(args != nil) + progname = hd args; + styx = load Styx Styx->PATH; + if(styx == nil) + noload(Styx->PATH); + styx->init(); + + if(args != nil) + progname = hd args; + mountopt := Sys->MREPL; + copt := 0; + stdio := 0; + + arg := load Arg Arg->PATH; + if(arg == nil) + noload(Arg->PATH); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'v' or 'D' => chatty = 1; showstyx = 1; + 'r' => mountopt = Sys->MREPL; + 'a' => mountopt = Sys->MAFTER; + 'b' => mountopt = Sys->MBEFORE; + 'c' => copt = Sys->MCREATE; + 's' => stdio = 1; + '9' => noplan9 = 1; + 'J' => nojoliet = 1; + 'R' => norock = 1; + * => usage(); + } + args = arg->argv(); + arg = nil; + + if(args == nil || tl args == nil) + usage(); + what := hd args; + mountpt := hd tl args; + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + noload(Daytime->PATH); + + iobufinit(Sectorsize); + + pip := array[2] of ref Sys->FD; + if(stdio){ + pip[0] = sys->fildes(0); + pip[1] = sys->fildes(1); + }else + if(sys->pipe(pip) < 0) + error(sys->sprint("can't create pipe: %r")); + + devname = what; + + sync := chan of int; + spawn fileserve(pip[1], sync); + <-sync; + + if(sys->mount(pip[0], nil, mountpt, mountopt|copt, nil) < 0) { + sys->fprint(sys->fildes(2), "%s: mount %s %s failed: %r\n", progname, what, mountpt); + exit; + } +} + +noload(s: string) +{ + sys->fprint(sys->fildes(2), "%s: can't load %s: %r\n", progname, s); + raise "fail:load"; +} + +error(p: string) +{ + sys->fprint(sys->fildes(2), "9660srv: %s\n", p); + raise "fail:error"; +} + +fileserve(rfd: ref Sys->FD, sync: chan of int) +{ + sys->pctl(Sys->NEWFD|Sys->FORKNS, list of {2, rfd.fd}); + rfd = sys->fildes(rfd.fd); + stderr = sys->fildes(2); + sync <-= 1; + while((m := Tmsg.read(rfd, 0)) != nil){ + if(showstyx) + chat(sys->sprint("%s...", m.text())); + r: ref Rmsg; + pick t := m { + Readerror => + error(sys->sprint("mount read error: %s", t.error)); + Version => + r = rversion(t); + Auth => + r = rauth(t); + Flush => + r = rflush(t); + Attach => + r = rattach(t); + Walk => + r = rwalk(t); + Open => + r = ropen(t); + Create => + r = rcreate(t); + Read => + r = rread(t); + Write => + r = rwrite(t); + Clunk => + r = rclunk(t); + Remove => + r = rremove(t); + Stat => + r = rstat(t); + Wstat => + r = rwstat(t); + * => + error(sys->sprint("invalid T-message tag: %d", tagof m)); + } + pick e := r { + Error => + r.tag = m.tag; + } + rbuf := r.pack(); + if(rbuf == nil) + error("bad R-message conversion"); + if(showstyx) + chat(r.text()+"\n"); + if(styx->write(rfd, rbuf, len rbuf) != len rbuf) + error(sys->sprint("connection write error: %r")); + } + + if(chatty) + chat("server end of file\n"); +} + +E(s: string): ref Rmsg.Error +{ + return ref Rmsg.Error(0, s); +} + +rversion(t: ref Tmsg.Version): ref Rmsg +{ + (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION); + return ref Rmsg.Version(t.tag, msize, version); +} + +rauth(t: ref Tmsg.Auth): ref Rmsg +{ + return ref Rmsg.Error(t.tag, "authentication not required"); +} + +rflush(t: ref Tmsg.Flush): ref Rmsg +{ + return ref Rmsg.Flush(t.tag); +} + +rattach(t: ref Tmsg.Attach): ref Rmsg +{ + dname := devname; + if(t.aname != "") + dname = t.aname; + (dev, err) := devattach(dname, Sys->OREAD, Sectorsize); + if(dev == nil) + return E(err); + + xf := Xfs.new(dev); + root := cleanfid(t.fid); + root.qid = Sys->Qid(big 0, 0, Sys->QTDIR); + root.xf = xf; + err = root.attach(); + if(err != nil){ + clunkfid(t.fid); + return E(err); + } + xf.rootqid = root.qid; + return ref Rmsg.Attach(t.tag, root.qid); +} + +walk1(f: ref Xfile, name: string): string +{ + if(!(f.qid.qtype & Sys->QTDIR)) + return Enotdir; + case name { + "." => + return nil; # nop, but shouldn't happen + ".." => + if(f.qid.path==f.xf.rootqid.path) + return nil; + return f.walkup(); + * => + return f.walk(name); + } +} + +rwalk(t: ref Tmsg.Walk): ref Rmsg +{ + f:=findfid(t.fid); + if(f == nil) + return E(Ebadfid); + nf, sf: ref Xfile; + if(t.newfid != t.fid){ + nf = cleanfid(t.newfid); + if(nf == nil) + return E(Efidinuse); + f.clone(nf); + f = nf; + }else + sf = f.save(); + + qids: array of Sys->Qid; + if(len t.names > 0){ + qids = array[len t.names] of Sys->Qid; + for(i := 0; i < len t.names; i++){ + e := walk1(f, t.names[i]); + if(e != nil){ + if(nf != nil){ + nf.clunk(); + clunkfid(t.newfid); + }else + f.restore(sf); + if(i == 0) + return E(e); + return ref Rmsg.Walk(t.tag, qids[0:i]); + } + qids[i] = f.qid; + } + } + return ref Rmsg.Walk(t.tag, qids); +} + +ropen(t: ref Tmsg.Open): ref Rmsg +{ + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + if(f.flags&Omodes) + return E("open on open file"); + e := f.open(t.mode); + if(e != nil) + return E(e); + f.flags = openflags(t.mode); + return ref Rmsg.Open(t.tag, f.qid, Styx->MAXFDATA); +} + +rcreate(t: ref Tmsg.Create): ref Rmsg +{ + name := t.name; + if(name == "." || name == "..") + return E(Esyntax); + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + if(f.flags&Omodes) + return E("create on open file"); + if(!(f.qid.qtype&Sys->QTDIR)) + return E("create in non-directory"); + e := f.create(name, t.perm, t.mode); + if(e != nil) + return E(e); + f.flags = openflags(t.mode); + return ref Rmsg.Create(t.tag, f.qid, Styx->MAXFDATA); +} + +rread(t: ref Tmsg.Read): ref Rmsg +{ + err: string; + + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + if (!(f.flags&Oread)) + return E("file not opened for reading"); + if(t.count < 0 || t.offset < big 0) + return E("negative offset or count"); + b := array[Styx->MAXFDATA] of byte; + count: int; + if(f.qid.qtype & Sys->QTDIR) + (count, err) = f.readdir(b, int t.offset, t.count); + else + (count, err) = f.read(b, int t.offset, t.count); + if(err != nil) + return E(err); + if(count != len b) + b = b[0:count]; + return ref Rmsg.Read(t.tag, b); +} + +rwrite(nil: ref Tmsg.Write): ref Rmsg +{ + return E(Eperm); +} + +rclunk(t: ref Tmsg.Clunk): ref Rmsg +{ + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + f.clunk(); + clunkfid(t.fid); + return ref Rmsg.Clunk(t.tag); +} + +rremove(t: ref Tmsg.Remove): ref Rmsg +{ + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + f.clunk(); + clunkfid(t.fid); + return E(Eperm); +} + +rstat(t: ref Tmsg.Stat): ref Rmsg +{ + f := findfid(t.fid); + if(f == nil) + return E(Ebadfid); + (dir, nil) := f.stat(); + return ref Rmsg.Stat(t.tag, *dir); +} + +rwstat(nil: ref Tmsg.Wstat): ref Rmsg +{ + return E(Eperm); +} + +openflags(mode: int): int +{ + flags := 0; + case mode & ~(Sys->OTRUNC|Sys->ORCLOSE) { + Sys->OREAD => + flags = Oread; + Sys->OWRITE => + flags = Owrite; + Sys->ORDWR => + flags = Oread|Owrite; + } + if(mode & Sys->ORCLOSE) + flags |= Orclose; + return flags; +} + +chat(s: string) +{ + if(chatty) + sys->fprint(stderr, "%s", s); +} + +Fid: adt { + fid: int; + file: ref Xfile; +}; + +FIDMOD: con 127; # prime +fids := array[FIDMOD] of list of ref Fid; + +hashfid(fid: int): (ref Fid, array of list of ref Fid) +{ + nl: list of ref Fid; + + hp := fids[fid%FIDMOD:]; + nl = nil; + for(l := hp[0]; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid){ + l = tl l; # excluding f + for(; nl != nil; nl = tl nl) + l = (hd nl) :: l; # put examined ones back, in order + hp[0] = l; + return (f, hp); + } else + nl = f :: nl; + } + return (nil, hp); +} + +findfid(fid: int): ref Xfile +{ + (f, hp) := hashfid(fid); + if(f == nil){ + chat("unassigned fid"); + return nil; + } + hp[0] = f :: hp[0]; + return f.file; +} + +cleanfid(fid: int): ref Xfile +{ + (f, hp) := hashfid(fid); + if(f != nil){ + chat("fid in use"); + return nil; + } + f = ref Fid; + f.fid = fid; + f.file = Xfile.new(); + hp[0] = f :: hp[0]; + return f.file.clean(); +} + +clunkfid(fid: int) +{ + (f, nil) := hashfid(fid); + if(f != nil) + f.file.clean(); +} + +# +# +# + +Xfs: adt { + d: ref Device; + inuse: int; + issusp: int; # system use sharing protocol in use? + suspoff: int; # LEN_SKP, if so + isplan9: int; # has Plan 9-specific directory info + isrock: int; # is rock ridge + rootqid: Sys->Qid; + ptr: int; # tag for private data + + new: fn(nil: ref Device): ref Xfs; + incref: fn(nil: self ref Xfs); + decref: fn(nil: self ref Xfs); +}; + +Xfile: adt { + xf: ref Xfs; + flags: int; + qid: Sys->Qid; + ptr: ref Isofile; # tag for private data + + new: fn(): ref Xfile; + clean: fn(nil: self ref Xfile): ref Xfile; + + save: fn(nil: self ref Xfile): ref Xfile; + restore: fn(nil: self ref Xfile, s: ref Xfile); + + attach: fn(nil: self ref Xfile): string; + clone: fn(nil: self ref Xfile, nil: ref Xfile); + walkup: fn(nil: self ref Xfile): string; + walk: fn(nil: self ref Xfile, nil: string): string; + open: fn(nil: self ref Xfile, nil: int): string; + create: fn(nil: self ref Xfile, nil: string, nil: int, nil: int): string; + readdir: fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string); + read: fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string); + write: fn(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string); + clunk: fn(nil: self ref Xfile); + remove: fn(nil: self ref Xfile): string; + stat: fn(nil: self ref Xfile): (ref Sys->Dir, string); + wstat: fn(nil: self ref Xfile, nil: ref Sys->Dir): string; +}; + +Oread, Owrite, Orclose: con 1<<iota; +Omodes: con 3; # mask + +VOLDESC: con 16; # sector number + +Drec: adt { + reclen: int; + attrlen: int; + addr: int; # should be big? + size: int; # should be big? + date: array of byte; + time: int; + tzone: int; # not in high sierra + flags: int; + unitsize: int; + gapsize: int; + vseqno: int; + name: array of byte; + data: array of byte; # system extensions +}; + +Isofile: adt { + fmt: int; # 'z' if iso, 'r' if high sierra + blksize: int; + offset: int; # true offset when reading directory + doffset: int; # styx offset when reading directory + d: ref Drec; +}; + +Xfile.new(): ref Xfile +{ + f := ref Xfile; + return f.clean(); +} + +Xfile.clean(f: self ref Xfile): ref Xfile +{ + if(f.xf != nil){ + f.xf.decref(); + f.xf = nil; + } + f.ptr = nil; + f.flags = 0; + f.qid = Qid(big 0, 0, 0); + return f; +} + +Xfile.save(f: self ref Xfile): ref Xfile +{ + s := ref Xfile; + *s = *f; + s.ptr = ref *f.ptr; + s.ptr.d = ref *f.ptr.d; + return s; +} + +Xfile.restore(f: self ref Xfile, s: ref Xfile) +{ + f.flags = s.flags; + f.qid = s.qid; + *f.ptr = *s.ptr; +} + +Xfile.attach(root: self ref Xfile): string +{ + fmt := 0; + blksize := 0; + haveplan9 := 0; + dirp: ref Block; + dp := ref Drec; + for(a:=VOLDESC;a<VOLDESC+100;a++){ + p := Block.get(root.xf.d, a); + if(p == nil){ + if(dirp != nil) + dirp.put(); + return "can't read volume descriptor"; + } + v := p.data; # Voldesc + if(eqs(v[0:7], "\u0001CD001\u0001")){ # ISO + if(dirp != nil) + dirp.put(); + dirp = p; + fmt = 'z'; + convM2Drec(v[156:], dp, 0); # v.z.desc.rootdir + blksize = l16(v[128:]); # v.z.desc.blksize + if(chatty) + chat(sys->sprint("iso, blksize=%d...", blksize)); + haveplan9 = eqs(v[8:8+6], "PLAN 9"); # v.z.boot.sysid + if(haveplan9){ + if(noplan9) { + chat("ignoring plan9"); + haveplan9 = 0; + }else{ + fmt = '9'; + chat("plan9 iso..."); + } + } + continue; + } + if(eqs(v[8:8+7], "\u0001CDROM\u0001")){ # high sierra + if(dirp != nil) + dirp.put(); + dirp = p; + fmt = 'r'; + convM2Drec(v[180:], dp, 1); # v.r.desc.rootdir + blksize = l16(v[136:]); # v.r.desc.blksize + if(chatty) + chat(sys->sprint("high sierra, blksize=%d...", blksize)); + continue; + } + if(haveplan9==0 && !nojoliet && eqs(v[0:7], "\u0002CD001\u0001")){ + q := v[88:]; # v.z.desc.escapes + if(q[0] == byte 16r25 && q[1] == byte 16r2F && + (q[2] == byte 16r40 || q[2] == byte 16r43 || q[2] == byte 16r45)){ # joliet, it appears + if(dirp != nil) + dirp.put(); + dirp = p; + fmt = 'J'; + convM2Drec(v[156:], dp, 0); # v.z.desc.rootdir + if(blksize != l16(v[128:])) # v.z.desc.blksize + sys->fprint(stderr, "9660srv: warning: suspicious Joliet block size: %d\n", l16(v[128:])); + chat("joliet..."); + continue; + } + }else{ + p.put(); + if(v[0] == byte 16rFF) + break; + } + } + + if(fmt == 0){ + if(dirp != nil) + dirp.put(); + return "CD format not recognised"; + } + + if(chatty) + showdrec(stderr, fmt, dp); + if(blksize > Sectorsize){ + dirp.put(); + return "blocksize too big"; + } + fp := iso(root); + root.xf.isplan9 = haveplan9; + fp.fmt = fmt; + fp.blksize = blksize; + fp.offset = 0; + fp.doffset = 0; + fp.d = dp; + root.qid.path = big dp.addr; + root.qid.qtype = QTDIR; + root.qid.vers = 0; + dirp.put(); + dp = ref Drec; + if(getdrec(root, dp) >= 0){ + s := dp.data; + n := len s; + if(n >= 7 && s[0] == byte 'S' && s[1] == byte 'P' && s[2] == byte 7 && + s[3] == byte 1 && s[4] == byte 16rBE && s[5] == byte 16rEF){ + root.xf.issusp = 1; + root.xf.suspoff = int s[6]; + n -= root.xf.suspoff; + s = s[root.xf.suspoff:]; + while(n >= 4){ + l := int s[2]; + if(s[0] == byte 'E' && s[1] == byte 'R'){ + if(int s[4] == 10 && eqs(s[8:18], "RRIP_1991A")) + root.xf.isrock = 1; + break; + } else if(s[0] == byte 'C' && s[1] == byte 'E' && int s[2] >= 28){ + (s, n) = getcontin(root.xf.d, s); + continue; + } else if(s[0] == byte 'R' && s[1] == byte 'R'){ + if(!norock) + root.xf.isrock = 1; + break; # can skip search for ER + } else if(s[0] == byte 'S' && s[1] == byte 'T') + break; + s = s[l:]; + n -= l; + } + } + } + if(root.xf.isrock) + chat("Rock Ridge..."); + fp.offset = 0; + fp.doffset = 0; + return nil; +} + +Xfile.clone(oldf: self ref Xfile, newf: ref Xfile) +{ + *newf = *oldf; + newf.ptr = nil; + newf.xf.incref(); + ip := iso(oldf); + np := iso(newf); + *np = *ip; # might not be right; shares ip.d +} + +Xfile.walkup(f: self ref Xfile): string +{ + pf := Xfile.new(); + ppf := Xfile.new(); + e := walkup(f, pf, ppf); + pf.clunk(); + ppf.clunk(); + return e; +} + +walkup(f, pf, ppf: ref Xfile): string +{ + e := opendotdot(f, pf); + if(e != nil) + return sys->sprint("can't open pf: %s", e); + paddr := iso(pf).d.addr; + if(iso(f).d.addr == paddr) + return nil; + e = opendotdot(pf, ppf); + if(e != nil) + return sys->sprint("can't open ppf: %s", e); + d := ref Drec; + while(getdrec(ppf, d) >= 0){ + if(d.addr == paddr){ + newdrec(f, d); + f.qid.path = big paddr; + f.qid.qtype = QTDIR; + f.qid.vers = 0; + return nil; + } + } + return "can't find addr of .."; +} + +Xfile.walk(f: self ref Xfile, name: string): string +{ + ip := iso(f); + if(!f.xf.isplan9){ + for(i := 0; i < len name; i++) + if(name[i] == ';') + break; + if(i >= Maxname) + i = Maxname-1; + name = name[0:i]; + } + if(chatty) + chat(sys->sprint("%d \"%s\"...", len name, name)); + ip.offset = 0; + dir := ref Dir; + d := ref Drec; + while(getdrec(f, d) >= 0) { + dvers := rzdir(f.xf, dir, ip.fmt, d); + if(name != dir.name) + continue; + newdrec(f, d); + f.qid.path = dir.qid.path; + f.qid.qtype = dir.qid.qtype; + f.qid.vers = dir.qid.vers; + if(dvers){ + # versions ignored + } + return nil; + } + return Enonexist; +} + +Xfile.open(f: self ref Xfile, mode: int): string +{ + if(mode != Sys->OREAD) + return Eperm; + ip := iso(f); + ip.offset = 0; + ip.doffset = 0; + return nil; +} + +Xfile.create(nil: self ref Xfile, nil: string, nil: int, nil: int): string +{ + return Eperm; +} + +Xfile.readdir(f: self ref Xfile, buf: array of byte, offset: int, count: int): (int, string) +{ + ip := iso(f); + d := ref Dir; + drec := ref Drec; + if(offset < ip.doffset){ + ip.offset = 0; + ip.doffset = 0; + } + rcnt := 0; + while(rcnt < count && getdrec(f, drec) >= 0){ + if(len drec.name == 1){ + if(drec.name[0] == byte 0) + continue; + if(drec.name[0] == byte 1) + continue; + } + rzdir(f.xf, d, ip.fmt, drec); + d.qid.vers = f.qid.vers; + a := styx->packdir(*d); + if(ip.doffset < offset){ + ip.doffset += len a; + continue; + } + if(rcnt+len a > count) + break; + buf[rcnt:] = a; # BOTCH: copy + rcnt += len a; + } + ip.doffset += rcnt; + return (rcnt, nil); +} + +Xfile.read(f: self ref Xfile, buf: array of byte, offset: int, count: int): (int, string) +{ + ip := iso(f); + if(offset >= ip.d.size) + return (0, nil); + if(offset+count > ip.d.size) + count = ip.d.size - offset; + addr := (ip.d.addr+ip.d.attrlen)*ip.blksize + offset; + o := addr % Sectorsize; + addr /= Sectorsize; + if(chatty) + chat(sys->sprint("d.addr=0x%x, addr=0x%x, o=0x%x...", ip.d.addr, addr, o)); + n := Sectorsize - o; + rcnt := 0; + while(count > 0){ + if(n > count) + n = count; + p := Block.get(f.xf.d, addr); + if(p == nil) + return (-1, "i/o error"); + buf[rcnt:] = p.data[o:o+n]; + p.put(); + count -= n; + rcnt += n; + addr++; + o = 0; + n = Sectorsize; + } + return (rcnt, nil); +} + +Xfile.write(nil: self ref Xfile, nil: array of byte, nil: int, nil: int): (int, string) +{ + return (-1, Eperm); +} + +Xfile.clunk(f: self ref Xfile) +{ + f.ptr = nil; +} + +Xfile.remove(nil: self ref Xfile): string +{ + return Eperm; +} + +Xfile.stat(f: self ref Xfile): (ref Dir, string) +{ + ip := iso(f); + d := ref Dir; + rzdir(f.xf, d, ip.fmt, ip.d); + d.qid.vers = f.qid.vers; + if(d.qid.path==f.xf.rootqid.path){ + d.qid.path = big 0; + d.qid.qtype = QTDIR; + } + return (d, nil); +} + +Xfile.wstat(nil: self ref Xfile, nil: ref Dir): string +{ + return Eperm; +} + +Xfs.new(d: ref Device): ref Xfs +{ + xf := ref Xfs; + xf.inuse = 1; + xf.d = d; + xf.isplan9 = 0; + xf.issusp = 0; + xf.isrock = 0; + xf.suspoff = 0; + xf.ptr = 0; + xf.rootqid = Qid(big 0, 0, QTDIR); + return xf; +} + +Xfs.incref(xf: self ref Xfs) +{ + xf.inuse++; +} + +Xfs.decref(xf: self ref Xfs) +{ + xf.inuse--; + if(xf.inuse == 0){ + if(xf.d != nil) + xf.d.detach(); + } +} + +showdrec(fd: ref Sys->FD, fmt: int, d: ref Drec) +{ + if(d.reclen == 0) + return; + sys->fprint(fd, "%d %d %d %d ", + d.reclen, d.attrlen, d.addr, d.size); + sys->fprint(fd, "%s 0x%2.2x %d %d %d ", + rdate(d.date, fmt), d.flags, + d.unitsize, d.gapsize, d.vseqno); + sys->fprint(fd, "%d %s", len d.name, nstr(d.name)); + syslen := len d.data; + if(syslen != 0) + sys->fprint(fd, " %s", nstr(d.data)); + sys->fprint(fd, "\n"); +} + +newdrec(f: ref Xfile, dp: ref Drec) +{ + x := iso(f); + n := ref Isofile; + n.fmt = x.fmt; + n.blksize = x.blksize; + n.offset = 0; + n.doffset = 0; + n.d = dp; + f.ptr = n; +} + +getdrec(f: ref Xfile, d: ref Drec): int +{ + if(f.ptr == nil) + return -1; + boff := 0; + ip := iso(f); + size := ip.d.size; + while(ip.offset<size){ + addr := (ip.d.addr+ip.d.attrlen)*ip.blksize + ip.offset; + boff = addr % Sectorsize; + if(boff > Sectorsize-34){ + ip.offset += Sectorsize-boff; + continue; + } + p := Block.get(f.xf.d, addr/Sectorsize); + if(p == nil) + return -1; + nb := int p.data[boff]; + if(nb >= 34) { + convM2Drec(p.data[boff:], d, ip.fmt=='r'); + #chat(sys->sprint("off %d", ip.offset)); + #showdrec(stderr, ip.fmt, d); + p.put(); + ip.offset += nb + (nb&1); + return 0; + } + p.put(); + p = nil; + ip.offset += Sectorsize-boff; + } + return -1; +} + +# getcontin returns a slice of the Iobuf, valid until next i/o call +getcontin(d: ref Device, a: array of byte): (array of byte, int) +{ + bn := l32(a[4:]); + off := l32(a[12:]); + n := l32(a[20:]); + p := Block.get(d, bn); + if(p == nil) + return (nil, 0); + return (p.data[off:off+n], n); +} + +iso(f: ref Xfile): ref Isofile +{ + if(f.ptr == nil){ + f.ptr = ref Isofile; + f.ptr.d = ref Drec; + } + return f.ptr; +} + +opendotdot(f: ref Xfile, pf: ref Xfile): string +{ + d := ref Drec; + ip := iso(f); + ip.offset = 0; + if(getdrec(f, d) < 0) + return "opendotdot: getdrec(.) failed"; + if(len d.name != 1 || d.name[0] != byte 0) + return "opendotdot: no . entry"; + if(d.addr != ip.d.addr) + return "opendotdot: bad . address"; + if(getdrec(f, d) < 0) + return "opendotdot: getdrec(..) failed"; + if(len d.name != 1 || d.name[0] != byte 1) + return "opendotdot: no .. entry"; + + pf.xf = f.xf; + pip := iso(pf); + pip.fmt = ip.fmt; + pip.blksize = ip.blksize; + pip.offset = 0; + pip.doffset = 0; + pip.d = d; + return nil; +} + +rzdir(fs: ref Xfs, d: ref Dir, fmt: int, dp: ref Drec): int +{ + Hmode, Hname: con 1<<iota; + vers := -1; + have := 0; + d.qid.path = big dp.addr; + d.qid.vers = 0; + d.qid.qtype = QTFILE; + n := len dp.name; + if(n == 1) { + case int dp.name[0] { + 0 => d.name = "."; have |= Hname; + 1 => d.name = ".."; have |= Hname; + * => d.name = ""; d.name[0] = tolower(int dp.name[0]); + } + } else { + if(fmt == 'J'){ # Joliet, 16-bit Unicode + d.name = ""; + for(i:=0; i<n; i+=2){ + r := (int dp.name[i]<<8) | int dp.name[i+1]; + d.name[len d.name] = r; + } + }else{ + if(n >= Maxname) + n = Maxname-1; + d.name = ""; + for(i:=0; i<n && int dp.name[i] != '\r'; i++) + d.name[i] = tolower(int dp.name[i]); + } + } + + if(fs.isplan9 && dp.reclen>34+len dp.name) { + # + # get gid, uid, mode and possibly name + # from plan9 directory extension + # + s := dp.data; + n = int s[0]; + if(n) + d.name = string s[1:1+n]; + l := 1+n; + n = int s[l++]; + d.uid = string s[l:l+n]; + l += n; + n = int s[l++]; + d.gid = string s[l:l+n]; + l += n; + if(l & 1) + l++; + d.mode = l32(s[l:]); + if(d.mode & DMDIR) + d.qid.qtype = QTDIR; + } else { + d.mode = 8r444; + case fmt { + 'z' => + if(fs.isrock) + d.gid = "ridge"; + else + d.gid = "iso"; + 'r' => + d.gid = "sierra"; + 'J' => + d.gid = "joliet"; + * => + d.gid = "???"; + } + flags := dp.flags; + if(flags & 2){ + d.qid.qtype = QTDIR; + d.mode |= DMDIR|8r111; + } + d.uid = "cdrom"; + for(i := 0; i < len d.name; i++) + if(d.name[i] == ';') { + vers = int string d.name[i+1:]; # inefficient + d.name = d.name[0:i]; # inefficient + break; + } + n = len dp.data - fs.suspoff; + if(fs.isrock && n >= 4){ + s := dp.data[fs.suspoff:]; + nm := 0; + while(n >= 4 && have != (Hname|Hmode)){ + l := int s[2]; + if(s[0] == byte 'P' && s[1] == byte 'X' && s[3] == byte 1){ + # posix file attributes + mode := l32(s[4:12]); + d.mode = mode & 8r777; + if((mode & 8r170000) == 8r0040000){ + d.mode |= DMDIR; + d.qid.qtype = QTDIR; + } + have |= Hmode; + } else if(s[0] == byte 'N' && s[1] == byte 'M' && s[3] == byte 1){ + # alternative name + flags = int s[4]; + if((flags & ~1) == 0){ + if(nm == 0){ + d.name = string s[5:l]; + nm = 1; + } else + d.name += string s[5:l]; + if(flags == 0) + have |= Hname; # no more + } + } else if(s[0] == byte 'C' && s[1] == byte 'E' && int s[2] >= 28){ + (s, n) = getcontin(fs.d, s); + continue; + } else if(s[0] == byte 'S' && s[1] == byte 'T') + break; + n -= l; + s = s[l:]; + } + } + } + d.length = big 0; + if((d.mode & DMDIR) == 0) + d.length = big dp.size; + d.dtype = 0; + d.dev = 0; + d.atime = dp.time; + d.mtime = d.atime; + return vers; +} + +convM2Drec(a: array of byte, d: ref Drec, highsierra: int) +{ + d.reclen = int a[0]; + d.attrlen = int a[1]; + d.addr = int l32(a[2:10]); + d.size = int l32(a[10:18]); + d.time = gtime(a[18:24]); + d.date = array[7] of byte; + d.date[0:] = a[18:25]; + if(highsierra){ + d.tzone = 0; + d.flags = int a[24]; + d.unitsize = 0; + d.gapsize = 0; + d.vseqno = 0; + } else { + d.tzone = int a[24]; + d.flags = int a[25]; + d.unitsize = int a[26]; + d.gapsize = int a[27]; + d.vseqno = l32(a[28:32]); + } + n := int a[32]; + d.name = array[n] of byte; + d.name[0:] = a[33:33+n]; + n += 33; + if(n & 1) + n++; # check this + syslen := d.reclen - n; + if(syslen > 0){ + d.data = array[syslen] of byte; + d.data[0:] = a[n:n+syslen]; + } else + d.data = nil; +} + +nstr(p: array of byte): string +{ + q := ""; + n := len p; + for(i := 0; i < n; i++){ + if(int p[i] == '\\') + q[len q] = '\\'; + if(' ' <= int p[i] && int p[i] <= '~') + q[len q] = int p[i]; + else + q += sys->sprint("\\%2.2ux", int p[i]); + } + return q; +} + +rdate(p: array of byte, fmt: int): string +{ + c: int; + + s := sys->sprint("%2.2d.%2.2d.%2.2d %2.2d:%2.2d:%2.2d", + int p[0], int p[1], int p[2], int p[3], int p[4], int p[5]); + if(fmt == 'z'){ + htz := int p[6]; + if(htz >= 128){ + htz = 256-htz; + c = '-'; + }else + c = '+'; + s += sys->sprint(" (%c%.1f)", c, real htz/2.0); + } + return s; +} + +dmsize := array[] of { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, +}; + +dysize(y: int): int +{ + if((y%4) == 0) + return 366; + return 365; +} + +gtime(p: array of byte): int # yMdhms +{ + y:=int p[0]; M:=int p[1]; d:=int p[2]; + h:=int p[3]; m:=int p[4]; s:=int p[5];; + if(y < 70) + return 0; + if(M < 1 || M > 12) + return 0; + if(d < 1 || d > dmsize[M-1]) + return 0; + if(h > 23) + return 0; + if(m > 59) + return 0; + if(s > 59) + return 0; + y += 1900; + t := 0; + for(i:=1970; i<y; i++) + t += dysize(i); + if(dysize(y)==366 && M >= 3) + t++; + M--; + while(M-- > 0) + t += dmsize[M]; + t += d-1; + t = 24*t + h; + t = 60*t + m; + t = 60*t + s; + return t; +} + +l16(p: array of byte): int +{ + v := (int p[1]<<8)| int p[0]; + if (v >= 16r8000) + v -= 16r10000; + return v; +} + +l32(p: array of byte): int +{ + return (((((int p[3]<<8)| int p[2])<<8)| int p[1])<<8)| int p[0]; +} + +eqs(a: array of byte, b: string): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(int a[i] != b[i]) + return 0; + return 1; +} + +tolower(c: int): int +{ + if(c >= 'A' && c <= 'Z') + return c-'A' + 'a'; + return c; +} + +# +# I/O buffers +# + +Device: adt { + inuse: int; # attach count + name: string; # of underlying file + fd: ref Sys->FD; + sectorsize: int; + qid: Sys->Qid; # (qid,dtype,dev) identify uniquely + dtype: int; + dev: int; + + detach: fn(nil: self ref Device); +}; + +Block: adt { + dev: ref Device; + addr: int; + data: array of byte; + + # internal + next: cyclic ref Block; + prev: cyclic ref Block; + busy: int; + + get: fn(nil: ref Device, addr: int): ref Block; + put: fn(nil: self ref Block); +}; + +devices: list of ref Device; + +NIOB: con 100; # for starters +HIOB: con 127; # prime + +hiob := array[HIOB] of list of ref Block; # hash buckets +iohead: ref Block; +iotail: ref Block; +bufsize := 0; + +iobufinit(bsize: int) +{ + bufsize = bsize; + for(i:=0; i<NIOB; i++) + newblock(); +} + +newblock(): ref Block +{ + p := ref Block; + p.busy = 0; + p.addr = -1; + p.dev = nil; + p.data = array[bufsize] of byte; + p.next = iohead; + if(iohead != nil) + iohead.prev = p; + iohead = p; + if(iotail == nil) + iotail = p; + return p; +} + +Block.get(dev: ref Device, addr: int): ref Block +{ + p: ref Block; + + dh := hiob[addr%HIOB:]; + for(l := dh[0]; l != nil; l = tl l) { + p = hd l; + if(p.addr == addr && p.dev == dev) { + p.busy++; + return p; + } + } + # Find a non-busy buffer from the tail + for(p = iotail; p != nil && p.busy; p = p.prev) + ; + if(p == nil) + p = newblock(); + + # Delete from hash chain + if(p.addr >= 0) { + hp := hiob[p.addr%HIOB:]; + l = nil; + for(f := hp[0]; f != nil; f = tl f) + if(hd f != p) + l = (hd f) :: l; + hp[0] = l; + } + + # Hash and fill + p.addr = addr; + p.dev = dev; + p.busy++; + sys->seek(dev.fd, big addr*big dev.sectorsize, 0); + if(sys->read(dev.fd, p.data, dev.sectorsize) != dev.sectorsize){ + p.addr = -1; # stop caching + p.put(); + purge(dev); + return nil; + } + dh[0] = p :: dh[0]; + return p; +} + +Block.put(p: self ref Block) +{ + p.busy--; + if(p.busy < 0) + panic("Block.put"); + + if(p == iohead) + return; + + # Link onto head for lru + if(p.prev != nil) + p.prev.next = p.next; + else + iohead = p.next; + + if(p.next != nil) + p.next.prev = p.prev; + else + iotail = p.prev; + + p.prev = nil; + p.next = iohead; + iohead.prev = p; + iohead = p; +} + +purge(dev: ref Device) +{ + for(i := 0; i < HIOB; i++){ + l := hiob[i]; + hiob[i] = nil; + for(; l != nil; l = tl l){ # reverses bucket's list, but never mind + p := hd l; + if(p.dev == dev) + p.busy = 0; + else + hiob[i] = p :: hiob[i]; + } + } +} + +devattach(name: string, mode: int, sectorsize: int): (ref Device, string) +{ + if(sectorsize > bufsize) + return (nil, "sector size too big"); + fd := sys->open(name, mode); + if(fd == nil) + return(nil, sys->sprint("%s: can't open: %r", name)); + (rc, dir) := sys->fstat(fd); + if(rc < 0) + return (nil, sys->sprint("%r")); + for(dl := devices; dl != nil; dl = tl dl){ + d := hd dl; + if(d.qid.path != dir.qid.path || d.qid.vers != dir.qid.vers) + continue; + if(d.dtype != dir.dtype || d.dev != dir.dev) + continue; + d.inuse++; + if(chatty) + sys->print("inuse=%d, \"%s\", dev=%H...\n", d.inuse, d.name, d.fd); + return (d, nil); + } + if(chatty) + sys->print("alloc \"%s\", dev=%H...\n", name, fd); + d := ref Device; + d.inuse = 1; + d.name = name; + d.qid = dir.qid; + d.dtype = dir.dtype; + d.dev = dir.dev; + d.fd = fd; + d.sectorsize = sectorsize; + devices = d :: devices; + return (d, nil); +} + +Device.detach(d: self ref Device) +{ + d.inuse--; + if(d.inuse < 0) + panic("putxdata"); + if(chatty) + sys->print("decref=%d, \"%s\", dev=%H...\n", d.inuse, d.name, d.fd); + if(d.inuse == 0){ + if(chatty) + sys->print("purge...\n"); + purge(d); + dl := devices; + devices = nil; + for(; dl != nil; dl = tl dl) + if((hd dl) != d) + devices = (hd dl) :: devices; + } +} + +panic(s: string) +{ + sys->print("panic: %s\n", s); + a: array of byte; + a[5] = byte 0; # trap +} diff --git a/appl/cmd/9export.b b/appl/cmd/9export.b new file mode 100644 index 00000000..5df1c8cf --- /dev/null +++ b/appl/cmd/9export.b @@ -0,0 +1,180 @@ +implement P9export; + +include "sys.m"; + sys: Sys; + +include "draw.m"; +include "keyring.m"; +include "security.m"; +include "factotum.m"; +include "encoding.m"; +include "arg.m"; + +P9export: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +factotumfile := "/mnt/factotum/rpc"; + +fail(status, msg: string) +{ + sys->fprint(sys->fildes(2), "9export: %s\n", msg); + raise "fail:"+status; +} + +nomod(mod: string) +{ + fail("load", sys->sprint("can't load %s: %r", mod)); +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + + arg->init(args); + arg->setusage("9export [-aA9] [-k keyspec] [-e enc digest]"); + flags := 0; + cryptalg := ""; # will be rc4_256 sha1 + keyspec := ""; + noauth := 0; + xflag := Sys->EXPWAIT; + while((o := arg->opt()) != 0) + case o { + 'a' => + xflag = Sys->EXPASYNC; + 'A' => + noauth = 1; + 'e' => + cryptalg = arg->earg(); + if(cryptalg == "clear") + cryptalg = nil; + 'k' => + keyspec = arg->earg(); + '9' => + ; + * => + arg->usage(); + } + args = arg->argv(); + arg = nil; + + sys->pctl(Sys->FORKFD|Sys->FORKNS, nil); + + fd := sys->fildes(0); + + secret: array of byte; + if(noauth == 0){ + factotum := load Factotum Factotum->PATH; + if(factotum == nil) + nomod(Factotum->PATH); + factotum->init(); + facfd := sys->open(factotumfile, Sys->ORDWR); + if(facfd == nil) + fail("factotum", sys->sprint("can't open %s: %r", factotumfile)); + ai := factotum->proxy(fd, facfd, "proto=p9any role=server "+keyspec); + if(ai == nil) + fail("auth", sys->sprint("can't authenticate 9export: %r")); + secret = ai.secret; + } + + # read tree; it's a Plan 9 bug that there's no reliable delimiter + btree := array[2048] of byte; + n := sys->read(fd, btree, len btree); + if(n <= 0) + fail("tree", sys->sprint("can't read tree: %r")); + tree := string btree[0:n]; + if(sys->chdir(tree) < 0){ + sys->fprint(fd, "chdir(%d:\"%s\"): %r", n, tree); + fail("tree", sys->sprint("bad tree: %s", tree)); + } + if(sys->write(fd, array of byte "OK", 2) != 2) + fail("tree", sys->sprint("can't OK tree: %r")); + impo := array[2048] of byte; + for(n = 0; n < len impo; n++) + if(sys->read(fd, impo[n:], 1) != 1) + fail("impo", sys->sprint("can't read impo: %r")); + else if(impo[n] == byte 0 || impo[n] == byte '\n') + break; + if(n < 4 || string impo[0:4] != "impo") + fail("impo", "wasn't impo: possibly old import/cpu"); + if(noauth == 0 && cryptalg != nil){ + if(secret == nil) + fail("import", "didn't establish shared secret"); + random := load Random Random->PATH; + if(random == nil) + nomod(Random->PATH); + kr := load Keyring Keyring->PATH; + if(kr == nil) + nomod(Keyring->PATH); + ssl := load SSL SSL->PATH; + if(ssl == nil) + nomod(SSL->PATH); + base64 := load Encoding Encoding->BASE64PATH; + if(base64 == nil) + nomod(Encoding->BASE64PATH); + key := array[16] of byte; # myrand[4] secret[8] hisrand[4] + key[0:] = random->randombuf(Random->ReallyRandom, 4); + ns := len secret; + if(ns > 8) + ns = 8; + key[12:] = secret[0:ns]; + if(sys->write(fd, key[12:], 4) != 4) + fail("import", sys->sprint("can't write key to remote: %r")); + if(readn(fd, key, 4) != 4) + fail("import", sys->sprint("can't read remote key: %r")); + digest := array[Keyring->SHA1dlen] of byte; + kr->sha1(key, len key, digest, nil); + err: string; + (fd, err) = pushssl(fd, base64->dec(S(digest[10:20])), base64->dec(S(digest[0:10])), cryptalg); + if(err != nil) + fail("import", sys->sprint("can't push security layer: %s", err)); + } + if(sys->export(fd, ".", xflag) < 0) + fail("export", sys->sprint("can't export %s: %r", tree)); +} + +readn(fd: ref Sys->FD, buf: array of byte, nb: int): int +{ + for(nr := 0; nr < nb;){ + n := sys->read(fd, buf[nr:], nb-nr); + if(n <= 0){ + if(nr == 0) + return n; + break; + } + nr += n; + } + return nr; +} + +S(a: array of byte): string +{ + s := ""; + for(i:=0; i<len a; i++) + s += sys->sprint("%.2ux", int a[i]); + return s; +} + +pushssl(fd: ref Sys->FD, secretin, secretout: array of byte, alg: string): (ref Sys->FD, string) +{ + ssl := load SSL SSL->PATH; + if(ssl == nil) + nomod(SSL->PATH); + + (err, c) := ssl->connect(fd); + if(err != nil) + return (nil, "can't connect ssl: " + err); + + err = ssl->secret(c, secretin, secretout); + if(err != nil) + return (nil, "can't write secret: " + err); + if(sys->fprint(c.cfd, "alg %s", alg) < 0) + return (nil, sys->sprint("can't push algorithm %s: %r", alg)); + + return (c.dfd, nil); +} diff --git a/appl/cmd/9srvfs.b b/appl/cmd/9srvfs.b new file mode 100644 index 00000000..d152d1bb --- /dev/null +++ b/appl/cmd/9srvfs.b @@ -0,0 +1,99 @@ +implement P9srvfs; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "sh.m"; + sh: Sh; + +include "arg.m"; + +P9srvfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + if(str == nil) + nomod(String->PATH); + + perm := 8r600; + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + arg->setusage("9srvfs [-p perm] name path|{command}"); + while((o := arg->opt()) != 0) + case o { + 'p' => + s := arg->earg(); + if(s == nil) + arg->usage(); + (perm, s) = str->toint(s, 8); + if(s != nil) + arg->usage(); + * => + arg->usage(); + } + args = arg->argv(); + if(len args != 2) + arg->usage(); + arg = nil; + + srvname := hd args; + args = tl args; + dest := hd args; + if(dest == nil) + dest = "."; + iscmd := dest[0] == '{' && dest[len dest-1] == '}'; + if(!iscmd){ # quick check before creating service file + (ok, d) := sys->stat(dest); + if(ok < 0) + error(sys->sprint("can't stat %s: %r", dest)); + if((d.mode & Sys->DMDIR) == 0) + error(sys->sprint("%s: not a directory", dest)); + }else{ + sh = load Sh Sh->PATH; + if(sh == nil) + nomod(Sh->PATH); + } + srvfd := sys->create("/srv/"+srvname, Sys->ORDWR, perm); + if(srvfd == nil) + error(sys->sprint("can't create /srv/%s: %r", srvname)); + if(iscmd){ + sync := chan of int; + spawn runcmd(sh, ctxt, dest :: nil, srvfd, sync); + <-sync; + }else{ + if(sys->export(srvfd, dest, Sys->EXPWAIT) < 0) + error(sys->sprint("export failed: %r")); + } +} + +error(msg: string) +{ + sys->fprint(sys->fildes(2), "9srvfs: %s\n", msg); + raise "fail:error"; +} + +nomod(mod: string) +{ + error(sys->sprint("can't load %s: %r", mod)); +} + +runcmd(sh: Sh, ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD, sync: chan of int) +{ + sys->pctl(Sys->FORKFD, nil); + sys->dup(stdin.fd, 0); + stdin = nil; + sync <-= 0; + sh->run(ctxt, argv); +} diff --git a/appl/cmd/9win.b b/appl/cmd/9win.b new file mode 100644 index 00000000..b2d2bd47 --- /dev/null +++ b/appl/cmd/9win.b @@ -0,0 +1,453 @@ +implement Ninewin; +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Image, Display, Pointer: import draw; +include "arg.m"; +include "keyboard.m"; +include "tk.m"; +include "wmclient.m"; + wmclient: Wmclient; + Window: import wmclient; +include "sh.m"; + sh: Sh; + +# run a p9 graphics program (default rio) under inferno wm, +# making available to it: +# /dev/winname - naming the current inferno window (changing on resize) +# /dev/mouse - pointer file + resize events; write to change position +# /dev/cursor - change appearance of cursor. +# /dev/draw - inferno draw device +# /dev/cons - read keyboard events, write to 9win stdout. + +Ninewin: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; +winname: string; + +init(ctxt: ref Draw->Context, argv: list of string) +{ + size := Draw->Point(500, 500); + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + wmclient = load Wmclient Wmclient->PATH; + wmclient->init(); + sh = load Sh Sh->PATH; + + buts := Wmclient->Resize; + if(ctxt == nil){ + ctxt = wmclient->makedrawcontext(); + buts = Wmclient->Plain; + } + arg := load Arg Arg->PATH; + arg->init(argv); + arg->setusage("9win [-s] [-x width] [-y height]"); + exportonly := 0; + while(((opt := arg->opt())) != 0){ + case opt { + 's' => + exportonly = 1; + 'x' => + size.x = int arg->earg(); + 'y' => + size.y = int arg->earg(); + * => + arg->usage(); + } + } + if(size.x < 1 || size.y < 1) + arg->usage(); + argv = arg->argv(); + if(argv != nil && hd argv == "-s"){ + exportonly = 1; + argv = tl argv; + } + if(argv == nil && !exportonly) + argv = "rio" :: nil; + if(argv != nil && exportonly){ + sys->fprint(sys->fildes(2), "9win: no command allowed with -s flag\n"); + raise "fail:usage"; + } + title := "9win"; + if(!exportonly) + title += " " + hd argv; + w := wmclient->window(ctxt, title, buts); + w.reshape(((0, 0), size)); + w.onscreen(nil); + if(w.image == nil){ + sys->fprint(sys->fildes(2), "9win: cannot get image to draw on\n"); + raise "fail:no window"; + } + + sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil); + ld := "/n/9win"; + if(sys->bind("#s", ld, Sys->MREPL) == -1 && + sys->bind("#s", ld = "/n/local", Sys->MREPL) == -1){ + sys->fprint(sys->fildes(2), "9win: cannot bind files: %r\n"); + raise "fail:error"; + } + w.startinput("kbd" :: "ptr" :: nil); + spawn ptrproc(rq := chan of Sys->Rread, ptr := chan[10] of ref Pointer, reshape := chan[1] of int); + + + fwinname := sys->file2chan(ld, "winname"); + fconsctl := sys->file2chan(ld, "consctl"); + fcons := sys->file2chan(ld, "cons"); + fmouse := sys->file2chan(ld, "mouse"); + fcursor := sys->file2chan(ld, "cursor"); + if(!exportonly){ + spawn run(sync := chan of string, w.ctl, ld, argv); + if((e := <-sync) != nil){ + sys->fprint(sys->fildes(2), "9win: %s", e); + raise "fail:error"; + } + } + spawn serveproc(w, rq, fwinname, fconsctl, fcons, fmouse, fcursor); + if(!exportonly){ + # handle events synchronously so that we don't get a "killed" message + # from the shell. + handleevents(w, ptr, reshape); + }else{ + spawn handleevents(w, ptr, reshape); + sys->bind(ld, "/dev", Sys->MBEFORE); + export(sys->fildes(0), w.ctl); + } +} + +handleevents(w: ref Window, ptr: chan of ref Pointer, reshape: chan of int) +{ + for(;;)alt{ + c := <-w.ctxt.ctl or + c = <-w.ctl => + e := w.wmctl(c); + if(e != nil) + sys->fprint(sys->fildes(2), "9win: ctl error: %s\n", e); + if(e == nil && c != nil && c[0] == '!'){ + alt{ + reshape <-= 1 => + ; + * => + ; + } + winname = nil; + } + p := <-w.ctxt.ptr => + if(w.pointer(*p) == 0){ + # XXX would block here if client isn't reading mouse... but we do want to + # extert back-pressure, which conflicts. + alt{ + ptr <-= p => + ; + * => + ; # sys->fprint(sys->fildes(2), "9win: discarding mouse event\n"); + } + } + } +} + +serveproc(w: ref Window, mouserq: chan of Sys->Rread, fwinname, fconsctl, fcons, fmouse, fcursor: ref Sys->FileIO) +{ + winid := 0; + krc: list of Sys->Rread; + ks: string; + + for(;;)alt { + c := <-w.ctxt.kbd => + ks[len ks] = inf2p9key(c); + if(krc != nil){ + hd krc <-= (array of byte ks, nil); + ks = nil; + krc = tl krc; + } + (nil, d, nil, wc) := <-fcons.write => + if(wc != nil){ + sys->write(sys->fildes(1), d, len d); + wc <-= (len d, nil); + } + (nil, nil, nil, rc) := <-fcons.read => + if(rc != nil){ + if(ks != nil){ + rc <-= (array of byte ks, nil); + ks = nil; + }else + krc = rc :: krc; + } + (offset, nil, nil, rc) := <-fwinname.read => + if(rc != nil){ + if(winname == nil){ + winname = sys->sprint("noborder.9win.%d", winid++); + if(w.image.name(winname, 1) == -1){ + sys->fprint(sys->fildes(2), "9win: namewin %q failed: %r", winname); + rc <-= (nil, "namewin failure"); + break; + } + } + d := array of byte winname; + if(offset < len d) + d = d[offset:]; + else + d = nil; + rc <-= (d, nil); + } + (nil, nil, nil, wc) := <-fwinname.write => + if(wc != nil) + wc <-= (-1, "permission denied"); + (nil, nil, nil, rc) := <-fconsctl.read => + if(rc != nil) + rc <-= (nil, "permission denied"); + (nil, d, nil, wc) := <-fconsctl.write => + if(wc != nil){ + if(string d != "rawon") + wc <-= (-1, "cannot change console mode"); + else + wc <-= (len d, nil); + } + (nil, nil, nil, rc) := <-fmouse.read => + if(rc != nil) + mouserq <-= rc; + (nil, d, nil, wc) := <-fmouse.write => + if(wc != nil){ + e := cursorset(w, string d); + if(e == nil) + wc <-= (len d, nil); + else + wc <-= (-1, e); + } + (nil, nil, nil, rc) := <-fcursor.read => + if(rc != nil) + rc <-= (nil, "permission denied"); + (nil, d, nil, wc) := <-fcursor.write => + if(wc != nil){ + e := cursorswitch(w, d); + if(e == nil) + wc <-= (len d, nil); + else + wc <-= (-1, e); + } + } +} + +ptrproc(rq: chan of Sys->Rread, ptr: chan of ref Pointer, reshape: chan of int) +{ + rl: list of Sys->Rread; + c := ref Pointer(0, (0, 0), 0); + for(;;){ + ch: int; + alt{ + p := <-ptr => + ch = 'm'; + c = p; + <-reshape => + ch = 'r'; + rc := <-rq => + rl = rc :: rl; + continue; + } + if(rl == nil) + rl = <-rq :: rl; + hd rl <-= (sys->aprint("%c%11d %11d %11d %11d ", ch, c.xy.x, c.xy.y, c.buttons, c.msec), nil); + rl = tl rl; + } +} + +cursorset(w: ref Window, m: string): string +{ + if(m == nil || m[0] != 'm') + return "invalid mouse message"; + x := int m[1:]; + for(i := 1; i < len m; i++) + if(m[i] == ' '){ + while(m[i] == ' ') + i++; + break; + } + if(i == len m) + return "invalid mouse message"; + y := int m[i:]; + return w.wmctl(sys->sprint("ptr %d %d", x, y)); +} + +cursorswitch(w: ref Window, d: array of byte): string +{ + Hex: con "0123456789abcdef"; + if(len d != 2*4+64) + return w.wmctl("cursor"); + hot := Draw->Point(bglong(d, 0*4), bglong(d, 1*4)); + s := sys->sprint("cursor %d %d 16 32 ", hot.x, hot.y); + for(i := 2*4; i < len d; i++){ + c := int d[i]; + s[len s] = Hex[c >> 4]; + s[len s] = Hex[c & 16rf]; + } + return w.wmctl(s); +} + +run(sync, ctl: chan of string, ld: string, argv: list of string) +{ + Rcmeta: con "|<>&^*[]?();"; + sys->pctl(Sys->FORKNS, nil); + if(sys->bind("#₪", "/srv", Sys->MCREATE) == -1){ + sync <-= sys->sprint("cannot bind srv device: %r"); + exit; + } + srvname := "/srv/9win."+string sys->pctl(0, nil); # XXX do better. + fd := sys->create(srvname, Sys->ORDWR, 8r600); + if(fd == nil){ + sync <-= sys->sprint("cannot create %s: %r", srvname); + exit; + } + sync <-= nil; + spawn export(fd, ctl); + sh->run(nil, "os" :: + "rc" :: "-c" :: + "mount "+srvname+" /mnt/term;"+ + "rm "+srvname+";"+ + "bind -b /mnt/term"+ld+" /dev;"+ + "bind /mnt/term/dev/draw /dev/draw ||"+ + "bind -a /mnt/term/dev /dev;"+ + quotedc("cd"::"/mnt/term"+cwd()::nil, Rcmeta)+";"+ + quotedc(argv, Rcmeta)+";":: + nil + ); +} + +export(fd: ref Sys->FD, ctl: chan of string) +{ + sys->export(fd, "/", Sys->EXPWAIT); + ctl <-= "exit"; +} + +inf2p9key(c: int): int +{ + KF: import Keyboard; + + P9KF: con 16rF000; + Spec: con 16rF800; + Khome: con P9KF|16r0D; + Kup: con P9KF|16r0E; + Kpgup: con P9KF|16r0F; + Kprint: con P9KF|16r10; + Kleft: con P9KF|16r11; + Kright: con P9KF|16r12; + Kdown: con Spec|16r00; + Kview: con Spec|16r00; + Kpgdown: con P9KF|16r13; + Kins: con P9KF|16r14; + Kend: con P9KF|16r18; + Kalt: con P9KF|16r15; + Kshift: con P9KF|16r16; + Kctl: con P9KF|16r17; + + case c { + Keyboard->LShift => + return Kshift; + Keyboard->LCtrl => + return Kctl; + Keyboard->LAlt => + return Kalt; + Keyboard->Home => + return Khome; + Keyboard->End => + return Kend; + Keyboard->Up => + return Kup; + Keyboard->Down => + return Kdown; + Keyboard->Left => + return Kleft; + Keyboard->Right => + return Kright; + Keyboard->Pgup => + return Kpgup; + Keyboard->Pgdown => + return Kpgdown; + Keyboard->Ins => + return Kins; + + # function keys + KF|1 or + KF|2 or + KF|3 or + KF|4 or + KF|5 or + KF|6 or + KF|7 or + KF|8 or + KF|9 or + KF|10 or + KF|11 or + KF|12 => + return (c - KF) + P9KF; + } + return c; +} + +cwd(): string +{ + return sys->fd2path(sys->open(".", Sys->OREAD)); +} + +# from string.b, waiting for declaration to be uncommented. +quotedc(argv: list of string, cl: string): string +{ + s := ""; + while (argv != nil) { + arg := hd argv; + for (i := 0; i < len arg; i++) { + c := arg[i]; + if (c == ' ' || c == '\t' || c == '\n' || c == '\'' || in(c, cl)) + break; + } + if (i < len arg || arg == nil) { + s += "'" + arg[0:i]; + for (; i < len arg; i++) { + if (arg[i] == '\'') + s[len s] = '\''; + s[len s] = arg[i]; + } + s[len s] = '\''; + } else + s += arg; + if (tl argv != nil) + s[len s] = ' '; + argv = tl argv; + } + return s; +} + +in(c: int, s: string): int +{ + n := len s; + if(n == 0) + return 0; + ans := 0; + negate := 0; + if(s[0] == '^') { + negate = 1; + s = s[1:]; + n--; + } + for(i := 0; i < n; i++) { + if(s[i] == '-' && i > 0 && i < n-1) { + if(c >= s[i-1] && c <= s[i+1]) { + ans = 1; + break; + } + i++; + } + else + if(c == s[i]) { + ans = 1; + break; + } + } + if(negate) + ans = !ans; + return ans; +} + +bglong(d: array of byte, i: int): int +{ + return int d[i] | (int d[i+1]<<8) | (int d[i+2]<<16) | (int d[i+3]<<24); +} diff --git a/appl/cmd/B.b b/appl/cmd/B.b new file mode 100644 index 00000000..910e3d06 --- /dev/null +++ b/appl/cmd/B.b @@ -0,0 +1,107 @@ +implement B; + +include "sys.m"; +include "draw.m"; +include "workdir.m"; + +FD: import Sys; +Context: import Draw; + +B: module +{ + init: fn(nil: ref Context, argv: list of string); +}; + +sys: Sys; +stderr: ref FD; +wkdir: string; + +init(nil: ref Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + if(len argv < 2) { + sys->fprint(stderr, "Usage: B file ...\n"); + return; + } + argv = tl argv; + + cmd := "exec B "; + while(argv != nil) { + f := hd argv; + if(len f > 0 && f[0] != '/' && f[0] != '-') + f = wd() + f; + cmd += "/usr/inferno"+f; + argv = tl argv; + if(argv != nil) + cmd += " "; + } + cfd := sys->open("/cmd/clone", sys->ORDWR); + if(cfd == nil) { + sys->fprint(stderr, "B: open /cmd/clone: %r\n"); + return; + } + + buf := array[32] of byte; + n := sys->read(cfd, buf, len buf); + if(n <= 0) { + sys->fprint(stderr, "B: read /cmd/#/ctl: %r\n"); + return; + } + dir := "/cmd/"+string buf[0:n]; + + # Start the Command + n = sys->fprint(cfd, "%s", cmd); + if(n <= 0) { + sys->fprint(stderr, "B: exec: %r\n"); + return; + } + + io := sys->open(dir+"/data", sys->ORDWR); + if(io == nil) { + sys->fprint(stderr, "B: open /cmd/#/data: %r\n"); + return; + } + + sys->pctl(sys->NEWPGRP, nil); + copy(io, sys->fildes(1), nil); +} + +wd(): string +{ + if(wkdir != nil) + return wkdir; + + gwd := load Workdir Workdir->PATH; + + wkdir = gwd->init(); + if(wkdir == nil) { + sys->fprint(stderr, "B: can't get working dir: %r"); + exit; + } + wkdir = wkdir+"/"; + return wkdir; +} + +copy(f, t: ref FD, c: chan of int) +{ + if(c != nil) + c <-= sys->pctl(0, nil); + + buf := array[8192] of byte; + for(;;) { + r := sys->read(f, buf, len buf); + if(r <= 0) + break; + w := sys->write(t, buf, r); + if(w != r) + break; + } +} + +kill(pid: int) +{ + fd := sys->open("/prog/"+string pid+"/ctl", sys->OWRITE); + sys->fprint(fd, "kill"); +} diff --git a/appl/cmd/archfs.b b/appl/cmd/archfs.b new file mode 100644 index 00000000..11567731 --- /dev/null +++ b/appl/cmd/archfs.b @@ -0,0 +1,630 @@ +implement Archfs; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + +include "string.m"; + str: String; + +include "daytime.m"; + daytime: Daytime; + +include "styx.m"; + styx: Styx; + NOFID: import Styx; + +include "arg.m"; + +Archfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Ahdr: adt { + name: string; + modestr: string; + d: ref Sys->Dir; +}; + +Archive: adt { + b: ref Bufio->Iobuf; + nexthdr: big; + canseek: int; + hdr: ref Ahdr; + err: string; +}; + +Iobuf: import bufio; +Tmsg, Rmsg: import styx; + +Einuse : con "fid already in use"; +Ebadfid : con "bad fid"; +Eopen : con "fid already opened"; +Enotfound : con "file does not exist"; +Enotdir : con "not a directory"; +Eperm : con "permission denied"; + +UID: con "inferno"; +GID: con "inferno"; + +debug := 0; + +Dir: adt { + dir: Sys->Dir; + offset: big; + parent: cyclic ref Dir; + child: cyclic ref Dir; + sibling: cyclic ref Dir; +}; + +Fid: adt { + fid: int; + open: int; + dir: ref Dir; +}; + +HTSZ: con 32; +fidtab := array[HTSZ] of list of ref Fid; + +root: ref Dir; +qid: int; +mtpt := "/mnt/arch"; +bio: ref Iobuf; +buf: array of byte; +skip := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + str = load String String->PATH; + daytime = load Daytime Daytime->PATH; + styx = load Styx Styx->PATH; + if(bufio == nil || styx == nil || daytime == nil || str == nil) + fatal("failed to load modules"); + styx->init(); + + flags := Sys->MREPL; + arg := load Arg Arg->PATH; + if(arg == nil) + fatal("failed to load "+Arg->PATH); + arg->init(args); + arg->setusage("archfs [-ab] [-m mntpt] archive [prefix ...]"); + while((c := arg->opt()) != 0){ + case c { + 'D' => + debug = 1; + 'a' => + flags = Sys->MAFTER; + 'b' => + flags = Sys->MBEFORE; + 'm' => + mtpt = arg->earg(); + 's' => + skip = 1; + * => + arg->usage(); + } + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + buf = array[Sys->ATOMICIO] of byte; + # root = newdir("/", UID, GID, 8r755|Sys->DMDIR, daytime->now()); + root = newdir(basename(mtpt), UID, GID, 8r555|Sys->DMDIR, daytime->now()); + root.parent = root; + readarch(hd args, tl args); + p := array[2] of ref Sys->FD; + if(sys->pipe(p) < 0) + fatal("can't create pipe"); + pidch := chan of int; + spawn serve(p[1], pidch); + pid := <- pidch; + if(sys->mount(p[0], nil, mtpt, flags, nil) < 0) + fatal(sys->sprint("cannot mount archive on %s: %r", mtpt)); +} + +reply(fd: ref Sys->FD, m: ref Rmsg): int +{ + if(debug) + sys->fprint(sys->fildes(2), "-> %s\n", m.text()); + s := m.pack(); + if(s == nil) + return -1; + return sys->write(fd, s, len s); +} + +error(fd: ref Sys->FD, m: ref Tmsg, e: string) +{ + reply(fd, ref Rmsg.Error(m.tag, e)); +} + +serve(fd: ref Sys->FD, pidch: chan of int) +{ + e: string; + f: ref Fid; + + pidch <-= sys->pctl(Sys->NEWNS|Sys->NEWFD, 1 :: 2 :: fd.fd :: bio.fd.fd :: nil); + bio.fd = sys->fildes(bio.fd.fd); + fd = sys->fildes(fd.fd); +Work: + while((m0 := Tmsg.read(fd, Styx->MAXRPC)) != nil){ + if(debug) + sys->fprint(sys->fildes(2), "<- %s\n", m0.text()); + pick m := m0 { + Readerror => + fatal("read error on styx server"); + Version => + (s, v) := styx->compatible(m, Styx->MAXRPC, Styx->VERSION); + reply(fd, ref Rmsg.Version(m.tag, s, v)); + Auth => + error(fd, m, "authentication not required"); + Flush => + reply(fd, ref Rmsg.Flush(m.tag)); + Walk => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + if(f.open){ + error(fd, m, Eopen); + continue; + } + dir := f.dir; + nq := 0; + nn := len m.names; + qids := array[nn] of Sys->Qid; + if(nn > 0){ + for(k := 0; k < nn; k++){ + if((dir.dir.mode & Sys->DMDIR) == 0){ + if(k == 0){ + error(fd, m, Enotdir); + continue Work; + } + break; + } + dir = lookup(dir, m.names[k]); + if(dir == nil){ + if(k == 0){ + error(fd, m, Enotfound); + continue Work; + } + break; + } + qids[nq++] = dir.dir.qid; + } + } + if(nq < nn) + qids = qids[0: nq]; + if(nq == nn){ + if(m.newfid != m.fid){ + f = newfid(m.newfid); + if(f == nil){ + error(fd, m, Einuse); + continue Work; + } + } + f.dir = dir; + } + reply(fd, ref Rmsg.Walk(m.tag, qids)); + Open => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + if(m.mode != Sys->OREAD){ + error(fd, m, Eperm); + continue; + } + f.open = 1; + reply(fd, ref Rmsg.Open(m.tag, f.dir.dir.qid, Styx->MAXFDATA)); + Create => + error(fd, m, Eperm); + Read => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + data := read(f.dir, m.offset, m.count); + reply(fd, ref Rmsg.Read(m.tag, data)); + Write => + error(fd, m, Eperm); + Clunk => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + freefid(f); + reply(fd, ref Rmsg.Clunk(m.tag)); + Stat => + (f, e) = mapfid(m.fid); + if(e != nil){ + error(fd, m, e); + continue; + } + reply(fd, ref Rmsg.Stat(m.tag, f.dir.dir)); + Remove => + error(fd, m, Eperm); + Wstat => + error(fd, m, Eperm); + Attach => + f = newfid(m.fid); + if(f == nil){ + error(fd, m, Einuse); + continue; + } + f.dir = root; + reply(fd, ref Rmsg.Attach(m.tag, f.dir.dir.qid)); + * => + fatal("unknown styx message"); + } + } +} + +newfid(fid: int): ref Fid +{ + if(fid == NOFID) + return nil; + hv := hashval(fid); + ff: ref Fid; + for(l := fidtab[hv]; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid) + return nil; + if(ff == nil && f.fid == NOFID) + ff = f; + } + if((f := ff) == nil){ + f = ref Fid; + fidtab[hv] = f :: fidtab[hv]; + } + f.fid = fid; + f.open = 0; + return f; +} + +freefid(f: ref Fid) +{ + hv := hashval(f.fid); + for(l := fidtab[hv]; l != nil; l = tl l) + if(hd l == f){ + f.fid = NOFID; + f.dir = nil; + f.open = 0; + return; + } + fatal("cannot find fid"); +} + +mapfid(fid: int): (ref Fid, string) +{ + if(fid == NOFID) + return (nil, Ebadfid); + hv := hashval(fid); + for(l := fidtab[hv]; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid){ + if(f.dir == nil) + return (nil, Enotfound); + return (f, nil); + } + } + return (nil, Ebadfid); +} + +hashval(n: int): int +{ + n %= HTSZ; + if(n < 0) + n += HTSZ; + return n; +} + +readarch(f: string, args: list of string) +{ + ar := openarch(f); + if(ar == nil || ar.b == nil) + fatal(sys->sprint("cannot open %s: %r", f)); + bio = ar.b; + while((a := gethdr(ar)) != nil){ + if(args != nil){ + if(!selected(a.name, args)){ + if(skip) + return; + #drain(ar, int a.d.length); + continue; + } + mkdirs("/", a.name); + } + d := mkdir(a.name, a.d.mode, a.d.mtime, a.d.uid, a.d.gid, 0); + if((a.d.mode & Sys->DMDIR) == 0){ + d.dir.length = a.d.length; + d.offset = bio.offset(); + } + #drain(ar, int a.d.length); + } + if(ar.err != nil) + fatal(ar.err); +} + +selected(s: string, args: list of string): int +{ + for(; args != nil; args = tl args) + if(fileprefix(hd args, s)) + return 1; + return 0; +} + +fileprefix(prefix, s: string): int +{ + n := len prefix; + m := len s; + if(n > m || !str->prefix(prefix, s)) + return 0; + if(m > n && s[n] != '/') + return 0; + return 1; +} + +basename(f: string): string +{ + for(i := len f; i > 0; ) + if(f[--i] == '/') + return f[i+1:]; + return f; +} + +split(p: string): (string, string) +{ + if(p == nil) + fatal("nil string in split"); + if(p[0] != '/') + fatal("p0 not / in split"); + while(p[0] == '/') + p = p[1:]; + i := 0; + while(i < len p && p[i] != '/') + i++; + if(i == len p) + return (p, nil); + else + return (p[0:i], p[i:]); +} + +mkdirs(basedir, name: string) +{ + (nil, names) := sys->tokenize(name, "/"); + while(names != nil){ + # sys->print("mkdir %s\n", basedir); + mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1); + if(tl names == nil) + break; + basedir = basedir + "/" + hd names; + names = tl names; + } +} + +read(d: ref Dir, offset: big, n: int): array of byte +{ + if(d.dir.mode & Sys->DMDIR) + return readdir(d, int offset, n); + return readfile(d, offset, n); +} + +readdir(d: ref Dir, o: int, n: int): array of byte +{ + k := 0; + m := 0; + b := array[n] of byte; + for(s := d.child; s != nil; s = s.sibling){ + l := styx->packdirsize(s.dir); + if(k < o){ + k += l; + continue; + } + if(m+l > n) + break; + b[m: ] = styx->packdir(s.dir); + m += l; + } + return b[0: m]; +} + +readfile(d: ref Dir, offset: big, n: int): array of byte +{ + if(offset+big n > d.dir.length) + n = int(d.dir.length-offset); + if(n <= 0 || offset < big 0) + return nil; + bio.seek(d.offset+offset, Bufio->SEEKSTART); + a := array[n] of byte; + p := 0; + m := 0; + for( ; n != 0; n -= m){ + l := len buf; + if(n < l) + l = n; + m = bio.read(buf, l); + if(m <= 0 || m != l) + fatal("premature eof"); + a[p:] = buf[0:m]; + p += m; + } + return a; +} + +mkdir(f: string, mode: int, mtime: int, uid: string, gid: string, existsok: int): ref Dir +{ + if(f == "/") + return nil; + d := newdir(basename(f), uid, gid, mode, mtime); + addfile(d, f, existsok); + return d; +} + +addfile(d: ref Dir, path: string, existsok: int) +{ + elem: string; + + opath := path; + p := prev := root; + basedir := ""; +# sys->print("addfile %s: %s\n", d.dir.name, path); + while(path != nil){ + (elem, path) = split(path); + basedir += "/" + elem; + op := p; + p = lookup(p, elem); + if(path == nil){ + if(p != nil){ + if(!existsok && (p.dir.mode&Sys->DMDIR) == 0) + sys->fprint(sys->fildes(2), "addfile: %s already there", opath); + # fatal(sys->sprint("addfile: %s already there", opath)); + return; + } + if(prev.child == nil) + prev.child = d; + else { + for(s := prev.child; s.sibling != nil; s = s.sibling) + ; + s.sibling = d; + } + d.parent = prev; + } + else { + if(p == nil){ + mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1); + p = lookup(op, elem); + if(p == nil) + fatal("bad file system"); + } + } + prev = p; + } +} + +lookup(p: ref Dir, f: string): ref Dir +{ + if((p.dir.mode&Sys->DMDIR) == 0) + fatal("not a directory in lookup"); + if(f == ".") + return p; + if(f == "..") + return p.parent; + for(d := p.child; d != nil; d = d.sibling) + if(d.dir.name == f) + return d; + return nil; +} + +newdir(name, uid, gid: string, mode, mtime: int): ref Dir +{ + dir := sys->zerodir; + dir.name = name; + dir.uid = uid; + dir.gid = gid; + dir.mode = mode; + dir.qid.path = big (qid++); + dir.qid.qtype = mode>>24; + dir.qid.vers = 0; + dir.atime = dir.mtime = mtime; + dir.length = big 0; + + d := ref Dir; + d.dir = dir; + d.offset = big 0; + return d; +} + +prd(d: ref Dir) +{ + dir := d.dir; + sys->print("%q %q %q %bx %x %x %d %d %bd %d %d %bd\n", + dir.name, dir.uid, dir.gid, dir.qid.path, dir.qid.vers, dir.mode, dir.atime, dir.mtime, dir.length, dir.dtype, dir.dev, d.offset); +} + +fatal(e: string) +{ + sys->fprint(sys->fildes(2), "archfs: %s\n", e); + raise "fail:error"; +} + +openarch(file: string): ref Archive +{ + b := bufio->open(file, Bufio->OREAD); + if(b == nil) + return nil; + ar := ref Archive; + ar.b = b; + ar.nexthdr = big 0; + ar.canseek = 1; + ar.hdr = ref Ahdr; + ar.hdr.d = ref Sys->Dir; + return ar; +} + +NFLDS: con 6; + +gethdr(ar: ref Archive): ref Ahdr +{ + a := ar.hdr; + b := ar.b; + m := b.offset(); + n := ar.nexthdr; + if(m != n){ + if(ar.canseek) + b.seek(n, Bufio->SEEKSTART); + else { + if(m > n) + fatal(sys->sprint("bad offset in gethdr: m=%bd n=%bd", m, n)); + if(drain(ar, int(n-m)) < 0) + return nil; + } + } + if((s := b.gets('\n')) == nil){ + ar.err = "premature end of archive"; + return nil; + } + if(s == "end of archive\n") + return nil; + (nf, fs) := sys->tokenize(s, " \t\n"); + if(nf != NFLDS){ + ar.err = "too few fields in file header"; + return nil; + } + a.name = hd fs; fs = tl fs; + (a.d.mode, nil) = str->toint(hd fs, 8); fs = tl fs; + a.d.uid = hd fs; fs = tl fs; + a.d.gid = hd fs; fs = tl fs; + (a.d.mtime, nil) = str->toint(hd fs, 10); fs = tl fs; + (tmp, nil) := str->toint(hd fs, 10); fs = tl fs; + a.d.length = big tmp; + ar.nexthdr = b.offset()+a.d.length; + return a; +} + +drain(ar: ref Archive, n: int): int +{ + while(n > 0){ + m := n; + if(m > len buf) + m = len buf; + p := ar.b.read(buf, m); + if(p != m){ + ar.err = "unexpectedly short read"; + return -1; + } + n -= m; + } + return 0; +} diff --git a/appl/cmd/auplay.b b/appl/cmd/auplay.b new file mode 100644 index 00000000..0be6f556 --- /dev/null +++ b/appl/cmd/auplay.b @@ -0,0 +1,114 @@ +implement AuPlay; + +include "sys.m"; +include "draw.m"; + +sys: Sys; +FD: import sys; +stderr: ref FD; + +include "string.m"; + +str: String; + +prog: string; +play: int; +Magic: con "rate"; +data: con "/dev/audio"; +ctl: con "/dev/audioctl"; +buffz: con Sys->ATOMICIO; + +AuPlay: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +process(f: string) +{ + buff := array[buffz] of byte; + inf := sys->open(f, Sys->OREAD); + if (inf == nil) { + sys->fprint(stderr, "%s: could not open %s: %r\n", prog, f); + return; + } + n := sys->read(inf, buff, buffz); + if (n < 0) { + sys->fprint(stderr, "%s: could not read %s: %r\n", prog, f); + return; + } + if (n < 10 || string buff[0:4] != Magic) { + sys->fprint(stderr, "%s: %s: not an audio file\n", prog, f); + return; + } + i := 0; + for (;;) { + if (i == n) { + sys->fprint(stderr, "%s: %s: bad header\n", prog, f); + return; + } + if (buff[i] == byte '\n') { + i++; + if (i == n) { + sys->fprint(stderr, "%s: %s: bad header\n", prog, f); + return; + } + if (buff[i] == byte '\n') { + i++; + if ((i % 4) != 0) { + sys->fprint(stderr, "%s: %s: unpadded header\n", prog, f); + return; + } + break; + } + } + else + i++; + } + if (!play) { + sys->write(stderr, buff, i - 1); + return; + } + df := sys->open(data, Sys->OWRITE); + if (df == nil) { + sys->fprint(stderr, "%s: could not open %s: %r\n", prog, data); + return; + } + cf := sys->open(ctl, Sys->OWRITE); + if (cf == nil) { + sys->fprint(stderr, "%s: could not open %s: %r\n", prog, ctl); + return; + } + if (sys->write(cf, buff, i - 1) < 0) { + sys->fprint(stderr, "%s: could not write %s: %r\n", prog, ctl); + return; + } + if (n > i && sys->write(df, buff[i:n], n - i) < 0) { + sys->fprint(stderr, "%s: could not write %s: %r\n", prog, data); + return; + } + if (sys->stream(inf, df, Sys->ATOMICIO) < 0) { + sys->fprint(stderr, "%s: could not stream %s: %r\n", prog, data); + return; + } +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + stderr = sys->fildes(2); + p := hd argv; + v := tl argv; + (nil, b) := str->splitr(p, "/"); + if (b != nil) + p = b; + (b, nil) = str->splitr(p, "."); + if (b != nil) + p = b[0:len b - 1]; + prog = p; + play = prog == "auplay"; + while (v != nil) { + process(hd v); + v = tl v; + } +} diff --git a/appl/cmd/auth/aescbc.b b/appl/cmd/auth/aescbc.b new file mode 100644 index 00000000..c5b6e301 --- /dev/null +++ b/appl/cmd/auth/aescbc.b @@ -0,0 +1,254 @@ +implement Aescbc; + +# +# broadly transliterated from the Plan 9 command +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "keyring.m"; + kr: Keyring; + AESbsize, MD5dlen, SHA1dlen: import Keyring; + +include "arg.m"; + +Aescbc: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +# +# encrypted file: v2hdr, 16 byte IV, AES-CBC(key, random || file), HMAC_SHA1(md5(key), AES-CBC(random || file)) +# + +Checkpat: con "XXXXXXXXXXXXXXXX"; +Checklen: con len Checkpat; +Bufsize: con 4096; +AESmaxkey: con 32; + +V2hdr: con "AES CBC SHA1 2\n"; + +bin: ref Iobuf; +bout: ref Iobuf; +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + bufio = load Bufio Bufio->PATH; + + sys->pctl(Sys->FORKFD, nil); + stderr = sys->fildes(2); + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("auth/aescbc -d [-k key] [-f keyfile] <file.aes >clear.txt\n or: auth/aescbc -e [-k key] [-f keyfile] <clear.txt >file.aes"); + encrypt := -1; + keyfile: string; + pass: string; + while((o := arg->opt()) != 0) + case o { + 'd' or 'e' => + if(encrypt >= 0) + arg->usage(); + encrypt = o == 'e'; + 'f' => + keyfile = arg->earg(); + 'k' => + pass = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + if(args != nil || encrypt < 0) + arg->usage(); + arg = nil; + + bin = bufio->fopen(sys->fildes(0), Bufio->OREAD); + bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + + buf := array[Bufsize+SHA1dlen] of byte; # Checklen <= SHA1dlen + + pwd: array of byte; + if(keyfile != nil){ + fd := sys->open(keyfile, Sys->OREAD); + if(fd == nil) + error(sys->sprint("can't open %q: %r", keyfile), "keyfile"); + n := readn(fd, buf, len buf); + while(n > 0 && buf[n-1] == byte '\n') + n--; + if(n <= 0) + error("no key", "no key"); + pwd = buf[0:n]; + }else{ + if(pass == nil) + pass = readpassword("password"); + if(pass == nil) + error("no key", "no key"); + pwd = array of byte pass; + for(i := 0; i < len pass; i++) + pass[i] = 0; + } + key := array[AESmaxkey] of byte; + key2 := array[SHA1dlen] of byte; + dstate := kr->sha1(array of byte "aescbc file", 11, nil, nil); + kr->sha1(pwd, len pwd, key2, dstate); + for(i := 0; i < len pwd; i++) + pwd[i] = byte 0; + key[0:] = key2[0:MD5dlen]; + nkey := MD5dlen; + kr->md5(key, nkey, key2, nil); # protect key even if HMAC_SHA1 is broken + key2 = key2[0:MD5dlen]; + + if(encrypt){ + Write(array of byte V2hdr, AESbsize); + genrandom(buf, 2*AESbsize); # CBC is semantically secure if IV is unpredictable. + aes := kr->aessetup(key[0:nkey], buf); # use first AESbsize bytes as IV + kr->aescbc(aes, buf[AESbsize:], AESbsize, Keyring->Encrypt); # use second AESbsize bytes as initial plaintext + Write(buf, 2*AESbsize); + dstate = kr->hmac_sha1(buf[AESbsize:], AESbsize, key2, nil, nil); + while((n := bin.read(buf, Bufsize)) > 0){ + kr->aescbc(aes, buf, n, Keyring->Encrypt); + Write(buf, n); + dstate = kr->hmac_sha1(buf, n, key2, nil, dstate); + if(n < Bufsize) + break; + } + if(n < 0) + error(sys->sprint("read error: %r"), "read error"); + kr->hmac_sha1(nil, 0, key2, buf, dstate); + Write(buf, SHA1dlen); + }else{ # decrypt + Read(buf, AESbsize); + if(string buf[0:AESbsize] == V2hdr){ + Read(buf, 2*AESbsize); # read IV and random initial plaintext + aes := kr->aessetup(key[0:nkey], buf); + dstate = kr->hmac_sha1(buf[AESbsize:], AESbsize, key2, nil, nil); + kr->aescbc(aes, buf[AESbsize:], AESbsize, Keyring->Decrypt); + Read(buf, SHA1dlen); + while((n := bin.read(buf[SHA1dlen:], Bufsize)) > 0){ + dstate = kr->hmac_sha1(buf, n, key2, nil, dstate); + kr->aescbc(aes, buf, n, Keyring->Decrypt); + Write(buf, n); + buf[0:] = buf[n:n+SHA1dlen]; # these bytes are not yet decrypted + } + kr->hmac_sha1(nil, 0, key2, buf[SHA1dlen:], dstate); + if(!eqbytes(buf, buf[SHA1dlen:], SHA1dlen)) + error("decrypted file failed to authenticate", "failed to authenticate"); + }else{ # compatibility with past mistake; assume we're decrypting secstore files + aes := kr->aessetup(key[0:AESbsize], buf); + Read(buf, Checklen); + kr->aescbc(aes, buf, Checklen, Keyring->Decrypt); + while((n := bin.read(buf[Checklen:], Bufsize)) > 0){ + kr->aescbc(aes, buf[Checklen:], n, Keyring->Decrypt); + Write(buf, n); + buf[0:] = buf[n:n+Checklen]; + } + if(string buf[0:Checklen] != Checkpat) + error("decrypted file failed to authenticate", "failed to authenticate"); + } + } + bout.flush(); +} + +error(s: string, why: string) +{ + bout.flush(); + sys->fprint(stderr, "aescbc: %s\n", s); + raise "fail:"+why; +} + +eqbytes(a: array of byte, b: array of byte, n: int): int +{ + if(len a < n || len b < n) + return 0; + for(i := 0; i < n; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +readn(fd: ref Sys->FD, buf: array of byte, nb: int): int +{ + for(nr := 0; nr < nb;){ + n := sys->read(fd, buf[nr:], nb-nr); + if(n <= 0){ + if(nr == 0) + return n; + break; + } + nr += n; + } + return nr; +} + +Read(buf: array of byte, n: int) +{ + if(bin.read(buf, n) != n){ + sys->fprint(sys->fildes(2), "aescbc: unexpectedly short read\n"); + raise "fail:read error"; + } +} + +Write(buf: array of byte, n: int) +{ + if(bout.write(buf, n) != n){ + sys->fprint(sys->fildes(2), "aescbc: write error: %r\n"); + raise "fail:write error"; + } +} + +readpassword(prompt: string): string +{ + cons := sys->open("/dev/cons", Sys->ORDWR); + if(cons == nil) + return nil; + stdin := bufio->fopen(cons, Sys->OREAD); + if(stdin == nil) + return nil; + cfd := sys->open("/dev/consctl", Sys->OWRITE); + if (cfd == nil || sys->fprint(cfd, "rawon") <= 0) + sys->fprint(stderr, "aescbc: warning: cannot hide typed password\n"); + s: string; +L: + for(;;){ + sys->fprint(cons, "%s: ", prompt); + s = ""; + while ((c := stdin.getc()) >= 0){ + case c { + '\n' => + break L; + '\b' or 8r177 => + if(len s > 0) + s = s[0:len s - 1]; + 'u' & 8r037 => + sys->fprint(cons, "\n"); + continue L; + * => + s[len s] = c; + } + } + } + sys->fprint(cons, "\n"); + return s; +} + +genrandom(b: array of byte, n: int) +{ + fd := sys->open("/dev/notquiterandom", Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "aescbc: can't open /dev/notquiterandom: %r\n"); + raise "fail:random"; + } + if(sys->read(fd, b, n) != n){ + sys->fprint(stderr, "aescbc: can't read random numbers: %r\n"); + raise "fail:read random"; + } +} diff --git a/appl/cmd/auth/changelogin.b b/appl/cmd/auth/changelogin.b new file mode 100644 index 00000000..97141408 --- /dev/null +++ b/appl/cmd/auth/changelogin.b @@ -0,0 +1,305 @@ +implement Changelogin; + +include "sys.m"; + sys: Sys; + +include "daytime.m"; + daytime: Daytime; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +Changelogin: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin, stdout: ref Sys->FD; +keydb := "/mnt/keys"; + +init(nil: ref Draw->Context, args: list of string) +{ + ok: int; + word: string; + + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + argv0 := hd args; + args = tl args; + + if(args == nil){ + sys->fprint(stderr, "usage: %s userid\n", argv0); + raise "fail:usage"; + } + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) { + sys->fprint(stderr, "%s: can't load Daytime: %r\n", argv0); + raise "fail:load"; + } + + # get password + id := hd args; + (dbdir, secret, expiry, err) := getuser(id); + if(dbdir == nil){ + if(err != nil){ + sys->fprint(stderr, "%s: can't get auth info for %s in %s: %s\n", argv0, id, keydb, err); + raise "fail:no key"; + } + sys->print("new account\n"); + } + for(;;){ + if(secret != nil) + sys->print("secret [default = don't change]: "); + else + sys->print("secret: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok) + exit; + if(word == "" && secret != nil) + break; + if(len word >= 8) + break; + sys->print("!secret must be at least 8 characters\n"); + } + newsecret: array of byte; + if(word != ""){ + # confirm password change + word1 := word; + sys->print("confirm: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok || word != word1) { + sys->print("Entries do not match. Authinfo record unchanged.\n"); + raise "fail:mismatch"; + } + + pwbuf := array of byte word; + newsecret = array[Keyring->SHA1dlen] of byte; + kr->sha1(pwbuf, len pwbuf, newsecret, nil); + } + + # get expiration time (midnight of date specified) + maxdate := "17012038"; # largest date possible without incurring integer overflow + now := daytime->now(); + tm := daytime->local(now); + tm.sec = 59; + tm.min = 59; + tm.hour = 23; + tm.year += 1; + if(dbdir == nil) + expsecs := daytime->tm2epoch(tm); # set expiration date to 23:59:59 one year from today + else + expsecs = expiry; + for(;;){ + defexpdate := "permanent"; + if(expsecs != 0) { + otm := daytime->local(expsecs); + defexpdate = sys->sprint("%2.2d%2.2d%4.4d", otm.mday, otm.mon+1, otm.year+1900); + } + sys->print("expires [DDMMYYYY/permanent, return = %s]: ", defexpdate); + (ok, word) = readline(stdin, "rawoff"); + if(!ok) + exit; + if(word == "") + word = defexpdate; + if(word == "permanent"){ + expsecs = 0; + break; + } + if(len word != 8){ + sys->print("!bad date format %s\n", word); + continue; + } + tm.mday = int word[0:2]; + if(tm.mday > 31 || tm.mday < 1){ + sys->print("!bad day of month %d\n", tm.mday); + continue; + } + tm.mon = int word[2:4] - 1; + if(tm.mon > 11 || tm.mday < 0){ + sys->print("!bad month %d\n", tm.mon + 1); + continue; + } + tm.year = int word[4:8] - 1900; + if(tm.year < 70){ + sys->print("!bad year %d (year may be no earlier than 1970)\n", tm.year + 1900); + continue; + } + expsecs = daytime->tm2epoch(tm); + if(expsecs > now) + break; + else { + newexpdate := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900); + tm = daytime->local(daytime->now()); + today := sys->sprint("%2.2d%2.2d%4.4d", tm.mday, tm.mon+1, tm.year+1900); + sys->print("!bad expiration date %s (must be between %s and %s)\n", newexpdate, today, maxdate); + expsecs = now; + } + } + newexpiry := expsecs; + +# # get the free form field +# if(pw != nil) +# npw.other = pw.other; +# else +# npw.other = ""; +# sys->print("free form info [return = %s]: ", npw.other); +# (ok, word) = readline(stdin,"rawoff"); +# if(!ok) +# exit; +# if(word != "") +# npw.other = word; + + if(dbdir == nil){ + dbdir = keydb+"/"+id; + fd := sys->create(dbdir, Sys->OREAD, Sys->DMDIR|8r700); + if(fd == nil){ + sys->fprint(stderr, "%s: can't create account %s: %r\n", argv0, id); + raise "fail:create user"; + } + } + changed := 0; + if(!eq(newsecret, secret)){ + if(putsecret(dbdir, newsecret) < 0){ + sys->fprint(stderr, "%s: can't update secret for %s: %r\n", argv0, id); + raise "fail:update"; + } + changed = 1; + } + if(newexpiry != expiry){ + if(putexpiry(dbdir, newexpiry) < 0){ + sys->fprint(stderr, "%s: can't update expiry time for %s: %r\n", argv0, id); + raise "fail:update"; + } + changed = 1; + } + sys->print("change written\n"); +} + +getuser(id: string): (string, array of byte, int, string) +{ + (ok, nil) := sys->stat(keydb); + if(ok < 0) + return (nil, nil, 0, sys->sprint("can't stat %s: %r", id)); + dbdir := keydb+"/"+id; + (ok, nil) = sys->stat(dbdir); + if(ok < 0) + return (nil, nil, 0, nil); + fd := sys->open(dbdir+"/secret", Sys->OREAD); + if(fd == nil) + return (nil, nil, 0, sys->sprint("can't open %s/secret: %r", id)); + d: Sys->Dir; + (ok, d) = sys->fstat(fd); + if(ok < 0) + return (nil, nil, 0, sys->sprint("can't stat %s/secret: %r", id)); + l := int d.length; + secret: array of byte; + if(l > 0){ + secret = array[l] of byte; + if(sys->read(fd, secret, len secret) != len secret) + return (nil, nil, 0, sys->sprint("error reading %s/secret: %r", id)); + } + expiry := 0; + fd = sys->open(dbdir+"/expire", Sys->OREAD); + if(fd == nil) + return (nil, nil, 0, sys->sprint("can't open %s/expiry: %r", id)); + b := array[32] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return (nil, nil, 0, sys->sprint("error reading %s/expiry: %r", id)); + return (dbdir, secret, int string b[0:n], nil); +} + +eq(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +putsecret(dir: string, secret: array of byte): int +{ + fd := sys->create(dir+"/secret", Sys->OWRITE, 8r600); + if(fd == nil) + return -1; + return sys->write(fd, secret, len secret); +} + +putexpiry(dir: string, expiry: int): int +{ + fd := sys->open(dir+"/expire", Sys->OWRITE); + if(fd == nil) + return -1; + return sys->fprint(fd, "%d", expiry); +} + +readline(io: ref Sys->FD, mode: string): (int, string) +{ + r : int; + line : string; + buf := array[8192] of byte; + fdctl : ref Sys->FD; + rawoff := array of byte "rawoff"; + + # + # Change console mode to rawon + # + if(mode == "rawon"){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->write(fdctl,array of byte mode,len mode) != len mode){ + sys->fprint(stderr, "unable to change console mode"); + return (0,nil); + } + } + + # + # Read up to the CRLF + # + line = ""; + for(;;) { + r = sys->read(io, buf, len buf); + if(r <= 0){ + sys->fprint(stderr, "error read from console mode"); + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + return (0, nil); + } + + line += string buf[0:r]; + if ((len line >= 1) && (line[(len line)-1] == '\n')){ + if(mode == "rawon"){ + r = sys->write(stdout,array of byte "\n",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + break; + } + else { + if(mode == "rawon"){ + #r = sys->write(stdout, array of byte "*",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + } + } + + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + + # Total success! + return (1, line[0:len line - 1]); +} diff --git a/appl/cmd/auth/convpasswd.b b/appl/cmd/auth/convpasswd.b new file mode 100644 index 00000000..8463b0fb --- /dev/null +++ b/appl/cmd/auth/convpasswd.b @@ -0,0 +1,120 @@ +implement Convpasswd; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "keyring.m"; + keyring: Keyring; + IPint: import keyring; + +include "security.m"; + +include "arg.m"; + +Convpasswd: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +PW: adt { + id: string; # user id + pw: array of byte; # password hashed by SHA + expire: int; # expiration time (epoch seconds) + other: string; # about the account +}; + +mntpt := "/mnt/keys"; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + noload(Arg->PATH); + arg := load Arg Arg->PATH; + if(arg == nil) + noload(Arg->PATH); + force := 0; + verbose := 0; + arg->init(args); + arg->setusage("convpasswd [-f] [-v] [-m /mnt/keys] [passwordfile]"); + while((o := arg->opt()) != 0) + case o { + 'f' => force = 1; + 'm' => mntpt = arg->earg(); + 'v' => verbose = 1; + * => arg->usage(); + } + args = arg->argv(); + arg = nil; + + f := "/keydb/password"; + if(args != nil) + f = hd args; + iob := bufio->open(f, Bufio->OREAD); + if(iob == nil) + error(sys->sprint("%s: %r", f)); + for(line := 1; (s := iob.gets('\n')) != nil; line++) { + (n, tokl) := sys->tokenize(s, ":\n"); + if (n < 3){ + sys->fprint(sys->fildes(2), "convpasswd: %s:%d: invalid format\n", f, line); + continue; + } + pw := ref PW; + pw.id = hd tokl; + pw.pw = IPint.b64toip(hd tl tokl).iptobytes(); + pw.expire = int hd tl tl tokl; + if (n==3) + pw.other = nil; + else + pw.other = hd tl tl tl tokl; + err := writekey(pw, force); + if(err != nil) + error(sys->sprint("error writing /mnt/keys entry for %s: %s", pw.id, err)); + if(verbose) + sys->print("%s\n", pw.id); + } +} + +noload(p: string) +{ + error(sys->sprint("can't load %s: %r", p)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "convpasswd: %s\n", s); + raise "fail:error"; +} + +writekey(pw: ref PW, force: int): string +{ + dir := mntpt+"/"+pw.id; + if(sys->open(dir, Sys->OREAD) == nil){ + # make it + d := sys->create(dir, Sys->OREAD, Sys->DMDIR|8r600); + if(d == nil) + return sys->sprint("can't create %s: %r", dir); + }else if(!force) + return nil; # leave existing entry alone + secret := dir+"/secret"; + fd := sys->open(secret, Sys->OWRITE); + if(fd == nil) + return sys->sprint("can't open %s: %r", secret); + if(sys->write(fd, pw.pw, len pw.pw) != len pw.pw) + return sys->sprint("error writing %s: %r", secret); + expire := dir+"/expire"; + fd = sys->open(expire, Sys->OWRITE); + if(fd == nil) + return sys->sprint("can't open %s: %r", expire); + if(sys->fprint(fd, "%d", pw.expire) < 0) + return sys->sprint("error writing %s: %r", expire); + # no equivalent of `other' + return nil; +} diff --git a/appl/cmd/auth/countersigner.b b/appl/cmd/auth/countersigner.b new file mode 100644 index 00000000..a444f807 --- /dev/null +++ b/appl/cmd/auth/countersigner.b @@ -0,0 +1,59 @@ +implement Countersigner; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + +include "keyring.m"; + kr: Keyring; + +include "security.m"; + +Countersigner: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin, stdout: ref Sys->FD; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + sys->pctl(Sys->FORKNS, nil); + if(sys->chdir("/keydb") < 0){ + sys->fprint(stderr, "countersigner: no key database\n"); + raise "fail:no keydb"; + } + + # get boxid + buf := kr->getmsg(stdin); + if(buf == nil){ + sys->fprint(stderr, "countersigner: client hung up\n"); + raise "fail:hungup"; + } + boxid := string buf; + + # read file + file := "countersigned/"+boxid; + fd := sys->open(file, Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "countersigner: can't open %s: %r\n", file); + raise "fail:bad boxid"; + } + blind := kr->getmsg(fd); + if(blind == nil){ + sys->fprint(stderr, "countersigner: can't read %s\n", file); + raise "fail:no blind"; + } + + # answer client + kr->sendmsg(stdout, blind, len blind); +} diff --git a/appl/cmd/auth/createsignerkey.b b/appl/cmd/auth/createsignerkey.b new file mode 100644 index 00000000..90a54b6f --- /dev/null +++ b/appl/cmd/auth/createsignerkey.b @@ -0,0 +1,144 @@ +implement Createsignerkey; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "daytime.m"; + +include "keyring.m"; + kr: Keyring; + +include "arg.m"; + +# signer key never expires +SKexpire: con 0; + +# size in bits of modulus for public keys +PKmodlen: con 512; + +# size in bits of modulus for diffie hellman +DHmodlen: con 512; + +algs := array[] of {"rsa", "elgamal"}; # first entry is default + +Createsignerkey: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + err: string; + + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + if(kr == nil) + loaderr(Keyring->PATH); + arg := load Arg Arg->PATH; + if(arg == nil) + loaderr(Arg->PATH); + + arg->init(argv); + arg->setusage("createsignerkey [-a algorithm] [-f keyfile] [-e ddmmyyyy] [-b size-in-bits] name-of-owner"); + alg := algs[0]; + filename := "/keydb/signerkey"; + expire := SKexpire; + bits := PKmodlen; + while((c := arg->opt()) != 0){ + case c { + 'a' => + alg = arg->arg(); + if(alg == nil) + arg->usage(); + for(i:=0;; i++){ + if(i >= len algs) + error(sys->sprint("unknown algorithm: %s", alg)); + else if(alg == algs[i]) + break; + } + 'f' or 'k' => + filename = arg->earg(); + 'e' => + s := arg->earg(); + (err, expire) = checkdate(s); + if(err != nil) + error(err); + 'b' => + s := arg->earg(); + bits = int s; + if(bits < 32 || bits > 4096) + error("modulus must be in the range of 32 to 4096 bits"); + * => + arg->usage(); + } + } + argv = arg->argv(); + if(argv == nil) + arg->usage(); + arg = nil; + + owner := hd argv; + + # generate a local key, self-signed + info := ref Keyring->Authinfo; + info.mysk = kr->genSK(alg, owner, bits); + if(info.mysk == nil) + error(sys->sprint("algorithm %s not configured in system", alg)); + info.mypk = kr->sktopk(info.mysk); + info.spk = kr->sktopk(info.mysk); + myPKbuf := array of byte kr->pktostr(info.mypk); + state := kr->sha1(myPKbuf, len myPKbuf, nil, nil); + info.cert = kr->sign(info.mysk, expire, state, "sha1"); + (info.alpha, info.p) = kr->dhparams(DHmodlen); + + if(kr->writeauthinfo(filename, info) < 0) + error(sys->sprint("can't write signerkey file %s: %r", filename)); +} + +loaderr(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "createsignerkey: %s\n", s); + raise "fail:error"; +} + +checkdate(word: string): (string, int) +{ + if(len word != 8) + return ("!date must be in form ddmmyyyy", 0); + + daytime := load Daytime Daytime->PATH; + if(daytime == nil) + loaderr(Daytime->PATH); + + now := daytime->now(); + + tm := daytime->local(now); + tm.sec = 59; + tm.min = 59; + tm.hour = 24; + + tm.mday = int word[0:2]; + if(tm.mday > 31 || tm.mday < 1) + return ("!bad day of month", 0); + + tm.mon = int word[2:4] - 1; + if(tm.mon > 11 || tm.mday < 0) + return ("!bad month", 0); + + tm.year = int word[4:8] - 1900; + if(tm.year < 70) + return ("!bad year", 0); + + newdate := daytime->tm2epoch(tm); + if(newdate < now) + return ("!expiration date must be in the future", 0); + + return (nil, newdate); +} diff --git a/appl/cmd/auth/factotum/authio.m b/appl/cmd/auth/factotum/authio.m new file mode 100644 index 00000000..7c0565b5 --- /dev/null +++ b/appl/cmd/auth/factotum/authio.m @@ -0,0 +1,80 @@ +Authio: module +{ + + Aattr, Aval, Aquery: con iota; + + Attr: adt { + tag: int; + name: string; + val: string; + + text: fn(a: self ref Attr): string; + }; + + Key: adt { + attrs: list of ref Attr; + secrets: list of ref Attr; + # proto: Authproto; + + mk: fn(attrs: list of ref Attr): ref Key; + text: fn(k: self ref Key): string; + safetext: fn(k: self ref Key): string; + }; + + Fid: adt + { + fid: int; + pid: int; + err: string; + attrs: list of ref Attr; + write: chan of (array of byte, Sys->Rwrite); + read: chan of (int, Sys->Rread); + # proto: Authproto; + done: int; + ai: ref Authinfo; + }; + + Rpc: adt { + r: ref Fid; + cmd: int; + arg: array of byte; + nbytes: int; + rc: chan of (array of byte, string); + }; + + IO: adt { + f: ref Fid; + rpc: ref Rpc; + + findkey: fn(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string); + needkey: fn(io: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string); + read: fn(io: self ref IO): array of byte; + readn: fn(io: self ref IO, n: int): array of byte; + write: fn(io: self ref IO, buf: array of byte, n: int): int; + toosmall: fn(io: self ref IO, n: int); + error: fn(io: self ref IO, s: string); + ok: fn(io: self ref IO); + done: fn(io: self ref IO, ai: ref Authinfo); + }; + + # need more ... ? + Authinfo: adt { + cuid: string; # caller id + suid: string; # server id + cap: string; # capability (only valid on server side) + secret: array of byte; + }; + + memrandom: fn(a: array of byte, n: int); + eqbytes: fn(a, b: array of byte): int; + netmkaddr: fn(addr, net, svc: string): string; + user: fn(): string; + lookattrval: fn(a: list of ref Attr, n: string): string; + parseline: fn(s: string): list of ref Attr; +}; + +Authproto: module +{ + init: fn(f: Authio): string; + interaction: fn(attrs: list of ref Authio->Attr, io: ref Authio->IO): string; +}; diff --git a/appl/cmd/auth/factotum/factotum.b b/appl/cmd/auth/factotum/factotum.b new file mode 100644 index 00000000..5f5b02a3 --- /dev/null +++ b/appl/cmd/auth/factotum/factotum.b @@ -0,0 +1,978 @@ +implement Factotum, Authio; + +# +# Copyright © 2003-2004 Vita Nuova Holdings Limited +# + +include "sys.m"; + sys: Sys; + Rread, Rwrite: import Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "keyring.m"; + +include "authio.m"; + +include "arg.m"; + +Factotum: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +#confirm, log + +Files: adt { + ctl: ref Sys->FileIO; + rpc: ref Sys->FileIO; + proto: ref Sys->FileIO; + needkey: ref Sys->FileIO; +}; + +Debug: con 0; +debug := Debug; + +files: Files; +authio: Authio; + +keymanc: chan of (list of ref Attr, int, chan of (ref Key, string)); + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + authio = load Authio "$self"; + + svcname := "#sfactotum"; + mntpt := "/mnt/factotum"; + arg := load Arg Arg->PATH; + if(arg != nil){ + arg->init(args); + arg->setusage("auth/factotum [-d] [-m /mnt/factotum] [-s factotum]"); + while((o := arg->opt()) != 0) + case o { + 'd' => debug = 1; + 'm' => mntpt = arg->earg(); + 's' => svcname = "#s"+arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + } + sys->unmount(nil, mntpt); + if(sys->bind(svcname, mntpt, Sys->MREPL) < 0) + err(sys->sprint("can't bind %s on %s: %r", svcname, mntpt)); + files.ctl = sys->file2chan(mntpt, "ctl"); + files.rpc = sys->file2chan(mntpt, "rpc"); + files.proto = sys->file2chan(mntpt, "proto"); + files.needkey = sys->file2chan(mntpt, "needkey"); + if(files.ctl == nil || files.rpc == nil || files.proto == nil || files.needkey == nil) + err(sys->sprint("can't create %s/*: %r", mntpt)); + keymanc = chan of (list of ref Attr, int, chan of (ref Key, string)); + spawn factotumsrv(); +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + return nil; + b := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return nil; + return string b[0:n]; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "factotum: %s\n", s); + raise "fail:error"; +} + +rlist: list of ref Fid; + +factotumsrv() +{ + sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKENV, nil); + if(!Debug) + privacy(); + allkeys := array[0] of ref Key; + pidc := chan of int; + donec := chan of ref Fid; +# keyc := chan of (list of ref Attr, chan of (ref Key, string)); + needfid := -1; + needed, needy: list of (int, list of ref Attr, chan of (ref Key, string)); + needread: Sys->Rread; + needtag := 0; + for(;;) X: alt{ + r := <-donec => + r.pid = 0; + cleanfid(r.fid); + + (off, nbytes, nil, rc) := <-files.ctl.read => + if(rc == nil) + break; + s := ""; + for(i := 0; i < len allkeys; i++) + if((k := allkeys[i]) != nil) + s += k.safetext()+"\n"; + rc <-= reads(s, off, nbytes); + (nil, data, nil, wc) := <-files.ctl.write => + if(wc == nil) + break; + (nf, flds) := sys->tokenize(string data, "\n\r"); + if(nf > 1){ + # compatibility with plan 9; has the advantage you can tell which key is wrong + wc <-= (0, "multiline write not allowed"); + break; + } + s := hd flds; + if(s == nil || s[0] == '#'){ + wc <-= (len data, nil); + break; + } + for(i := 0; i < len s && s[i] != ' '; i++){ + # skip + } + verb := s[0:i]; + if(i < len s) + i++; + s = s[i:]; + case verb { + "key" => + k := Key.mk(parseline(s)); + if(k == nil){ + wc <-= (len data, nil); # ignore it + break; + } + if(lookattrval(k.attrs, "proto") == nil){ + wc <-= (0, "key without proto"); + break; + } + allkeys = addkey(allkeys, k); + wc <-= (len data, nil); + "delkey" => + attrs := parseline(s); + for(al := attrs; al != nil; al = tl al){ + a := hd al; + if(a.name[0] == '!' && (a.val != nil || a.tag != Aquery)){ + wc <-= (0, "cannot specify values for private fields"); + break X; + } + } + if(delkey(allkeys, attrs) == 0) + wc <-= (0, "no matching keys"); + else + wc <-= (len data, nil); + "debug" => + wc <-= (len data, nil); + * => + wc <-= (0, "unknown ctl request"); + } + + (nil, nbytes, fid, rc) := <-files.rpc.read => + if(rc == nil) + break; + r := findfid(fid); + if(r == nil){ + rc <-= (nil, "unknown request"); + break; + } + alt{ + r.read <-= (nbytes, rc) => + ; + * => + rc <-= (nil, "concurrent rpc read not allowed"); + } + (nil, data, fid, wc) := <-files.rpc.write => + if(wc == nil){ + cleanfid(fid); + break; + } + r := findfid(fid); + if(r == nil){ + r = ref Fid(fid, 0, nil, nil, chan[1] of (array of byte, Rwrite), chan[1] of (int, Rread), 0, nil); + spawn request(r, pidc, donec); + r.pid = <-pidc; + rlist = r :: rlist; + } + # this non-blocking write avoids a potential deadlock situation that + # can happen when a proto module calls findkey at the same time + # a client tries to write to the rpc file. this might not be the correct fix! + alt{ + r.write <-= (data, wc) => + ; + * => + wc <-= (-1, "concurrent rpc write not allowed"); + } + + (off, nbytes, nil, rc) := <-files.proto.read => + if(rc == nil) + break; + rc <-= reads("pass\np9any\n", off, nbytes); # TO DO + (nil, nil, nil, wc) := <-files.proto.write => + if(wc != nil) + wc <-= (0, "illegal operation"); + + (nil, nil, fid, rc) := <-files.needkey.read => + if(rc == nil) + break; + if(needfid >= 0 && fid != needfid){ + rc <-= (nil, "file in use"); + break; + } + needfid = fid; + if(needy != nil){ + (tag, attr, kc) := hd needy; + needy = tl needy; + needed = (tag, attr, kc) :: needed; + rc <-= (sys->aprint("needkey tag=%ud %s", tag, attrtext(attr)), nil); + break; + } + if(needread != nil){ + rc <-= (nil, "already reading"); + break; + } + needread = rc; + (nil, data, fid, wc) := <-files.needkey.write => + if(wc == nil){ + if(needfid == fid){ + needfid = -1; # TO DO? give needkey errors back to request + needread = nil; + } + break; + } + if(needfid >= 0 && fid != needfid){ + wc <-= (0, "file in use"); + break; + } + needfid = fid; + tagline := parseline(string data); + if(len tagline != 1 || (t := lookattrval(tagline, "tag")) == nil){ + wc <-= (0, "no tag"); + break; + } + tag := int t; + nl: list of (int, list of ref Attr, chan of (ref Key, string)); + found := 0; + for(l := needed; l != nil; l = tl l){ + (ntag, attrs, kc) := hd l; + if(tag == ntag){ + found = 1; + k := findkey(allkeys, attrs); + if(k != nil) + kc <-= (k, nil); + else + kc <-= (nil, "needkey "+attrtext(attrs)); + while((l = tl l) != nil) + nl = hd l :: nl; + break; + } + nl = hd l :: nl; + } + if(found) + wc <-= (len data, nil); + else + wc <-= (0, "tag not found"); + + (attrs, required, kc) := <-keymanc => + # look for key and reply + k := findkey(allkeys, attrs); + if(k != nil){ + kc <-= (k, nil); + break; + }else if(!required || needfid == -1){ + kc <-= (nil, "needkey "+attrtext(attrs)); + break; + } + # query surrounding environment using needkey + if(needread != nil){ + needed = (needtag, attrs, kc) :: needed; + needread <-= (sys->aprint("needkey tag=%ud %s", needtag, attrtext(attrs)), nil); + needread = nil; + needtag++; + }else + needy = (needtag++, attrs, kc) :: needy; + } +} + +findfid(fid: int): ref Fid +{ + for(rl := rlist; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid == fid) + return r; + } + return nil; +} + +cleanfid(fid: int) +{ + rl := rlist; + rlist = nil; + for(; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid != fid) + rlist = r :: rlist; + else if(r.pid) + kill(r.pid); + } +} + +kill(pid: int) +{ + fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +privacy() +{ + fd := sys->open("#p/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "private") < 0) + sys->fprint(sys->fildes(2), "factotum: warning: unable to make memory private: %r\n"); +} + +reads(str: string, off, nbytes: int): (array of byte, string) +{ + bstr := array of byte str; + slen := len bstr; + if(off < 0 || off >= slen) + return (nil, nil); + if(off + nbytes > slen) + nbytes = slen - off; + if(nbytes <= 0) + return (nil, nil); + return (bstr[off:off+nbytes], nil); +} + +Ogok, Ostart, Oread, Owrite, Oauthinfo, Oattr: con iota; + +ops := array[] of { + (Ostart, "start"), + (Oread, "read"), + (Owrite, "write"), + (Oauthinfo, "authinfo"), + (Oattr, "attr"), +}; + +request(r: ref Fid, pidc: chan of int, donec: chan of ref Fid) +{ + pidc <-= sys->pctl(0, nil); + rpc := rio(r); + while(rpc != nil){ + if(rpc.cmd == Ostart){ + (proto, attrs, e) := startproto(string rpc.arg); + if(e != nil){ + reply(rpc, "error "+e); + rpc = rio(r); + continue; + } + r.attrs = attrs; # saved for attr request + ok(rpc); + io := ref IO(r, nil); + { + err := proto->interaction(attrs, io); + if(debug && err != nil) + sys->fprint(sys->fildes(2), "factotum: failure: %s\n", err); + if(r.err == nil) + r.err = err; + r.done = 1; + }exception ex{ + "*" => + r.done = 0; + r.err = "exception "+ex; + } + if(r.err != nil) + io.error(r.err); + rpc = finish(r); + r.attrs = nil; + r.err = nil; + r.done = 0; + r.ai = nil; + }else + reply(rpc, "no current protocol"); + } + flushreq(r, donec); +} + +startproto(request: string): (Authproto, list of ref Attr, string) +{ + attrs := parseline(request); + if(Debug) + sys->print("-> %s <-\n", attrtext(attrs)); + p := lookattrval(attrs, "proto"); + if(p == nil) + return (nil, nil, "did not specify protocol"); + if(Debug) + sys->print("proto=%s\n", p); + if(any(p, "./")) # avoid unpleasantness + return (nil, nil, "illegal protocol: "+p); + proto := load Authproto "/dis/auth/proto/"+p+".dis"; + if(proto == nil) + return (nil, nil, sys->sprint("protocol %s: %r", p)); + if(Debug) + sys->print("start %s\n", p); + e: string; + { + e = proto->init(authio); + }exception ex{ + "*" => + e = "exception "+ex; + } + if(e != nil) + return (nil, nil, e); + return (proto, attrs, nil); +} + +finish(r: ref Fid): ref Rpc +{ + while((rpc := rio(r)) != nil) + case rpc.cmd { + Owrite => + phase(rpc, "protocol phase error"); + Oread => + if(r.err != nil) + reply(rpc, "error "+r.err); + else + done(rpc, r.ai); + Oauthinfo => + if(r.done){ + if(r.ai == nil) + reply(rpc, "error no authinfo available"); + else{ + a := packai(r.ai); + if(rpc.nbytes-3 < len a) + reply(rpc, sys->sprint("toosmall %d", len a + 3)); + else + okdata(rpc, a); + } + }else + reply(rpc, "error authentication unfinished"); + Ostart => + return rpc; + * => + reply(rpc, "error unexpected request"); + } + return nil; +} + +flushreq(r: ref Fid, donec: chan of ref Fid) +{ + for(;;) alt{ + donec <-= r => + exit; + (nil, wc) := <-r.write => + wc <-= (0, "write rpc protocol error"); + (nil, rc) := <-r.read => + rc <-= (nil, "read rpc protocol error"); + } +} + +rio(r: ref Fid): ref Rpc +{ + req: array of byte; + for(;;) alt{ + (data, wc) := <-r.write => + if(req != nil){ + wc <-= (0, "rpc pending; read to clear"); + break; + } + req = data; + wc <-= (len data, nil); + + (nbytes, rc) := <-r.read => + if(req == nil){ + rc <-= (nil, "no rpc pending"); + break; + } + (cmd, arg) := op(req, ops); + req = nil; + rpc := ref Rpc(r, cmd, arg, nbytes, rc); + case cmd { + Ogok => + reply(rpc, "error unknown rpc"); + break; + Oattr => + if(r.attrs == nil) + reply(rpc, "error no attributes"); + else + reply(rpc, "ok "+attrtext(r.attrs)); + break; + * => + return rpc; + } + } +} + +ok(rpc: ref Rpc) +{ + reply(rpc, "ok"); +} + +okdata(rpc: ref Rpc, a: array of byte) +{ + b := array[len a + 3] of byte; + b[0] = byte 'o'; + b[1] = byte 'k'; + b[2] = byte ' '; + b[3:] = a; + rpc.rc <-= (b, nil); +} + +done(rpc: ref Rpc, ai: ref Authinfo) +{ + rpc.r.ai = ai; + rpc.r.done = 1; + if(ai != nil) + reply(rpc, "done haveai"); + else + reply(rpc, "done"); +} + +phase(rpc: ref Rpc, s: string) +{ + reply(rpc, "phase "+s); +} + +needkey(rpc: ref Rpc, attrs: list of ref Attr) +{ + reply(rpc, "needkey "+attrtext(attrs)); +} + +reply(rpc: ref Rpc, s: string) +{ + rpc.rc <-= reads(s, 0, rpc.nbytes); +} + +puta(a: array of byte, n: int, v: array of byte): int +{ + if(n < 0) + return -1; + c := len v; + if(n+2+c > len a) + return -1; + a[n++] = byte c; + a[n++] = byte (c>>8); + a[n:] = v; + return n + len v; +} + +packai(ai: ref Authinfo): array of byte +{ + a := array[1024] of byte; + i := puta(a, 0, array of byte ai.cuid); + i = puta(a, i, array of byte ai.suid); + i = puta(a, i, array of byte ai.cap); + i = puta(a, i, ai.secret); + if(i < 0) + return nil; + return a[0:i]; +} + +op(a: array of byte, ops: array of (int, string)): (int, array of byte) +{ + arg: array of byte; + for(i := 0; i < len a; i++) + if(a[i] == byte ' '){ + if(i+1 < len a) + arg = a[i+1:]; + break; + } + s := string a[0:i]; + for(i = 0; i < len ops; i++){ + (cmd, name) := ops[i]; + if(s == name) + return (cmd, arg); + } + return (Ogok, arg); +} + +parseline(s: string): list of ref Attr +{ + fld := str->unquoted(s); + rfld := fld; + for(fld = nil; rfld != nil; rfld = tl rfld) + fld = (hd rfld) :: fld; + attrs: list of ref Attr; + for(; fld != nil; fld = tl fld){ + n := hd fld; + a := ""; + tag := Aattr; + for(i:=0; i<len n; i++) + if(n[i] == '='){ + a = n[i+1:]; + n = n[0:i]; + tag = Aval; + } + if(len n == 0) + continue; + if(tag == Aattr && len n > 1 && n[len n-1] == '?'){ + tag = Aquery; + n = n[0:len n-1]; + } + attrs = ref Attr(tag, n, a) :: attrs; + } + return attrs; +} + +Attr.text(a: self ref Attr): string +{ + case a.tag { + Aattr => + return a.name; + Aval => + return a.name+"="+a.val; + Aquery => + return a.name+"?"; + * => + return "??"; + } +} + +attrtext(attrs: list of ref Attr): string +{ + s := ""; + sp := 0; + for(; attrs != nil; attrs = tl attrs){ + if(sp) + s[len s] = ' '; + sp = 1; + s += (hd attrs).text(); + } + return s; +} + +lookattr(attrs: list of ref Attr, n: string): ref Attr +{ + for(; attrs != nil; attrs = tl attrs) + if((a := hd attrs).tag != Aquery && a.name == n) + return a; + return nil; +} + +lookattrval(attrs: list of ref Attr, n: string): string +{ + if((a := lookattr(attrs, n)) != nil) + return a.val; + return nil; +} + +anyattr(attrs: list of ref Attr, n: string): ref Attr +{ + for(; attrs != nil; attrs = tl attrs) + if((a := hd attrs).name == n) + return a; + return nil; +} + +reverse[T](l: list of T): list of T +{ + r: list of T; + for(; l != nil; l = tl l) + r = hd l :: r; + return r; +} + +setattrs(lv: list of ref Attr, rv: list of ref Attr): list of ref Attr +{ + # new attributes + nl: list of ref Attr; + for(rl := rv; rl != nil; rl = tl rl) + if(anyattr(lv, (hd rl).name) == nil) + nl = ref(*hd rl) :: nl; + + # new values + for(; lv != nil; lv = tl lv){ + a := lookattr(rv, (hd lv).name); # won't take queries + if(a != nil) + nl = ref *a :: nl; + } + + return reverse(nl); +} + +delattrs(lv: list of ref Attr, rv: list of ref Attr): list of ref Attr +{ + nl: list of ref Attr; + for(; lv != nil; lv = tl lv) + if(anyattr(rv, (hd lv).name) == nil) + nl = hd lv :: nl; + return reverse(nl); +} + +matchattr(attrs: list of ref Attr, pat: ref Attr): int +{ + return (b := lookattr(attrs, pat.name)) != nil && (pat.tag == Aquery || b.val == pat.val); +} + +matchattrs(pub: list of ref Attr, secret: list of ref Attr, pats: list of ref Attr): int +{ + for(pl := pats; pl != nil; pl = tl pl) + if(!matchattr(pub, hd pl) && !matchattr(secret, hd pl)) + return 0; + return 1; +} + +sortattrs(attrs: list of ref Attr): list of ref Attr +{ + a := array[len attrs] of ref Attr; + i := 0; + for(l := attrs; l != nil; l = tl l) + a[i++] = hd l; + shellsort(a); + for(i = 0; i < len a; i++) + l = a[i] :: l; + return l; +} + +# sort into decreasing order (we'll reverse the list) +shellsort(a: array of ref Attr) +{ + n := len a; + for(gap := n; gap > 0; ) { + gap /= 2; + max := n-gap; + ex: int; + do{ + ex = 0; + for(i := 0; i < max; i++) { + j := i+gap; + if(a[i].name > a[j].name || a[i].name == nil) { + t := a[i]; a[i] = a[j]; a[j] = t; + ex = 1; + } + } + }while(ex); + } +} + +findkey(keys: array of ref Key, attrs: list of ref Attr): ref Key +{ + if(Debug) + sys->print("findkey %q\n", attrtext(attrs)); + for(i := 0; i < len keys; i++) + if((k := keys[i]) != nil && matchattrs(k.attrs, k.secrets, attrs)) + return k; + return nil; +} + +delkey(keys: array of ref Key, attrs: list of ref Attr): int +{ + nk := 0; + for(i := 0; i < len keys; i++) + if((k := keys[i]) != nil) + if(matchattrs(k.attrs, k.secrets, attrs)){ + nk++; + keys[i] = nil; + } + return nk; +} + +Key.mk(attrs: list of ref Attr): ref Key +{ + k := ref Key; + for(; attrs != nil; attrs = tl attrs){ + a := hd attrs; + if(a.name != nil){ + if(a.name[0] == '!') + k.secrets = a :: k.secrets; + else + k.attrs = a :: k.attrs; + } + } + if(k.attrs != nil || k.secrets != nil) + return k; + return nil; +} + +addkey(keys: array of ref Key, k: ref Key): array of ref Key +{ + for(i := 0; i < len keys; i++) + if(keys[i] == nil){ + keys[i] = k; + return keys; + } + n := array[len keys+1] of ref Key; + n[0:] = keys; + n[len keys] = k; + return n; +} + +Key.text(k: self ref Key): string +{ + s := attrtext(k.attrs); + if(s != nil && k.secrets != nil) + s[len s] = ' '; + return s + attrtext(k.secrets); +} + +Key.safetext(k: self ref Key): string +{ + s := attrtext(sortattrs(k.attrs)); + sp := s != nil; + for(sl := k.secrets; sl != nil; sl = tl sl){ + if(sp) + s[len s] = ' '; + s += sys->sprint("%s?", (hd sl).name); + } + return s; +} + +any(s: string, t: string): int +{ + for(i := 0; i < len s; i++) + for(j := 0; j < len t; j++) + if(s[i] == t[j]) + return 1; + return 0; +} + +IO.findkey(nil: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string) +{ + ea := parseline(extra); + for(; ea != nil; ea = tl ea) + attrs = hd ea :: attrs; + kc := chan of (ref Key, string); + keymanc <-= (attrs, 1, kc); # TO DO: 1 => 0 for not needed + return <-kc; +} + +IO.needkey(nil: self ref IO, attrs: list of ref Attr, extra: string): (ref Key, string) +{ + ea := parseline(extra); + for(; ea != nil; ea = tl ea) + attrs = hd ea :: attrs; + kc := chan of (ref Key, string); + keymanc <-= (attrs, 1, kc); + return <-kc; +} + +IO.read(io: self ref IO): array of byte +{ + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + * => + phase(rpc, "protocol phase error"); + Oauthinfo => + reply(rpc, "error authentication unfinished"); + Owrite => + io.rpc = rpc; + if(rpc.arg == nil) + rpc.arg = array[0] of byte; + return rpc.arg; + } + exit; +} + +IO.readn(io: self ref IO, n: int): array of byte +{ + while((buf := io.read()) != nil && len buf < n) + io.toosmall(n); + return buf; +} + +IO.write(io: self ref IO, buf: array of byte, n: int): int +{ + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + Oread => + if(rpc.nbytes-3 >= n){ + okdata(rpc, buf[0:n]); + return n; + } + io.toosmall(n+3); + Oauthinfo => + reply(rpc, "error authentication unfinished"); + * => + phase(rpc, "protocol phase error"); + } + exit; +} + +IO.ok(io: self ref IO) +{ + if(io.rpc != nil){ + reply(io.rpc, "ok"); + io.rpc = nil; + } +} + +IO.toosmall(io: self ref IO, n: int) +{ + if(io.rpc != nil){ + reply(io.rpc, sys->sprint("toosmall %d", n)); + io.rpc = nil; + } +} + +IO.error(io: self ref IO, s: string) +{ + if(io.rpc != nil){ + io.rpc.rc <-= (nil, "error "+s); + io.rpc = nil; + } +} + +IO.done(io: self ref IO, ai: ref Authinfo) +{ + io.f.ai = ai; + io.ok(); + while((rpc := rio(io.f)) != nil) + case rpc.cmd { + Oread or Owrite => + done(rpc, ai); + return; + * => + phase(rpc, "protocol phase error"); + } +} + +memrandom(a: array of byte, n: int) +{ + if(0){ + # speed up testing + for(i := 0; i < len a; i++) + a[i] = byte i; + return; + } + fd := sys->open("/dev/notquiterandom", Sys->OREAD); + if(fd == nil) + err("can't open /dev/notquiterandom"); + if(sys->read(fd, a, n) != n) + err("can't read /dev/notquiterandom"); +} + +eqbytes(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/auth/factotum/feedkey.b b/appl/cmd/auth/factotum/feedkey.b new file mode 100644 index 00000000..606f065a --- /dev/null +++ b/appl/cmd/auth/factotum/feedkey.b @@ -0,0 +1,321 @@ +implement Feedkey; + +# +# Copyright © 2004 Vita Nuova Holdings Limited +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "tk.m"; + tk: Tk; + +include "tkclient.m"; + tkclient: Tkclient; + +include "string.m"; + str: String; + +Feedkey: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +config := array[] of { + "frame .f", + "button .f.done -command {send cmd done} -text {Done}", + "frame .f.key -bg white", + "pack .f.key .f.done .f", + "update" +}; + +Debug: con 0; + +init(ctxt: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + tk = load Tk Tk->PATH; + tkclient = load Tkclient Tkclient->PATH; + str = load String String->PATH; + + needfile := "/mnt/factotum/needkey"; + if(Debug) + needfile = "/dev/null"; + + needs := chan of list of ref Attr; + acks := chan of int; + + sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2}); + + fd := sys->open(needfile, Sys->ORDWR); + if(fd == nil) + err(sys->sprint("can't open %s: %r", needfile)); + spawn needy(fd, needs, acks); + fd = nil; + + ctlfile := "/mnt/factotum/ctl"; + keyfd := sys->open(ctlfile, Sys->ORDWR); + if(keyfd == nil) + err(sys->sprint("can't open %s: %r", ctlfile)); + + tkclient->init(); + + spawn feedkey(ctxt, keyfd, needs, acks); +} + +feedkey(ctxt: ref Draw->Context, keyfd: ref Sys->FD, needs: chan of list of ref Attr, acks: chan of int) +{ + (top, tkctl) := tkclient->toplevel(ctxt, nil, "Need key", Tkclient->Appl); + + cmd := chan of string; + tk->namechan(top, cmd, "cmd"); + + for(i := 0; i < len config; i++) + tkcmd(top, config[i]); + tkclient->startinput(top, "ptr" :: nil); + tkclient->onscreen(top, nil); + if(!Debug) + tkclient->wmctl(top, "task"); + + attrs: list of ref Attr; + for(;;) alt{ + s :=<-tkctl or + s = <-top.ctxt.ctl or + s = <-top.wreq => + tkclient->wmctl(top, s); + p := <-top.ctxt.ptr => + tk->pointer(top, *p); + c := <-top.ctxt.kbd => + tk->keyboard(top, c); + + s := <-cmd => + case s { + "done" => + result := extract(top, ".f.key", attrs); + if(Debug) + sys->print("result: %s\n", attrtext(result)); + if(sys->fprint(keyfd, "key %s", attrtext(result)) < 0) + sys->fprint(sys->fildes(2), "feedkey: can't install key %q: %r\n", attrtext(result)); + acks <-= 0; + tkclient->wmctl(top, "task"); + tk->cmd(top, "pack forget .f.key"); + * => + sys->fprint(sys->fildes(2), "feedkey: odd command: %q\n", s); + } + + attrs = <-needs => + if(attrs == nil) + exit; + tkclient->startinput(top, "kbd" :: nil); + tkcmd(top, "destroy .f.key"); + tkcmd(top, "frame .f.key -bg white"); + populate(top, ".f.key", attrs); + tkcmd(top, "pack forget .f.done"); + tkcmd(top, "pack .f.key .f.done .f"); + tkcmd(top, "update"); + tkclient->wmctl(top, "unhide"); + } +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "feedkey: %s\n", s); + raise "fail:error"; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + return nil; + b := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return nil; + return string b[0:n]; +} + +tkcmd(top: ref Tk->Toplevel, cmd: string): string +{ + if(0) + sys->print("tk: %q\n", cmd); + r := tk->cmd(top, cmd); + if(r != nil && r[0] == '!') + sys->fprint(sys->fildes(2), "feedkey: tk: %q on %q\n", r, cmd); + return r; +} + +populate(top: ref Tk->Toplevel, tag: string, attrs: list of ref Attr) +{ + c := 0; + for(al := attrs; al != nil; al = tl al){ + a := hd al; + if(a.name == nil) + tkcmd(top, sys->sprint("entry %s.n%d -bg yellow", tag, c)); + else + tkcmd(top, sys->sprint("label %s.n%d -bg white -text '%s", tag, c, a.name)); + tkcmd(top, sys->sprint("label %s.e%d -bg white -text ' = ", tag, c)); + case a.tag { + Aquery => + show := ""; + if(a.name != nil && a.name[0] == '!') + show = " -show {•}"; + tkcmd(top, sys->sprint("entry %s.v%d%s -bg yellow", tag, c, show)); + if(a.val == nil && a.name == "user") + a.val = user(); + tkcmd(top, sys->sprint("%s.v%d insert 0 '%s", tag, c, a.val)); + tkcmd(top, sys->sprint("grid %s.n%d %s.e%d %s.v%d -in %s -sticky w -pady 1", tag, c, tag, c, tag, c, tag)); + Aval => + if(a.name != nil){ + val := a.val; + if(a.name[0] == '!') + val = "..."; # just in case + tkcmd(top, sys->sprint("label %s.v%d -bg white -text %s", tag, c, val)); + }else + tkcmd(top, sys->sprint("entry %s.v%d -bg yellow", tag, c)); + tkcmd(top, sys->sprint("grid %s.n%d %s.e%d %s.v%d -in %s -sticky w -pady 1", tag, c, tag, c, tag, c, tag)); + Aattr => + tkcmd(top, sys->sprint("grid %s.n%d x x -in %s -sticky w -pady 1", tag, c, tag)); + } + c++; + } +} + +extract(top: ref Tk->Toplevel, tag: string, attrs: list of ref Attr): list of ref Attr +{ + c := 0; + nl: list of ref Attr; + for(al := attrs; al != nil; al = tl al){ + a := ref *hd al; + if(a.tag == Aquery){ + a.val = tkcmd(top, sys->sprint("%s.v%d get", tag, c)); + if(a.name == nil) + a.name = tk->cmd(top, sys->sprint("%s.n%d get", tag, c)); # name might start with `!' + if(a.name != nil){ + a.tag = Aval; + nl = a :: nl; + } + }else + nl = a :: nl; + c++; + } + return nl; +} + +reverse[T](l: list of T): list of T +{ + rl: list of T; + for(; l != nil; l = tl l) + rl = hd l :: rl; + return rl; +} + +needy(fd: ref Sys->FD, needs: chan of list of ref Attr, acks: chan of int) +{ + if(Debug){ + for(;;){ + needs <-= parseline("proto=pass user? server=fred.com service=ftp confirm !password?"); + <-acks; + } + } + + buf := array[512] of byte; + while((n := sys->read(fd, buf, len buf)) > 0){ + s := string buf[0:n]; + for(i := 0; i < len s; i++) + if(s[i] == ' ') + break; + if(i >= len s) + continue; + attrs := parseline(s[i+1:]); + nl: list of ref Attr; + tag: ref Attr; + for(; attrs != nil; attrs = tl attrs){ + a := hd attrs; + if(a.name == "tag") + tag = a; + else + nl = a :: nl; + } + if(nl == nil) + continue; + attrs = reverse(ref Attr(Aquery, nil, nil) :: ref Attr(Aquery, nil, nil) :: nl); # add a few blank + if(attrs != nil && tag != nil && tag.val != nil){ + needs <-= attrs; + <-acks; + sys->fprint(fd, "tag=%d", int tag.val); + } + } + if(n < 0) + sys->fprint(sys->fildes(2), "feedkey: error reading needkey: %r\n"); + needs <-= nil; +} + +# need a library module + +Aattr, Aval, Aquery: con iota; + +Attr: adt { + tag: int; + name: string; + val: string; + + text: fn(a: self ref Attr): string; +}; + +parseline(s: string): list of ref Attr +{ + fld := str->unquoted(s); + rfld := fld; + for(fld = nil; rfld != nil; rfld = tl rfld) + fld = (hd rfld) :: fld; + attrs: list of ref Attr; + for(; fld != nil; fld = tl fld){ + n := hd fld; + a := ""; + tag := Aattr; + for(i:=0; i<len n; i++) + if(n[i] == '='){ + a = n[i+1:]; + n = n[0:i]; + tag = Aval; + } + if(len n == 0) + continue; + if(tag == Aattr && len n > 1 && n[len n-1] == '?'){ + tag = Aquery; + n = n[0:len n-1]; + } + attrs = ref Attr(tag, n, a) :: attrs; + } + return attrs; +} + +Attr.text(a: self ref Attr): string +{ + case a.tag { + Aattr => + return a.name; + Aval => + return sys->sprint("%q=%q", a.name, a.val); + Aquery => + return a.name+"?"; + * => + return "??"; + } +} + +attrtext(attrs: list of ref Attr): string +{ + s := ""; + sp := 0; + for(; attrs != nil; attrs = tl attrs){ + if(sp) + s[len s] = ' '; + sp = 1; + s += (hd attrs).text(); + } + return s; +} diff --git a/appl/cmd/auth/factotum/mkfile b/appl/cmd/auth/factotum/mkfile new file mode 100644 index 00000000..1979a14c --- /dev/null +++ b/appl/cmd/auth/factotum/mkfile @@ -0,0 +1,27 @@ +<../../../../mkconfig + +DIRS=\ + proto\ + +TARG=\ + factotum.dis\ + feedkey.dis\ + rpc.dis\ + +SYSMODULES=\ + arg.m\ + keyring.m\ + security.m\ + rand.m\ + sys.m\ + draw.m\ + bufio.m\ + string.m\ + +MODULES=\ + authio.m\ + +DISBIN=$ROOT/dis/auth + +<$ROOT/mkfiles/mkdis +<$ROOT/mkfiles/mksubdirs diff --git a/appl/cmd/auth/factotum/proto/infauth.b b/appl/cmd/auth/factotum/proto/infauth.b new file mode 100644 index 00000000..244979bc --- /dev/null +++ b/appl/cmd/auth/factotum/proto/infauth.b @@ -0,0 +1,362 @@ +implement Authproto; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "keyring.m"; + keyring: Keyring; + IPint: import keyring; + SK, PK, Certificate, DigestState: import Keyring; +include "security.m"; +include "bufio.m"; +include "sexprs.m"; + sexprs: Sexprs; + Sexp: import sexprs; +include "spki.m"; + spki: SPKI; +include "daytime.m"; + daytime: Daytime; +include "keyreps.m"; + keyreps: Keyreps; + Keyrep: import keyreps; +include "../authio.m"; + authio: Authio; + Aattr, Aval, Aquery: import Authio; + Attr, IO, Key, Authinfo: import authio; + +# at end of authentication, sign a hash of the authenticated username and +# a secret known only to factotum. that certificate can act as +# a later proof that this factotum has authenticated that user, +# and hence factotum will disclose certificates that allow disclosure +# only to that username. + +Debug: con 0; + +Maxmsg: con 4000; + +Error0, Error1: exception(string); + +init(f: Authio): string +{ + authio = f; + sys = load Sys Sys->PATH; + spki = load SPKI SPKI->PATH; + spki->init(); + sexprs = load Sexprs Sexprs->PATH; + sexprs->init(); + keyring = load Keyring Keyring->PATH; + daytime = load Daytime Daytime->PATH; + keyreps = load Keyreps Keyreps->PATH; + keyreps->init(); + return nil; +} + +interaction(attrs: list of ref Attr, io: ref IO): string +{ + ai: ref Authinfo; + (key, err) := io.findkey(attrs, "proto=infauth"); + if(key == nil) + return err; + info: ref Keyring->Authinfo; + (info, err) = keytoauthinfo(key); + if(info == nil) + return err; + anysigner := int authio->lookattrval(key.attrs, "anysigner"); + rattrs: list of ref Sexp; + { + # send auth protocol version number + sendmsg(io, array of byte "1"); + + # get auth protocol version number + if(int string getmsg(io) != 1) + raise Error0("incompatible authentication protocol"); + + # generate alpha**r0 + p := info.p; + low := p.shr(p.bits()/4); + r0 := rand(low, p, Random->NotQuiteRandom); + αr0 := info.alpha.expmod(r0, p); + # trim(αr0); the IPint library should do this for us, i think. + + # send alpha**r0 mod p, mycert, and mypk + sendmsg(io, array of byte αr0.iptob64()); + sendmsg(io, array of byte keyring->certtostr(info.cert)); + sendmsg(io, array of byte keyring->pktostr(info.mypk)); + + # get alpha**r1 mod p, hiscert, hispk + αr1 := IPint.b64toip(string getmsg(io)); + + # trying a fast one + if(p.cmp(αr1) <= 0) + raise Error0("implausible parameter value"); + + # if alpha**r1 == alpha**r0, someone may be trying a replay + if(αr0.eq(αr1)) + raise Error0("possible replay attack"); + + hiscert := keyring->strtocert(string getmsg(io)); + if(hiscert == nil && !anysigner) + raise Error0(sys->sprint("bad certificate: %r")); + + buf := getmsg(io); + hispk := keyring->strtopk(string buf); + if(!anysigner){ + # verify their public key + if(verify(info.spk, hiscert, buf) == 0) + raise Error0("pk doesn't match certificate"); # likely the signers don't match. + + # check expiration date - in seconds of epoch + if(hiscert.exp != 0 && hiscert.exp <= now()) + raise Error0("certificate expired"); + } + buf = nil; + + # sign alpha**r0 and alpha**r1 and send + αcert := sign(info.mysk, "sha", 0, array of byte (αr0.iptob64() + αr1.iptob64())); + sendmsg(io, array of byte keyring->certtostr(αcert)); + + # get signature of alpha**r1 and alpha**r0 and verify + αcert = keyring->strtocert(string getmsg(io)); + if(αcert == nil) + raise Error0("alpha**r1 doesn't match certificate"); + + if(verify(hispk, αcert, array of byte (αr1.iptob64() + αr0.iptob64())) == 0) + raise Error0(sys->sprint("bad certificate: %r")); + + ai = ref Authinfo; + # we are now authenticated and have a common secret, alpha**(r0*r1) + if(!anysigner) + rattrs = sl(ss("signer") :: principal(info.spk) :: nil) :: rattrs; + rattrs = sl(ss("remote-pk") :: principal(hispk) :: nil) :: rattrs; + rattrs = sl(ss("local-pk") :: principal(info.mypk) :: nil) :: rattrs; + rattrs = sl(ss("secret") :: sb(αr1.expmod(r0, p).iptobytes()) :: nil) :: rattrs; + ai.suid = hispk.owner; + ai.cuid = info.mypk.owner; + sendmsg(io, array of byte "OK"); + }exception e{ + Error0 => + err = e; + senderr(io, e); + break; + Error1 => + senderr(io, "missing your authentication data"); + x: string = e; + return "remote: "+x; + } + + { + while(string getmsg(io) != "OK") + ; + }exception e{ + Error0 => + return e; + Error1 => + x: string = e; + return "remote: "+x; + } + if(err != nil) + return err; + + return negotiatecrypto(io, key, ai, rattrs); +} + +negotiatecrypto(io: ref IO, key: ref Key, ai: ref Authinfo, attrs: list of ref Sexp): string +{ + role := authio->lookattrval(key.attrs, "role"); + alg: string; + { + if(role == "client"){ + alg = authio->lookattrval(key.attrs, "alg"); + if(alg == nil) + alg = "md5/rc4_256"; + sendmsg(io, array of byte alg); + }else if(role == "server"){ + alg = string getmsg(io); + if(!algcompatible(alg, sys->tokenize(authio->lookattrval(key.attrs, "algs"), " ").t1)) + raise Error0("unsupported client algorithm"); + } + }exception e{ + Error0 or + Error1 => + return e; + } + + if(alg != nil) + attrs = sl(ss("alg") :: ss(alg) :: nil) :: attrs; + ai.secret = sl(attrs).pack(); + + io.done(ai); + return nil; +} + +algcompatible(nil: string, nil: list of string): int +{ + return 1; # XXX +} + +principal(pk: ref Keyring->PK): ref Sexp +{ + return spki->(Keyrep.pk(pk).mkkey()).sexp(); +} + +ipint(i: int): ref IPint +{ + return IPint.inttoip(i); +} + +rand(p, q: ref IPint, nil: int): ref IPint +{ + if(p.cmp(q) > 0) + (p, q) = (q, p); + diff := q.sub(p); + q = nil; + if(diff.cmp(ipint(2)) < 0){ + sys->print("rand range must be at least 2"); + return IPint.inttoip(0); + } + l := diff.bits(); + T := ipint(1).shl(l); + l = ((l + 7) / 8) * 8; + slop := T.div(diff).t1; + r: ref IPint; + do{ + r = IPint.random(0, l); + }while(r.cmp(slop) < 0); + r = r.div(diff).t1.add(p); + return r; +} + +now(): int +{ + return daytime->now(); +} + +Hashfn: type ref fn(a: array of byte, alen: int, digest: array of byte, state: ref DigestState): ref DigestState; + +hashalg(ha: string): Hashfn +{ + case ha { + "sha" or + "sha1" => + return keyring->sha1; + "md4" => + return keyring->md4; + "md5" => + return keyring->md5; + } + return nil; +} + +sign(sk: ref SK, ha: string, exp: int, buf: array of byte): ref Certificate +{ + state := hashalg(ha)(buf, len buf, nil, nil); + return keyring->sign(sk, exp, state, ha); +} + +verify(pk: ref PK, cert: ref Certificate, buf: array of byte): int +{ + state := hashalg(cert.ha)(buf, len buf, nil, nil); + return keyring->verify(pk, cert, state); +} + +getmsg(io: ref IO): array of byte raises (Error0, Error1) +{ + while((buf := io.read()) == nil || (n := len buf) < 5) + io.toosmall(5); + if(len buf != 5) + raise Error0("io error: (impossible?) msg length " + string n); + h := string buf; + if(h[0] == '!') + m := int h[1:]; + else + m = int h; + while((buf = io.read()) == nil || (n = len buf) < m) + io.toosmall(m); + if(len buf != m) + raise Error0("io error: (impossible?) msg length " + string m); + if(h[0] == '!'){ +sys->print("got remote error: %s, len %d\n", string buf, len string buf); + raise Error1(string buf); + } + return buf; +} + +sendmsg(io: ref IO, buf: array of byte) +{ + h := sys->aprint("%4.4d\n", len buf); + io.write(h, len h); + io.write(buf, len buf); +} + +senderr(io: ref IO, e: string) +{ + buf := array of byte e; + h := sys->aprint("!%3.3d\n", len buf); + io.write(h, len h); + io.write(buf, len buf); +} + +keytoauthinfo(key:ref Key): (ref Keyring->Authinfo, string) +{ + if((s := authio->lookattrval(key.secrets, "!authinfo")) == nil){ + # XXX could look up authinfo by hash at this point + return (nil, "no authinfo attribute"); + } + + return strtoauthinfo(s); +} + +strtoauthinfo(s: string): (ref Keyring->Authinfo, string) +{ + (se, err, nil) := Sexp.parse(s); + if(se == nil) + return (nil, err); + els := se.els(); + if(len els != 5) + return (nil, "bad authinfo contents"); + ai := ref Keyring->Authinfo; + if((ai.spk = keyring->strtopk((hd els).astext())) == nil) + return (nil, "bad signer public key"); + els = tl els; + if((ai.cert = keyring->strtocert((hd els).astext())) == nil) + return (nil, "bad certificate"); + els = tl els; + if((ai.mysk = keyring->strtosk((hd els).astext())) == nil) + return (nil, "bad secret/public key"); + if((ai.mypk = keyring->sktopk(ai.mysk)) == nil) + return (nil, "cannot make pk from sk"); + els = tl els; + if((ai.alpha = IPint.bytestoip((hd els).asdata())) == nil) + return (nil, "bad value for alpha"); + els = tl els; + if((ai.p = IPint.bytestoip((hd els).asdata())) == nil) + return (nil, "bad value for p"); + return (ai, nil); +} + +authinfotostr(ai: ref Keyring->Authinfo): string +{ + return (ref Sexp.List( + ss(keyring->pktostr(ai.spk)) :: + ss(keyring->certtostr(ai.cert)) :: + ss(keyring->sktostr(ai.mysk)) :: + sb(ai.alpha.iptobytes()) :: + sb(ai.p.iptobytes()) :: + nil + )).b64text(); +} + +ss(s: string): ref Sexp.String +{ + return ref Sexp.String(s, nil); +} + +sb(d: array of byte): ref Sexp.Binary +{ + return ref Sexp.Binary(d, nil); +} + +sl(l: list of ref Sexp): ref Sexp +{ + return ref Sexp.List(l); +} diff --git a/appl/cmd/auth/factotum/proto/keyreps.b b/appl/cmd/auth/factotum/proto/keyreps.b new file mode 100644 index 00000000..5fdac2c0 --- /dev/null +++ b/appl/cmd/auth/factotum/proto/keyreps.b @@ -0,0 +1,173 @@ +implement Keyreps; +include "sys.m"; + sys: Sys; +include "keyring.m"; + kr: Keyring; + IPint: import kr; +include "sexprs.m"; +include "spki.m"; +include "encoding.m"; + base64: Encoding; +include "keyreps.m"; + +init() +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + base64 = load Encoding Encoding->BASE64PATH; +} + +keyextract(flds: list of string, names: list of (string, int)): list of (string, ref IPint) +{ + a := array[len flds] of ref IPint; + for(i := 0; i < len a; i++){ + a[i] = IPint.b64toip(hd flds); + flds = tl flds; + } + rl: list of (string, ref IPint); + for(; names != nil; names = tl names){ + (n, p) := hd names; + if(p < len a) + rl = (n, a[p]) :: rl; + } + return revt(rl); +} + +Keyrep.pk(pk: ref Keyring->PK): ref Keyrep.PK +{ + s := kr->pktostr(pk); + (nf, flds) := sys->tokenize(s, "\n"); + if((nf -= 2) < 0) + return nil; + case hd flds { + "rsa" => + return ref Keyrep.PK(hd flds, hd tl flds, + keyextract(tl tl flds, list of {("e",1), ("n",0)})); + "elgamal" or "dsa" => + return ref Keyrep.PK(hd flds, hd tl flds, + keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2)})); + * => + return nil; + } +} + +Keyrep.sk(pk: ref Keyring->SK): ref Keyrep.SK +{ + s := kr->pktostr(pk); + (nf, flds) := sys->tokenize(s, "\n"); + if((nf -= 2) < 0) + return nil; + case hd flds { + "rsa" => + return ref Keyrep.SK(hd flds, hd tl flds, + keyextract(tl tl flds,list of {("e",1), ("n",0), ("!dk",2), ("!p",3), ("!q",4), ("!kp",5), ("!kq",6), ("!c2",7)})); + "elgamal" or "dsa" => + return ref Keyrep.SK(hd flds, hd tl flds, + keyextract(tl tl flds, list of {("p",0), ("alpha",1), ("key",2), ("!secret",3)})); + * => + return nil; + } +} + +Keyrep.get(k: self ref Keyrep, n: string): ref IPint +{ + for(el := k.els; el != nil; el = tl el) + if((hd el).t0 == n) + return (hd el).t1; + return nil; +} + +Keyrep.getb(k: self ref Keyrep, n: string): array of byte +{ + v := k.get(n); + if(v == nil) + return nil; + return pre0(v.iptobebytes()); +} + +pre0(a: array of byte): array of byte +{ + for(i:=0; i<len a-1; i++) + if(a[i] != a[i+1] && (a[i] != byte 0 || (int a[i+1] & 16r80) != 0)) + break; + if(i > 0) + a = a[i:]; + if(len a < 1 || (int a[0] & 16r80) == 0) + return a; + b := array[len a + 1] of byte; + b[0] = byte 0; + b[1:] = a; + return b; +} + +Keyrep.mkpk(k: self ref Keyrep): (ref Keyring->PK, int) +{ + case k.alg { + "rsa" => + e := k.get("e"); + n := k.get("n"); + return (kr->strtopk(sys->sprint("rsa\n%s\n%s\n%s\n", k.owner, n.iptob64(), e.iptob64())), n.bits()); + * => + raise "Keyrep: unknown algorithm" + k.alg; + } +} + +Keyrep.mksk(k: self ref Keyrep): ref Keyring->SK +{ + case k.alg { + "rsa" => + e := k.get("e"); + n := k.get("n"); + dk := k.get("!dk"); + p := k.get("!p"); + q := k.get("!q"); + kp := k.get("!kp"); + kq := k.get("!kq"); + c12 := k.get("!c2"); + return kr->strtosk(sys->sprint("rsa\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + k.owner, n.iptob64(), e.iptob64(), dk.iptob64(), p.iptob64(), q.iptob64(), + kp.iptob64(), kq.iptob64(), c12.iptob64())); + * => + raise "Keyrep: unknown algorithm"; + } +} + +Keyrep.eq(k1: self ref Keyrep, k2: ref Keyrep): int +{ + # n but n is small + for(l1 := k1.els; l1 != nil; l1 = tl l1){ + (n, v1) := hd l1; + v2 := k2.get(n); + if(v2 == nil || !v1.eq(v2)) + return 0; + } + for(l2 := k2.els; l2 != nil; l2 = tl l2) + if(k1.get((hd l2).t0) == nil) + return 0; + return 1; +} + +Keyrep.mkkey(kr: self ref Keyrep): ref SPKI->Key +{ + k := ref SPKI->Key; + (k.pk, k.nbits) = kr.mkpk(); + k.sk = kr.mksk(); + return k; +} + +sig2icert(sig: ref SPKI->Signature, signer: string, exp: int): ref Keyring->Certificate +{ + if(sig.sig == nil) + return nil; + s := sys->sprint("%s\n%s\n%s\n%d\n%s\n", "rsa", sig.hash.alg, signer, exp, base64->enc((hd sig.sig).t1)); +#sys->print("alg %s *** %s\n", sig.sa, base64->enc((hd sig.sig).t1)); + return kr->strtocert(s); +} + +revt[S,T](l: list of (S,T)): list of (S,T) +{ + rl: list of (S,T); + for(; l != nil; l = tl l) + rl = hd l :: rl; + return rl; +} diff --git a/appl/cmd/auth/factotum/proto/keyreps.m b/appl/cmd/auth/factotum/proto/keyreps.m new file mode 100644 index 00000000..ddfd7f0d --- /dev/null +++ b/appl/cmd/auth/factotum/proto/keyreps.m @@ -0,0 +1,23 @@ +Keyreps: module +{ + PATH: con "/dis/lib/spki/keyreps.dis"; + init: fn(); + Keyrep: adt { + alg: string; + owner: string; + els: list of (string, ref Keyring->IPint); + pick{ # keeps a type distance between public and private keys + PK => + SK => + } + + pk: fn(pk: ref Keyring->PK): ref Keyrep.PK; + sk: fn(sk: ref Keyring->SK): ref Keyrep.SK; + mkpk: fn(k: self ref Keyrep): (ref Keyring->PK, int); + mksk: fn(k: self ref Keyrep): ref Keyring->SK; + get: fn(k: self ref Keyrep, n: string): ref Keyring->IPint; + getb: fn(k: self ref Keyrep, n: string): array of byte; + eq: fn(k1: self ref Keyrep, k2: ref Keyrep): int; + mkkey: fn(k: self ref Keyrep): ref SPKI->Key; + }; +}; diff --git a/appl/cmd/auth/factotum/proto/mkfile b/appl/cmd/auth/factotum/proto/mkfile new file mode 100644 index 00000000..efdd73da --- /dev/null +++ b/appl/cmd/auth/factotum/proto/mkfile @@ -0,0 +1,22 @@ +<../../../../../mkconfig + +TARG=\ + p9any.dis\ + pass.dis\ + +SYSMODULES=\ + factotum.m\ + keyring.m\ + security.m\ + rand.m\ + sys.m\ + draw.m\ + bufio.m\ + string.m\ + +MODULES=\ + ../authio.m\ + +DISBIN=$ROOT/dis/auth/proto + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/auth/factotum/proto/p9any.b b/appl/cmd/auth/factotum/proto/p9any.b new file mode 100644 index 00000000..1668a701 --- /dev/null +++ b/appl/cmd/auth/factotum/proto/p9any.b @@ -0,0 +1,232 @@ +implement Authproto; + +# currently includes p9sk1 + +include "sys.m"; + sys: Sys; + Rread, Rwrite: import Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +include "auth9.m"; + auth9: Auth9; + ANAMELEN, AERRLEN, DOMLEN, DESKEYLEN, CHALLEN, SECRETLEN: import Auth9; + TICKREQLEN, TICKETLEN, AUTHENTLEN: import Auth9; + Ticketreq, Ticket, Authenticator: import auth9; + +include "../authio.m"; + authio: Authio; + Aattr, Aval, Aquery: import Authio; + Attr, IO, Key, Authinfo: import authio; + netmkaddr, eqbytes, memrandom: import authio; + +include "encoding.m"; + base16: Encoding; + +Debug: con 0; + +# init, addkey, closekey, write, read, close, keyprompt + +init(f: Authio): string +{ + authio = f; + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + auth9 = load Auth9 Auth9->PATH; + auth9->init(); + base16 = load Encoding Encoding->BASE16PATH; + return nil; +} + +version := 1; + +interaction(attrs: list of ref Attr, io: ref IO): string +{ + return p9any(io); +} + +p9any(io: ref IO): string +{ + while((buf := io.read()) == nil || (n := len buf) == 0 || buf[n-1] != byte 0) + io.toosmall(2048); + s := string buf[0:n-1]; + if(Debug) + sys->print("s: %q\n", s); + (nil, flds) := sys->tokenize(s, " \t"); + if(flds != nil && len hd flds >= 2 && (hd flds)[0:2] == "v."){ + if(hd flds == "v.2"){ + version = 2; + flds = tl flds; + if(Debug) + sys->print("version 2\n"); + }else + return "p9any: unknown version"; + } + doms: list of string; + for(; flds != nil; flds = tl flds){ + (nf, subf) := sys->tokenize(hd flds, "@"); + if(nf == 2 && hd subf == "p9sk1") + doms = hd tl subf :: doms; + } + if(doms == nil) + return "p9any: unsupported protocol"; + if(Debug){ + for(l := doms; l != nil; l = tl l) + sys->print("dom: %q\n", hd l); + } + r := array of byte ("p9sk1 "+hd doms); + buf[0:] = r; + buf[len r] = byte 0; + io.write(buf, len r + 1); + if(version == 2){ + b := io.readn(3); + if(b == nil || b[0] != byte 'O' || b[1] != byte 'K' || b[2] != byte 0) + return "p9any: AS protocol botch: not OK"; + if(Debug) + sys->print("OK\n"); + } + return p9sk1client(io, hd doms); +} + +#p9sk1: +# C->S: nonce-C +# S->C: nonce-S, uid-S, domain-S +# C->A: nonce-S, uid-S, domain-S, uid-C, factotum-C +# A->C: Kc{nonce-S, uid-C, uid-S, Kn}, Ks{nonce-S, uid-C, uid-S, K-n} +# C->S: Ks{nonce-S, uid-C, uid-S, K-n}, Kn{nonce-S, counter} +# S->C: Kn{nonce-C, counter} + +#asserts that uid-S and uid-C share new secret Kn +#increment the counter to reuse the ticket. + +p9sk1client(io: ref IO, udom: string): string +{ + + # C->S: nonce-C + cchal := array[CHALLEN] of byte; + memrandom(cchal, CHALLEN); + if(io.write(cchal, len cchal) != len cchal) + return sys->sprint("p9sk1: can't write cchal: %r"); + + # S->C: nonce-S, uid-S, domain-S + trbuf := io.readn(TICKREQLEN); + if(trbuf == nil) + return sys->sprint("p9sk1: can't read ticketreq: %r"); + + (nil, tr) := Ticketreq.unpack(trbuf); + if(tr == nil) + return "p9sk1: can't unpack ticket request"; + if(Debug) + sys->print("ticketreq: type=%d authid=%q authdom=%q chal= hostid=%q uid=%q\n", + tr.rtype, tr.authid, tr.authdom, tr.hostid, tr.uid); + + (mykey, diag) := io.findkey(nil, sys->sprint("dom=%q proto=p9sk1 user? !password?", udom)); + if(mykey == nil) + return "can't find key: "+diag; + ukey: array of byte; + if((a := authio->lookattrval(mykey.secrets, "!hex")) != nil){ + ukey = base16->dec(a); + if(len ukey != DESKEYLEN) + return "p9sk1: invalid !hex key"; + }else if((a = authio->lookattrval(mykey.secrets, "!password")) != nil) + ukey = auth9->passtokey(a); + else + return "no !password (or !hex) in key"; + + # A->C: Kc{nonce-S, uid-C, uid-S, Kn}, Ks{nonce-S, uid-C, uid-S, K-n} + user := authio->lookattrval(mykey.attrs, "user"); + if(user == nil) + user = authio->user(); # shouldn't happen + tr.rtype = Auth9->AuthTreq; + tr.hostid = user; + tr.uid = tr.hostid; # not speaking for anyone else + (tick, serverbits) := getastickets(tr, ukey); + if(tick == nil) + return sys->sprint("p9sk1: getasticket failed: %r"); + if(tick.num != Auth9->AuthTc) + return "p9sk1: getasticket: failed: wrong key?"; + if(Debug) + sys->print("ticket: num=%d chal= cuid=%q suid=%q key=\n", tick.num, tick.cuid, tick.suid); + + # C->S: Ks{nonce-S, uid-C, uid-S, K-n}, Kn{nonce-S, counter} + ar := ref Authenticator; + ar.num = Auth9->AuthAc; + ar.chal = tick.chal; + ar.id = 0; + obuf := array[TICKETLEN+AUTHENTLEN] of byte; + obuf[0:] = serverbits; + obuf[TICKETLEN:] = ar.pack(tick.key); + if(io.write(obuf, len obuf) != len obuf) + return "p9sk1: error writing authenticator: %r"; + + # S->C: Kn{nonce-C, counter} + sbuf := io.readn(AUTHENTLEN); + if(sbuf == nil) + return sys->sprint("p9sk1: can't read server's authenticator: %r"); + (nil, ar) = Authenticator.unpack(sbuf, tick.key); + if(ar.num != Auth9->AuthAs || !eqbytes(ar.chal, cchal) || ar.id != 0) + return "invalid authenticator from server"; + + ai := ref Authinfo(tick.cuid, tick.suid, nil, auth9->des56to64(tick.key)); + io.done(ai); + + return nil; +} + +getastickets(tr: ref Ticketreq, key: array of byte): (ref Ticket, array of byte) +{ + afd := authdial(nil, tr.authdom); + if(afd == nil) + return (nil, nil); + return auth9->_asgetticket(afd, tr, key); +} + +# +# where to put the following functions? +# + +csgetvalue(netroot: string, keytag: string, keyval: string, needtag: string): string +{ + cs := "/net/cs"; + if(netroot != nil) + cs = netroot+"/cs"; + fd := sys->open(cs, Sys->ORDWR); # TO DO: choice of root + if(fd == nil) + return nil; + if(sys->fprint(fd, "!%s=%s %s=*", keytag, keyval, needtag) < 0) + return nil; + sys->seek(fd, big 0, 0); + buf := array[1024] of byte; + while((n := sys->read(fd, buf, len buf)) > 0){ + al := authio->parseline(string buf[0:n]); # assume the conventions match factotum's + for(; al != nil; al = tl al) + if((hd al).name == needtag) + return (hd al).val; + } + return nil; +} + +authdial(netroot: string, dom: string): ref Sys->FD +{ + p: string; + if(dom != nil){ + # look up an auth server in an authentication domain + p = csgetvalue(netroot, "authdom", dom, "auth"); + + # if that didn't work, just try the IP domain + if(p == nil) + p = csgetvalue(netroot, "dom", dom, "auth"); + if(p == nil) + p = "$auth"; # temporary ... + if(p == nil){ + sys->werrstr("no auth server found for "+dom); + return nil; + } + }else + p = "$auth"; # look for one relative to my machine + (nil, conn) := sys->dial(netmkaddr(p, netroot, "ticket"), nil); + return conn.dfd; +} diff --git a/appl/cmd/auth/factotum/proto/pass.b b/appl/cmd/auth/factotum/proto/pass.b new file mode 100644 index 00000000..9c4462b3 --- /dev/null +++ b/appl/cmd/auth/factotum/proto/pass.b @@ -0,0 +1,29 @@ +implement Authproto; + +include "sys.m"; + sys: Sys; + +include "../authio.m"; + authio: Authio; + Attr, IO: import authio; + +init(f: Authio): string +{ + sys = load Sys Sys->PATH; + authio = f; + return nil; +} + +interaction(attrs: list of ref Attr, io: ref Authio->IO): string +{ + (key, err) := io.findkey(attrs, "user? !password?"); + if(key == nil) + return err; + user := authio->lookattrval(key.attrs, "user"); + if(user == nil) + return "unknown user"; + pass := authio->lookattrval(key.secrets, "!password"); + a := sys->aprint("%q %q", user, pass); + io.write(a, len a); + return nil; +} diff --git a/appl/cmd/auth/factotum/rpc.b b/appl/cmd/auth/factotum/rpc.b new file mode 100644 index 00000000..220980a8 --- /dev/null +++ b/appl/cmd/auth/factotum/rpc.b @@ -0,0 +1,68 @@ +implement Rpcio; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Rpcio: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: rpc\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + cantload(Bufio->PATH); + + file := "/mnt/factotum/rpc"; + if(len args > 1) + file = hd tl args; + rfd := sys->open(file, Sys->ORDWR); + if(rfd == nil){ + sys->fprint(sys->fildes(2), "rpc: can't open %s: %r\n", file); + raise "fail:load"; + } + f := bufio->fopen(sys->fildes(0), Sys->OREAD); + for(;;){ + sys->print("> "); + s := f.gets('\n'); + if(s == nil) + break; + rpc(rfd, s[0:len s-1]); + } +} + +cantload(s: string) +{ + sys->fprint(sys->fildes(2), "csquery: can't load %s: %r\n", s); + raise "fail:load"; +} + +rpc(f: ref Sys->FD, addr: string) +{ + b := array of byte addr; + if(sys->write(f, b, len b) > 0){ + sys->seek(f, big 0, Sys->SEEKSTART); + buf := array[256] of byte; + if((n := sys->read(f, buf, len buf)) > 0) + sys->print("%s\n", string buf[0:n]); + if(n >= 0) + return; + } + sys->print("!%r\n"); +} diff --git a/appl/cmd/auth/getpk.b b/appl/cmd/auth/getpk.b new file mode 100644 index 00000000..24283340 --- /dev/null +++ b/appl/cmd/auth/getpk.b @@ -0,0 +1,83 @@ +implement Getpk; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; +include "keyring.m"; + keyring: Keyring; + +Getpk: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +badmodule(p: string) +{ + sys->fprint(sys->fildes(2), "getpk: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + if(keyring == nil) + badmodule(Keyring->PATH); + arg := load Arg Arg->PATH; + if(arg == nil) + badmodule(Arg->PATH); + arg->init(argv); + arg->setusage("usage: getpk [-asu] file..."); + aflag := 0; + sflag := 0; + uflag := 0; + while((opt := arg->opt()) != 0){ + case opt { + 's' => + sflag++; + 'a' => + aflag++; + 'u' => + uflag++; + * => + arg->usage(); + } + } + argv = arg->argv(); + if(argv == nil) + arg->usage(); + multi := len argv > 1; + for(; argv != nil; argv = tl argv){ + info := keyring->readauthinfo(hd argv); + if(info == nil){ + sys->fprint(sys->fildes(2), "getpk: cannot read %s: %r\n", hd argv); + continue; + } + pk := info.mypk; + if(sflag) + pk = info.spk; + s := keyring->pktostr(pk); + if(!aflag) + s = hex(hash(s)); + if(multi) + s = hd argv + ": " + s; + if(uflag) + s += " " + pk.owner; + sys->print("%s\n", s); + } +} + +hash(s: string): array of byte +{ + d := array of byte s; + digest := array[Keyring->SHA1dlen] of byte; + keyring->sha1(d, len d, digest, nil); + return digest; +} + +hex(a: array of byte): string +{ + s := ""; + for(i := 0; i < len a; i++) + s += sys->sprint("%2.2ux", int a[i]); + return s; +} diff --git a/appl/cmd/auth/keyfs.b b/appl/cmd/auth/keyfs.b new file mode 100644 index 00000000..f81c3ee7 --- /dev/null +++ b/appl/cmd/auth/keyfs.b @@ -0,0 +1,806 @@ +implement Keyfs; + +# +# Copyright © 2002,2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + Qid: import Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + AESbsize, AESstate: import kr; + +include "rand.m"; + rand: Rand; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + +include "styxservers.m"; + styxservers: Styxservers; + Fid, Styxserver, Navigator, Navop: import styxservers; + Enotfound, Eperm, Ebadarg, Edot: import styxservers; + +include "arg.m"; + +Keyfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +User: adt +{ + x: int; # table index + name: string; + secret: array of byte; # eg, password hashed by SHA1 + expire: int; # expiration time (epoch seconds) + status: int; + failed: int; # count of failed attempts + path: big; +}; + +Qroot, Quser, Qsecret, Qlog, Qstatus, Qexpire: con iota; +files := array[] of { + (Qsecret, "secret"), + (Qlog, "log"), + (Qstatus, "status"), + (Qexpire, "expire") +}; + +Maxsecret: con 255; +Maxname: con 255; +Maxfail: con 50; +users: array of ref User; +Sok, Sdisabled: con iota; +status := array[] of {Sok => "ok", Sdisabled => "disabled" }; +Never: con 0; # expiry time + +Eremoved: con "user has been removed"; + +pathgen := 0; +keyversion := 0; +user: string; +now: int; + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: keyfs [-D] [-m mountpoint] [keyfile]\n"); + raise "fail:usage"; +} + +nomod(s: string) +{ + sys->fprint(sys->fildes(2), "keyfs: can't load %s: %r\n", s); + raise "fail:load"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->NEWPGRP, nil); + kr = load Keyring Keyring->PATH; + if(kr == nil) + nomod(Keyring->PATH); + styx = load Styx Styx->PATH; + if(styx == nil) + nomod(Styx->PATH); + styxservers = load Styxservers Styxservers->PATH; + if(styxservers == nil) + nomod(Styxservers->PATH); + rand = load Rand Rand->PATH; + if(rand == nil) + nomod(Rand->PATH); + + styx->init(); + styxservers->init(styx); + rand->init(sys->millisec()); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + arg->setusage("keyfs [-m mntpt] [-D] [-n nvramfile] [keyfile]"); + mountpt := "/mnt/keys"; + keyfile := "/keydb/keys"; + nvram: string; + while((o := arg->opt()) != 0) + case o { + 'm' => + mountpt = arg->earg(); + 'D' => + styxservers->traceset(1); + 'n' => + nvram = arg->earg(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(args != nil) + keyfile = hd args; + + pwd, err: string; + if(nvram != nil){ + pwd = rf(nvram); + if(pwd == nil) + error(sys->sprint("can't read %s: %r", nvram)); + } + if(pwd == nil){ + (pwd, err) = readconsline("Key: ", 1); + if(pwd == nil || err == "exit") + exit; + if(err != nil) + error(sys->sprint("couldn't get key: %s", err)); + (rc, d) := sys->stat(keyfile); + if(rc == -1 || d.length == big 0){ + pwd0 := pwd; + (pwd, err) = readconsline("Confirm key: ", 1); + if(pwd == nil || err == "exit") + exit; + if(pwd != pwd0) + error("key mismatch"); + for(i := 0; i < len pwd0; i++) + pwd0[i] = ' '; # clear it out + } + } + + thekey = hashkey(pwd); + for(i:=0; i<len pwd; i++) + pwd[i] = ' '; # clear it out + + sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil); # immediately avoid sharing keyfd + + readkeys(keyfile); + + user = rf("/dev/user"); + if(user == nil) + user = "keyfs"; + + fds := array[2] of ref Sys->FD; + if(sys->pipe(fds) < 0) + error(sys->sprint("can't create pipe: %r")); + + navops := chan of ref Navop; + spawn navigator(navops); + + (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qroot); + fds[0] = nil; + + pidc := chan of int; + spawn serveloop(tchan, srv, pidc, navops, keyfile); + <-pidc; + + if(sys->mount(fds[1], nil, mountpt, Sys->MREPL|Sys->MCREATE, nil) < 0) + error(sys->sprint("mount on %s failed: %r", mountpt)); +} + +rf(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd == nil) + return nil; + b := array[256] of byte; + n := sys->read(fd, b, len b); + if(n < 0) + return nil; + return string b[0:n]; +} + +quit(err: string) +{ + fd := sys->open("/prog/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "killgrp"); + if(err != nil) + raise "fail:"+err; + exit; +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "keyfs: %s\n", s); + quit("error"); +} + +thekey: array of byte; + +hashkey(s: string): array of byte +{ + key := array of byte s; + skey := array[Keyring->SHA1dlen] of byte; + sha := kr->sha1(array of byte "aescbc file", 11, nil, nil); + kr->sha1(key, len key, skey, sha); + for(i:=0; i<len key; i++) + key[i] = byte 0; # clear it out +#{sys->print("HEX="); for(i:=0;i<len skey&&i<AESbsize; i++)sys->print("%.2ux", int skey[i]);sys->print("\n");} + return skey[0:AESbsize]; +} + +readconsline(prompt: string, raw: int): (string, string) +{ + fd := sys->open("/dev/cons", Sys->ORDWR); + if(fd == nil) + return (nil, sys->sprint("can't open cons: %r")); + sys->fprint(fd, "%s", prompt); + fdctl: ref Sys->FD; + if(raw){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->fprint(fdctl, "rawon") < 0) + return (nil, sys->sprint("can't open consctl: %r")); + } + line := array[256] of byte; + o := 0; + err: string; + buf := array[1] of byte; + Read: + while((r := sys->read(fd, buf, len buf)) > 0){ + c := int buf[0]; + case c { + 16r7F => + err = "interrupt"; + break Read; + '\b' => + if(o > 0) + o--; + '\n' or '\r' or 16r4 => + break Read; + * => + if(o > len line){ + err = "line too long"; + break Read; + } + line[o++] = byte c; + } + } + sys->fprint(fd, "\n"); + if(r < 0) + err = sys->sprint("can't read cons: %r"); + if(raw) + sys->fprint(fdctl, "rawoff"); + if(err != nil) + return (nil, err); + return (string line[0:o], err); +} + +serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop, keyfile: string) +{ + pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::nil); + while((gm := <-tchan) != nil){ + now = time(); + pick m := gm { + Readerror => + error(sys->sprint("mount read error: %s", m.error)); + Create => + (c, mode, nil, err) := srv.cancreate(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + case TYPE(c.path) { # parent + Qroot => + if((m.perm & Sys->DMDIR) == 0){ + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + u := findusername(m.name); + if(u != nil){ + srv.reply(ref Rmsg.Error(m.tag, "user already exists")); + continue; + } + if(len m.name > Maxname){ + srv.reply(ref Rmsg.Error(m.tag, "user name too long")); + continue; + } + u = newuser(m.name, nil); + qid := Qid((u.path | big Quser), 0, Sys->QTDIR); + c.open(mode, qid); + writekeys(keyfile); + srv.reply(ref Rmsg.Create(m.tag, qid, srv.iounit())); + * => + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + Read => + (c, err) := srv.canread(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + if(c.qtype & Sys->QTDIR){ + srv.read(m); # does readdir + break; + } + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + case TYPE(c.path) { + Qsecret => + if(u.status != Sok){ + srv.reply(ref Rmsg.Error(m.tag, "user disabled")); + break; + } + if(u.expire < now && u.expire != Never){ + srv.reply(ref Rmsg.Error(m.tag, "user expired")); + break; + } + srv.reply(styxservers->readbytes(m, u.secret)); + Qlog => + srv.reply(styxservers->readstr(m, sys->sprint("%d", u.failed))); + Qstatus => + s := status[u.status]; + if(u.status == Sok && u.expire != Never && u.expire < now) + s = "expired"; + srv.reply(styxservers->readstr(m, s)); + Qexpire => + s: string; + if(u.expire != Never) + s = sys->sprint("%ud", u.expire); + else + s = "never"; + srv.reply(styxservers->readstr(m, s)); + * => + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + } + Write => + (c, merr) := srv.canwrite(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, merr)); + break; + } + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + Case: + case TYPE(c.path) { + Qsecret => + if(m.offset != big 0 || len m.data > Maxsecret){ + srv.reply(ref Rmsg.Error(m.tag, "illegal write")); + break; + } + u.secret = m.data; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + Qexpire => + s := trim(string m.data); + if(s != "never"){ + if(!isnumeric(s)){ + srv.reply(ref Rmsg.Error(m.tag, "illegal expiry time")); + break; + } + u.expire = int s; + }else + u.expire = Never; + u.failed = 0; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + Qstatus => + s := trim(string m.data); + for(i := 0; i < len status; i++) + if(s == status[i]){ + u.status = i; + if(i == Sok) + u.failed = 0; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + break Case; + } + srv.reply(ref Rmsg.Error(m.tag, "unknown status")); + Qlog => + s := trim(string m.data); + if(s != "good" && s != "ok"){ + if(++u.failed >= Maxfail) + u.status = Sdisabled; + }else + u.failed = 0; + writekeys(keyfile); + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + * => + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + } + Remove => + c := srv.getfid(m.fid); + if(c == nil){ + srv.remove(m); # let it diagnose the errors + break; + } + case TYPE(c.path) { + Quser => + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + removeuser(u); + writekeys(keyfile); + srv.delfid(c); + srv.reply(ref Rmsg.Remove(m.tag)); + Qsecret => + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + u.secret = nil; + writekeys(keyfile); + srv.delfid(c); + srv.reply(ref Rmsg.Remove(m.tag)); + * => + srv.remove(m); # let it reject it + } + Wstat => + # rename user + c := srv.getfid(m.fid); + if(c == nil || TYPE(c.path) != Quser){ + srv.default(gm); # let it reject it + break; + } + u := finduserpath(c.path); + if(u == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + if((new := m.stat.name) == nil){ + srv.default(gm); + break; + } + if(new == "." || new == ".."){ + srv.reply(ref Rmsg.Error(m.tag, Edot)); + break; + } + if(findusername(new) != nil){ + srv.reply(ref Rmsg.Error(m.tag, "user already exists")); + break; + } + # unhashuser(u); + u.name = new; + # hashuser(u); + writekeys(keyfile); + srv.reply(ref Rmsg.Wstat(m.tag)); + * => + srv.default(gm); + } + } + navops <-= nil; # shut down navigator +} + +trim(s: string): string +{ + (nf, flds) := sys->tokenize(s, " \t\n"); + if(nf == 0) + return nil; + return hd flds; +} + +isnumeric(s: string): int +{ + for(i:=0; i<len s; i++) + if(!(s[i]>='0' && s[i]<='9')) + return 0; + return i>0; +} + +TYPE(path: big): int +{ + return int path & 16rF; +} + +INDEX(path: big): int +{ + return (int path & 16rFFFF) >> 4; +} + +finduserpath(path: big): ref User +{ + i := INDEX(path); + if(i >= len users || (u := users[i]) == nil || u.path != (path & ~big 16rF)) + return nil; + return u; +} + +findusername(name: string): ref User +{ + for(i := 0; i < len users; i++) + if((u := users[i]) != nil && u.name == name) + return u; + return nil; +} + +newuser(name: string, u: ref User): ref User +{ + for(i := 0; i < len users; i++) + if(users[i] == nil) + break; + if(i >= len users) + users = (array[i+16] of ref User)[0:] = users; + path := big ((pathgen++ << 16) | (i<<4)); + if(u == nil) + u = ref User(i, name, nil, Never, Sok, 0, path); + else{ + u.x = i; + u.path = path; + } + users[i] = u; + return u; +} + +removeuser(u: ref User) +{ + if(u != nil) + users[u.x] = nil; +} + +dirslot(n: int): int +{ + for(i := 0; i < len users; i++){ + u := users[i]; + if(u != nil){ + if(n == 0) + break; + n--; + } + } + return i; +} + +dir(qid: Sys->Qid, name: string, length: big, perm: int): ref Sys->Dir +{ + d := ref sys->zerodir; + d.qid = qid; + if(qid.qtype & Sys->QTDIR) + perm |= Sys->DMDIR; + d.mode = perm; + d.name = name; + d.uid = user; + d.gid = user; + d.length = length; + d.atime = now; + d.mtime = now; + return d; +} + +dirgen(p: big, name: string, u: ref User): (ref Sys->Dir, string) +{ + case t := TYPE(p) { + Qroot => + return (dir(Qid(big Qroot, keyversion,Sys->QTDIR), "/", big 0, 8r755), nil); + Quser => + if(name == nil){ + if(u == nil){ + u = finduserpath(p); + if(u == nil) + return (nil, Enotfound); + } + name = u.name; + } + return (dir(Qid(p,0,Sys->QTDIR), name, big 0, 8r500), nil); # note: unwritable + * => + l := 0; + if(t == Qsecret){ + if(u == nil) + u = finduserpath(p); + if(u != nil) + l = len u.secret; + } + return (dir(Qid(p,0,Sys->QTFILE), name, big l, 8r600), nil); + } +} + +navigator(navops: chan of ref Navop) +{ + while((m := <-navops) != nil){ + Pick: + pick n := m { + Stat => + n.reply <-= dirgen(n.path, nil, nil); + Walk => + case TYPE(n.path) { + Qroot => + if(n.name == ".."){ + n.reply <-= dirgen(n.path, nil, nil); + break; + } + u := findusername(n.name); + if(u == nil){ + n.reply <-= (nil, Enotfound); + break; + } + n.reply <-= dirgen(u.path | big Quser, u.name, u); + Quser => + if(n.name == ".."){ + n.reply <-= dirgen(big Qroot, nil, nil); + break; + } + for(j := 0; j < len files; j++){ + (ftype, name) := files[j]; + if(n.name == name){ + n.reply <-= dirgen((n.path & ~big 16rF) | big ftype, name, nil); + break Pick; + } + } + n.reply <-= (nil, Enotfound); + * => + if(n.name != ".."){ + n.reply <-= (nil, Enotfound); + break; + } + n.reply <-= dirgen((n.path & ~big 16rF) | big Quser, nil, nil); # parent directory + } + Readdir => + case TYPE(n.path) { + Qroot => + for(j := dirslot(n.offset); --n.count >= 0 && j < len users; j++) + if((u := users[j]) != nil) + n.reply <-= dirgen(u.path | big Quser, u.name, u); + n.reply <-= (nil, nil); + Quser => + u := finduserpath(n.path); + if(u == nil){ + n.reply <-= (nil, Eremoved); + break; + } + for(j := n.offset; --n.count >= 0 && j < len files; j++){ + (ftype, name) := files[j]; + n.reply <-= dirgen((n.path & ~big 16rF)|big ftype, name, u); + } + n.reply <-= (nil, nil); + } + } + } +} + +timefd: ref Sys->FD; + +time(): int +{ + if(timefd == nil){ + timefd = sys->open("/dev/time", Sys->OREAD); + if(timefd == nil) + return 0; + } + buf := array[128] of byte; + sys->seek(timefd, big 0, 0); + n := sys->read(timefd, buf, len buf); + if(n < 0) + return 0; + t := (big string buf[0:n]) / big 1000000; + return int t; +} + +Checkpat: con "XXXXXXXXXXXXXXXX"; # it's what Plan 9's aescbc uses +Checklen: con len Checkpat; + +Hdrlen: con 1+1+4; + +packedsize(u: ref User): int +{ + return Hdrlen+(1+len array of byte u.name)+(1+len u.secret); +} + +pack(u: ref User): array of byte +{ + a := array[packedsize(u)] of byte; + a[0] = byte u.status; + a[1] = byte u.failed; + a[2] = byte u.expire; + a[3] = byte (u.expire>>8); + a[4] = byte (u.expire>>16); + a[5] = byte (u.expire>>24); + bn := array of byte u.name; + n := len bn; + if(n > 255) + error(sys->sprint("overlong user name: %s", u.name)); # shouldn't happen + a[6] = byte n; + a[7:] = bn; + n += 7; + a[n] = byte len u.secret; + a[n+1:] = u.secret; + return a; +} + +unpack(a: array of byte): (ref User, int) +{ + if(len a < Hdrlen+2) + return (nil, 0); + u := ref User; + u.status = int a[0]; + u.failed = int a[1]; + u.expire = (int a[5] << 24) | (int a[4] << 16) | (int a[3] << 8) | int a[2]; + n := int a[6]; + j := 7+n; + if(j > len a) + return (nil, 0); + u.name = string a[7:j]; + if(j >= len a) + return (nil, 0); + n = int a[j++]; + if(j+n > len a) + return (nil, 0); + if(n > 0){ + u.secret = array[n] of byte; + u.secret[0:] = a[j:j+n]; + } + return (u, j+n); +} + +corrupt(keyfile: string) +{ + error(sys->sprint("%s: incorrect key or corrupt/damaged keyfile", keyfile)); +} + +readkeys(keyfile: string) +{ + fd := sys->open(keyfile, Sys->OREAD); + if(fd == nil) + error(sys->sprint("can't open %s: %r", keyfile)); + (rc, d) := sys->fstat(fd); + if(rc < 0) + error(sys->sprint("can't get status of %s: %r", keyfile)); + length := int d.length; + if(length == 0) + return; + if(length < AESbsize+Checklen) + corrupt(keyfile); + buf := array[length] of byte; + if(sys->read(fd, buf, len buf) != len buf) + error(sys->sprint("can't read %s: %r", keyfile)); + state := kr->aessetup(thekey, buf[0:AESbsize]); + if(state == nil) + error("can't initialise AES"); + kr->aescbc(state, buf[AESbsize:], length-AESbsize, Keyring->Decrypt); + if(string buf[length-Checklen:] != Checkpat) + corrupt(keyfile); + length -= Checklen; + for(i := AESbsize; i < length;){ + (u, n) := unpack(buf[i:]); + if(u == nil) + corrupt(keyfile); + newuser(u.name, u); + i += n; + } +} + +writekeys(keyfile: string) +{ + length := 0; + for(i := 0; i < len users; i++) + if((u := users[i]) != nil) + length += packedsize(u); + if(length == 0){ + # leave it empty for clarity + fd := sys->create(keyfile, Sys->OWRITE, 8r600); + if(fd == nil) + error(sys->sprint("can't create %s: %r", keyfile)); + return; + } + length += AESbsize+Checklen; + buf := array[length] of byte; + for(i=0; i<AESbsize; i++) + buf[i] = byte rand->rand(256); + j := AESbsize; + for(i = 0; i < len users; i++) + if((u = users[i]) != nil){ + a := pack(u); + buf[j:] = a; + j += len a; + } + buf[length-Checklen:] = array of byte Checkpat; + state := kr->aessetup(thekey, buf[0:AESbsize]); + if(state == nil) + error("can't initialise AES"); + kr->aescbc(state, buf[AESbsize:], length-AESbsize, Keyring->Encrypt); + fd := sys->create(keyfile, Sys->OWRITE, 8r600); + if(fd == nil) + error(sys->sprint("can't create %s: %r", keyfile)); + if(sys->write(fd, buf, len buf) != len buf) + error(sys->sprint("error writing to %s: %r", keyfile)); +} diff --git a/appl/cmd/auth/keysrv.b b/appl/cmd/auth/keysrv.b new file mode 100644 index 00000000..c7144256 --- /dev/null +++ b/appl/cmd/auth/keysrv.b @@ -0,0 +1,199 @@ +implement Keysrv; + +# +# remote access to keys (currently only to change secret) +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +include "security.m"; + auth: Auth; + +include "arg.m"; + +keydb := "/mnt/keys"; + +Keysrv: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: keysrv\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + if(sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil) < 0) + err(sys->sprint("can't fork name space: %r")); + + keyfile := "/usr/"+user()+"/keyring/default"; + + arg := load Arg Arg->PATH; + if(arg == nil) + err("can't load Arg"); + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 'k' => + keyfile = arg->arg(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + kr = load Keyring Keyring->PATH; + if(kr == nil) + err("can't load Keyring"); + + auth = load Auth Auth->PATH; + if(auth == nil) + err("can't load Auth"); + auth->init(); + + ai := kr->readauthinfo(keyfile); + if(ai == nil) + err(sys->sprint("can't read server key file %s: %r", keyfile)); + + (fd, id_or_err) := auth->server("sha1" :: "rc4_256" :: nil, ai, sys->fildes(0), 0); + if(fd == nil) + err(sys->sprint("can't authenticate: %s", id_or_err)); + + if(sys->bind("#s", "/mnt/keysrv", Sys->MREPL) < 0) + err(sys->sprint("can't bind #s on /mnt/keysrv: %r")); + srv := sys->file2chan("/mnt/keysrv", "secret"); + if(srv == nil) + err(sys->sprint("can't create file2chan on /mnt/keysrv: %r")); + exitc := chan of int; + spawn worker(srv, id_or_err, exitc); + if(sys->export(fd, "/mnt/keysrv", Sys->EXPWAIT) < 0){ + exitc <-= 1; + err(sys->sprint("can't export %s: %r", "/mnt/keysrv")); + } + exitc <-= 1; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "keysrv: %s\n", s); + raise "fail:error"; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open /dev/user: %r")); + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + err(sys->sprint("error reading /dev/user: %r")); + + return string buf[0:n]; +} + +worker(file: ref Sys->FileIO, user: string, exitc: chan of int) +{ + (keydir, secret, err) := getuser(user); + if(keydir == nil || secret == nil){ + if(err == nil) + err = "no existing secret"; # can't change it remotely until set + } + (nil, hash) := hashkey(secret); + for(;;)alt{ + <-exitc => + exit; + (nil, nbytes, fid, rc) := <-file.read => + if(rc == nil) + break; + if(err != nil){ + rc <-= (nil, err); + break; + } + rc <-= (nil, nil); + (nil, data, fid, wc) := <-file.write => + if(wc == nil) + break; + if(err != nil){ + wc <-= (0, err); + break; + } + for(i := 0; i < len data; i++) + if(data[i] == byte ' ') + break; + if(string data[0:i] != hash){ + wc <-= (0, "wrong secret"); + break; + } + if(++i >= len data){ + wc <-= (0, nil); + break; + } + if(len data - i < 8){ + wc <-= (0, "unacceptable secret"); + break; + } + if(putsecret(keydir, data[i:]) < 0){ + wc <-= (0, sys->sprint("can't update secret: %r")); + break; + } + wc <-= (len data, nil); + } +} + +hashkey(a: array of byte): (array of byte, string) +{ + hash := array[Keyring->SHA1dlen] of byte; + kr->sha1(a, len a, hash, nil); + s := ""; + for(i := 0; i < len hash; i++) + s += sys->sprint("%2.2ux", int hash[i]); + return (hash, s); +} + +getuser(id: string): (string, array of byte, string) +{ + (ok, nil) := sys->stat(keydb); + if(ok < 0) + return (nil, nil, sys->sprint("can't stat %s: %r", id)); + dbdir := keydb+"/"+id; + (ok, nil) = sys->stat(dbdir); + if(ok < 0) + return (nil, nil, sys->sprint("user not registered: %s", id)); + fd := sys->open(dbdir+"/secret", Sys->OREAD); + if(fd == nil) + return (nil, nil, sys->sprint("can't open %s/secret: %r", id)); + d: Sys->Dir; + (ok, d) = sys->fstat(fd); + if(ok < 0) + return (nil, nil, sys->sprint("can't stat %s/secret: %r", id)); + l := int d.length; + secret: array of byte; + if(l > 0){ + secret = array[l] of byte; + if(sys->read(fd, secret, len secret) != len secret) + return (nil, nil, sys->sprint("error reading %s/secret: %r", id)); + } + return (dbdir, secret, nil); +} + +putsecret(dir: string, secret: array of byte): int +{ + fd := sys->create(dir+"/secret", Sys->OWRITE, 8r600); + if(fd == nil) + return -1; + return sys->write(fd, secret, len secret); +} diff --git a/appl/cmd/auth/logind.b b/appl/cmd/auth/logind.b new file mode 100644 index 00000000..f9d14616 --- /dev/null +++ b/appl/cmd/auth/logind.b @@ -0,0 +1,244 @@ +implement Logind; + +# +# certification service (signer) +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + IPint: import kr; + +include "security.m"; + ssl: SSL; + +include "daytime.m"; + daytime: Daytime; + +Logind: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +TimeLimit: con 5*60*1000; # five minutes +keydb := "/mnt/keys"; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->open("/dev/cons", sys->OWRITE); + + kr = load Keyring Keyring->PATH; + + ssl = load SSL SSL->PATH; + if(ssl == nil) + nomod(SSL->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + nomod(Daytime->PATH); + + (err, c) := ssl->connect(sys->fildes(0)); + if(c == nil) + fatal("pushing ssl: " + err); + + # impose time out to ensure dead network connections recovered well before TCP/IP's long time out + + grpid := sys->pctl(Sys->NEWPGRP,nil); + pidc := chan of int; + spawn stalker(pidc, grpid); + tpid := <-pidc; + err = dologin(c); + if(err != nil){ + sys->fprint(stderr, "logind: %s\n", err); + kr->puterror(c.dfd, err); + } + kill(tpid, "kill"); +} + +dologin(c: ref Sys->Connection): string +{ + ivec: array of byte; + + (info, err) := signerkey("/keydb/signerkey"); + if(info == nil) + return "can't read signer's own key: "+err; + + # get user name; ack + s: string; + (s, err) = kr->getstring(c.dfd); + if(err != nil) + return err; + name := s; + kr->putstring(c.dfd, name); + + # get initialization vector + (ivec, err) = kr->getbytearray(c.dfd); + if(err != nil) + return "can't get initialization vector: "+err; + + # lookup password + pw := getsecret(s); + if(pw == nil) + return sys->sprint("no password entry for %s: %r", s); + if(len pw < Keyring->SHA1dlen) + return "bad password for "+s+": not SHA1 hashed?"; + userexp := getexpiry(s); + if(userexp < 0) + return sys->sprint("expiry time for %s: %r", s); + + # generate our random diffie hellman part + bits := info.p.bits(); + r0 := kr->IPint.random(bits/4, bits); + + # generate alpha0 = alpha**r0 mod p + alphar0 := info.alpha.expmod(r0, info.p); + + # start encrypting + pwbuf := array[8] of byte; + for(i := 0; i < 8; i++) + pwbuf[i] = pw[i] ^ pw[8+i]; + for(i = 0; i < 4; i++) + pwbuf[i] ^= pw[16+i]; + for(i = 0; i < 8; i++) + pwbuf[i] ^= ivec[i]; + err = ssl->secret(c, pwbuf, pwbuf); + if(err != nil) + return "can't set ssl secret: "+err; + + if(sys->fprint(c.cfd, "alg rc4") < 0) + return sys->sprint("can't push alg rc4: %r"); + + # send P(alpha**r0 mod p) + if(kr->putstring(c.dfd, alphar0.iptob64()) < 0) + return sys->sprint("can't send (alpha**r0 mod p): %r"); + + # stop encrypting + if(sys->fprint(c.cfd, "alg clear") < 0) + return sys->sprint("can't clear alg: %r"); + + # send alpha, p + if(kr->putstring(c.dfd, info.alpha.iptob64()) < 0 || + kr->putstring(c.dfd, info.p.iptob64()) < 0) + return sys->sprint("can't send alpha, p: %r"); + + # get alpha**r1 mod p + (s, err) = kr->getstring(c.dfd); + if(err != nil) + return "can't get alpha**r1 mod p:"+err; + alphar1 := kr->IPint.b64toip(s); + + # compute alpha**(r0*r1) mod p + alphar0r1 := alphar1.expmod(r0, info.p); + + # turn on digesting + secret := alphar0r1.iptobytes(); + err = ssl->secret(c, secret, secret); + if(err != nil) + return "can't set digest secret: "+err; + if(sys->fprint(c.cfd, "alg sha1") < 0) + return sys->sprint("can't push alg sha1: %r"); + + # send our public key + if(kr->putstring(c.dfd, kr->pktostr(kr->sktopk(info.mysk))) < 0) + return sys->sprint("can't send signer's public key: %r"); + + # get his public key + (s, err) = kr->getstring(c.dfd); + if(err != nil) + return "client public key: "+err; + hisPKbuf := array of byte s; + hisPK := kr->strtopk(s); + if(hisPK.owner != name) + return "pk name doesn't match user name"; + + # sign and return + state := kr->sha1(hisPKbuf, len hisPKbuf, nil, nil); + cert := kr->sign(info.mysk, userexp, state, "sha1"); + + if(kr->putstring(c.dfd, kr->certtostr(cert)) < 0) + return sys->sprint("can't send certificate: %r"); + + return nil; +} + +nomod(mod: string) +{ + fatal(sys->sprint("can't load %s: %r",mod)); +} + +fatal(msg: string) +{ + sys->fprint(stderr, "logind: %s\n", msg); + exit; +} + +signerkey(filename: string): (ref Keyring->Authinfo, string) +{ + + info := kr->readauthinfo(filename); + if(info == nil) + return (nil, sys->sprint("readauthinfo %r")); + + # validate signer key + now := daytime->now(); + if(info.cert.exp != 0 && info.cert.exp < now) + return (nil, sys->sprint("signer key expired")); + + return (info, nil); +} + +getsecret(id: string): array of byte +{ + fd := sys->open(sys->sprint("%s/%s/secret", keydb, id), Sys->OREAD); + if(fd == nil) + return nil; + (ok, d) := sys->fstat(fd); + if(ok < 0) + return nil; + a := array[int d.length] of byte; + n := sys->read(fd, a, len a); + if(n < 0) + return nil; + return a[0:n]; +} + +getexpiry(id: string): int +{ + fd := sys->open(sys->sprint("%s/%s/expire", keydb, id), Sys->OREAD); + if(fd == nil) + return -1; + a := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, a, len a); + if(n < 0) + return -1; + s := string a[0:n]; + if(s == "never") + return 0; + if(s == "expired"){ + sys->werrstr(sys->sprint("entry for %s expired", id)); + return -1; + } + return int s; +} + +stalker(pidc: chan of int, killpid: int) +{ + pidc <-= sys->pctl(0, nil); + sys->sleep(TimeLimit); + sys->fprint(stderr, "logind: login timed out\n"); + kill(killpid, "killgrp"); +} + +kill(pid: int, how: string) +{ + fd := sys->open("#p/" + string pid + "/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "%s", how) < 0) + sys->fprint(stderr, "logind: can't %s %d: %r\n", how, pid); +} diff --git a/appl/cmd/auth/mkauthinfo.b b/appl/cmd/auth/mkauthinfo.b new file mode 100644 index 00000000..33feffbb --- /dev/null +++ b/appl/cmd/auth/mkauthinfo.b @@ -0,0 +1,125 @@ +implement Mkauthinfo; + +# +# sign a new key to produce a certificate +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + IPint: import kr; + +include "security.m"; + auth: Auth; + +include "daytime.m"; + daytime: Daytime; + +include "arg.m"; + +Mkauthinfo: module{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->open("/dev/cons", sys->OWRITE); + + kr = load Keyring Keyring->PATH; + + auth = load Auth Auth->PATH; + if(auth == nil) + nomod(Auth->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + nomod(Daytime->PATH); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + arg->setusage("auth/mkauthinfo [-k keyspec] [-e ddmmyyyy] user [keyfile]"); + keyspec := "key=default"; + expiry := 0; + while((o := arg->opt()) != 0) + case o { + 'k' => + keyspec = arg->earg(); + 'e' => + expiry = parsedate(arg->earg()); + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + user := hd args; + args = tl args; + dstfile := "/fd/1"; + if(args != nil) + dstfile = hd args; + arg = nil; + + sai := auth->key(keyspec); + if(sai == nil){ + sys->fprint(stderr, "sign: can't find key matching %q: %r\n", keyspec); + raise "fail:no key"; + } + + info := ref Keyring->Authinfo; + info.alpha = sai.alpha; + info.p = sai.p; + info.mysk = kr->genSKfromPK(sai.spk, user); + info.mypk = kr->sktopk(info.mysk); + info.spk = sai.mypk; + pkbuf := array of byte kr->pktostr(info.mypk); + state := kr->sha1(pkbuf, len pkbuf, nil, nil); + info.cert = kr->sign(sai.mysk, expiry, state, "sha1"); + if(kr->writeauthinfo("/fd/1", info) < 0){ + sys->fprint(stderr, "sign: error writing certificate: %r\n"); + raise "fail:write error"; + } +} + +parsedate(s: string): int +{ + now := daytime->now(); + tm := daytime->local(now); + if(s == "permanent") + return 0; + if(len s != 8) + fatal("bad date format "+s+" (expected DDMMYYYY)"); + tm.mday = int s[0:2]; + if(tm.mday > 31 || tm.mday < 1) + fatal(sys->sprint("bad day of month %d", tm.mday)); + tm.mon = int s[2:4] - 1; + if(tm.mon > 11 || tm.mday < 0) + fatal(sys->sprint("bad month %d\n", tm.mon + 1)); + tm.year = int s[4:8] - 1900; + if(tm.year < 70) + fatal(sys->sprint("bad year %d (year may be no earlier than 1970)", tm.year + 1900)); + expiry := daytime->tm2epoch(tm); + expiry += 60; + if(expiry <= now) + fatal("expiry date has already passed"); + return expiry; +} + +nomod(mod: string) +{ + fatal(sys->sprint("can't load %s: %r",mod)); +} + +fatal(msg: string) +{ + sys->fprint(stderr, "mkauthinfo: %s\n", msg); + raise "fail:error"; +} diff --git a/appl/cmd/auth/mkfile b/appl/cmd/auth/mkfile new file mode 100644 index 00000000..112ba66a --- /dev/null +++ b/appl/cmd/auth/mkfile @@ -0,0 +1,38 @@ +<../../../mkconfig + +DIRS=\ + factotum\ + +TARG=\ + aescbc.dis\ + changelogin.dis\ + countersigner.dis\ + convpasswd.dis\ + createsignerkey.dis\ + keyfs.dis\ + keysrv.dis\ + getpk.dis\ + logind.dis\ + mkauthinfo.dis\ + passwd.dis\ + secstore.dis\ + signer.dis\ + verify.dis\ + +SYSMODULES=\ + arg.m\ + keyring.m\ + security.m\ + rand.m\ + sys.m\ + draw.m\ + bufio.m\ + secstore.m\ + string.m\ + styx.m\ + styxservers.m\ + +DISBIN=$ROOT/dis/auth + +<$ROOT/mkfiles/mkdis +<$ROOT/mkfiles/mksubdirs diff --git a/appl/cmd/auth/passwd.b b/appl/cmd/auth/passwd.b new file mode 100644 index 00000000..d10b5c95 --- /dev/null +++ b/appl/cmd/auth/passwd.b @@ -0,0 +1,290 @@ +implement Passwd; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +include "security.m"; + auth: Auth; + +include "arg.m"; + +Passwd: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin, stdout: ref Sys->FD; +keysrv := "/mnt/keysrv"; +signer := "$SIGNER"; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: passwd [-u user] [-s signer] [keyfile]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + kr = load Keyring Keyring->PATH; + if(kr == nil) + noload(Keyring->PATH); + auth = load Auth Auth->PATH; + if(auth == nil) + noload(Auth->PATH); + auth->init(); + + keyfile, id: string; + arg := load Arg Arg->PATH; + if(arg == nil) + noload(Arg->PATH); + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 's' => + signer = arg->arg(); + 'u' => + id = arg->arg(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(args == nil) + args = "default" :: nil; + + if(id == nil) + id= user(); + + if(args != nil) + keyfile = hd args; + else + keyfile = "default"; + if(len keyfile > 0 && keyfile[0] != '/') + keyfile = "/usr/" + id + "/keyring/" + keyfile; + + ai := kr->readauthinfo(keyfile); + if(ai == nil) + err(sys->sprint("can't read certificate from %s: %r", keyfile)); +sys->print("key owner: %s\n", ai.mypk.owner); + + sys->pctl(Sys->FORKNS|Sys->FORKFD, nil); + remid := mountsrv(ai); + + # get password + ok: int; + secret: array of byte; + oldhash: array of byte; + word: string; + for(;;){ + sys->print("Inferno secret: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok || word == nil) + exit; + secret = array of byte word; + (nil, s) := hashkey(secret); + for(i := 0; i < len word; i++) + word[i] = ' '; + oldhash = array of byte s; + e := putsecret(oldhash, nil); + if(e != "wrong secret"){ + if(e == nil) + break; + err(e); + } + sys->fprint(stderr, "!wrong secret\n"); + } + newsecret: array of byte; + for(;;){ + for(;;){ + sys->print("new secret [default = don't change]: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok) + exit; + if(word == "" && secret != nil) + break; + if(len word >= 8) + break; + sys->print("!secret must be at least 8 characters\n"); + } + if(word != ""){ + # confirm password change + word1 := word; + sys->print("confirm: "); + (ok, word) = readline(stdin, "rawon"); + if(!ok || word != word1){ + sys->fprint(stderr, "!entries didn't match\n"); + continue; + } + # TO DO... + #pwbuf := array of byte word; + #newsecret = array[Keyring->SHA1dlen] of byte; + #kr->sha1(pwbuf, len pwbuf, newsecret, nil); + newsecret = array of byte word; + } + if(!eq(newsecret, secret)){ + if((e := putsecret(oldhash, newsecret)) != nil){ + sys->fprint(stderr, "passwd: can't update secret for %s: %s\n", id, e); + continue; + } + } + break; + } +} + +noload(s: string) +{ + err(sys->sprint("can't load %s: %r", s)); +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "passwd: %s\n", s); + raise "fail:error"; +} + +mountsrv(ai: ref Keyring->Authinfo): string +{ + (rc, c) := sys->dial(netmkaddr(signer, "net", "infkey"), nil); + if(rc < 0) + err(sys->sprint("can't dial %s: %r", signer)); + (fd, id_or_err) := auth->client("sha1/rc4_256", ai, c.dfd); + if(fd == nil) + err(sys->sprint("can't authenticate with %s: %r", signer)); + if(sys->mount(fd, nil, keysrv, Sys->MREPL, nil) < 0) + err(sys->sprint("can't mount %s on %s: %r", signer, keysrv)); + return id_or_err; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open /dev/user: %r")); + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + err(sys->sprint("error reading /dev/user: %r")); + + return string buf[0:n]; +} + +eq(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +hashkey(a: array of byte): (array of byte, string) +{ + hash := array[Keyring->SHA1dlen] of byte; + kr->sha1(a, len a, hash, nil); + s := ""; + for(i := 0; i < len hash; i++) + s += sys->sprint("%2.2ux", int hash[i]); + return (hash, s); +} + +putsecret(oldhash: array of byte, secret: array of byte): string +{ + fd := sys->create(keysrv+"/secret", Sys->OWRITE, 8r600); + if(fd == nil) + return sys->sprint("%r"); + n := len oldhash; + if(secret != nil) + n += 1 + len secret; + buf := array[n] of byte; + buf[0:] = oldhash; + if(secret != nil){ + buf[len oldhash] = byte ' '; + buf[len oldhash+1:] = secret; + } + if(sys->write(fd, buf, len buf) < 0) + return sys->sprint("%r"); + return nil; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} + +readline(io: ref Sys->FD, mode: string): (int, string) +{ + r : int; + line : string; + buf := array[8192] of byte; + fdctl : ref Sys->FD; + rawoff := array of byte "rawoff"; + + if(mode == "rawon"){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->write(fdctl,array of byte mode,len mode) != len mode){ + sys->fprint(stderr, "unable to change console mode"); + return (0,nil); + } + } + + line = ""; + for(;;) { + r = sys->read(io, buf, len buf); + if(r <= 0){ + sys->fprint(stderr, "error read from console mode"); + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + return (0, nil); + } + + line += string buf[0:r]; + if ((len line >= 1) && (line[(len line)-1] == '\n')){ + if(mode == "rawon"){ + r = sys->write(stdout,array of byte "\n",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + break; + } + else { + if(mode == "rawon"){ + #r = sys->write(stdout, array of byte "*",1); + if(r <= 0) { + sys->write(fdctl,rawoff,6); + return (0, nil); + } + } + } + } + + if(mode == "rawon") + sys->write(fdctl,rawoff,6); + + return (1, line[0:len line - 1]); +} diff --git a/appl/cmd/auth/secstore.b b/appl/cmd/auth/secstore.b new file mode 100644 index 00000000..5a63b78d --- /dev/null +++ b/appl/cmd/auth/secstore.b @@ -0,0 +1,317 @@ +implement Secstorec; + +# +# interact with the Plan 9 secstore +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "secstore.m"; + secstore: Secstore; + +include "arg.m"; + +Secstorec: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Maxfilesize: con 128*1024; + +stderr: ref Sys->FD; +conn: ref Sys->Connection; +seckey: array of byte; +filekey: array of byte; +file: array of byte; +verbose := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + secstore = load Secstore Secstore->PATH; + + sys->pctl(Sys->FORKFD, nil); + stderr = sys->fildes(2); + secstore->init(); + secstore->privacy(); + + addr := "net!$auth!secstore"; + user := readfile("/dev/user"); + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("auth/secstore [-iv] [-k key] [-p pin] [-s net!server!secstore] [-u user] [{drptx} file ...]"); + iflag := 0; + pass, pin: string; + while((o := arg->opt()) != 0) + case o { + 'i' => iflag = 1; + 'k' => pass = arg->earg(); + 'v' => verbose = 1; + 's' => addr = arg->earg(); + 'u' => user = arg->earg(); + 'p' => pin = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + op := -1; + if(args != nil){ + if(len hd args != 1) + arg->usage(); + op = (hd args)[0]; + args = tl args; + case op { + 'd' or 'r' or 'p' or 'x' => + if(args == nil) + arg->usage(); + 't' => + ; + * => + arg->usage(); + } + } + arg = nil; + + if(iflag){ + buf := array[Secstore->Maxmsg] of byte; + stdin := sys->fildes(0); + for(nr := 0; nr < len buf && (n := sys->read(stdin, buf, len buf-nr)) > 0;) + nr += n; + s := string buf[0:nr]; + secstore->erasekey(buf[0:nr]); + (nf, flds) := sys->tokenize(s, "\n"); + for(i := 0; i < len s; i++) + s[i] = 0; + if(nf < 1) + error("no password on standard input"); + pass = hd flds; + if(nf > 1) + pin = hd tl flds; + } + conn: ref Sys->Connection; +Auth: + for(;;){ + if(!iflag) + pass = readpassword("secstore password"); + if(pass == nil) + exit; + erase(); + seckey = secstore->mkseckey(pass); + filekey = secstore->mkfilekey(pass); + for(i := 0; i < len pass; i++) + pass[i] = 0; # clear it + conn = secstore->dial(netmkaddr(addr, "net", "secstore")); + if(conn == nil) + error(sys->sprint("can't connect to secstore: %r")); + (srvname, diag) := secstore->auth(conn, user, seckey); + if(srvname == nil){ + secstore->bye(conn); + sys->fprint(stderr, "secstore: authentication failed: %s\n", diag); + if(iflag) + raise "fail:auth"; + continue; + } + case diag { + "" => + if(verbose) + sys->fprint(stderr, "server: %s\n", srvname); + secstore->erasekey(seckey); + seckey = nil; + break Auth; + "need pin" => + if(!iflag){ + pin = readpassword("STA PIN+SecureID"); + if(len pin == 0){ + sys->fprint(stderr, "cancelled"); + exit; + } + }else if(pin == nil) + raise "fail:no pin"; + if(secstore->sendpin(conn, pin) < 0){ + sys->fprint(stderr, "secstore: pin rejected: %r\n"); + if(iflag) + raise "fail:bad pin"; + continue; + } + } + } + if(op == 't'){ + erase(); # no longer need the keys + entries := secstore->files(conn); + for(; entries != nil; entries = tl entries){ + (name, size, date, hash, nil) := hd entries; + if(args != nil){ + for(l := args; l != nil; l = tl l) + if((hd args) == name) + break; + if(args == nil) + continue; + } + if(verbose) + sys->print("%-14q %10d %s %s\n", name, size, date, hash); + else + sys->print("%q\n", name); + } + exit; + } + for(; args != nil; args = tl args){ + fname := hd args; + case op { + 'd' => + checkname(fname, 1); + if(secstore->remove(conn, fname) < 0) + error(sys->sprint("can't remove %q: %r", fname)); + verb('d', fname); + 'p' => + checkname(fname, 1); + file = getfile(conn, fname, filekey); + lines := secstore->lines(file); + lno := 1; + for(; lines != nil; lines = tl lines){ + l := hd lines; + if(sys->write(sys->fildes(1), l, len l) != len l) + sys->fprint(sys->fildes(2), "secstore (%s:%d): %r\n", fname, lno); + lno++; + } + secstore->erasekey(file); + file = nil; + verb('p', fname); + 'x' => + checkname(fname, 1); + file = getfile(conn, fname, filekey); + ofd := sys->create(fname, Sys->OWRITE, 8r600); + if(ofd == nil) + error(sys->sprint("can't create %q: %r", fname)); + if(sys->write(ofd, file, len file) != len file) + error(sys->sprint("error writing to %q: %r", fname)); + secstore->erasekey(file); + file = nil; + verb('x', fname); + 'r' or * => + error(sys->sprint("op %c not implemented", op)); + } + } + erase(); +} + +checkname(s: string, noslash: int): string +{ + tail := s; + for(i := 0; i < len s; i++){ + if(s[i] == '/'){ + if(noslash) + break; + tail = s[i+1:]; + } + if(s[i] == '\n' || s[i] <= ' ') + break; + } + if(s == nil || tail == nil || i < len s || s == "..") + error(sys->sprint("can't use %q as a secstore file name", s)); # server checks as well, of course + return tail; +} + +verb(op: int, n: string) +{ + if(verbose) + sys->fprint(stderr, "%c %q\n", op, n); +} + +getfile(conn: ref Sys->Connection, fname: string, key: array of byte): array of byte +{ + f := secstore->getfile(conn, fname, 0); + if(f == nil) + error(sys->sprint("can't fetch %q: %r", fname)); + if(fname != "."){ + f = secstore->decrypt(f, key); + if(f == nil) + error(sys->sprint("can't decrypt %q: %r", fname)); + } + return f; +} + +erase() +{ + if(secstore != nil){ + secstore->erasekey(seckey); + secstore->erasekey(filekey); + secstore->erasekey(file); + } +} + +error(s: string) +{ + erase(); + sys->fprint(stderr, "secstore: %s\n", s); + raise "fail:error"; +} + +readpassword(prompt: string): string +{ + cons := sys->open("/dev/cons", Sys->ORDWR); + if(cons == nil) + return nil; + stdin := bufio->fopen(cons, Sys->OREAD); + if(stdin == nil) + return nil; + cfd := sys->open("/dev/consctl", Sys->OWRITE); + if (cfd == nil || sys->fprint(cfd, "rawon") <= 0) + sys->fprint(stderr, "secstore: warning: cannot hide typed password\n"); +L: + for(;;){ + sys->fprint(cons, "%s: ", prompt); + s := ""; + while ((c := stdin.getc()) >= 0){ + case c { + '\n' or ('d'&8r037) => + sys->fprint(cons, "\n"); + return s; + '\b' or 8r177 => + if(len s > 0) + s = s[0:len s - 1]; + 'u' & 8r037 => + sys->fprint(cons, "\n"); + continue L; + * => + s[len s] = c; + } + } + break; + } + return nil; +} + +readfile(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd == nil) + return ""; + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return ""; + return string buf[0:n]; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/auth/signer.b b/appl/cmd/auth/signer.b new file mode 100644 index 00000000..b3f4669d --- /dev/null +++ b/appl/cmd/auth/signer.b @@ -0,0 +1,132 @@ +implement Signer; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + IPint: import kr; + +include "security.m"; + random: Random; + +Signer: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +# size in bits of modulus for public keys +PKmodlen: con 512; + +# size in bits of modulus for diffie hellman +DHmodlen: con 512; + +stderr, stdin, stdout: ref Sys->FD; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + random = load Random Random->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + sys->pctl(Sys->FORKNS, nil); + if(sys->chdir("/keydb") < 0){ + sys->fprint(stderr, "signer: no key database\n"); + raise "fail:no keydb"; + } + + err := sign(); + if(err != nil){ + sys->fprint(stderr, "signer: %s\n", err); + raise "fail:error"; + } +} + +sign(): string +{ + info := signerkey("signerkey"); + if(info == nil) + return "can't read key"; + + # send public part to client + mypkbuf := array of byte kr->pktostr(kr->sktopk(info.mysk)); + kr->sendmsg(stdout, mypkbuf, len mypkbuf); + alphabuf := array of byte info.alpha.iptob64(); + kr->sendmsg(stdout, alphabuf, len alphabuf); + pbuf := array of byte info.p.iptob64(); + kr->sendmsg(stdout, pbuf, len pbuf); + + # get client's public key + hisPKbuf := kr->getmsg(stdin); + if(hisPKbuf == nil) + return "caller hung up"; + hisPK := kr->strtopk(string hisPKbuf); + if(hisPK == nil) + return "illegal caller PK"; + + # hash, sign, and blind + state := kr->sha1(hisPKbuf, len hisPKbuf, nil, nil); + cert := kr->sign(info.mysk, 0, state, "sha1"); + + # sanity clause + state = kr->sha1(hisPKbuf, len hisPKbuf, nil, nil); + if(kr->verify(info.mypk, cert, state) == 0) + return "bad signer certificate"; + + certbuf := array of byte kr->certtostr(cert); + blind := random->randombuf(random->ReallyRandom, len certbuf); + for(i := 0; i < len blind; i++) + certbuf[i] = certbuf[i] ^ blind[i]; + + # sum PKs and blinded certificate + state = kr->md5(mypkbuf, len mypkbuf, nil, nil); + kr->md5(hisPKbuf, len hisPKbuf, nil, state); + digest := array[Keyring->MD5dlen] of byte; + kr->md5(certbuf, len certbuf, digest, state); + + # save sum and blinded cert in a file + file := "signed/"+hisPK.owner; + fd := sys->create(file, Sys->OWRITE, 8r600); + if(fd == nil) + return "can't create "+file+sys->sprint(": %r"); + if(kr->sendmsg(fd, blind, len blind) < 0 || + kr->sendmsg(fd, digest, len digest) < 0){ + sys->remove(file); + return "can't write "+file+sys->sprint(": %r"); + } + + # send blinded cert to client + kr->sendmsg(stdout, certbuf, len certbuf); + + return nil; +} + +signerkey(filename: string): ref Keyring->Authinfo +{ + info := kr->readauthinfo(filename); + if(info != nil) + return info; + + # generate a local key + info = ref Keyring->Authinfo; + info.mysk = kr->genSK("elgamal", "*", PKmodlen); + info.mypk = kr->sktopk(info.mysk); + info.spk = kr->sktopk(info.mysk); + myPKbuf := array of byte kr->pktostr(info.mypk); + state := kr->sha1(myPKbuf, len myPKbuf, nil, nil); + info.cert = kr->sign(info.mysk, 0, state, "sha1"); + (info.alpha, info.p) = kr->dhparams(DHmodlen); + + if(kr->writeauthinfo(filename, info) < 0){ + sys->fprint(stderr, "can't write signerkey file: %r\n"); + return nil; + } + + return info; +} diff --git a/appl/cmd/auth/verify.b b/appl/cmd/auth/verify.b new file mode 100644 index 00000000..d829a76c --- /dev/null +++ b/appl/cmd/auth/verify.b @@ -0,0 +1,85 @@ +implement Verify; + +include "sys.m"; + sys: Sys; + +include "keyring.m"; + kr: Keyring; + +include "draw.m"; + +Verify: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr, stdin: ref Sys->FD; + +pro := array[] of { + "alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", + "hotel", "india", "juliet", "kilo", "lima", "mike", "nancy", "oscar", + "papa", "quebec", "romeo", "sierra", "tango", "uniform", + "victor", "whisky", "xray", "yankee", "zulu" +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + + stdin = sys->fildes(0); + stderr = sys->fildes(2); + + if(args != nil) + args = tl args; + if(args == nil){ + sys->fprint(stderr, "usage: verify boxid\n"); + raise "fail:usage"; + } + + sys->pctl(Sys->FORKNS, nil); + if(sys->chdir("/keydb") < 0){ + sys->fprint(stderr, "signer: no key database\n"); + raise "fail:no keydb"; + } + + boxid := hd args; + file := "signed/"+boxid; + fd := sys->open(file, Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "signer: can't open %s: %r\n", file); + raise "fail:no certificate"; + } + certbuf := kr->getmsg(fd); + digest := kr->getmsg(fd); + if(digest == nil || certbuf == nil){ + sys->fprint(stderr, "signer: can't read %s: %r\n", file); + raise "fail:bad certificate"; + } + + s: string; + for(i := 0; i < len digest; i++){ + s = s + (string (2*i)) + ": " + pro[((int digest[i])>>4)%len pro] + "\t"; + s = s + (string (2*i+1)) + ": " + pro[(int digest[i])%len pro] + "\n"; + } + + sys->print("%s\naccept (y or n)? ", s); + buf := array[5] of byte; + n := sys->read(stdin, buf, len buf); + if(n < 1 || buf[0] != byte 'y'){ + sys->print("\nrejected\n"); + raise "fail:rejected"; + } + sys->print("\naccepted\n"); + + nfile := "countersigned/"+boxid; + fd = sys->create(nfile, Sys->OWRITE, 8r600); + if(fd == nil){ + sys->fprint(stderr, "signer: can't create %s: %r\n", nfile); + raise "fail:create"; + } + if(kr->sendmsg(fd, certbuf, len certbuf) < 0){ + sys->fprint(stderr, "signer: can't write %s: %r\n", nfile); + raise "fail:write"; + } +} diff --git a/appl/cmd/auxi/cpuslave.b b/appl/cmd/auxi/cpuslave.b new file mode 100644 index 00000000..66b409ac --- /dev/null +++ b/appl/cmd/auxi/cpuslave.b @@ -0,0 +1,79 @@ +implement CPUslave; + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Context, Display, Screen: import draw; +include "arg.m"; + +include "sh.m"; + +stderr: ref Sys->FD; + +CPUslave: module +{ + init: fn(ctxt: ref Context, args: list of string); +}; + +usage() +{ + sys->fprint(stderr, "usage: cpuslave [-s screenid] command args\n"); + raise "fail:usage"; +} + +init(nil: ref Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + draw = load Draw Draw->PATH; + + arg := load Arg Arg->PATH; + if (arg == nil) { + sys->fprint(stderr, "cpuslave: cannot load %s: %r\n", Arg->PATH); + raise "fail:bad module"; + } + screenid := -1; + arg->init(args); + while ((opt := arg->opt()) != 0) { + if (opt != 's' || (a := arg->arg()) == nil) + usage(); + screenid = int a; + } + args = arg->argv(); + if(args == nil) + usage(); + + file := hd args + ".dis"; + cmd := load Command file; + if(cmd == nil) + cmd = load Command "/dis/"+file; + if(cmd == nil){ + sys->fprint(stderr, "cpuslave: can't load %s: %r\n", hd args); + raise "fail:bad command"; + } + + ctxt: ref Context; + if (screenid >= 0) { + display := Display.allocate(nil); + if(display == nil){ + sys->fprint(stderr, "cpuslave: can't initialize display: %r\n"); + raise "fail:no display"; + } + + screen: ref Screen; + if(screenid >= 0){ + screen = display.publicscreen(screenid); + if(screen == nil){ + sys->fprint(stderr, "cpuslave: cannot access screen %d: %r\n", screenid); + raise "fail:bad screen"; + } + } + + ctxt = ref Context; + ctxt.screen = screen; + ctxt.display = display; + } + + spawn cmd->init(ctxt, args); +} diff --git a/appl/cmd/auxi/digest.b b/appl/cmd/auxi/digest.b new file mode 100644 index 00000000..108de205 --- /dev/null +++ b/appl/cmd/auxi/digest.b @@ -0,0 +1,91 @@ +implement Digest; + +# +# read a classifier example file and write its digest +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "strokes.m"; + strokes: Strokes; + Classifier, Penpoint, Stroke: import strokes; + readstrokes: Readstrokes; + writestrokes: Writestrokes; + +include "arg.m"; + +Digest: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: digest [file.cl ...]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + strokes = load Strokes Strokes->PATH; + if(strokes == nil) + nomod(Strokes->PATH); + strokes->init(); + readstrokes = load Readstrokes Readstrokes->PATH; + if(readstrokes == nil) + nomod(Readstrokes->PATH); + readstrokes->init(strokes); + writestrokes = load Writestrokes Writestrokes->PATH; + if(writestrokes == nil) + nomod(Writestrokes->PATH); + writestrokes->init(strokes); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + while((opt := arg->opt()) != 0) + case opt { + * => + usage(); + } + args = arg->argv(); + arg = nil; + + for(; args != nil; args = tl args){ + ofile := file := hd args; + n := len file; + if(n >= 3 && ofile[n-3:] == ".cl") + ofile = ofile[0:n-3]; + ofile += ".clx"; + (err, rec) := readstrokes->read_classifier(hd args, 1, 0); + if(err != nil) + error(sys->sprint("error reading classifier from %s: %s", file, err)); + fd := sys->create(ofile, Sys->OWRITE, 8r666); + if(fd == nil) + error(sys->sprint("can't create %s: %r", file)); + err = writestrokes->write_digest(fd, rec.cnames, rec.dompts); + if(err != nil) + error(sys->sprint("error writing digest to %s: %s", file, err)); + } +} + +nomod(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "digest: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/auxi/fpgaload.b b/appl/cmd/auxi/fpgaload.b new file mode 100644 index 00000000..5c37b80b --- /dev/null +++ b/appl/cmd/auxi/fpgaload.b @@ -0,0 +1,67 @@ +implement Fpgaload; + +include"sys.m"; + sys: Sys; + +include "draw.m"; + +include "arg.m"; + +Fpgaload: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + arg := load Arg Arg->PATH; + if(arg == nil) + error(sys->sprint("can't load %s: %r", Arg->PATH)); + arg->init(args); + arg->setusage("fpgaload [-c clock] file.rbf"); + clock := -1; + while((c := arg->opt()) != 0) + case c { + 'c' => + clock = int arg->earg(); + if(clock <= 0) + error("invalid clock value"); + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + fd := sys->open(hd args, Sys->OREAD); + if(fd == nil) + error(sys->sprint("can't open %s: %r", hd args)); + ofd := sys->open("#G/fpgaprog", Sys->OWRITE); + if(ofd == nil) + error(sys->sprint("can't open %s: %r", "#G/fpgaprog")); + a := array[128*1024] of byte; + while((n := sys->read(fd, a, len a)) > 0) + if(sys->write(ofd, a, n) != n) + error(sys->sprint("write error: %r")); + if(n < 0) + error(sys->sprint("read error: %r")); + if(clock >= 0) + setclock(clock); +} + +setclock(n: int) +{ + fd := sys->open("#G/fpgactl", Sys->OWRITE); + if(fd == nil) + error(sys->sprint("can't open %s: %r", "#G/fpgactl")); + if(sys->fprint(fd, "bclk %d", n) < 0) + error(sys->sprint("can't set clock to %d: %r", n)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "fpgaload: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/auxi/mangaload.b b/appl/cmd/auxi/mangaload.b new file mode 100644 index 00000000..380dd22e --- /dev/null +++ b/appl/cmd/auxi/mangaload.b @@ -0,0 +1,362 @@ +implement Mangaload; + +# to do: +# - set arp entry based on /lib/ndb if necessary + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "ip.m"; + ip: IP; + IPaddr: import ip; + +include "timers.m"; + timers: Timers; + Timer: import timers; + +include "arg.m"; + +Mangaload: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +# manga parameters +FlashBlocksize: con 16r10000; +FlashSize: con 16r400000; # 4meg for now +FlashUserArea: con 16r3C0000; + +# magic values +FooterOffset: con 16rFFEC; +FooterSig: con 16rA0FFFF9F; # ARM flash library +FileInfosize: con 64; +FileNamesize: con FileInfosize - 3*4; # x, y, z +Packetdatasize: con 1500-28; # ether data less IP + ICMP header +RequestTimeout: con 500; +Probecount: con 10; # query unit every so many packets + +# manga uses extended TFTP ops in ICMP InfoRequest packets +Tftp_Req: con 0; +Tftp_Read: con 1; +Tftp_Write: con 2; +Tftp_Data: con 3; +Tftp_Ack: con 4; +Tftp_Error: con 5; +Tftp_Last: con 6; + +Icmp: adt +{ + ttl: int; # time to live + src: IPaddr; + dst: IPaddr; + ptype: int; + code: int; + id: int; + seq: int; + data: array of byte; + munged: int; # packet received but corrupt + + unpack: fn(b: array of byte): ref Icmp; +}; + +# ICMP packet types +EchoReply: con 0; +Unreachable: con 3; +SrcQuench: con 4; +EchoRequest: con 8; +TimeExceed: con 11; +Timestamp: con 13; +TimestampReply: con 14; +InfoRequest: con 15; +InfoReply: con 16; + +Nmsg: con 32; +Interval: con 1000; # ms + +debug := 0; +flashblock := 1; # never 0, that's the boot firmware +maxfilesize := 8*FlashBlocksize; +flashlim := FlashSize/FlashBlocksize; +loadinitrd := 0; +maxlen := 512*1024; +mypid := 0; +Datablocksize: con 4096; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + timers = load Timers Timers->PATH; + ip = load IP IP->PATH; + ip->init(); + + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("mangaload [-48dr] destination file"); + while((o := arg->opt()) != 0) + case o { + '4' => + flashlim = 4*1024*1024/FlashBlocksize; + '8' => + flashlim = 8*1024*1024/FlashBlocksize; + 'r' => + loadinitrd = 1; + flashblock = 9; + if(flashlim > 4*1024*1024/FlashBlocksize) + maxfilesize = 113*FlashBlocksize; + else + maxfilesize = 50*FlashBlocksize; + 'd' => + debug++; + } + args = arg->argv(); + if(len args != 2) + arg->usage(); + arg = nil; + + sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil); + + filename := hd tl args; + fd := sys->open(filename, Sys->OREAD); + if(fd == nil){ + sys->fprint(sys->fildes(2), "mangaload: can't open %s: %r\n", filename); + raise "fail:open"; + } + (ok, d) := sys->fstat(fd); + if(ok < 0){ + sys->fprint(sys->fildes(2), "mangaload: can't stat %s: %r\n", filename); + raise "fail:stat"; + } + if(d.length > big maxfilesize){ + sys->fprint(sys->fildes(2), "mangaload: file %s too long (must not exceed %d bytes)\n", + filename, maxfilesize); + raise "fail:size"; + } + filesize := int d.length; + + port := sys->sprint("%d", 16r8695); + addr := netmkaddr(hd args, "icmp", port); + (rok, c) := sys->dial(addr, port); + if(rok < 0){ + sys->fprint(sys->fildes(2), "mangaload: can't dial %s: %r\n", addr); + raise "fail:dial"; + } + + tpid := timers->init(20); + + pids := chan of int; + replies := chan [2] of ref Icmp; + spawn reader(c.dfd, replies, pids); + rpid := <-pids; + + flashoffset := flashblock * FlashBlocksize; + + # file name first + bname := array of byte filename; + l := len bname; + buf := array[Packetdatasize] of byte; + ip->put4(buf, 0, filesize); + ip->put4(buf, 4, l); + buf[8:] = bname; + l += 2*4; + buf[l++] = byte 0; + ip->put4(buf, l, flashoffset); + l += 4; + { + if(send(c.dfd, buf[0:l], Tftp_Write, 0) < 0) + senderr(); + (op, iseq, data) := recv(replies, 400); + sys->print("initial reply: %d %d\n", op, iseq); + if(op != Tftp_Ack){ + why := "no response"; + if(op == Tftp_Error) + why = "manga cannot receive file"; + sys->fprint(sys->fildes(2), "mangaload: %s\n", why); + raise "fail:error"; + } + sys->print("sending %s size %d at address %d (0x%x)\n", filename, filesize, flashoffset, flashoffset); + seq := 1; + nsent := 0; + last := 0; + while((n := sys->read(fd, buf, len buf)) >= 0 && !last){ + last = n != len buf; + nretry := 0; + Retry: + for(;;){ + if(++nsent%10 == 0){ # probe + o = Tftp_Req; + send(c.dfd, array[0] of byte, Tftp_Req, seq); + (op, iseq, data) = recv(replies, 500); + if(debug || op != Tftp_Ack) + sys->print("ack reply: %d %d\n", op, iseq); + if(op == Tftp_Last || op == Tftp_Error){ + if(op == Tftp_Last) + sys->print("timed out\n"); + else + sys->print("error reply\n"); + raise "disaster"; + } + if(debug) + sys->print("ok\n"); + continue Retry; + } + send(c.dfd, buf[0:n], Tftp_Data, seq); + (op, iseq, data) = recv(replies, 40); + case op { + Tftp_Error => + sys->fprint(sys->fildes(2), "mangaload: manga refused data\n"); + raise "disaster"; + Tftp_Ack => + if(seq == iseq){ + seq++; + break Retry; + } + sys->print("sequence error: rcvd %d expected %d\n", iseq, seq); + if(iseq > seq){ + sys->print("unrecoverable sequence error\n"); + send(c.dfd, array[0] of byte, Tftp_Data, ++seq); # stop manga + raise "disaster"; + } + # resend + sys->seek(fd, -big ((seq-iseq)*len buf), 1); + seq = iseq; + Tftp_Last => + seq++; + break Retry; # timeout ok: manga doesn't usually reply unless packet lost + } + } + } + }exception{ + * => + ; + } + kill(rpid); + kill(tpid); + sys->print("ok?\n"); +} + +kill(pid: int) +{ + if(pid) + sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill"); +} + +senderr() +{ + sys->fprint(sys->fildes(2), "mangaload: icmp write failed: %r\n"); + raise "disaster"; +} + +send(fd: ref Sys->FD, data: array of byte, op: int, seq: int): int +{ + buf := array[64*1024+512] of {* => byte 0}; + buf[Odata:] = data; + ip->put2(buf, Oseq, seq); + buf[Otype] = byte InfoRequest; + buf[Ocode] = byte op; + if(sys->write(fd, buf, Odata+len data) < Odata+len data) + return -1; + if(debug) + sys->print("sent op=%d seq=%d ld=%d\n", op, seq, len data); + return 0; +} + +flush(input: chan of ref Icmp) +{ + for(;;)alt{ + <-input => + ; + * => + return; + } +} + +recv(input: chan of ref Icmp, msec: int): (int, int, array of byte) +{ + t := Timer.start(msec); + alt{ + <-t.timeout => + return (Tftp_Last, 0, nil); + ic := <-input => + t.stop(); + if(ic.ptype == InfoReply) + return (ic.code, ic.seq, ic.data); + return (Tftp_Last, 0, nil); + } +} + +reader(fd: ref Sys->FD, out: chan of ref Icmp, pid: chan of int) +{ + pid <-= sys->pctl(0, nil); + for(;;){ + buf := array[64*1024+512] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0){ + if(n == 0) + sys->werrstr("unexpected eof"); + break; + } + ic := Icmp.unpack(buf[0:n]); + if(ic != nil){ + if(debug) + sys->print("recv type=%d op=%d seq=%d id=%d\n", ic.ptype, ic.code, ic.seq, ic.id); + out <-= ic; + }else + sys->fprint(sys->fildes(2), "mangaload: corrupt icmp packet rcvd\n"); + } + sys->print("read: %r\n"); + out <-= nil; +} + +# IP and ICMP packet header +Ovihl: con 0; +Otos: con 1; +Olength: con 2; +Oid: con Olength+2; +Ofrag: con Oid+2; +Ottl: con Ofrag+2; +Oproto: con Ottl+1; +Oipcksum: con Oproto+1; +Osrc: con Oipcksum+2; +Odst: con Osrc+4; +Otype: con Odst+4; +Ocode: con Otype+1; +Ocksum: con Ocode+1; +Oicmpid: con Ocksum+2; +Oseq: con Oicmpid+2; +Odata: con Oseq+2; + +Icmp.unpack(b: array of byte): ref Icmp +{ + if(len b < Odata) + return nil; + ic := ref Icmp; + ic.ttl = int b[Ottl]; + ic.src = IPaddr.newv4(b[Osrc:]); + ic.dst = IPaddr.newv4(b[Odst:]); + ic.ptype = int b[Otype]; + ic.code = int b[Ocode]; + ic.seq = ip->get2(b, Oseq); + ic.id = ip->get2(b, Oicmpid); + ic.munged = 0; + if(len b > Odata) + ic.data = b[Odata:]; + return ic; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/auxi/mkfile b/appl/cmd/auxi/mkfile new file mode 100644 index 00000000..6d8dfc88 --- /dev/null +++ b/appl/cmd/auxi/mkfile @@ -0,0 +1,24 @@ +<../../../mkconfig + +TARG=\ + cpuslave.dis\ + digest.dis\ + fpgaload.dis\ + mangaload.dis\ + pcmcia.dis\ + rdbgsrv.dis\ + rstyxd.dis\ + +SYSMODULES=\ + arg.m\ + bufio.m\ + draw.m\ + sh.m\ + string.m\ + strokes.m\ + styx.m\ + sys.m\ + +DISBIN=$ROOT/dis/auxi + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/auxi/pcmcia.b b/appl/cmd/auxi/pcmcia.b new file mode 100644 index 00000000..d5d998b0 --- /dev/null +++ b/appl/cmd/auxi/pcmcia.b @@ -0,0 +1,491 @@ +implement Pcmcia; + +# +# Copyright © 1995-2001 Lucent Technologies Inc. All rights reserved. +# Revisions Copyright © 2001-2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + print, fprint: import sys; + +include "draw.m"; + +Pcmcia: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +End: con 16rFF; + +fd: ref Sys->FD; +stderr: ref Sys->FD; +pos := 0; + +hex := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + if(args != nil) + args = tl args; + if(args != nil && hd args == "-x"){ + hex = 1; + args = tl args; + } + + file := "#y/pcm0attr"; + if(args != nil) + file = hd args; + + fd = sys->open(file, Sys->OREAD); + if(fd == nil) + fatal(sys->sprint("can't open %s: %r", file)); + + for(next := 0; next >= 0;) + next = dtuple(next); +} + +fatal(s: string) +{ + fprint(stderr, "pcmcia: %s\n", s); + raise "fail:error"; +} + +readc(): int +{ + x := array[1] of byte; + sys->seek(fd, big(2*pos), 0); + pos++; + rv := sys->read(fd, x, 1); + if(rv != 1){ + if(rv < 0) + sys->print("readc err: %r\n"); + return -1; + } + v := int x[0]; + if(hex) + print("%2.2ux ", v); + return v; +} + +dtuple(next: int): int +{ + pos = next; + if((ttype := readc()) < 0) + return -1; + if(ttype == End) + return -1; + if((link := readc()) < 0) + return -1; + case ttype { + * => print("unknown tuple type #%2.2ux\n", ttype); + 16r01 => tdevice(ttype, link); + 16r15 => tvers1(ttype, link); + 16r17 => tdevice(ttype, link); + 16r1A => tcfig(ttype, link); + 16r1B => tentry(ttype, link); + } + if(link == End) + next = -1; + else + next = next+2+link; + return next; +} + +speedtab := array[16] of { +0 => 0, +1 => 250, +2 => 200, +3 => 150, +4 => 100, +}; + +mantissa := array[16] of { +1 => 10, +2 => 12, +3 => 13, +4 => 15, +5 => 20, +6 => 25, +7 => 30, +8 => 35, +9 => 40, +10=> 45, +11=> 50, +12=> 55, +13=> 60, +14=> 70, +15=> 80, +}; + +exponent := array[] of { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, +}; + +typetab := array [256] of { +1=> "Masked ROM", +2=> "PROM", +3=> "EPROM", +4=> "EEPROM", +5=> "FLASH", +6=> "SRAM", +7=> "DRAM", +16rD=> "IO+MEM", +* => "Unknown", +}; + +getlong(size: int): int +{ + x := 0; + for(i := 0; i < size; i++){ + if((c := readc()) < 0) + break; + x |= c<<(i*8); + } + return x; +} + +tdevice(dtype: int, tlen: int) +{ + while(tlen > 0){ + if((id := readc()) < 0) + return; + tlen--; + if(id == End) + return; + + speed := id & 16r7; + ns := 0; + if(speed == 16r7){ + if((speed = readc()) < 0) + return; + tlen--; + if(speed & 16r80){ + if((aespeed := readc()) < 0) + return; + ns = 0; + } else + ns = (mantissa[(speed>>3)&16rF]*exponent[speed&7])/10; + } else + ns = speedtab[speed]; + + ttype := id>>4; + if(ttype == 16rE){ + if((ttype = readc()) < 0) + return; + tlen--; + } + tname := typetab[ttype]; + if(tname == nil) + tname = "unknown"; + + if((size := readc()) < 0) + return; + tlen--; + bytes := ((size>>3)+1) * 512 * (1<<(2*(size&16r7))); + + ttname := "attr device"; + if(dtype == 1) + ttname = "device"; + print("%s %d bytes of %dns %s\n", ttname, bytes, ns, tname); + } +} + +tvers1(nil: int, tlen: int) +{ + if((major := readc()) < 0) + return; + tlen--; + if((minor := readc()) < 0) + return; + tlen--; + print("version %d.%d\n", major, minor); + while(tlen > 0){ + s := ""; + while(tlen > 0){ + if((c := readc()) < 0) + return; + tlen--; + if(c == 0) + break; + if(c == End){ + if(s != "") + print("\t%s<missing null>\n", s); + return; + } + s[len s] = c; + } + print("\t%s\n", s); + } +} + +tcfig(nil: int, nil: int) +{ + if((size := readc()) < 0) + return; + rasize := (size&16r3) + 1; + rmsize := ((size>>2)&16rf) + 1; + if((last := readc()) < 0) + return; + caddr := getlong(rasize); + cregs := getlong(rmsize); + + print("configuration registers at"); + for(i := 0; i < 16; i++) + if((1<<i) & cregs) + print(" (%d) #%ux", i, caddr + i*2); + print("\n"); +} + +intrname := array[16] of { +0 => "memory", +1 => "I/O", +4 => "Custom 0", +5 => "Custom 1", +6 => "Custom 2", +7 => "Custom 3", +* => "unknown" +}; + +vexp := array[8] of { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000 +}; +vmant := array[16] of { + 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, 90, +}; + +volt(name: string) +{ + if((c := readc()) < 0) + return; + exp := vexp[c&16r7]; + microv := vmant[(c>>3)&16rf]*exp; + while(c & 16r80){ + if((c = readc()) < 0) + return; + case c { + 16r7d => + break; # high impedence when sleeping + 16r7e or 16r7f => + microv = 0; # no connection + * => + exp /= 10; + microv += exp*(c&16r7f); + } + } + print(" V%s %duV", name, microv); +} + +amps(name: string) +{ + if((c := readc()) < 0) + return; + amps := vexp[c&16r7]*vmant[(c>>3)&16rf]; + while(c & 16r80){ + if((c = readc()) < 0) + return; + if(c == 16r7d || c == 16r7e || c == 16r7f) + amps = 0; + } + if(amps >= 1000000) + print(" I%s %dmA", name, amps/100000); + else if(amps >= 1000) + print(" I%s %duA", name, amps/100); + else + print(" I%s %dnA", name, amps*10); +} + +power(name: string) +{ + print("\t%s: ", name); + if((feature := readc()) < 0) + return; + if(feature & 1) + volt("nominal"); + if(feature & 2) + volt("min"); + if(feature & 4) + volt("max"); + if(feature & 8) + amps("static"); + if(feature & 16r10) + amps("avg"); + if(feature & 16r20) + amps("peak"); + if(feature & 16r40) + amps("powerdown"); + print("\n"); +} + +ttiming(name: string, scale: int) +{ + if((unscaled := readc()) < 0) + return; + scaled := (mantissa[(unscaled>>3)&16rf]*exponent[unscaled&7])/10; + scaled = scaled * vexp[scale]; + print("\t%s %dns\n", name, scaled); +} + +timing() +{ + if((c := readc()) < 0) + return; + i := c&16r3; + if(i != 3) + ttiming("max wait", i); + i = (c>>2)&16r7; + if(i != 7) + ttiming("max ready/busy wait", i); + i = (c>>5)&16r7; + if(i != 7) + ttiming("reserved wait", i); +} + +range(asize: int, lsize: int) +{ + address := getlong(asize); + alen := getlong(lsize); + print("\t\t%ux - %ux\n", address, address+alen); +} + +ioaccess := array[] of { + 0 => " no access", + 1 => " 8bit access only", + 2 => " 8bit or 16bit access", + 3 => " selectable 8bit or 8&16bit access", +}; + +iospace(c: int): int +{ + print("\tIO space %d address lines%s\n", c&16r1f, ioaccess[(c>>5)&3]); + if((c & 16r80) == 0) + return -1; + + if((c = readc()) < 0) + return -1; + + for(i := (c&16rf)+1; i; i--) + range((c>>4)&16r3, (c>>6)&16r3); + return 0; +} + +iospaces() +{ + if((c := readc()) < 0) + return; + iospace(c); +} + +irq() +{ + if((c := readc()) < 0) + return; + irqs: int; + if(c & 16r10){ + if((irq1 := readc()) < 0) + return; + if((irq2 := readc()) < 0) + return; + irqs = irq1|(irq2<<8); + } else + irqs = 1<<(c&16rf); + level := ""; + if(c & 16r20) + level = " level"; + pulse := ""; + if(c & 16r40) + pulse = " pulse"; + shared := ""; + if(c & 16r80) + shared = " shared"; + print("\tinterrupts%s%s%s", level, pulse, shared); + for(i := 0; i < 16; i++) + if(irqs & (1<<i)) + print(", %d", i); + print("\n"); +} + +memspace(asize: int, lsize: int, host: int) +{ + alen := getlong(lsize)*256; + address := getlong(asize)*256; + if(host){ + haddress := getlong(asize)*256; + print("\tmemory address range #%ux - #%ux hostaddr #%ux\n", + address, address+alen, haddress); + } else + print("\tmemory address range #%ux - #%ux\n", address, address+alen); +} + +misc() +{ +} + +tentry(nil: int, nil: int) +{ + if((c := readc()) < 0) + return; + def := ""; + if(c & 16r40) + def = " (default)"; + print("configuration %d%s\n", c&16r3f, def); + if(c & 16r80){ + if((i := readc()) < 0) + return; + tname := intrname[i & 16rf]; + if(tname == "") + tname = sys->sprint("type %d", i & 16rf); + attrib := ""; + if(i & 16r10) + attrib += " Battery status active"; + if(i & 16r20) + attrib += " Write Protect active"; + if(i & 16r40) + attrib += " Ready/Busy active"; + if(i & 16r80) + attrib += " Memory Wait required"; + print("\t%s device, %s\n", tname, attrib); + } + if((feature := readc()) < 0) + return; + case feature&16r3 { + 1 => + power("Vcc"); + 2 => + power("Vcc"); + power("Vpp"); + 3 => + power("Vcc"); + power("Vpp1"); + power("Vpp2"); + } + if(feature&16r4) + timing(); + if(feature&16r8) + iospaces(); + if(feature&16r10) + irq(); + case (feature>>5)&16r3 { + 1 => + memspace(0, 2, 0); + 2 => + memspace(2, 2, 0); + 3 => + if((c = readc()) < 0) + return; + for(i := 0; i <= (c&16r7); i++) + memspace((c>>5)&16r3, (c>>3)&16r3, c&16r80); + break; + } + if(feature&16r80) + misc(); +} diff --git a/appl/cmd/auxi/rdbgsrv.b b/appl/cmd/auxi/rdbgsrv.b new file mode 100644 index 00000000..2a958eee --- /dev/null +++ b/appl/cmd/auxi/rdbgsrv.b @@ -0,0 +1,222 @@ +implement RDbgSrv; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "styx.m"; + styx: Styx; + Rmsg, Tmsg: import styx; + +include "arg.m"; + arg: Arg; + +RDbgSrv: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +debug:= 0; +dev:= "/dev/eia0"; +speed:= 38400; +progname: string; +rpid := 0; +wpid := 0; + +usage() +{ + sys->fprint(stderr(), "Usage: rdbgsrv [-d n] [-s speed] [-f dev] mountpoint\n"); + raise "fail: usage"; +} + +init(nil: ref Draw->Context, av: list of string) +{ + sys = load Sys Sys->PATH; + if(sys == nil) + return; + styx = load Styx Styx->PATH; + if(styx == nil){ + sys->fprint(stderr(), "rdbgsrv: can't load %s; %r\n", Styx->PATH); + raise "fail:load"; + } + arg = load Arg Arg->PATH; + if(arg == nil){ + sys->fprint(stderr(), "rdbgsrv: can't load %s: %r\n", Arg->PATH); + raise "fail:load"; + } + + arg->init(av); + progname = arg->progname(); + while(o := arg->opt()) + case o { + 'd' => + d := arg->arg(); + if(d == nil) + usage(); + debug = int d; + 's' => + s := arg->arg(); + if(s == nil) + usage(); + speed = int s; + 'f' => + s := arg->arg(); + if(s == nil) + usage(); + dev = s; + 'h' => + usage(); + } + + mtpt := arg->arg(); + if(mtpt == nil) + usage(); + + ctl := dev + "ctl"; + cfd := sys->open(ctl, Sys->OWRITE); + if(cfd == nil){ + sys->fprint(stderr(), "%s: can't open %s: %r\n", progname, ctl); + raise "fail: open eia\n"; + } + + sys->fprint(cfd, "b%d", speed); + sys->fprint(cfd, "l8"); + sys->fprint(cfd, "pn"); + sys->fprint(cfd, "s1"); + + (rfd, wfd) := start(dev); + if(rfd == nil){ + sys->fprint(stderr(), "%s: failed to start protocol\n", progname); + raise "fail:proto start"; + } + + fds := array[2] of ref Sys->FD; + + if(sys->pipe(fds) == -1){ + sys->fprint(stderr(), "%s: pipe: %r\n", progname); + raise "fail:no pipe"; + } + + if(debug) + sys->fprint(stderr(), "%s: starting server\n", progname); + + rc := chan of int; + spawn copymsg(fds[1], wfd, "->", rc); + rpid = <-rc; + spawn copymsg(rfd, fds[1], "<-", rc); + wpid = <-rc; + + if(sys->mount(fds[0], nil, mtpt, Sys->MREPL, nil) == -1) { + fds[1] = nil; + sys->fprint(stderr(), "%s: can't mount on %s: %r\n", progname, mtpt); + quit("mount"); + } +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + +killpid(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +quit(err: string) +{ + killpid(rpid); + killpid(wpid); + if(err != nil) + raise "fail:"+err; + exit; +} + +start(name:string): (ref Sys->FD, ref Sys->FD) +{ + rfd := sys->open(name, Sys->OREAD); + wfd := sys->open(name, Sys->OWRITE); + if(rfd == nil || wfd == nil) + return (nil, nil); + if(sys->fprint(wfd, "go") < 0) + return (nil, nil); + c := array[1] of byte; + state := 0; + for(;;) { + if(sys->read(rfd, c, 1) != 1) + return (nil, nil); + if(state == 0 && c[0] == byte 'o') + state = 1; + else if(state == 1 && c[0] == byte 'k') + break; + else + state = 0; + } + return (rfd, wfd); +} + +copymsg(f: ref Sys->FD, t: ref Sys->FD, dir: string, pidc: chan of int) +{ + pidc <-= sys->pctl(0, nil); + + { + for(;;) { + (msg, err) := styx->readmsg(f, 0); + if(msg == nil){ + sys->fprint(stderr(), "%s: %s: read error: %s\n", progname, dir, err); + quit("error"); + } + if(debug &1) + trace(dir, msg); + if(debug & 2) + dump(dir, msg, len msg); + if(sys->write(t, msg, len msg) != len msg){ + sys->fprint(stderr(), "%s: %s: write error: %r\n", progname, dir); + quit("error"); + } + } + }exception e{ + "*" => + sys->print("%s: %s: %s: exiting\n", progname, dir, e); + quit("exception"); + } +} + +trace(sourcept: string, op: array of byte ) +{ + if(styx->istmsg(op)){ + (nil, m) := Tmsg.unpack(op); + if(m != nil) + sys->print("%s: %s\n", sourcept, m.text()); + else + sys->print("%s: unknown\n", sourcept); + }else{ + (nil, m) := Rmsg.unpack(op); + if(m != nil) + sys->print("%s: %s\n", sourcept, m.text()); + else + sys->print("%s: unknown\n", sourcept); + } +} + +dump(msg: string, buf: array of byte, n: int) +{ + sys->print("%s: [%d bytes]: ", msg, n); + s := ""; + for(i:=0;i<n;i++) { + if((i % 20) == 0) { + sys->print(" %s\n", s); + s = ""; + } + sys->print("%2.2x ", int buf[i]); + if(int buf[i] >= 32 && int buf[i] < 127) + s[len s] = int buf[i]; + else + s += "."; + } + for(i %= 20; i < 20; i++) + sys->print(" "); + sys->print(" %s\n\n", s); +} diff --git a/appl/cmd/auxi/rstyxd.b b/appl/cmd/auxi/rstyxd.b new file mode 100644 index 00000000..2f853ad5 --- /dev/null +++ b/appl/cmd/auxi/rstyxd.b @@ -0,0 +1,114 @@ +implement Rstyxd; + +include "sys.m"; +include "draw.m"; +include "sh.m"; +include "string.m"; + +sys: Sys; +str: String; +stderr: ref Sys->FD; + +Rstyxd: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +# +# argv is a list of Inferno supported algorithms from Security->Auth +# +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + if (str == nil) + badmod(String->PATH); + + fd := sys->fildes(0); + stderr = sys->fildes(2); + sys->pctl(sys->FORKFD, fd.fd :: nil); + + args := readargs(fd); + if(args == nil) + err(sys->sprint("error reading arguments: %r")); + + cmd := hd args; + s := ""; + for (a := args; a != nil; a = tl a) + s += hd a + " "; + sys->fprint(stderr, "rstyxd: cmd: %s\n", s); + s = nil; + file: string; + if(cmd == "sh") + file = "/dis/sh.dis"; + else + file = cmd + ".dis"; + mod := load Command file; + if(mod == nil){ + mod = load Command "/dis/"+file; + if(mod == nil) + badmod("/dis/"+file); + } + + sys->pctl(Sys->FORKNS|Sys->FORKENV, nil); + + if(sys->mount(fd, nil, "/n/client", Sys->MREPL, "") < 0) + err(sys->sprint("cannot mount connection on /n/client: %r")); + + if(sys->bind("/n/client/dev", "/dev", Sys->MBEFORE) < 0) + err(sys->sprint("cannot bind /n/client/dev to /dev: %r")); + + fd = sys->open("/dev/cons", sys->OREAD); + sys->dup(fd.fd, 0); + fd = sys->open("/dev/cons", sys->OWRITE); + sys->dup(fd.fd, 1); + sys->dup(fd.fd, 2); + fd = nil; + + mod->init(nil, args); +} + +readargs(fd: ref Sys->FD): list of string +{ + buf := array[1024] of byte; + c := array[1] of byte; + for(i:=0; ; i++){ + if(i>=len buf || sys->read(fd, c, 1)!=1) + return nil; + buf[i] = c[0]; + if(c[0] == byte '\n') + break; + } + nb := int string buf[0:i]; + if(nb <= 0) + return nil; + args := readn(fd, nb); + if (args == nil) + return nil; + return str->unquoted(string args[0:nb]); +} + +readn(fd: ref Sys->FD, nb: int): array of byte +{ + buf:= array[nb] of byte; + for(n:=0; n<nb;){ + m := sys->read(fd, buf[n:], nb-n); + if(m <= 0) + return nil; + n += m; + } + return buf; +} + + +err(s: string) +{ + sys->fprint(stderr, "rstyxd: %s\n", s); + raise "fail:error"; +} + +badmod(s: string) +{ + sys->fprint(stderr, "rstyxd: can't load %s: %r\n", s); + raise "fail:load"; +} diff --git a/appl/cmd/avr/burn.b b/appl/cmd/avr/burn.b new file mode 100644 index 00000000..d1004cd1 --- /dev/null +++ b/appl/cmd/avr/burn.b @@ -0,0 +1,859 @@ +implement Burn; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "timers.m"; + timers: Timers; + Timer: import timers; + +include "string.m"; + str: String; + +include "arg.m"; + +Burn: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Avr: adt { + id: int; + rev: int; + flashsize: int; + eepromsize: int; + fusebytes: int; + lockbytes: int; + serfprog: int; # serial fuse programming support + serlprog: int; # serial lockbit programming support + serflread: int; # serial fuse/lockbit reading support + commonlfr: int; # lockbits and fuses are combined + sermemprog: int; # serial memory programming support + pagesize: int; + eeprompagesize: int; + selftimed: int; # all instructions are self-timed + fullpar: int; # part has full parallel interface + polling: int; # polling can be used during SPI access + fpoll: int; # flash poll value + epoll1: int; # eeprom poll value 1 + epoll2: int; # eeprom poll value 2 + name: string; + signalpagel: int; # posn of PAGEL signal (16rD7 by default) + signalbs2: int; # posn of BS2 signal (16rA0 by default) +}; + +F, T: con iota; +ATMEGA128: con 16rB2; # 128k devices + +avrs: array of Avr = array[] of { + (ATMEGA128, 1, 131072, 4096, 3, 1, T, T, T, F, T, 256, 8, T, T, T, 16rFF, 16rFF, 16rFF, "ATmega128", 16rD7, 16rA0), +}; + +sfd: ref Sys->FD; +cfd: ref Sys->FD; +rd: ref Rd; +mib510 := 1; + +Rd: adt { + c: chan of array of byte; + pid: int; + fd: ref Sys->FD; + buf: array of byte; + new: fn(fd: ref Sys->FD): ref Rd; + read: fn(r: self ref Rd, ms: int): array of byte; + readn: fn(r: self ref Rd, n: int, ms: int): array of byte; + flush: fn(r: self ref Rd); + stop: fn(r: self ref Rd); + reader: fn(r: self ref Rd, c: chan of int); +}; + +debug := 0; +verify := 0; +erase := 1; +ignore := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = ckl(load Bufio Bufio->PATH, Bufio->PATH); + str = ckl(load String String->PATH, String->PATH); + timers = ckl(load Timers Timers->PATH, Timers->PATH); + + serial := "/dev/eia0"; + fuseext := -1; + fuselow := -1; + fusehigh := -1; + arg := ckl(load Arg Arg->PATH, Arg->PATH); + arg->init(args); + arg->setusage("burn [-rD] [-d serialdev] file.out"); + while((o := arg->opt()) != 0) + case o { + 'D' => debug++; + 'e' => erase = 0; + 'r' => verify = 1; + 'd' => serial = arg->earg(); + 'i' => ignore = 1; + 'E' => fuseext = fuseval(arg->earg()); + 'L' => fuselow = fuseval(arg->earg()); + 'H' => fusehigh = fuseval(arg->earg()); + * => arg->usage(); + } + args = arg->argv(); + if(len args != 1) + arg->usage(); + arg = nil; + + sfile := hd args; + fd := bufio->open(sfile, Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open %s: %r", sfile)); + + timers->init(2); + sfd = sys->open(serial, Sys->ORDWR); + if(sfd == nil) + err(sys->sprint("can't open %s: %r", "/dev/eia0")); + cfd = sys->open(serial+"ctl", Sys->ORDWR); + sys->fprint(cfd, "f"); + sys->fprint(cfd, "b115200"); + sys->fprint(cfd, "i8"); +# sys->fprint(cfd, "f\nb115200\ni8"); + rd = Rd.new(sfd); + + initialise(); + if(fuseext >= 0 || fuselow >= 0 || fusehigh >= 0){ + if(fuselow >= 0 && (fuselow & 16rF) == 0) + err("don't program external clock"); + if(fuseext >= 0 && (fuseext & (1<<0)) == 0) + err("don't program ATmega103 compatibility"); + if(fusehigh >= 0 && (fusehigh & (1<<7)) == 0) + err("don't program OCDEN=0"); + if(fusehigh >= 0 && writefusehigh(fusehigh) >= 0) + sys->print("set fuse high=%.2ux\n", fusehigh); + if(fuselow >= 0 && writefuselow(fuselow) >= 0) + sys->print("set fuse low=%.2ux\n", fuselow); + if(fuseext >= 0 && writefuseext(fuseext) >= 0) + sys->print("set fuse ext=%.2ux\n", fuseext); + shutdown(); + exit; + } + + if(!verify && erase){ + chiperase(); + sys->print("Erased flash\n"); + } + + totbytes := 0; + while((l := fd.gets('\n')) != nil){ + (c, addr, data) := sdecode(l); + if(c >= '1' && c <= '3'){ + if(verify){ + fdata := readflashdata(addr, len data); + if(!eq(fdata, data)) + sys->print("mismatch: %d::%d at %4.4ux\n", len data, len fdata, addr); + }else if(writeflashdata(addr, data) != len data) + err("failed to program device"); + totbytes += len data; + } else if(c == '0') + sys->print("title: %q\n", string data); + } + if(!verify){ + flushpage(); + sys->print("Programmed %ud (0x%4.4ux) bytes\n", totbytes, totbytes); + } + + shutdown(); +} + +ckl[T](m: T, s: string): T +{ + if(m == nil) + err(sys->sprint("can't load %s: %r", s)); + return m; +} + +fuseval(s: string): int +{ + (n, t) := str->toint(s, 16); + if(t != nil || n < 0 || n > 255) + err("illegal fuse value"); + return n; +} + +cache: (int, array of byte); + +readflashdata(addr: int, nbytes: int): array of byte +{ + data := array[nbytes] of byte; + ia := addr; + ea := addr+nbytes; + while(addr < ea){ + (ca, cd) := cache; + if(addr >= ca && addr < ca+len cd){ + n := nbytes; + o := addr-ca; + if(o+n > len cd) + n = len cd - o; + if(addr-ia+n > len data) + n = len data - (addr-ia); + data[addr-ia:] = cd[o:o+n]; + addr += n; + }else{ + ca = addr & ~16rFF; + cd = readflashpage(ca, 16r100); + cache = (ca, cd); + } + } + return data; +} + +writeflashdata(addr: int, data: array of byte): int +{ + pagesize := avrs[0].pagesize; + ia := addr; + ea := addr+len data; + while(addr < ea){ + (ca, cd) := cache; + if(addr >= ca && addr < ca+len cd){ + n := len data; + o := addr-ca; + if(o+n > len cd) + n = len cd - o; + cd[o:] = data[0:n]; + addr += n; + data = data[n:]; + }else{ + if(flushpage() < 0) + break; + cache = (addr & ~16rFF, array[pagesize] of {* => byte 16rFF}); + } + } + return addr-ia; +} + +flushpage(): int +{ + (ca, cd) := cache; + if(len cd == 0) + return 0; + cache = (0, nil); + if(writeflashpage(ca, cd) != len cd) + return -1; + return len cd; +} + +shutdown() +{ +# setisp(0); + if(rd != nil){ + rd.stop(); + rd = nil; + } + if(timers != nil) + timers->shutdown(); +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "burn: %s\n", s); + shutdown(); + raise "fail:error"; +} + +dump(a: array of byte): string +{ + s := sys->sprint("[%d]", len a); + for(i := 0; i < len a; i++) + s += sys->sprint(" %.2ux", int a[i]); + return s; +} + +initialise() +{ + if(mib510){ + # MIB510-specific: switch rs232 to STK500 + for(i:=0; i<8; i++){ + setisp0(1); + sys->sleep(10); + rd.flush(); + if(setisp(1)) + break; + } + if(!setisp(1)) + err("no response from programmer"); + } + resync(); + resync(); + if(!mib510){ + r := rpc(array[] of {Cmd_STK_GET_SIGN_ON}, 7); + if(r != nil) + sys->print("got: %q\n", string r); + } + r := readsig(); + if(len r > 0 && r[0] != byte 16rFF) + sys->print("sig: %s\n", dump(r)); + (min, maj) := version(); + sys->print("Firmware version: %s.%s\n", min, maj); + setdevice(avrs[0]); + pgmon(); + r = readsig(); + sys->print("sig: %s\n", dump(r)); + pgmoff(); + if(len r < 3 || r[0] != byte 16r1e || r[1] != byte 16r97 || r[2] != byte 16r02) + if(!ignore) + err("unlikely response: check connections"); + + # could set voltages here... + sys->print("fuses: h=%.2ux l=%.2ux e=%.2ux\n", readfusehigh(), readfuselow(), readfuseext()); +} + +resync() +{ + for(i := 0; i < 8; i++){ + rd.flush(); + r := rpc(array[] of {Cmd_STK_GET_SYNC}, 0); + if(r != nil) + return; + } + err("lost sync with programmer"); +} + +getparam(p: byte): int +{ + r := rpc(array[] of {Cmd_STK_GET_PARAMETER, p}, 1); + if(len r > 0) + return int r[0]; + return -1; +} + +version(): (string, string) +{ + maj := getparam(Parm_STK_SW_MAJOR); + min := getparam(Parm_STK_SW_MINOR); + if(mib510) + return (sys->sprint("%c", maj), sys->sprint("%c", min)); + return (sys->sprint("%d", maj), sys->sprint("%d", min)); +} + +eq(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +# +# Motorola S records +# + +badsrec(s: string) +{ + err("bad S record: "+s); +} + +hexc(c: int): int +{ + if(c >= '0' && c <= '9') + return c-'0'; + if(c >= 'a' && c <= 'f') + return c-'a'+10; + if(c >= 'A' && c <= 'F') + return c-'A'+10; + return -1; +} + +g8(s: string): int +{ + if(len s >= 2){ + c0 := hexc(s[0]); + c1 := hexc(s[1]); + if(c0 >= 0 && c1 >= 0) + return (c0<<4) | c1; + } + return -1; +} + +# S d len +sdecode(s: string): (int, int, array of byte) +{ + while(len s > 0 && (s[len s-1] == '\r' || s[len s-1] == '\n')) + s = s[0:len s-1]; + if(len s < 4 || s[0] != 'S') + badsrec(s); + l := g8(s[2:4]); + if(l < 0) + badsrec("length: "+s); + if(2*l != len s - 4) + badsrec("length: "+s); + csum := l; + na := 2; + if(s[1] >= '1' && s[1] <= '3') + na = s[1]-'1'+2; + addr := 0; + for(i:=0; i<na; i++){ + b := g8(s[4+i*2:]); + if(b < 0) + badsrec(s); + csum += b; + addr = (addr << 8) | b; + } + case s[1] { + '0' or # used as segment name (seems to be srec file name with TinyOS) + '1' to '3' or # data + '5' or # plot so far + '7' to '9' => # end/start address + ; + * => + badsrec("type: "+s); + } + data := array[l-na-1] of byte; + for(i = 0; i < len data; i++){ + c := g8(s[4+(na+i)*2:]); + csum += c; + data[i] = byte c; + } + v := g8(s[4+l*2-2:]); + csum += v; + if((csum & 16rFF) != 16rFF) + badsrec("checksum: "+s); + return (s[1], addr, data); +} + +# +# serial port +# + +Rd.new(fd: ref Sys->FD): ref Rd +{ + r := ref Rd(chan[4] of array of byte, 0, fd, nil); + c := chan of int; + spawn r.reader(c); + <-c; + return r; +} + +Rd.reader(r: self ref Rd, c: chan of int) +{ + r.pid = sys->pctl(0, nil); + c <-= 1; + for(;;){ + buf := array[258] of byte; + n := sys->read(r.fd, buf, len buf); + if(n <= 0){ + r.pid = 0; + err(sys->sprint("read error: %r")); + } + if(debug) + sys->print("<- %s\n", dump(buf[0:n])); + r.c <-= buf[0:n]; + } +} + +Rd.read(r: self ref Rd, ms: int): array of byte +{ + if((a := r.buf) != nil){ + r.buf = nil; + return a; + } + t := Timer.start(ms); + alt{ + a = <-r.c => + t.stop(); + Acc: + for(;;){ + sys->sleep(5); + alt{ + b := <-r.c => + if(b == nil) + break Acc; + a = cat(a, b); + * => + break Acc; + } + } + return a; + <-t.timeout => + return nil; + } +} + +Rd.readn(r: self ref Rd, n: int, ms: int): array of byte +{ + a: array of byte; + + while((need := n - len a) > 0){ + b := r.read(ms); + if(b == nil) + break; + if(len b > need){ + r.buf = b[need:]; + b = b[0:need]; + } + a = cat(a, b); + } + return a; +} + +Rd.flush(r: self ref Rd) +{ + r.buf = nil; + sys->sleep(5); + for(;;){ + alt{ + <-r.c => + ; + * => + return; + } + } +} + +Rd.stop(r: self ref Rd) +{ + pid := r.pid; + if(pid){ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); + } +} + +cat(a, b: array of byte): array of byte +{ + if(len b == 0) + return a; + if(len a == 0) + return b; + c := array[len a + len b] of byte; + c[0:] = a; + c[len a:] = b; + return c; +} + +# +# STK500 communication protocol +# + +STK_SIGN_ON_MESSAGE: con "AVR STK"; # Sign on string for Cmd_STK_GET_SIGN_ON + +# Responses + +Resp_STK_OK: con byte 16r10; +Resp_STK_FAILED: con byte 16r11; +Resp_STK_UNKNOWN: con byte 16r12; +Resp_STK_NODEVICE: con byte 16r13; +Resp_STK_INSYNC: con byte 16r14; +Resp_STK_NOSYNC: con byte 16r15; + +Resp_ADC_CHANNEL_ERROR: con byte 16r16; +Resp_ADC_MEASURE_OK: con byte 16r17; +Resp_PWM_CHANNEL_ERROR: con byte 16r18; +Resp_PWM_ADJUST_OK: con byte 16r19; + +# Special constants + +Sync_CRC_EOP: con byte 16r20; + +# Commands + +Cmd_STK_GET_SYNC: con byte 16r30; +Cmd_STK_GET_SIGN_ON: con byte 16r31; + +Cmd_STK_SET_PARAMETER: con byte 16r40; +Cmd_STK_GET_PARAMETER: con byte 16r41; +Cmd_STK_SET_DEVICE: con byte 16r42; +Cmd_STK_SET_DEVICE_EXT: con byte 16r45; + +Cmd_STK_ENTER_PROGMODE: con byte 16r50; +Cmd_STK_LEAVE_PROGMODE: con byte 16r51; +Cmd_STK_CHIP_ERASE: con byte 16r52; +Cmd_STK_CHECK_AUTOINC: con byte 16r53; +Cmd_STK_LOAD_ADDRESS: con byte 16r55; +Cmd_STK_UNIVERSAL: con byte 16r56; +Cmd_STK_UNIVERSAL_MULTI: con byte 16r57; + +Cmd_STK_PROG_FLASH: con byte 16r60; +Cmd_STK_PROG_DATA: con byte 16r61; +Cmd_STK_PROG_FUSE: con byte 16r62; +Cmd_STK_PROG_LOCK: con byte 16r63; +Cmd_STK_PROG_PAGE: con byte 16r64; +Cmd_STK_PROG_FUSE_EXT: con byte 16r65; + +Cmd_STK_READ_FLASH: con byte 16r70; +Cmd_STK_READ_DATA: con byte 16r71; +Cmd_STK_READ_FUSE: con byte 16r72; +Cmd_STK_READ_LOCK: con byte 16r73; +Cmd_STK_READ_PAGE: con byte 16r74; +Cmd_STK_READ_SIGN: con byte 16r75; +Cmd_STK_READ_OSCCAL: con byte 16r76; +Cmd_STK_READ_FUSE_EXT: con byte 16r77; +Cmd_STK_READ_OSCCAL_EXT: con byte 16r78; + +# Parameter constants + +Parm_STK_HW_VER: con byte 16r80; # ' ' - R +Parm_STK_SW_MAJOR: con byte 16r81; # ' ' - R +Parm_STK_SW_MINOR: con byte 16r82; # ' ' - R +Parm_STK_LEDS: con byte 16r83; # ' ' - R/W +Parm_STK_VTARGET: con byte 16r84; # ' ' - R/W +Parm_STK_VADJUST: con byte 16r85; # ' ' - R/W +Parm_STK_OSC_PSCALE: con byte 16r86; # ' ' - R/W +Parm_STK_OSC_CMATCH: con byte 16r87; # ' ' - R/W +Parm_STK_RESET_DURATION: con byte 16r88; # ' ' - R/W +Parm_STK_SCK_DURATION: con byte 16r89; # ' ' - R/W + +Parm_STK_BUFSIZEL: con byte 16r90; # ' ' - R/W, Range {0..255} +Parm_STK_BUFSIZEH: con byte 16r91; # ' ' - R/W, Range {0..255} +Parm_STK_DEVICE: con byte 16r92; # ' ' - R/W, Range {0..255} +Parm_STK_PROGMODE: con byte 16r93; # ' ' - 'P' or 'S' +Parm_STK_PARAMODE: con byte 16r94; # ' ' - TRUE or FALSE +Parm_STK_POLLING: con byte 16r95; # ' ' - TRUE or FALSE +Parm_STK_SELFTIMED: con byte 16r96; # ' ' - TRUE or FALSE + +# status bits + +Stat_STK_INSYNC: con byte 16r01; # INSYNC status bit, '1' - INSYNC +Stat_STK_PROGMODE: con byte 16r02; # Programming mode, '1' - PROGMODE +Stat_STK_STANDALONE: con byte 16r04; # Standalone mode, '1' - SM mode +Stat_STK_RESET: con byte 16r08; # RESET button, '1' - Pushed +Stat_STK_PROGRAM: con byte 16r10; # Program button, ' 1' - Pushed +Stat_STK_LEDG: con byte 16r20; # Green LED status, '1' - Lit +Stat_STK_LEDR: con byte 16r40; # Red LED status, '1' - Lit +Stat_STK_LEDBLINK: con byte 16r80; # LED blink ON/OFF, '1' - Blink + +ispmode := array[] of {byte 16rAA, byte 16r55, byte 16r55, byte 16rAA, byte 16r17, byte 16r51, byte 16r31, byte 16r13, byte 0}; # last byte is 1 to switch isp on 0 to switch off + +ck(r: array of byte) +{ + if(r == nil) + err("programming failed"); +} + +pgmon() +{ + ck(rpc(array[] of {Cmd_STK_ENTER_PROGMODE}, 0)); +} + +pgmoff() +{ + ck(rpc(array[] of {Cmd_STK_LEAVE_PROGMODE}, 0)); +} + +setisp0(on: int) +{ + rd.flush(); + buf := array[len ispmode] of byte; + buf[0:] = ispmode; + buf[8] = byte on; + sys->write(sfd, buf, len buf); +} + +setisp(on: int): int +{ + rd.flush(); + buf := array[len ispmode] of byte; + buf[0:] = ispmode; + buf[8] = byte on; + r := send(buf, 2); + return len r == 2 && ok(r); +} + +readsig(): array of byte +{ + r := send(array[] of {Cmd_STK_READ_SIGN, Sync_CRC_EOP}, 5); + # doesn't behave as documented in AVR061: it repeats the command bytes instead + if(len r != 5 || r[0] != Cmd_STK_READ_SIGN || r[4] != Sync_CRC_EOP){ + sys->fprint(sys->fildes(2), "bad reply %s\n", dump(r)); + return nil; + } + return r[1:len r-1]; # trim proto bytes +} + +pgrpc(a: array of byte, repn: int): array of byte +{ + pgmon(); + r := rpc(a, repn); + pgmoff(); + return r; +} + +eop := array[] of {Sync_CRC_EOP}; + +rpc(a: array of byte, repn: int): array of byte +{ + r := send(cat(a, eop), repn+2); + if(!ok(r)){ + if(len r >= 2 && r[0] == Resp_STK_INSYNC && r[len r-1] == Resp_STK_NODEVICE) + err("internal error: programming parameters not correctly set"); + if(len r >= 1 && r[0] == Resp_STK_NOSYNC) + err("lost synchronisation"); + sys->fprint(sys->fildes(2), "bad reply %s\n", dump(r)); + return nil; + } + return r[1:len r-1]; # trim sync bytes +} + +send(a: array of byte, repn: int): array of byte +{ + if(debug) + sys->print("-> %s\n", dump(a)); + if(sys->write(sfd, a, len a) != len a) + err(sys->sprint("write error: %r")); + return rd.readn(repn, 2000); +} + +ok(r: array of byte): int +{ + return len r >= 2 && r[0] == Resp_STK_INSYNC && r[len r -1] == Resp_STK_OK; +} + +universal(req: array of byte): int +{ + r := pgrpc(cat(array[] of {Cmd_STK_UNIVERSAL}, req), 1); + if(r == nil) + return -1; + return int r[0]; +} + +setdevice(d: Avr) +{ + b := array[] of { + Cmd_STK_SET_DEVICE, + byte d.id, + byte d.rev, + byte 0, # prog type (CHECK) + byte d.fullpar, + byte d.polling, + byte d.selftimed, + byte d.lockbytes, + byte d.fusebytes, + byte d.fpoll, + byte d.fpoll, + byte d.epoll1, + byte d.epoll2, + byte (d.pagesize >> 8), byte d.pagesize, + byte (d.eepromsize>>8), byte d.eepromsize, + byte (d.flashsize>>24), byte (d.flashsize>>16), byte (d.flashsize>>8), byte d.flashsize + }; + ck(rpc(b, 0)); + if(mib510) + return; + b = array[] of { + Cmd_STK_SET_DEVICE_EXT, + byte 4, + byte d.eeprompagesize, + byte d.signalpagel, + byte d.signalbs2, + byte 0 # ResetDisable + }; + ck(rpc(b, 0)); +} + +chiperase() +{ + ck(pgrpc(array[] of {Cmd_STK_CHIP_ERASE}, 0)); +} + +readfuselow(): int +{ + return universal(array[] of {byte 16r50, byte 0, byte 0, byte 0}); +} + +readfusehigh(): int +{ + return universal(array[] of {byte 16r58, byte 8, byte 0, byte 0}); +} + +readfuseext(): int +{ + return universal(array[] of {byte 16r50, byte 8, byte 0, byte 0}); +} + +readlockfuse(): int +{ + return universal(array[] of {byte 16r58, byte 0, byte 0, byte 0}); +} + +readflashpage(addr: int, nb: int): array of byte +{ + return readmem('F', addr/2, nb); +} + +readeeprompage(addr: int, nb: int): array of byte +{ + return readmem('E', addr, nb); +} + +readmem(memtype: int, addr: int, nb: int): array of byte +{ + if(nb > 256) + nb = 256; + pgmon(); + r := rpc(array[] of {Cmd_STK_LOAD_ADDRESS, byte addr, byte (addr>>8)}, 0); + if(r != nil){ + r = send(array[] of {Cmd_STK_READ_PAGE, byte (nb>>8), byte nb, byte memtype, Sync_CRC_EOP}, nb+2); + l := len r; + # AVR601 says last byte should be Resp_STK_OK but it's not, at least on MIB; check for both + if(l >= 2 && r[0] == Resp_STK_INSYNC && (r[l-1] == Resp_STK_INSYNC || r[l-1] == Resp_STK_OK)) + r = r[1:l-1]; # trim framing bytes + else{ + sys->print("bad reply: %s\n", dump(r)); + r = nil; + } + if(len r < nb) + sys->print("short [%d@%4.4ux]\n", nb, addr); + } + pgmoff(); + return r; +} + +writeflashpage(addr: int, data: array of byte): int +{ + return writemem('F', addr/2, data); +} + +writeeeprompage(addr: int, data: array of byte): int +{ + return writemem('E', addr, data); +} + +writemem(memtype: int, addr: int, data: array of byte): int +{ + nb := len data; + if(nb > 256){ + nb = 256; + data = data[0:nb]; + } + pgmon(); + r := rpc(array[] of {Cmd_STK_LOAD_ADDRESS, byte addr, byte (addr>>8)}, 0); + if(r != nil){ + r = rpc(cat(array[] of {Cmd_STK_PROG_PAGE, byte (nb>>8), byte nb, byte memtype},data), 0); + if(r == nil) + nb = -1; + } + pgmoff(); + return nb; +} + +writefuseext(v: int): int +{ + return universal(array[] of {byte 16rAC, byte 16rA4, byte 16rFF, byte v}); +} + +writefuselow(v: int): int +{ + return universal(array[] of {byte 16rAC, byte 16rA0, byte 16rFF, byte v}); +} + +writefusehigh(v: int): int +{ + return universal(array[] of {byte 16rAC, byte 16rA8, byte 16rFF, byte v}); +} diff --git a/appl/cmd/avr/mkfile b/appl/cmd/avr/mkfile new file mode 100644 index 00000000..2c6a5a33 --- /dev/null +++ b/appl/cmd/avr/mkfile @@ -0,0 +1,10 @@ +<../../../mkconfig + +TARG=\ + burn.dis\ + +SYSMODULES=\ + +DISBIN=$ROOT/dis/avr + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/basename.b b/appl/cmd/basename.b new file mode 100644 index 00000000..8d0ad5a8 --- /dev/null +++ b/appl/cmd/basename.b @@ -0,0 +1,50 @@ +implement Basename; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "names.m"; + names: Names; + +include "arg.m"; + +Basename: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + names = load Names Names->PATH; + arg := load Arg Arg->PATH; + + dirname := 0; + arg->init(args); + arg->setusage("basename [-d] string [suffix]"); + while((o := arg->opt()) != 0) + case o { + 'd' => + dirname = 1; + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil || tl args != nil && (dirname || tl tl args != nil)) + arg->usage(); + arg = nil; + + if(dirname){ + s := names->dirname(hd args); + if(s == nil) + s = "."; + sys->print("%s\n", s); + exit; + } + suffix: string; + if(tl args != nil) + suffix = hd tl args; + sys->print("%s\n", names->basename(hd args, suffix)); +} diff --git a/appl/cmd/bind.b b/appl/cmd/bind.b new file mode 100644 index 00000000..fa6c734b --- /dev/null +++ b/appl/cmd/bind.b @@ -0,0 +1,66 @@ +implement Bind; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +Bind: module +{ + init: fn(ctxt: ref Draw->Context, args: list of string); +}; + +stderr: ref Sys->FD; + +usage() +{ + sys->fprint(stderr, "usage: bind [-a|-b|-c|-ac|-bc] [-q] source target\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + stderr = sys->fildes(2); + flags := 0; + qflag := 0; + if(args != nil) + args = tl args; + while(args != nil && (a := hd args) != "" && a[0] == '-'){ + args = tl args; + if(a == "--") + break; + for(o := 1; o < len a; o++) + case a[o] { + 'a' => + flags |= Sys->MAFTER; + 'b' => + flags |= Sys->MBEFORE; + 'c' => + flags |= Sys->MCREATE; + 'q' => + qflag = 1; + * => + usage(); + } + } + if(len args != 2 || flags&Sys->MAFTER && flags&Sys->MBEFORE) + usage(); + + f1 := hd args; + f2 := hd tl args; + if(sys->bind(f1, f2, flags) < 0){ + if(qflag) + exit; + # try to improve the error message + err := sys->sprint("%r"); + if(sys->stat(f1).t0 < 0) + sys->fprint(stderr, "bind: %s: %r\n", f1); + else if(sys->stat(f2).t0 < 0) + sys->fprint(stderr, "bind: %s: %r\n", f2); + else + sys->fprint(stderr, "bind: cannot bind %s onto %s: %s\n", f1, f2, err); + raise "fail:bind"; + } +} diff --git a/appl/cmd/bit2gif.b b/appl/cmd/bit2gif.b new file mode 100644 index 00000000..52788e76 --- /dev/null +++ b/appl/cmd/bit2gif.b @@ -0,0 +1,86 @@ +# +# bit2gif - +# +# A simple command line utility for converting inferno bitmaps +# to gif images. +# +# Craig Newell, Jan. 1999 CraigN@cheque.uq.edu.au +# +implement bit2gif; + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Display: import draw; +include "string.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "imagefile.m"; + +bit2gif : module +{ + init: fn(ctx: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->print("usage: bit2gif <inferno bitmap>\n"); + exit; +} + +init(ctx: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + + # check arguments + if (argv == nil) + usage(); + argv = tl argv; + if (argv == nil) + usage(); + s := hd argv; + if (len s && s[0] == '-') + usage(); + + # load the modules + str := load String String->PATH; + draw = load Draw Draw->PATH; + bufio = load Bufio Bufio->PATH; + imgfile := load WImagefile WImagefile->WRITEGIFPATH; + imgfile->init(bufio); + + # open the display + display: ref Draw->Display; + if (ctx == nil) { + display = Display.allocate(nil); + } else { + display = ctx.display; + } + + # process all the files + while (argv != nil) { + + # get the filenames + bit_name := hd argv; + (gif_name, nil) := str->splitstrl(bit_name, ".bit"); + gif_name = gif_name + ".gif"; + + # load inferno bitmap + img := display.open(bit_name); + if (img == nil) { + sys->print("bit2gif: unable to read <%s>\n", bit_name); + } else { + # save as gif + o := bufio->create(gif_name, Bufio->OWRITE, 8r644); + if (o != nil) { + imgfile->writeimage(o, img); + o.close(); + } + } + + # next argument + argv = tl argv; + } +} diff --git a/appl/cmd/broke.b b/appl/cmd/broke.b new file mode 100644 index 00000000..41f2dd89 --- /dev/null +++ b/appl/cmd/broke.b @@ -0,0 +1,84 @@ +implement Broke; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +Broke: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + fd := sys->open("/prog", Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open /prog: %r")); + killed := ""; + for(;;){ + (n, dir) := sys->dirread(fd); + if(n <= 0){ + if(n < 0) + err(sys->sprint("error reading /prog: %r")); + break; + } + for(i := 0; i < n; i++) + if(isbroken(dir[i].name) && kill(dir[i].name)) + killed += sys->sprint(" %s", dir[i].name); + } + if(killed != nil) + sys->print("%s\n", killed); +} + +isbroken(pid: string): int +{ + statf := "/prog/" + pid + "/status"; + fd := sys->open(statf, Sys->OREAD); + if (fd == nil) + return 0; + buf := array[256] of byte; + n := sys->read(fd, buf, len buf); + if (n < 0) { # process died or is exiting + # sys->fprint(stderr(), "broke: can't read %s: %r\n", statf); + return 0; + } + (nf, l) := sys->tokenize(string buf[0:n], " "); + return nf >= 5 && hd tl tl tl tl l == "broken"; +} + +kill(pid: string): int +{ + ctl := "/prog/" + pid + "/ctl"; + fd := sys->open(ctl, sys->OWRITE); + if(fd == nil || sys->fprint(fd, "kill") < 0){ + sys->fprint(stderr(), "broke: can't kill %s: %r\n", pid); # but press on + return 0; + } + return 1; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "broke: %s\n", s); + raise "fail:error"; +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + +user(): string +{ + fd := sys->open("/dev/user", sys->OREAD); + if(fd == nil) + return "inferno"; + + buf := array[64] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return "inferno"; + + return string buf[0:n]; +} diff --git a/appl/cmd/bytes.b b/appl/cmd/bytes.b new file mode 100644 index 00000000..e45c4fe4 --- /dev/null +++ b/appl/cmd/bytes.b @@ -0,0 +1,212 @@ +implement Bytes; +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +stdin, stdout: ref Iobuf; + +Bytes: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(stderr, "usage: bytes start end [bytes]\n"); + raise "fail:usage"; +} + +END: con 16r7fffffff; +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) { + sys->fprint(stderr, "bytes: cannot load %s: %r\n", Bufio->PATH); + raise "fail:bad module"; + } + stdin = bufio->fopen(sys->fildes(0), Sys->OREAD); + stdout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + start := end := END; + if (len argv < 3) + usage(); + argv = tl argv; + if (hd argv != "end") + start = int hd argv; + argv = tl argv; + if (hd argv != "end") + end = int hd argv; + if (end < start) { + sys->fprint(stderr, "bytes: out of order range\n"); + raise "fail:bad range"; + } + argv = tl argv; + if (argv == nil) + showbytes(start, end); + else { + if (tl argv != nil) + usage(); + b := s2bytes(hd argv); + setbytes(start, end, b); + } + stdout.close(); +} + +showbytes(start, end: int) +{ + buf := array[Sys->ATOMICIO] of byte; + hold := array[Sys->UTFmax] of byte; + tot := 0; + nhold := 0; + while (tot < end && (n := stdin.read(buf[nhold:], len buf - nhold)) > 0) { + sys->fprint(stderr, "bytes: read %d bytes\n", n); + if (tot + n < start) + continue; + sb := 0; + eb := n; + if (start > tot) + sb = start - tot; + if (tot + n > end) + eb = end - tot; + nhold = putbytes(buf[sb:eb], hold); + buf[0:] = hold[0:nhold]; + tot += n - nhold; + } + sys->fprint(stderr, "out of loop\n"); + flushbytes(hold[0:nhold]); +} + +setbytes(start, end: int, d: array of byte) +{ + buf := array[Sys->ATOMICIO] of byte; + tot := 0; + while ((n := stdin.read(buf, len buf)) > 0) { + if (tot + n < start || tot >= end) { + stdout.write(buf, n); + continue; + } + if (tot <= start) { + stdout.write(buf[0:start-tot], start-tot); + stdout.write(d, len d); + if (end == END) + return; + } + if (tot + n >= end) + stdout.write(buf[end - tot:], n - (end - tot)); + tot += n; + } + if (tot == start || start == END) + stdout.write(d, len d); +} + +putbytes(d: array of byte, hold: array of byte): int +{ + i := 0; + while (i < len d) { + (c, n, ok) := sys->byte2char(d, i); + if (ok && n > 0) { + if (c == '\\') + stdout.putc('\\'); + stdout.putc(c); + } else { + if (n == 0) { + hold[0:] = d[i:]; + return len d - i; + } else { + putbyte(d[i]); + n = 1; + } + } + i += n; + } + return 0; +} + +flushbytes(hold: array of byte) +{ + for (i := 0; i < len hold; i++) + putbyte(hold[i]); +} + +putbyte(b: byte) +{ + stdout.puts(sys->sprint("\\%2.2X", int b)); +} + +isbschar(c: int): int +{ + case c { + 'n' or 'r' or 't' or 'v' => + return 1; + } + return 0; +} + +s2bytes(s: string): array of byte +{ + d := array[len s + 2] of byte; + j := 0; + for (i := 0; i < len s; i++) { + if (s[i] == '\\') { + if (i >= len s - 1 || (!isbschar(s[i+1]) && i >= len s - 2)) { + sys->fprint(stderr, "bytes: invalid backslash sequence\n"); + raise "fail:bad args"; + } + d = assure(d, j + 1); + if (isbschar(s[i+1])) { + case s[i+1] { + 'n' => d[j++] = byte '\n'; + 'r' => d[j++] = byte '\r'; + 't' => d[j++] = byte '\t'; + 'v' => d[j++] = byte '\v'; + '\\' => d[j++] = byte '\\'; + * => + sys->fprint(stderr, "bytes: invalid backslash sequence\n"); + raise "fail:bad args"; + } + i++; + } else if (!ishex(s[i+1]) || !ishex(s[i+2])) { + sys->fprint(stderr, "bytes: invalid backslash sequence\n"); + raise "fail:bad args"; + } else { + d[j++] = byte ((hex(s[i+1]) << 4) + hex(s[i+2])); + i += 2; + } + } else { + d = assure(d, j + 3); + j += sys->char2byte(s[i], d, j); + } + } + return d[0:j]; +} + +assure(d: array of byte, n: int): array of byte +{ + if (len d >= n) + return d; + nd := array[n] of byte; + nd[0:] = d; + return nd; +} + +ishex(c: int): int +{ + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +hex(c: int): int +{ + case c { + '0' to '9' => + return c - '0'; + 'a' to 'f' => + return c - 'a' + 10; + 'A' to 'F' => + return c- 'A' + 10; + } + return 0; +} diff --git a/appl/cmd/cal.b b/appl/cmd/cal.b new file mode 100644 index 00000000..90c4f777 --- /dev/null +++ b/appl/cmd/cal.b @@ -0,0 +1,295 @@ +implement Cal; + +# +# Copyright © 1995-2002 Lucent Technologies Inc. All rights reserved. +# Limbo transliteration 2003 by Vita Nuova +# This software is subject to the Plan 9 Open Source Licence. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "daytime.m"; + daytime: Daytime; + Tm: import daytime; + +Cal: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +dayw := " S M Tu W Th F S"; +smon := array[] of { + "January", "February", "March", "April", + "May", "June", "July", "August", + "September", "October", "November", "December", +}; + +mon := array[] of { + 0, + 31, 29, 31, 30, + 31, 30, 31, 31, + 30, 31, 30, 31, +}; + +bout: ref Iobuf; + +init(nil: ref Draw->Context, args: list of string) +{ + y, m: int; + + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + daytime = load Daytime Daytime->PATH; + + argc := len args; + if(argc > 3){ + sys->fprint(sys->fildes(2), "usage: cal [month] [year]\n"); + raise "fail:usage"; + } + bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + +# +# no arg, print current month +# + if(argc <= 1) { + m = curmo(); + y = curyr(); + return xshort(m, y); + } + args = tl args; + +# +# one arg +# if looks like a month, print month +# else print year +# + if(argc == 2) { + y = number(hd args); + if(y < 0) + y = -y; + if(y >= 1 && y <= 12) + return xshort(y, curyr()); + return xlong(y); + } + +# +# two arg, month and year +# + m = number(hd args); + if(m < 0) + m = -m; + y = number(hd tl args); + return xshort(m, y); +} + +# +# print out just month +# +xshort(m: int, y: int) +{ + if(m < 1 || m > 12) + badarg(); + if(y < 1 || y > 9999) + badarg(); + bout.puts(sys->sprint(" %s %ud\n", smon[m-1], y)); + bout.puts(sys->sprint("%s\n", dayw)); + lines := cal(m, y); + for(i := 0; i < len lines; i++){ + bout.puts(lines[i]); + bout.putc('\n'); + } + bout.flush(); +} + +# +# print out complete year +# +xlong(y: int) +{ + if(y<1 || y>9999) + badarg(); + bout.puts("\n\n\n"); + bout.puts(sys->sprint(" %ud\n", y)); + bout.putc('\n'); + months := array[3] of array of string; + for(i:=0; i<12; i+=3) { + bout.puts(sys->sprint(" %.3s", smon[i])); + bout.puts(sys->sprint(" %.3s", smon[i+1])); + bout.puts(sys->sprint(" %.3s\n", smon[i+2])); + bout.puts(sys->sprint("%s %s %s\n", dayw, dayw, dayw)); + for(j := 0; j < 3; j++) + months[j] = cal(i+j+1, y); + for(l := 0; l < 6; l++){ + s := ""; + for(j = 0; j < 3; j++) + s += sys->sprint("%-20.20s ", months[j][l]); + for(j = len s; j > 0 && s[j-1] == ' ';) + j--; + bout.puts(s[0:j]); + bout.putc('\n'); + } + } + bout.flush(); +} + +badarg() +{ + sys->fprint(sys->fildes(2), "cal: bad argument\n"); + raise "fail:bad argument"; +} + +dict := array[] of { + ("january", 1), + ("february", 2), + ("march", 3), + ("april", 4), + ("may", 5), + ("june", 6), + ("july", 7), + ("august", 8), + ("sept", 9), + ("september", 9), + ("october", 10), + ("november", 11), + ("december", 12), +}; + +# +# convert to a number. +# if its a dictionary word, +# return negative number +# +number(s: string): int +{ + if(len s >= 3){ + for(n:=0; n < len dict; n++){ + (word, val) := dict[n]; + if(s == word || s == word[0:3]) + return -val; + } + } + n := 0; + for(i := 0; i < len s; i++){ + c := s[i]; + if(c<'0' || c>'9') + badarg(); + n = n*10 + c-'0'; + } + return n; +} + +pstr(str: string, n: int) +{ + bout.puts(sys->sprint("%-*.*s\n", n, n, str)); +} + +cal(m: int, y: int): array of string +{ + d := jan1(y); + mon[9] = 30; + + case (jan1(y+1)+7-d)%7 { + + # + # non-leap year + # + 1 => + mon[2] = 28; + + # + # leap year + # + 2 => + mon[2] = 29; + + # + # 1752 + # + * => + mon[2] = 29; + mon[9] = 19; + } + for(i:=1; i<m; i++) + d += mon[i]; + d %= 7; + lines := array[6] of string; + l := 0; + s := ""; + for(i = 0; i < d; i++) + s += " "; + for(i=1; i<=mon[m]; i++) { + if(i==3 && mon[m]==19) { + i += 11; + mon[m] += 11; + } + s += sys->sprint("%2d", i); + if(++d == 7) { + d = 0; + lines[l++] = s; + s = ""; + }else + s[len s] = ' '; + } + if(s != nil){ + while(s[len s-1] == ' ') + s = s[:len s-1]; + lines[l] = s; + } + return lines; +} + +# +# return day of the week +# of jan 1 of given year +# +jan1(y: int): int +{ +# +# normal gregorian calendar +# one extra day per four years +# + + d := 4+y+(y+3)/4; + +# +# julian calendar +# regular gregorian +# less three days per 400 +# + + if(y > 1800) { + d -= (y-1701)/100; + d += (y-1601)/400; + } + +# +# great calendar changeover instant +# + + if(y > 1752) + d += 3; + + return d%7; +} + +# +# get current month and year +# +curmo(): int +{ + tm := daytime->local(daytime->now()); + return tm.mon+1; +} + +curyr(): int +{ + tm := daytime->local(daytime->now()); + return tm.year+1900; +} diff --git a/appl/cmd/cat.b b/appl/cmd/cat.b new file mode 100644 index 00000000..24d62372 --- /dev/null +++ b/appl/cmd/cat.b @@ -0,0 +1,57 @@ +implement Cat; + +include "sys.m"; +include "draw.m"; + +Cat: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +sys: Sys; +stdout: ref Sys->FD; + +init(nil: ref Draw->Context, argl: list of string) +{ + sys = load Sys Sys->PATH; + + stdout = sys->fildes(1); + + argl = tl argl; + if(argl == nil) + argl = "-" :: nil; + while(argl != nil) { + cat(hd argl); + argl = tl argl; + } +} + +cat(file: string) +{ + n: int; + fd: ref Sys->FD; + buf := array[8192] of byte; + + if(file == "-") + fd = sys->fildes(0); + else { + fd = sys->open(file, sys->OREAD); + if(fd == nil) { + sys->fprint(sys->fildes(2), "cat: cannot open %s: %r\n", file); + raise "fail:bad open"; + } + } + for(;;) { + n = sys->read(fd, buf, len buf); + if(n <= 0) + break; + if(sys->write(stdout, buf, n) < n) { + sys->fprint(sys->fildes(2), "cat: write error: %r\n"); + raise "fail:write error"; + } + } + if(n < 0) { + sys->fprint(sys->fildes(2), "cat: read error: %r\n"); + raise "fail:read error"; + } +} diff --git a/appl/cmd/cd.b b/appl/cmd/cd.b new file mode 100644 index 00000000..57c94aba --- /dev/null +++ b/appl/cmd/cd.b @@ -0,0 +1,48 @@ +implement Cd; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +Cd: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + + stderr = sys->fildes(2); + + argv = tl argv; + if(argv == nil) + argv = "/usr/"+user() :: nil; + + if(tl argv != nil) { + sys->fprint(stderr, "Usage: cd [directory]\n"); + raise "fail:usage"; + } + + if(sys->chdir(hd argv) < 0) { + sys->fprint(stderr, "cd: %s: %r\n", hd argv); + raise "fail:failed"; + } +} + +user(): string +{ + fd := sys->open("/dev/user", sys->OREAD); + if(fd == nil) + return "inferno"; + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return "inferno"; + + return string buf[0:n]; +} diff --git a/appl/cmd/chgrp.b b/appl/cmd/chgrp.b new file mode 100644 index 00000000..ec473759 --- /dev/null +++ b/appl/cmd/chgrp.b @@ -0,0 +1,58 @@ +implement Chgrp; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "arg.m"; + +Chgrp: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: chgrp [-uo] group file ...\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + arg := load Arg Arg->PATH; + if(arg == nil){ + sys->fprint(sys->fildes(2), "chgrp: can't load %s: %r\n", Arg->PATH); + raise "fail:load"; + } + setuser := 0; + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 'o' or 'u' => + setuser = 1; + * => + usage(); + } + args = arg->argv(); + arg = nil; + if(args == nil) + usage(); + id := hd args; + err := 0; + while((args = tl args) != nil){ + d := sys->nulldir; + if(setuser) + d.uid = id; + else + d.gid = id; + if(sys->wstat(hd args, d) < 0){ + sys->fprint(sys->fildes(2), "chgrp: can't change %s: %r\n", hd args); + err = 1; + } + } + if(err) + raise "fail:error"; +} diff --git a/appl/cmd/chmod.b b/appl/cmd/chmod.b new file mode 100644 index 00000000..de7ecf2c --- /dev/null +++ b/appl/cmd/chmod.b @@ -0,0 +1,125 @@ +implement Chmod; + +include "sys.m"; +include "draw.m"; +include "string.m"; + +Chmod: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +sys: Sys; +stderr: ref Sys->FD; + +str: String; + +User: con 8r700; +Group: con 8r070; +Other: con 8r007; +All: con User | Group | Other; + +Read: con 8r444; +Write: con 8r222; +Exec: con 8r111; + +usage() +{ + sys->fprint(stderr, "usage: chmod [8r]777 file ... or chmod [augo][+-=][rwxal] file ...\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + str = load String String->PATH; + if(str == nil){ + sys->fprint(stderr, "chmod: cannot load %s: %r\n", String->PATH); + raise "fail:bad module"; + } + + if(len argv < 3) + usage(); + argv = tl argv; + m := hd argv; + argv = tl argv; + + mask := All; + if (str->prefix("8r", m)) + m = m[2:]; + (mode, s) := str->toint(m, 8); + if(s != "" || m == ""){ + ok := 0; + (ok, mask, mode) = parsemode(m); + if(!ok){ + sys->fprint(stderr, "chmod: bad mode '%s'\n", m); + usage(); + } + } + ndir := sys->nulldir; + for(; argv != nil; argv = tl argv){ + f := hd argv; + (ok, dir) := sys->stat(f); + if(ok < 0){ + sys->fprint(stderr, "chmod: cannot stat %s: %r\n", f); + continue; + } + ndir.mode = (dir.mode & ~mask) | (mode & mask); + if(sys->wstat(f, ndir) < 0) + sys->fprint(stderr, "chmod: cannot wstat %s: %r\n", f); + } +} + +parsemode(spec: string): (int, int, int) +{ + mask := Sys->DMAPPEND | Sys->DMEXCL | Sys->DMTMP; +loop: for(i := 0; i < len spec; i++){ + case spec[i] { + 'u' => + mask |= User; + 'g' => + mask |= Group; + 'o' => + mask |= Other; + 'a' => + mask |= All; + * => + break loop; + } + } + if(i == len spec) + return (0, 0, 0); + if(i == 0) + mask |= All; + + op := spec[i++]; + if(op != '+' && op != '-' && op != '=') + return (0, 0, 0); + + mode := 0; + for(; i < len spec; i++){ + case spec[i]{ + 'r' => + mode |= Read; + 'w' => + mode |= Write; + 'x' => + mode |= Exec; + 'a' => + mode |= Sys->DMAPPEND; + 'l' => + mode |= Sys->DMEXCL; + 't' => + mode |= Sys->DMTMP; + * => + return (0, 0, 0); + } + } + if(op == '+' || op == '-') + mask &= mode; + if(op == '-') + mode = ~mode; + return (1, mask, mode); +} diff --git a/appl/cmd/cleanname.b b/appl/cmd/cleanname.b new file mode 100644 index 00000000..0883e600 --- /dev/null +++ b/appl/cmd/cleanname.b @@ -0,0 +1,45 @@ +implement Cleanname; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "names.m"; + names: Names; + +include "arg.m"; + +Cleanname: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + names = load Names Names->PATH; + arg := load Arg Arg->PATH; + + dir: string; + arg->init(args); + arg->setusage("cleanname [-d pwd] name ..."); + while((o := arg->opt()) != 0) + case o { + 'd' => + dir = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + for(; args != nil; args = tl args){ + n := hd args; + if(dir != nil && n != nil && n[0] != '/' && n[0] != '#') + n = dir+"/"+n; + sys->print("%s\n", names->cleanname(n)); # %q? + } +} diff --git a/appl/cmd/cmp.b b/appl/cmd/cmp.b new file mode 100644 index 00000000..ce631b93 --- /dev/null +++ b/appl/cmd/cmp.b @@ -0,0 +1,151 @@ +implement Cmp; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + +include "arg.m"; + +BUF: con 65536; +stderr: ref Sys->FD; + +Cmp: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + lflag := Lflag := sflag := 0; + buf1 := array[BUF] of byte; + buf2 := array[BUF] of byte; + + stderr = sys->fildes(2); + + arg := load Arg Arg->PATH; + if(arg == nil){ + sys->fprint(stderr, "cmp: cannot load %s: %r\n", Arg->PATH); + raise "fail:load"; + } + arg->init(args); + while((op := arg->opt()) != 0) + case op { + 'l' => lflag = 1; + 'L' => Lflag = 1; + 's' => sflag = 1; + * => usage(); + } + args = arg->argv(); + arg = nil; + if(args == nil) + usage(); + + if(len args < 2) + usage(); + name1 := hd args; + args = tl args; + + if((f1 := sys->open(name1, Sys->OREAD)) == nil){ + sys->fprint(stderr, "cmp: can't open %s: %r\n",name1); + raise "fail:open"; + } + name2 := hd args; + args = tl args; + + if((f2 := sys->open(name2, Sys->OREAD)) == nil){ + sys->fprint(stderr, "cmp: can't open %s: %r\n",name2); + raise "fail:open"; + } + + if(args != nil){ + o := big hd args; + if(sys->seek(f1, o, 0) < big 0){ + sys->fprint(stderr, "cmp: seek by offset1 failed: %r\n"); + raise "fail:seek 1"; + } + args = tl args; + } + + if(args != nil){ + o := big hd args; + if(sys->seek(f2, o, 0) < big 0){ + sys->fprint(stderr, "cmp: seek by offset2 failed: %r"); + raise "fail:seek 2"; + } + args = tl args; + } + if(args != nil) + usage(); + nc := big 1; + l := big 1; + diff := 0; + b1, b2: array of byte; + for(;;){ + if(len b1 == 0){ + nr := sys->read(f1, buf1, BUF); + if(nr < 0){ + if(!sflag) + sys->print("error on %s after %bd bytes\n", name1, nc-big 1); + raise "fail:read error"; + } + b1 = buf1[0: nr]; + } + if(len b2 == 0){ + nr := sys->read(f2, buf2, BUF); + if(nr < 0){ + if(!sflag) + sys->print("error on %s after %bd bytes\n", name2, nc-big 1); + raise "fail:read error"; + } + b2 = buf2[0: nr]; + } + n := len b2; + if(n > len b1) + n = len b1; + if(n == 0) + break; + for(i:=0; i<n; i++){ + if(Lflag && b1[i]== byte '\n') + l++; + if(b1[i] != b2[i]){ + if(!lflag){ + if(!sflag){ + sys->print("%s %s differ: char %bd", name1, name2, nc+big i); + if(Lflag) + sys->print(" line %bd\n", l); + else + sys->print("\n"); + } + raise "fail:differ"; + } + sys->print("%6bd 0x%.2x 0x%.2x\n", nc+big i, int b1[i], int b2[i]); + diff = 1; + } + } + nc += big n; + b1 = b1[n:]; + b2 = b2[n:]; + } + if(len b1 != len b2) { + nc--; + if(len b1 > len b2) + sys->print("EOF on %s after %bd bytes\n", name2, nc); + else + sys->print("EOF on %s after %bd bytes\n", name1, nc); + raise "fail:EOF"; + } + if(diff) + raise "fail:differ"; + exit; +} + + +usage() +{ + sys->fprint(stderr, "Usage: cmp [-lsL] file1 file2 [offset1 [offset2] ]\n"); + raise "fail:usage"; +} diff --git a/appl/cmd/comm.b b/appl/cmd/comm.b new file mode 100755 index 00000000..1e56310e --- /dev/null +++ b/appl/cmd/comm.b @@ -0,0 +1,124 @@ +implement Comm; + +# Copyright © 2002 Lucent Technologies Inc. +# Subject to the Lucent Public Licence 1.02 +# Limbo translation by Vita Nuova 2004; bug fixed. + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Comm: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +One, Two, Three: con 1<<iota; +cols := One|Two|Three; +ldr := array[3] of {"", "\t", "\t\t"}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("comm [-123] file1 file2"); + while((c := arg->opt()) != 0){ + case c { + '1' to '3' => + cols &= ~(1 << (c-'1')); + * => + arg->usage(); + } + } + args = arg->argv(); + if(len args != 2) + arg->usage(); + arg = nil; + + if((cols & One) == 0){ + ldr[1] = ""; + ldr[2] = ldr[2][1:]; + } + if((cols & Two) == 0) + ldr[2] = ldr[2][1:]; + + ib1 := openfil(hd args); + ib2 := openfil(hd tl args); + if((lb1 := ib1.gets('\n')) == nil){ + if((lb2 := ib2.gets('\n')) == nil) + exit; + copy(ib2, lb2, 2); + } + if((lb2 := ib2.gets('\n')) == nil) + copy(ib1, lb1, 1); + for(;;) + case compare(lb1, lb2) { + 0 => + wr(lb1, 3); + if((lb1 = ib1.gets('\n')) == nil){ + if((lb2 = ib2.gets('\n')) == nil) + exit; + copy(ib2, lb2, 2); + } + if((lb2 = ib2.gets('\n')) == nil) + copy(ib1, lb1, 1); + 1 => + wr(lb1, 1); + if((lb1 = ib1.gets('\n')) == nil) + copy(ib2, lb2, 2); + 2 => + wr(lb2, 2); + if((lb2 = ib2.gets('\n')) == nil) + copy(ib1, lb1, 1); + } +} + +wr(str: string, n: int) +{ + if(cols & (1<<(n-1))) + sys->print("%s%s", ldr[n-1], str); +} + +copy(ibuf: ref Iobuf, lbuf: string, n: int) +{ + do + wr(lbuf, n); + while((lbuf = ibuf.gets('\n')) != nil); + exit; +} + +compare(a: string, b: string): int +{ + for(i := 0; i < len a; i++){ + if(i >= len b || a[i] < b[i]) + return 1; + if(a[i] != b[i]) + return 2; + } + if(i == len b) + return 0; + return 2; +} + +openfil(s: string): ref Iobuf +{ + if(s == "-") + b := bufio->fopen(sys->fildes(0), Bufio->OREAD); + else + b = bufio->open(s, Bufio->OREAD); + if(b != nil) + return b; + sys->fprint(sys->fildes(2), "comm: cannot open %s: %r\n", s); + raise "fail:open"; +} + diff --git a/appl/cmd/cook.b b/appl/cmd/cook.b new file mode 100644 index 00000000..0d333a4d --- /dev/null +++ b/appl/cmd/cook.b @@ -0,0 +1,1924 @@ +implement Cook; + +include "sys.m"; + sys: Sys; + FD: import Sys; + +include "draw.m"; + draw: Draw; + +include "bufio.m"; + B: Bufio; + Iobuf: import B; + +include "string.m"; + S: String; + splitl, splitr, splitstrl, drop, take, in, prefix, tolower : import S; + +include "brutus.m"; + Size6, Size8, Size10, Size12, Size16, NSIZE, + Roman, Italic, Bold, Type, NFONT, NFONTTAG, + Example, Caption, List, Listelem, Label, Labelref, + Exercise, Heading, Nofill, Author, Title, + Index, Indextopic, + DefFont, DefSize, TitleFont, TitleSize, HeadingFont, HeadingSize: import Brutus; + +# following are needed for types in brutusext.m +include "tk.m"; + tk: Tk; +include "tkclient.m"; + +include "brutusext.m"; + SGML, Text, Par, Extension, Float, Special, Celem, + FLatex, FLatexProc, FLatexBook, FLatexPart, FLatexSlides, FHtml: import Brutusext; + +include "strinttab.m"; + T: StringIntTab; + +Cook: module +{ + init: fn(ctxt: ref Draw->Context, args: list of string); +}; + +# keep this sorted by name +tagstringtab := array[] of { T->StringInt + ("Author", Author), + ("Bold.10", Bold*NSIZE + Size10), + ("Bold.12", Bold*NSIZE + Size12), + ("Bold.16", Bold*NSIZE + Size16), + ("Bold.6", Bold*NSIZE + Size6), + ("Bold.8", Bold*NSIZE + Size8), + ("Caption", Caption), + ("Example", Example), + ("Exercise", Exercise), + ("Extension", Extension), + ("Float", Float), + ("Heading", Heading), + ("Index", Index), + ("Index-topic", Indextopic), + ("Italic.10", Italic*NSIZE + Size10), + ("Italic.12", Italic*NSIZE + Size12), + ("Italic.16", Italic*NSIZE + Size16), + ("Italic.6", Italic*NSIZE + Size6), + ("Italic.8", Italic*NSIZE + Size8), + ("Label", Label), + ("Label-ref", Labelref), + ("List", List), + ("List-elem", Listelem), + ("No-fill", Nofill), + ("Par", Par), + ("Roman.10", Roman*NSIZE + Size10), + ("Roman.12", Roman*NSIZE + Size12), + ("Roman.16", Roman*NSIZE + Size16), + ("Roman.6", Roman*NSIZE + Size6), + ("Roman.8", Roman*NSIZE + Size8), + ("SGML", SGML), + ("Title", Title), + ("Type.10", Type*NSIZE + Size10), + ("Type.12", Type*NSIZE + Size12), + ("Type.16", Type*NSIZE + Size16), + ("Type.6", Type*NSIZE + Size6), + ("Type.8", Type*NSIZE + Size8), +}; + +# This table must be sorted +fmtstringtab := array[] of { T->StringInt + ("html", FHtml), + ("latex", FLatex), + ("latexbook", FLatexBook), + ("latexpart", FLatexPart), + ("latexproc", FLatexProc), + ("latexslides", FLatexSlides), +}; + +Transtab: adt +{ + ch: int; + trans: string; +}; + +# Order doesn't matter for these table + +ltranstab := array[] of { Transtab + ('$', "\\textdollar{}"), + ('&', "\\&"), + ('%', "\\%"), + ('#', "\\#"), + ('_', "\\textunderscore{}"), + ('{', "\\{"), + ('}', "\\}"), + ('~', "\\textasciitilde{}"), + ('^', "\\textasciicircum{}"), + ('\\', "\\textbackslash{}"), + ('+', "\\textplus{}"), + ('=', "\\textequals{}"), + ('|', "\\textbar{}"), + ('<', "\\textless{}"), + ('>', "\\textgreater{}"), + (' ', "~"), + ('-', "-"), # needs special case ligature treatment + ('\t', " "), # needs special case treatment +}; + +htranstab := array[] of { Transtab + ('α', "α"), + ('Æ', "Æ"), + ('Á', "Á"), + ('Â', "Â"), + ('À', "À"), + ('Å', "Å"), + ('Ã', "Ã"), + ('Ä', "Ä"), + ('Ç', "Ç"), + ('Ð', "Ð"), + ('É', "É"), + ('Ê', "Ê"), + ('È', "È"), + ('Ë', "Ë"), + ('Í', "Í"), + ('Î', "Î"), + ('Ì', "Ì"), + ('Ï', "Ï"), + ('Ñ', "Ñ"), + ('Ó', "Ó"), + ('Ô', "Ô"), + ('Ò', "Ò"), + ('Ø', "Ø"), + ('Õ', "Õ"), + ('Ö', "Ö"), + ('Þ', "Þ"), + ('Ú', "Ú"), + ('Û', "Û"), + ('Ù', "Ù"), + ('Ü', "Ü"), + ('Ý', "Ý"), + ('æ', "&aElig;"), + ('á', "á"), + ('â', "â"), + ('à', "à"), + ('α', "α"), + ('&', "&"), + ('å', "å"), + ('ã', "ã"), + ('ä', "ä"), + ('β', "β"), + ('ç', "ç"), + ('⋯', "&cdots;"), + ('χ', "χ"), + ('©', "©"), + ('⋱', "&ddots;"), + ('δ', "δ"), + ('é', "é"), + ('ê', "ê"), + ('è', "è"), + ('—', "&emdash;"), + (' ', " "), + ('–', "&endash;"), + ('ε', "ε"), + ('η', "η"), + ('ð', "ð"), + ('ë', "ë"), + ('γ', "γ"), + ('>', ">"), + ('í', "í"), + ('î', "î"), + ('ì', "ì"), + ('ι', "ι"), + ('ï', "ï"), + ('κ', "κ"), + ('λ', "λ"), + ('…', "&ldots;"), + ('<', "<"), + ('μ', "μ"), + (' ', " "), + ('ñ', "ñ"), + ('ν', "ν"), + ('ó', "ó"), + ('ô', "ô"), + ('ò', "ò"), + ('ω', "ω"), + ('ο', "ο"), + ('ø', "ø"), + ('õ', "õ"), + ('ö', "ö"), + ('φ', "φ"), + ('π', "π"), + ('ψ', "ψ"), + (' ', "&quad;"), + ('"', """), + ('®', "®"), + ('ρ', "ρ"), + ('', "­"), + ('σ', "σ"), + ('ß', "ß"), + ('τ', "τ"), + ('θ', "θ"), + (' ', " "), + ('þ', "þ"), + ('™', "™"), + ('ú', "ú"), + ('û', "û"), + ('ù', "ù"), + ('υ', "υ"), + ('ü', "ü"), + ('∈', "ϵ"), + ('ϕ', "ϕ"), + ('ϖ', "ϖ"), + ('ϱ', "ϱ"), + ('⋮', "&vdots;"), + ('ς', "&vsigma;"), + ('ϑ', "&vtheta;"), + ('ξ', "ξ"), + ('ý', "ý"), + ('ÿ', "ÿ"), + ('ζ', "ζ"), + ('−', "-"), +}; + +# For speedy lookups of ascii char translation, use asciitrans. +# It should be initialized by ascii elements from one of above tables +asciitrans := array[128] of string; + +stderr: ref FD; +infilename := ""; +outfilename := ""; +linenum := 0; +fin : ref Iobuf = nil; +fout : ref Iobuf = nil; +debug := 0; +fmt := FLatex; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + S = load String String->PATH; + B = load Bufio Bufio->PATH; + draw = load Draw Draw->PATH; + tk = load Tk Tk->PATH; + T = load StringIntTab StringIntTab->PATH; + stderr = sys->fildes(2); + + for(argv = tl argv; argv != nil; ) { + s := hd argv; + tlargv := tl argv; + case s { + "-f" => + if(tlargv == nil) + usage(); + fnd: int; + (fnd, fmt) = T->lookup(fmtstringtab, hd(tlargv)); + if(!fnd) { + sys->fprint(stderr, "unknown format: %s\n", hd(tlargv)); + exit; + } + argv = tlargv; + "-o" => + if(tlargv == nil) + usage(); + outfilename = hd(tlargv); + argv = tlargv; + "-d" => + debug = 1; + "-dd" => + debug = 2; + * => + if(tlargv == nil) + infilename = s; + else + usage(); + } + argv = tl argv; + } + if(infilename == "") { + fin = B->fopen(sys->fildes(0), sys->OREAD); + infilename = "<stdin>"; + } + else + fin = B->open(infilename, sys->OREAD); + if(fin == nil) { + sys->fprint(stderr, "cook: error opening %s: %r\n", infilename); + exit; + } + if(outfilename == "") { + fout = B->fopen(sys->fildes(1), sys->OWRITE); + outfilename = "<stdout>"; + } + else + fout = B->create(outfilename, sys->OWRITE, 8r664); + if(fout == nil) { + sys->fprint(stderr, "cook: error creating %s: %r\n", outfilename); + exit; + } + line0 := fin.gets('\n'); + if(line0 != "<SGML>\n") { + parse_err("not an SGML file\n"); + exit; + } + linenum = 1; + e := parse(SGML); + findpars(e, 1, nil); + e = delemptystrs(e); + (e, nil) = canonfonts(e, DefFont*NSIZE+DefSize, DefFont*NSIZE+DefSize); + mergeadjs(e); + findfloats(e); + cleanexts(e); + cleanpars(e); + if(debug) { + fout.puts("After Initial transformations:\n"); + printelem(e, "", 1); + fout.flush(); + } + case fmt { + FLatex or FLatexProc or FLatexBook or FLatexPart or FLatexSlides => + latexconv(e); + FHtml => + htmlconv(e); + } + fin.close(); + fout.close(); +} + +usage() +{ + sys->fprint(stderr, "Usage: cook [-f (latex|html)] [-o outfile] [infile]\n"); + exit; +} + +parse_err(msg: string) +{ + sys->fprint(stderr, "%s:%d: %s\n", infilename, linenum, msg); +} + +# Parse into elements. +# Assumes tags are balanced. +# String elements are split so that there is never an internal newline. +parse(id: int) : ref Celem +{ + els : ref Celem = nil; + elstail : ref Celem = nil; + for(;;) { + c := fin.getc(); + if(c == Bufio->EOF) { + if(id == SGML) + break; + else { + parse_err(sys->sprint("EOF while parsing %s", tagname(id))); + return nil; + } + } + if(c == '<') { + tag := ""; + start := 1; + i := 0; + for(;;) { + c = fin.getc(); + if(c == Bufio->EOF) { + parse_err("EOF in middle of tag"); + return nil; + } + if(c == '\n') { + linenum++; + parse_err("newline in middle of tag"); + break; + } + if(c == '>') + break; + if(i == 0 && c == '/') + start = 0; + else + tag[i++] = c; + } + (fnd, tid) := T->lookup(tagstringtab, tag); + if(!fnd) { + if(prefix("Extension ", tag)) { + el := ref Celem(Extension, tag[10:], nil, nil, nil, nil); + if(els == nil) { + els = el; + elstail = el; + } + else { + el.prev = elstail; + elstail.next = el; + elstail = el; + } + } + else + parse_err(sys->sprint("unknown tag <%s>\n", tag)); + continue; + } + if(start) { + el := parse(tid); + if(el == nil) + return nil; + if(els == nil) { + els = el; + elstail = el; + } + else { + el.prev = elstail; + elstail.next = el; + elstail = el; + } + } + else { + if(tid != id) { + parse_err(sys->sprint("<%s> ended by </%s>", + tagname(id), tag)); + continue; + } + break; + } + } + else { + s := ""; + i := 0; + for(;;) { + if(c == Bufio->EOF) + break; + if(c == '<') { + fin.ungetc(); + break; + } + if(c == ';' && i >=3 && s[i-1] == 't' && s[i-2] == 'l' && s[i-3] == '&') { + i -= 2; + s[i-1] = '<'; + s = s[0:i]; + } + else + s[i++] = c; + if(c == '\n') { + linenum++; + break; + } + else + c = fin.getc(); + } + if(s != "") { + el := ref Celem(Text, s, nil, nil, nil, nil); + if(els == nil) { + els = el; + elstail = el; + } + else { + el.prev = elstail; + elstail.next = el; + elstail = el; + } + } + } + } + ans := ref Celem(id, "", els, nil, nil, nil); + if(els != nil) + els.parent = ans; + return ans; +} + +# Modify tree e so that blank lines become Par elements. +# Only do it if parize is set, and unset parize when descending into TExample's. +# Pass in most recent TString or TPar element, and return updated most-recent-TString/TPar. +# This function may set some TString strings to "" +findpars(e: ref Celem, parize: int, prevspe: ref Celem) : ref Celem +{ + while(e != nil) { + prevnl := 0; + prevpar := 0; + if(prevspe != nil) { + if(prevspe.tag == Text && len prevspe.s != 0 + && prevspe.s[(len prevspe.s)-1] == '\n') + prevnl = 1; + else if(prevspe.tag == Par) + prevpar = 1; + } + if(e.tag == Text) { + if(parize && (prevnl || prevpar) && e.s[0] == '\n') { + if(prevnl) + prevspe.s = prevspe.s[0 : (len prevspe.s)-1]; + e.tag = Par; + e.s = nil; + } + prevspe = e; + } + else { + nparize := parize; + if(e.tag == Example) + nparize = 0; + prevspe = findpars(e.contents, nparize, prevspe); + } + e = e.next; + } + return prevspe; +} + +# Delete any empty strings from e's tree and return modified e. +# Also, delete any entity that has empty contents, except the +# Par ones +delemptystrs(e: ref Celem) : ref Celem +{ + if(e.tag == Text) { + if(e.s == "") + return nil; + else + return e; + } + if(e.tag == Par || e.tag == Extension || e.tag == Special) + return e; + h := e.contents; + while(h != nil) { + hnext := h.next; + hh := delemptystrs(h); + if(hh == nil) + delete(h); + h = hnext; + } + if(e.contents == nil) + return nil; + return e; +} + +# Change tree under e so that any font elems contain only strings +# (by pushing the font changes down). +# Answer an be a list, so return beginning and end of list. +# Leave strings bare if font change would be to deffont, +# and adjust deffont appropriately when entering Title and +# Heading environments. +canonfonts(e: ref Celem, curfont, deffont: int) : (ref Celem, ref Celem) +{ + f := curfont; + head : ref Celem = nil; + tail : ref Celem = nil; + tocombine : ref Celem = nil; + if(e.tag == Text) { + if(f == deffont) { + head = e; + tail = e; + } + else { + head = ref Celem(f, nil, e, nil, nil, nil); + e.parent = head; + tail = head; + } + } + else if(e.contents == nil) { + head = e; + tail = e; + } + else if(e.tag < NFONTTAG) { + f = e.tag; + allstrings := 1; + for(g := e.contents; g != nil; g = g.next) { + if(g.tag != Text) + allstrings = 0; + tail = g; + } + if(allstrings) { + if(f == deffont) + head = e.contents; + else { + head = e; + tail = e; + } + } + } + if(head == nil) { + if(e.tag == Title) + deffont = TitleFont*NSIZE+TitleSize; + else if(e.tag == Heading) + deffont = HeadingFont*NSIZE+HeadingSize; + for(h := e.contents; h != nil; ) { + prev := h.prev; + next := h.next; + excise(h); + (e1, en) := canonfonts(h, f, deffont); + splicebetween(e1, en, prev, next); + if(prev == nil) + head = e1; + tail = en; + h = next; + } + tocombine = head; + if(e.tag >= NFONTTAG) { + e.contents = head; + head.parent = e; + head = e; + tail = e; + } + } + if(tocombine != nil) { + # combine adjacent font changes to same font + r := tocombine; + while(r != nil) { + if(r.tag < NFONTTAG && r.next != nil && r.next.tag == r.tag) { + for(v := r.next; v != nil; v = v.next) { + if(v.tag != r.tag) + break; + if(v == tail) + tail = r; + } + # now r up to, not including v, all change to same font + for(p := r.next; p != v; p = p.next) { + append(r.contents, p.contents); + } + r.next = v; + if(v != nil) + v.prev = r; + r = v; + } + else + r = r.next; + } + } + head.parent = nil; + return (head, tail); +} + +# Remove Pars that appear just before or just after Heading, Title, Examples, Extensions +# Really should worry about this happening at different nesting levels, but in +# practice this happens all at the same nesting level +cleanpars(e: ref Celem) +{ + for(h := e.contents; h != nil; h = h.next) { + cleanpars(h); + if(h.tag == Title || h.tag == Heading || h.tag == Example || h.tag == Extension) { + hp := h.prev; + hn := h.next; + if(hp !=nil && hp.tag == Par) + delete(hp); + if(hn != nil && hn.tag == Par) + delete(hn); + } + } +} + +# Remove a single tab if it appears before an Extension +cleanexts(e: ref Celem) +{ + for(h := e.contents; h != nil; h = h.next) { + cleanexts(h); + if(h.tag == Extension) { + hp := h.prev; + if(hp != nil && stringof(hp) == "\t") + delete(hp); + } + } +} + +mergeable := array[] of { List, Exercise, Caption,Index, Indextopic }; + +# Merge some adjacent elements (which were probably created separate +# because of font changes) +mergeadjs(e: ref Celem) +{ + for(h := e.contents; h != nil; h = h.next) { + hn := h.next; + domerge := 0; + if(hn != nil) { + for(i := 0; i < len mergeable; i++) { + mi := mergeable[i]; + if(h.tag == mi && hn.tag == mi) + domerge = 1; + } + } + if(domerge) { + append(h.contents, hn.contents); + delete(hn); + } + else + mergeadjs(h); + } +} + +# Find floats: they are paragraphs with Captions at the end. +findfloats(e: ref Celem) +{ + lastpar : ref Celem = nil; + for(h := e.contents; h != nil; h = h.next) { + if(h.tag == Par) + lastpar = h; + else if(h.tag == Caption) { + ne := ref Celem(Float, "", nil, nil, nil, nil); + if(lastpar == nil) + flhead := e.contents; + else + flhead = lastpar.next; + insertbefore(ne, flhead); + # now move flhead ... h into contents of ne + ne.contents = flhead; + flhead.parent = ne; + flhead.prev = nil; + ne.next = h.next; + if(ne.next != nil) + ne.next.prev = ne; + h.next = nil; + h = ne; + } + else + findfloats(h); + } +} + +insertbefore(e, ebefore: ref Celem) +{ + e.prev = ebefore.prev; + if(e.prev == nil) { + e.parent = ebefore.parent; + ebefore.parent = nil; + e.parent.contents = e; + } + else + e.prev.next = e; + e.next = ebefore; + ebefore.prev = e; +} + +insertafter(e, eafter: ref Celem) +{ + e.next = eafter.next; + if(e.next != nil) + e.next.prev = e; + e.prev = eafter; + eafter.next = e; +} + +# remove e from its list, leaving siblings disconnected +excise(e: ref Celem) +{ + next := e. next; + prev := e.prev; + e.next = nil; + e.prev = nil; + if(prev != nil) + prev.next = nil; + if(next != nil) + next.prev = nil; + e.parent = nil; +} + +splicebetween(e1, en, prev, next: ref Celem) +{ + if(prev != nil) + prev.next = e1; + e1.prev = prev; + en.next = next; + if(next != nil) + next.prev = en; +} + +append(e1, e2: ref Celem) +{ + e1last := last(e1); + e1last.next = e2; + e2.prev = e1last; + e2.parent = nil; +} + +last(e: ref Celem) : ref Celem +{ + if(e != nil) + while(e.next != nil) + e = e.next; + return e; +} + +succ(e: ref Celem) : ref Celem +{ + if(e == nil) + return nil; + if(e.next != nil) + return e.next; + return succ(e.parent); +} + +delete(e: ref Celem) +{ + ep := e.prev; + en := e.next; + eu := e.parent; + if(ep == nil) { + if(eu != nil) + eu.contents = en; + if(en != nil) + en.parent = eu; + } + else + ep.next = en; + if(en != nil) + en.prev = ep; +} + +# return string represented by e, peering through font changes +stringof(e: ref Celem) : string +{ + if(e != nil) { + if(e.tag == Text) + return e.s; + if(e.tag < NFONTTAG) + return stringof(e.contents); + } + return ""; +} + +# remove any initial whitespace from e and its sucessors, +dropwhite(e: ref Celem) +{ + if(e == nil) + return; + del := 0; + if(e.tag == Text) { + e.s = drop(e.s, " \t\n"); + if(e.s == "") + del = 1;; + } + else if(e.tag < NFONTTAG) { + dropwhite(e.contents); + if(e.contents == nil) + del = 1; + } + if(del) { + enext := e.next; + delete(e); + dropwhite(enext); + } + +} + +firstchar(e: ref Celem) : int +{ + s := stringof(e); + if(len s >= 1) + return s[0]; + return -1; +} + +lastchar(e: ref Celem) : int +{ + if(e == nil) + return -1; + while(e.next != nil) + e = e.next; + s := stringof(e); + if(len s >= 1) + return s[len s -1]; + return -1; +} + +tlookup(t: array of Transtab, v: int) : string +{ + n := len t; + for(i := 0; i < n; i++) + if(t[i].ch == v) + return t[i].trans; + return ""; +} + +initasciitrans(t: array of Transtab) +{ + n := len t; + for(i := 0; i < n; i++) { + c := t[i].ch; + if(c < 128) + asciitrans[c] = t[i].trans; + } +} + +tagname(id: int) : string +{ + name := T->revlookup(tagstringtab, id); + if(name == nil) + name = "_unknown_"; + return name; +} + +printelem(e: ref Celem, indent: string, recurse: int) +{ + fout.puts(indent); + if(debug > 1) { + fout.puts(sys->sprint("%x: ", e)); + if(e != nil && e.parent != nil) + fout.puts(sys->sprint("(parent %x): ", e.parent)); + } + if(e == nil) + fout.puts("NIL\n"); + else if(e.tag == Text || e.tag == Special || e.tag == Extension) { + if(e.tag == Special) + fout.puts("S"); + else if(e.tag == Extension) + fout.puts("E"); + fout.puts("«"); + fout.puts(e.s); + fout.puts("»\n"); + } + else { + name := tagname(e.tag); + fout.puts("<" + name + ">\n"); + if(recurse && e.contents != nil) + printelems(e.contents, indent + " ", recurse); + } +} + +printelems(els: ref Celem, indent: string, recurse: int) +{ + for(; els != nil; els = els.next) + printelem(els, indent, recurse); +} + +check(e: ref Celem, msg: string) +{ + err := checke(e); + if(err != "") { + fout.puts(msg + ": tree is inconsistent:\n" + err); + printelem(e, "", 1); + fout.flush(); + exit; + } +} + +checke(e: ref Celem) : string +{ + err := ""; + if(e.tag == SGML && e.next != nil) + err = sys->sprint("root %x has a next field\n", e); + ec := e.contents; + if(ec != nil) { + if(ec.parent != e) + err += sys->sprint("node %x contents %x has bad parent %x\n", e, ec, e.parent); + if(ec.prev != nil) + err += sys->sprint("node %x contents %x has non-nil prev %x\n", e, ec, e.prev); + p := ec; + for(h := ec.next; h != nil; h = h.next) { + if(h.prev != p) + err += sys->sprint("node %x comes after %x, but prev is %x\n", h, p, h.prev); + if(h.parent != nil) + err += sys->sprint("node %x, not first in siblings, has parent %x\n", h, h.parent); + p = h; + } + for(h = ec; h != nil; h = h.next) { + err2 := checke(h); + if(err2 != nil) + err += err2; + } + } + return err; +} + +# Translation to Latex + +# state bits +SLT, SLB, SLI, SLS6, SLS8, SLS12, SLS16, SLE, SLO, SLF : con (1<<iota); + +SLFONTMASK : con SLT|SLB|SLI|SLS6|SLS8|SLS12|SLS16; +SLSIZEMASK : con SLS6|SLS8|SLS12|SLS16; + +# fonttag-to-state-bit table +lftagtostate := array[NFONTTAG] of { + Roman*NSIZE+Size6 => SLS6, + Roman*NSIZE+Size8 => SLS8, + Roman*NSIZE+Size10 => 0, + Roman*NSIZE+Size12 => SLS12, + Roman*NSIZE+Size16 => SLS16, + Italic*NSIZE+Size6 => SLI | SLS6, + Italic*NSIZE+Size8 => SLI | SLS8, + Italic*NSIZE+Size10 => SLI, + Italic*NSIZE+Size12 => SLI | SLS12, + Italic*NSIZE+Size16 => SLI | SLS16, + Bold*NSIZE+Size6 => SLB | SLS6, + Bold*NSIZE+Size8 => SLB | SLS8, + Bold*NSIZE+Size10 => SLB, + Bold*NSIZE+Size12 => SLB | SLS12, + Bold*NSIZE+Size16 => SLB | SLS16, + Type*NSIZE+Size6 => SLT | SLS6, + Type*NSIZE+Size8 => SLT | SLS8, + Type*NSIZE+Size10 => SLT, + Type*NSIZE+Size12 => SLT | SLS12, + Type*NSIZE+Size16 => SLT | SLS16 +}; + +lsizecmd := array[] of { "\\footnotesize", "\\small", "\\normalsize", "\\large", "\\Large"}; +llinepos : int; +lslidenum : int; +LTABSIZE : con 4; + +latexconv(e: ref Celem) +{ + initasciitrans(ltranstab); + + case fmt { + FLatex or FLatexProc => + if(fmt == FLatex) { + fout.puts("\\documentclass{article}\n"); + fout.puts("\\def\\encodingdefault{T1}\n"); + } + else { + fout.puts("\\documentclass[10pt,twocolumn]{article}\n"); + fout.puts("\\def\\encodingdefault{T1}\n"); + fout.puts("\\usepackage{latex8}\n"); + fout.puts("\\bibliographystyle{latex8}\n"); + } + fout.puts("\\usepackage{times}\n"); + fout.puts("\\usepackage{brutus}\n"); + fout.puts("\\usepackage{unicode}\n"); + fout.puts("\\usepackage{epsf}\n"); + title := lfindtitle(e); + authors := lfindauthors(e); + abstract := lfindabstract(e); + fout.puts("\\begin{document}\n"); + if(title != nil) { + fout.puts("\\title{"); + llinepos = 0; + lconvl(title, 0); + fout.puts("}\n"); + if(authors != nil) { + fout.puts("\\author{"); + for(l := authors; l != nil; l = tl l) { + llinepos = 0; + lconvl(hd l, SLO|SLI); + if(tl l != nil) + fout.puts("\n\\and\n"); + } + fout.puts("}\n"); + } + fout.puts("\\maketitle\n"); + } + fout.puts("\\pagestyle{empty}\\thispagestyle{empty}\n"); + if(abstract != nil) { + if(fmt == FLatexProc) { + fout.puts("\\begin{abstract}\n"); + llinepos = 0; + lconvl(abstract, 0); + fout.puts("\\end{abstract}\n"); + } + else { + fout.puts("\\section*{Abstract}\n"); + llinepos = 0; + lconvl(abstract, 0); + } + } + FLatexBook => + fout.puts("\\documentclass{ibook}\n"); + fout.puts("\\usepackage{brutus}\n"); + fout.puts("\\usepackage{epsf}\n"); + fout.puts("\\begin{document}\n"); + FLatexSlides => + fout.puts("\\documentclass[portrait]{seminar}\n"); + fout.puts("\\def\\encodingdefault{T1}\n"); + fout.puts("\\usepackage{times}\n"); + fout.puts("\\usepackage{brutus}\n"); + fout.puts("\\usepackage{unicode}\n"); + fout.puts("\\usepackage{epsf}\n"); + fout.puts("\\centerslidesfalse\n"); + fout.puts("\\slideframe{none}\n"); + fout.puts("\\slidestyle{empty}\n"); + fout.puts("\\pagestyle{empty}\n"); + fout.puts("\\begin{document}\n"); + lslidenum = 0; + } + + llinepos = 0; + if(e.tag == SGML) + lconvl(e.contents, 0); + + if(fmt == FLatexSlides && lslidenum > 0) + fout.puts("\\vfill\\end{slide*}\n"); + if(fmt != FLatexPart) + fout.puts("\\end{document}\n"); +} + +lconvl(el: ref Celem, state: int) +{ + for(e := el; e != nil; e = e.next) { + tag := e.tag; + op := ""; + cl := ""; + parlike := 1; + nstate := state; + if(tag < NFONTTAG) { + parlike = 0; + ss := lftagtostate[tag]; + if((state & SLFONTMASK) != ss) { + t := state & SLT; + b := state & SLB; + i := state & SLI; + newt := ss & SLT; + newb := ss & SLB; + newi := ss & SLI; + op = "{"; + cl = "}"; + if(t && !newt) + op += "\\rmfamily"; + else if(!t && newt) + op += "\\ttfamily"; + if(b && !newb) + op += "\\mdseries"; + else if(!b && newb) + op += "\\bfseries"; + if(i && !newi) + op += "\\upshape"; + else if(!i && newi) { + op += "\\itshape"; + bc := lastchar(e.contents); + ac := firstchar(e.next); + if(bc != -1 && bc != ' ' && bc != '\n' && ac != -1 && ac != '.' && ac != ',') + cl = "\\/}"; + } + if((state & SLSIZEMASK) != (ss & SLSIZEMASK)) { + nsize := 2; + if(ss & SLS6) + nsize = 0; + else if(ss & SLS8) + nsize = 1; + else if(ss & SLS12) + nsize = 3; + else if(ss & SLS16) + nsize = 4; + # examples shrunk one size + if((state & SLE) && nsize > 0) + nsize--; + op += lsizecmd[nsize]; + } + fc := firstchar(e.contents); + if(fc == ' ') + op += "{}"; + else + op += " "; + nstate = (state & ~SLFONTMASK) | ss; + } + } + else + case tag { + Text => + parlike = 0; + if(state & SLO) { + asciitrans[' '] = "\\ "; + asciitrans['\n'] = "\\\\\n"; + } + s := e.s; + n := len s; + for(k := 0; k < n; k++) { + c := s[k]; + x := ""; + if(c < 128) + x = asciitrans[c]; + else + x = tlookup(ltranstab, c); + if(x == "") { + fout.putc(c); + if(c == '\n') + llinepos = 0; + else + llinepos++; + } + else { + # split up ligatures + if(c == '-' && k < n-1 && s[k+1] == '-') + x = "-{}"; + # Avoid the 'no line to end here' latex error + if((state&SLO) && c == '\n' && llinepos == 0) + fout.puts("\\ "); + else if((state&SLO) && c == '\t') { + nspace := LTABSIZE - llinepos%LTABSIZE; + llinepos += nspace; + while(nspace-- > 0) + fout.puts("\\ "); + + } + else { + fout.puts(x); + if(x[len x - 1] == '\n') + llinepos = 0; + else + llinepos++; + } + } + } + if(state & SLO) { + asciitrans[' '] = nil; + asciitrans['\n'] = nil; + } + Example => + if(!(state&SLE)) { + op = "\\begin{example}"; + cl = "\\end{example}\\noindent "; + nstate |= SLE | SLO; + } + List => + (n, bigle) := lfindbigle(e.contents); + if(n <= 2) { + op = "\\begin{itemize}\n"; + cl = "\\end{itemize}"; + } + else { + fout.puts("\\begin{itemizew}{"); + lconvl(bigle.contents, nstate); + op = "}\n"; + cl = "\\end{itemizew}"; + } + Listelem => + op = "\\item[{"; + cl = "}]"; + Heading => + if(fmt == FLatexProc) + op = "\n\\Section{"; + else + op = "\n\\section{"; + cl = "}\n"; + nstate = (state & ~SLFONTMASK) | (SLB | SLS12); + Nofill => + op = "\\begin{nofill}"; + cl = "\\end{nofill}\\noindent "; + nstate |= SLO; + Title => + if(fmt == FLatexSlides) { + op = "\\begin{slide*}\n" + + "\\begin{center}\\Large\\bfseries "; + if(lslidenum > 0) + op = "\\vfill\\end{slide*}\n" + op; + cl = "\\end{center}\n"; + lslidenum++; + } + else { + if(stringof(e.contents) == "Index") { + op = "\\printindex\n"; + e.contents = nil; + } + else { + op = "\\chapter{"; + cl = "}\n"; + } + } + nstate = (state & ~SLFONTMASK) | (SLB | SLS16); + Par => + op = "\n\\par\n"; + while(e.next != nil && e.next.tag == Par) + e = e.next; + Extension => + e.contents = convextension(e.s); + if(e.contents != nil) + e.contents.parent = e; + Special => + fout.puts(e.s); + Float => + if(!(state&SLF)) { + isfig := lfixfloat(e); + if(isfig) { + op = "\\begin{figure}\\begin{center}\\leavevmode "; + cl = "\\end{center}\\end{figure}"; + } + else { + op = "\\begin{table}\\begin{center}\\leavevmode "; + cl = "\\end{center}\\end{table}"; + } + nstate |= SLF; + } + Caption=> + if(state&SLF) { + op = "\\caption{"; + cl = "}"; + nstate = (state & ~SLFONTMASK) | SLS8; + } + else { + op = "\\begin{center}"; + cl = "\\end{center}"; + } + Label or Labelref => + parlike = 0; + if(tag == Label) + op = "\\label"; + else + op = "\\ref"; + cl = "{" + stringof(e.contents) + "}"; + e.contents = nil; + Exercise => + lfixexercise(e); + op = "\\begin{exercise}"; + cl = "\\end{exercise}"; + Index or Indextopic => + parlike = 0; + if(tag == Index) + lconvl(e.contents, nstate); + fout.puts("\\showidx{"); + lconvl(e.contents, nstate); + fout.puts("}"); + lconvindex(e.contents, nstate); + e.contents = nil; + } + if(op != "") + fout.puts(op); + if(e.contents != nil) { + if(parlike) + llinepos = 0; + lconvl(e.contents, nstate); + if(parlike) + llinepos = 0; + } + if(cl != "") + fout.puts(cl); + } +} + +lfixfloat(e: ref Celem) : int +{ + dropwhite(e.contents); + fstart := e.contents; + fend := last(fstart); + hasfig := 0; + hastab := 0; + if(fend.tag == Caption) { + dropwhite(fend.prev); + if(fend.prev != nil && stringof(fstart) == "\t") + delete(fend.prev); + # If fend.contents is "YYY " <Label> "." rest + # where YYY is Figure or Table, + # then replace it with just rest, and move <Label> + # after the caption. + # Probably should be more robust about what to accept. + ec := fend.contents; + s := stringof(ec); + if(s == "Figure ") + hasfig = 1; + else if(s == "Table ") + hastab = 1; + if(hasfig || hastab) { + ec2 := ec.next; + ec3 : ref Celem = nil; + ec4 : ref Celem = nil; + if(ec2 != nil && ec2.tag == Label) { + ec3 = ec2.next; + if(ec3 != nil && stringof(ec3) == ".") + ec4 = ec3.next; + } + if(ec4 != nil) { + dropwhite(ec4); + ec4 = ec3.next; + if(ec4 != nil) { + excise(ec); + excise(ec2); + excise(ec3); + fend.contents = ec4; + ec4.parent = fend; + insertafter(ec2, fend); + } + } + } + } + return !hastab; +} + +lfixexercise(e: ref Celem) +{ + dropwhite(e.contents); + ec := e.contents; + # Expect: + # "Exercise " <Label> ":" rest + # If so, drop the first and third. + # Or + # "Exercise:" rest + # If so, drop the first. + s := stringof(ec); + if(s == "Exercise ") { + ec2 := ec.next; + ec3 : ref Celem = nil; + ec4 : ref Celem = nil; + if(ec2 != nil && ec2.tag == Label) { + ec3 = ec2.next; + if(ec3 != nil && stringof(ec3) == ":") + ec4 = ec3.next; + } + if(ec4 != nil) { + dropwhite(ec4); + ec4 = ec3.next; + if(ec4 != nil) { + excise(ec); + excise(ec3); + e.contents = ec2; + ec2.parent = e; + ec2.next = ec4; + ec4.prev = ec2; + } + } + } + else if(s == "Exercise:") { + dropwhite(ec.next); + e.contents = ec.next; + excise(ec); + if(e.contents != nil) + e.contents.parent = e; + } +} + +# convert content list headed by e to \\index{...} +lconvindex(e: ref Celem, state: int) +{ + fout.puts("\\index{"); + g := lsplitind(e); + gp := g; + needat := 0; + while(g != nil) { + gnext := g.next; + s := stringof(g); + if(s == "!" || s == "|") { + if(gp != g) { + g.next = nil; + g.s = ""; + lprintindsort(gp); + if(needat) { + fout.puts("@"); + lconvl(gp, state); + } + } + fout.puts(s); + gp = gnext; + needat = 0; + if(s == "|") { + if(g == nil) + break; + g = gnext; + # don't lconvl the Text items, so + # that "see{" and "}" come out untranslated. + # (code is wrong if stuff inside see is plain + # text but with special tex characters) + while(g != nil) { + gnext = g.next; + g.next = nil; + if(g.tag != Text) + lconvl(g, state); + else + fout.puts(g.s); + g = gnext; + } + gp = nil; + break; + } + } + else { + if(g.tag != Text) + needat = 1; + } + g = gnext; + } + if(gp != nil) { + lprintindsort(gp); + if(needat) { + fout.puts("@"); + lconvl(gp, state); + } + } + fout.puts("}"); +} + +lprintindsort(e: ref Celem) +{ + while(e != nil) { + fout.puts(stringof(e)); + e = e.next; + } +} + +# return copy of e +lsplitind(e: ref Celem) : ref Celem +{ + dummy := ref Celem; + for( ; e != nil; e = e.next) { + te := e; + if(e.tag < NFONTTAG) + te = te.contents; + if(te.tag != Text) + continue; + s := te.s; + i := 0; + for(j := 0; j < len s; j++) { + if(s[j] == '!' || s[j] == '|') { + if(j > i) { + nte := ref Celem(Text, s[i:j], nil, nil, nil, nil); + if(e == te) + ne := nte; + else + ne = ref Celem(e.tag, nil, nte, nil, nil, nil); + append(dummy, ne); + } + append(dummy, ref Celem(Text, s[j:j+1], nil, nil, nil, nil)); + i = j+1; + } + } + if(j > i) { + nte := ref Celem(Text, s[i:j], nil, nil, nil, nil); + if(e == te) + ne := nte; + else + ne = ref Celem(e.tag, nil, nte, nil, nil, nil); + append(dummy, ne); + } + } + return dummy.next; +} + +# return key part of an index entry corresponding to e list +indexkey(e: ref Celem) : string +{ + s := ""; + while(e != nil) { + s += stringof(e); + e = e.next; + } + return s; +} + +# find title, excise it from e, and return contents as list +lfindtitle(e: ref Celem) : ref Celem +{ + if(e.tag == Title) { + ans := e.contents; + delete(e); + return ans; + } + else if (e.contents != nil) { + for(h := e.contents; h != nil; h = h.next) { + a := lfindtitle(h); + if(a != nil) + return a; + } + } + return nil; +} + +# find authors, excise them from e, and return as list of lists +lfindauthors(e: ref Celem) : list of ref Celem +{ + if(e.tag == Author) { + a := e.contents; + en := e.next; + delete(e); + rans : list of ref Celem = a :: nil; + if(en != nil) { + e = en; + while(e != nil) { + if(e.tag == Par) { + en = e.next; + if(en.tag == Author) { + delete(e); + a = en.contents; + for(y := a; y != nil; ) { + yn := y.next; + if(y.tag == Par) + delete(y); + y = yn; + } + e = en.next; + delete(en); + rans = a :: rans; + } + else + break; + } + else + break; + } + } + ans : list of ref Celem = nil; + while(rans != nil) { + ans = hd rans :: ans; + rans = tl rans; + } + return ans; + } + else if (e.contents != nil) { + for(h := e.contents; h != nil; h = h.next) { + a := lfindauthors(h); + if(a != nil) + return a; + } + } + return nil; +} + +# find section called abstract, excise it from e, and return as list +lfindabstract(e: ref Celem) : ref Celem +{ + if(e.tag == Heading) { + c := e.contents; + if(c.tag == Text && c.s == "Abstract") { + for(h2 := e.next; h2 != nil; h2 = h2.next) { + if(h2.tag == Heading) + break; + } + ans := e.next; + ans.prev = nil; + ep := e.prev; + eu := e.parent; + if(ep == nil) { + if(eu != nil) + eu.contents = h2; + if(h2 != nil) + h2.parent = eu; + } + else + ep.next = h2; + if(h2 != nil) { + ansend := h2.prev; + ansend.next = nil; + h2.prev = ep; + } + return ans; + } + } + else if (e.contents != nil) { + for(h := e.contents; h != nil; h = h.next) { + a := lfindabstract(h); + if(a != nil) + return a; + } + } + return nil; +} + +# find biggest list element with longest contents in e list +lfindbigle(e: ref Celem) : (int, ref Celem) +{ + ans : ref Celem = nil; + maxlen := 0; + for(h := e; h != nil; h = h.next) { + if(h.tag == Listelem) { + n := 0; + for(p := h.contents; p != nil; p = p.next) { + if(p.tag == Text) + n += len p.s; + else if(p.tag < NFONTTAG) { + q := p.contents; + if(q.tag == Text) + n += len q.s; + } + } + if(n > maxlen) { + maxlen = n; + ans = h; + } + } + } + return (maxlen, ans); +} + +# Translation to HTML + +# state bits +SHA, SHO, SHFL, SHDT: con (1<<iota); + +htmlconv(e: ref Celem) +{ + initasciitrans(htranstab); + + fout.puts("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"); + fout.puts("<HTML>\n"); + + if(e.tag == SGML) { + # Conforming 3.2 documents require a Title. + # Use the Title tag both for the document title and + # for an H1-level heading. + # (SHDT state bit enforces: Font change markup, etc., not allowed in Title) + fout.puts("<TITLE>\n"); + title := hfindtitle(e); + if(title != nil) + hconvl(title.contents, SHDT); + else if(infilename != "") + fout.puts(infilename); + else + fout.puts("An HTML document"); + fout.puts("</TITLE>\n"); + fout.puts("<BODY>\n"); + hconvl(e.contents, 0); + fout.puts("</BODY>\n"); + } + + fout.puts("</HTML>\n"); +} + +hconvl(el: ref Celem, state: int) +{ + for(e := el; e != nil; e = e.next) { + tag := e.tag; + op := ""; + cl := ""; + nstate := state; + if(tag == Text) { + s := e.s; + n := len s; + for(k := 0; k < n; k++) { + c := s[k]; + x := ""; + if(c < 128) { + if(c == '\n' && (state&SHO)) + x = "\n\t"; + else + x = asciitrans[c]; + } + else + x = tlookup(htranstab, c); + if(x == "") + fout.putc(c); + else + fout.puts(x); + } + } + else if(!(state&SHDT)) + case tag { + Roman*NSIZE+Size6 => + op = "<FONT SIZE=1>"; + cl = "</FONT>"; + nstate |= SHA; + Roman*NSIZE+Size8 => + op = "<FONT SIZE=2>"; + cl = "</FONT>"; + nstate |= SHA; + Roman*NSIZE+Size10 => + if(state & SHA) { + op = "<FONT SIZE=3>"; + cl = "</FONT>"; + nstate &= ~SHA; + } + Roman*NSIZE+Size12 => + op = "<FONT SIZE=4>"; + cl = "</FONT>"; + nstate |= SHA; + Roman*NSIZE+Size16 => + op = "<FONT SIZE=5>"; + cl = "</FONT>"; + nstate |= SHA; + Italic*NSIZE+Size6 => + op = "<I><FONT SIZE=1>"; + cl = "</FONT></I>"; + nstate |= SHA; + Italic*NSIZE+Size8 => + op = "<I><FONT SIZE=2>"; + cl = "</FONT></I>"; + nstate |= SHA; + Italic*NSIZE+Size10 => + if(state & SHA) { + op = "<I><FONT SIZE=3>"; + cl = "</FONT></I>"; + nstate &= ~SHA; + } + else { + op = "<I>"; + cl = "</I>"; + } + Italic*NSIZE+Size12 => + op = "<I><FONT SIZE=4>"; + cl = "</FONT></I>"; + nstate |= SHA; + Italic*NSIZE+Size16 => + op = "<I><FONT SIZE=5>"; + cl = "</FONT></I>"; + nstate |= SHA; + Bold*NSIZE+Size6 => + op = "<B><FONT SIZE=1>"; + cl = "</FONT></B>"; + nstate |= SHA; + Bold*NSIZE+Size8 => + op = "<B><FONT SIZE=2>"; + cl = "</FONT></B>"; + nstate |= SHA; + Bold*NSIZE+Size10 => + if(state & SHA) { + op = "<B><FONT SIZE=3>"; + cl = "</FONT></B>"; + nstate &= ~SHA; + } + else { + op = "<B>"; + cl = "</B>"; + } + Bold*NSIZE+Size12 => + op = "<B><FONT SIZE=4>"; + cl = "</FONT></B>"; + nstate |= SHA; + Bold*NSIZE+Size16 => + op = "<B><FONT SIZE=5>"; + cl = "</FONT></B>"; + nstate |= SHA; + Type*NSIZE+Size6 => + op = "<TT><FONT SIZE=1>"; + cl = "</FONT></TT>"; + nstate |= SHA; + Type*NSIZE+Size8 => + op = "<TT><FONT SIZE=2>"; + cl = "</FONT></TT>"; + nstate |= SHA; + Type*NSIZE+Size10 => + if(state & SHA) { + op = "<TT><FONT SIZE=3>"; + cl = "</FONT></TT>"; + nstate &= ~SHA; + } + else { + op = "<TT>"; + cl = "</TT>"; + } + Type*NSIZE+Size12 => + op = "<TT><FONT SIZE=4>"; + cl = "</FONT></TT>"; + nstate |= SHA; + Type*NSIZE+Size16 => + op = "<TT><FONT SIZE=5>"; + cl = "</FONT></TT>"; + nstate |= SHA; + Example => + op = "<P><PRE>\t"; + cl = "</PRE><P>\n"; + nstate |= SHO; + List => + op = "<DL>"; + cl = "</DD></DL>"; + nstate |= SHFL; + Listelem => + if(state & SHFL) + op = "<DT>"; + else + op = "</DD><DT>"; + cl = "</DT><DD>"; + # change first-list-elem state for this level + state &= ~SHFL; + Heading => + op = "<H2>"; + cl = "</H2>\n"; + Nofill => + op = "<P><PRE>"; + cl = "</PRE>"; + Title => + op = "<H1>"; + cl = "</H1>\n"; + Par => + op = "<P>\n"; + Extension => + e.contents = convextension(e.s); + Special => + fout.puts(e.s); + } + if(op != "") + fout.puts(op); + hconvl(e.contents, nstate); + if(cl != "") + fout.puts(cl); + } +} + +# find title, if there is one, and return it (but leave it in contents too) +hfindtitle(e: ref Celem) : ref Celem +{ + if(e.tag == Title) + return e; + else if (e.contents != nil) { + for(h := e.contents; h != nil; h = h.next) { + a := hfindtitle(h); + if(a != nil) + return a; + } + } + return nil; +} + +Exten: adt +{ + name: string; + mod: Brutusext; +}; + +extens: list of Exten = nil; + +convextension(s: string) : ref Celem +{ + for(i:=0; i<len s; i++) + if(s[i] == ' ') + break; + if(i == len s) { + sys->fprint(stderr, "badly formed extension %s\n", s); + return nil; + } + modname := s[0:i]; + s = s[i+1:]; + mod: Brutusext = nil; + for(le := extens; le != nil; le = tl le) { + el := hd le; + if(el.name == modname) + mod = el.mod; + } + if(mod == nil) { + file := modname; + if(i < 4 || file[i-4:i] != ".dis") + file += ".dis"; + if(file[0] != '/') + file = "/dis/wm/brutus/" + file; + mod = load Brutusext file; + if(mod == nil) { + sys->fprint(stderr, "can't load extension module %s: %r\n", file); + return nil; + } + mod->init(sys, draw, B, tk, nil); + extens = Exten(modname, mod) :: extens; + } + f := infilename; + if(f == "<stdin>") + f = ""; + (ans, err) := mod->cook(f, fmt, s); + if(err != "") { + sys->fprint(stderr, "extension module %s cook error: %s\n", modname, err); + return nil; + } + return ans; +} diff --git a/appl/cmd/cp.b b/appl/cmd/cp.b new file mode 100644 index 00000000..23c49a15 --- /dev/null +++ b/appl/cmd/cp.b @@ -0,0 +1,237 @@ +implement Cp; + +include "sys.m"; + sys: Sys; + +include "draw.m"; +include "arg.m"; + +include "readdir.m"; + readdir: Readdir; + +Cp: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +stderr: ref Sys->FD; +errors := 0; +gflag := 0; +uflag := 0; +xflag := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + arg := load Arg Arg->PATH; + recursive := 0; + arg->init(args); + arg->setusage("\tcp [-gux] src target\n\tcp [-r] [-gux] src ... directory"); + while((opt := arg->opt()) != 0) + case opt { + 'r' => recursive = 1; + 'g' => gflag = 1; + 'u' => uflag = gflag = 1; + 'x' => xflag = 1; + * => arg->usage(); + } + args = arg->argv(); + argc := len args; + if(argc < 2) + arg->usage(); + arg = nil; + + dst: string; + for(t := args; t != nil; t = tl t) + dst = hd t; + + (ok, dir) := sys->stat(dst); + todir := (ok != -1 && (dir.mode & Sys->DMDIR)); + if(argc > 2 && !todir){ + sys->fprint(stderr, "cp: %s not a directory\n", dst); + raise "fail:error"; + } + if(recursive) + cpdir(args, dst); + else{ + for(; tl args != nil; args = tl args){ + if(todir) + cp(hd args, dst, basename(hd args)); + else + cp(hd args, dst, nil); + } + } + if(errors) + raise "fail:error"; +} + +basename(s: string): string +{ + for((nil, ls) := sys->tokenize(s, "/"); ls != nil; ls = tl ls) + s = hd ls; + return s; +} + +cp(src, dst: string, newname: string) +{ + dd: Sys->Dir; + + if(newname != nil) + dst += "/" + newname; + (ok, ds) := sys->stat(src); + if(ok < 0){ + warning(sys->sprint("%s: %r", src)); + return; + } + if(ds.mode & Sys->DMDIR){ + warning(src + " is a directory"); + return; + } + (ok, dd) = sys->stat(dst); + if(ok != -1 && samefile(ds, dd)){ + warning(src + " and " + dst + " are the same file"); + return; + } + sfd := sys->open(src, Sys->OREAD); + if(sfd == nil){ + warning(sys->sprint("cannot open %s: %r", src)); + return; + } + dfd := sys->create(dst, Sys->OWRITE, ds.mode & 8r777); + if(dfd == nil){ + warning(sys->sprint("cannot create %s: %r", dst)); + return; + } + if(copy(sfd, dfd, src, dst)!=0) + return; + if(wstat(dfd, ds, 0) < 0) + warning(sys->sprint("can't wstat %s: %r", src)); +} + +copy(sfd, dfd: ref Sys->FD, src, dst: string): int +{ + buf := array[Sys->ATOMICIO] of byte; + while((r := sys->read(sfd, buf, len buf)) > 0){ + if(sys->write(dfd, buf, r) != r){ + warning(sys->sprint("error writing %s: %r", dst)); + return -1; + } + } + if(r < 0){ + warning(sys->sprint("error reading %s: %r", src)); + return -1; + } + return 0; +} + +cpdir(args: list of string, dst: string) +{ + readdir = load Readdir Readdir->PATH; + if(readdir == nil){ + sys->fprint(stderr, "cp: cannot load %s: %r\n", Readdir->PATH); + raise "fail:bad module"; + } + cache = array[NCACHE] of list of ref Sys->Dir; + dexists := 0; + (ok, dd) := sys->stat(dst); + # destination file exists + if(ok != -1){ + if((dd.mode & Sys->DMDIR) == 0){ + warning(dst + ": destination not a directory"); + return; + } + dexists = 1; + } + for(; tl args != nil; args = tl args){ + ds: Sys->Dir; + src := hd args; + (ok, ds) = sys->stat(src); + if(ok < 0){ + warning(sys->sprint("can't stat %s: %r", src)); + continue; + } + if((ds.mode & Sys->DMDIR) == 0){ + cp(hd args, dst, basename(hd args)); + } else if(dexists){ + if(samefile(ds, dd)){ + warning("cannot copy " + src + " into itself"); + continue; + } + copydir(src, dst + "/" + basename(src), ds); + } else + copydir(src, dst, ds); + } +} + +copydir(src, dst: string, srcd: Sys->Dir) +{ + (ok, nil) := sys->stat(dst); + if(ok != -1){ + warning("cannot copy " + src + " onto another directory"); + return; + } + tmode := srcd.mode | 8r777; # Fix for Nt + dfd := sys->create(dst, Sys->OREAD, Sys->DMDIR | tmode); + if(dfd == nil){ + warning(sys->sprint("cannot make directory %s: %r", dst)); + return; + } + (entries, n) := readdir->init(src, Readdir->COMPACT); + for(i := 0; i < n; i++){ + e := entries[i]; + path := src + "/" + e.name; + if((e.mode & Sys->DMDIR) == 0) + cp(path, dst, e.name); + else if(seen(e)) + warning(path + ": directory loop found"); + else + copydir(path, dst + "/" + e.name, *e); + } + if(wstat(dfd, srcd, 1) < 0) + warning(sys->sprint("can't wstat %s: %r", dst)); +} + +wstat(dfd: ref Sys->FD, ds: Sys->Dir, mflag: int): int +{ + if(!xflag && !gflag && !uflag && !mflag) + return 0; + d := sys->nulldir; + if(xflag) + d.mtime = ds.mtime; + if(xflag || mflag) + d.mode = ds.mode; + if(uflag) + d.uid = ds.uid; + if(gflag) + d.gid = ds.gid; + return sys->fwstat(dfd, d); +} + +samefile(d1: Sys->Dir, d2: Sys->Dir): int +{ + return d1.dtype == d2.dtype && d1.dev == d2.dev && + d1.qid.qtype == d2.qid.qtype && d1.qid.path == d2.qid.path && + d1.qid.vers == d2.qid.vers; +} + +# Avoid loops in tangled namespaces. (from du.b) +NCACHE: con 64; # must be power of two +cache: array of list of ref sys->Dir; + +seen(dir: ref sys->Dir): int +{ + savlist := cache[int dir.qid.path&(NCACHE-1)]; + for(c := savlist; c!=nil; c = tl c) + if(samefile(*dir, *hd c)) + return 1; + cache[int dir.qid.path&(NCACHE-1)] = dir :: savlist; + return 0; +} + +warning(e: string) +{ + sys->fprint(stderr, "cp: %s\n", e); + errors++; +} diff --git a/appl/cmd/cprof.b b/appl/cmd/cprof.b new file mode 100644 index 00000000..846a75bd --- /dev/null +++ b/appl/cmd/cprof.b @@ -0,0 +1,190 @@ +implement Prof; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; + arg: Arg; +include "profile.m"; + profile: Profile; +include "sh.m"; + +stderr: ref Sys->FD; + +Prof: module { + init: fn(nil: ref Draw->Context, argv: list of string); + init0: fn(nil: ref Draw->Context, argv: list of string): Profile->Coverage; +}; + +exits(e: string) +{ + if(profile != nil) + profile->end(); + raise "fail:" + e; +} + +pfatal(s: string) +{ + sys->fprint(stderr, "cprof: %s: %s\n", s, profile->lasterror()); + exits("error"); +} + +badmodule(p: string) +{ + sys->fprint(stderr, "cprof: cannot load %s: %r\n", p); + exits("bad module"); +} + +usage(s: string) +{ + sys->fprint(stderr, "cprof: %s\n", s); + sys->fprint(stderr, "usage: cprof [-fner] [-m modname]... cmd [arg ... ]"); + exits("usage"); +} + +init(ctxt: ref Draw->Context, argv: list of string) +{ + init0(ctxt, argv); +} + +init0(ctxt: ref Draw->Context, argv: list of string): Profile->Coverage +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + arg = load Arg Arg->PATH; + if(arg == nil) + badmodule(Arg->PATH); + arg->init(argv); + profile = load Profile Profile->PATH; + if(profile == nil) + badmodule(Profile->PATH); + if(profile->init() < 0) + pfatal("cannot initialize profile device"); + + v := 0; + ep := 0; + rec := 0; + wm := 0; + exec, mods: list of string; + while((c := arg->opt()) != 0){ + case c { + 'n' => v |= profile->FULLHDR; + 'f' => v |= profile->FREQUENCY; + 'm' => + if((s := arg->arg()) == nil) + usage("missing module/file"); + mods = s :: mods; + 'e' => + ep = 1; + 'r' => + rec = 1; + 'g' => + wm = 1; + * => + usage(sys->sprint("unknown option -%c", c)); + } + } + exec = arg->argv(); + # if(exec == nil) + # usage("nothing to execute"); + for( ; mods != nil; mods = tl mods) + profile->profile(hd mods); + if(ep && exec != nil) + profile->profile(disname(hd exec)); + if(exec != nil){ + wfd := openwait(sys->pctl(0, nil)); + ci := chan of int; + spawn execute(ctxt, hd exec, exec, ci); + epid := <- ci; + if(profile->cpstart(epid) < 0){ + ci <-= 0; + pfatal("cannot start profiling"); + } + ci <-= 1; + wait(wfd, epid); + if(profile->stop() < 0) + pfatal("cannot stop profiling"); + } + if(exec == nil) + modl := profile->cpfstats(v); + else + modl = profile->cpstats(rec, v); + if(modl.mods == nil) + pfatal("no profile information"); + if(wm){ + cvr := profile->coverage(modl, v); + profile->end(); + return cvr; + } + if(!rec && profile->cpshow(modl, v) < 0) + pfatal("cannot show profile"); + profile->end(); + return nil; +} + +disname(cmd: string): string +{ + file := cmd; + if(len file<4 || file[len file-4:]!=".dis") + file += ".dis"; + if(exists(file)) + return file; + if(file[0]!='/' && file[0:2]!="./") + file = "/dis/"+file; + # if(exists(file)) + # return file; + return file; +} + +execute(ctxt: ref Draw->Context, cmd : string, argl : list of string, ci: chan of int) +{ + ci <-= sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: stderr.fd :: nil); + file := cmd; + err := ""; + if(len file<4 || file[len file-4:]!=".dis") + file += ".dis"; + c := load Command file; + if(c == nil) { + err = sys->sprint("%r"); + if(file[0]!='/' && file[0:2]!="./"){ + c = load Command "/dis/"+file; + if(c == nil) + err = sys->sprint("%r"); + } + } + if(<- ci){ + if(c == nil) + sys->fprint(stderr, "cprof: %s: %s\n", cmd, err); + else + c->init(ctxt, argl); + } +} + +openwait(pid : int) : ref Sys->FD +{ + w := sys->sprint("#p/%d/wait", pid); + fd := sys->open(w, Sys->OREAD); + if (fd == nil) + pfatal("fd == nil in wait"); + return fd; +} + +wait(wfd : ref Sys->FD, wpid : int) +{ + n : int; + + buf := array[Sys->WAITLEN] of byte; + status := ""; + for(;;) { + if ((n = sys->read(wfd, buf, len buf)) < 0) + pfatal("bad read in wait"); + status = string buf[0:n]; + if (int status == wpid) + break; + } +} + +exists(f: string): int +{ + return sys->open(f, Sys->OREAD) != nil; +} diff --git a/appl/cmd/cpu.b b/appl/cmd/cpu.b new file mode 100644 index 00000000..57a62fdf --- /dev/null +++ b/appl/cmd/cpu.b @@ -0,0 +1,168 @@ +implement CPU; + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; +include "draw.m"; + Context: import Draw; +include "string.m"; + str: String; +include "arg.m"; +include "keyring.m"; +include "security.m"; + +DEFCMD: con "/dis/sh"; + +CPU: module +{ + init: fn(ctxt: ref Context, argv: list of string); +}; + +badmodule(p: string) +{ + sys->fprint(stderr, "cpu: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +usage() +{ + sys->fprint(stderr, "Usage: cpu [-C cryptoalg] mach command args...\n"); + raise "fail:usage"; +} + +# The default level of security is NOSSL, unless +# the keyring directory doesn't exist, in which case +# it's disallowed. +init(nil: ref Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + arg := load Arg Arg->PATH; + if (arg == nil) badmodule(Arg->PATH); + + str = load String String->PATH; + if (str == nil) badmodule(String->PATH); + + au := load Auth Auth->PATH; + if (au == nil) badmodule(Auth->PATH); + + kr := load Keyring Keyring->PATH; + if (kr == nil) badmodule(Keyring->PATH); + + arg->init(argv); + alg := ""; + while ((opt := arg->opt()) != 0) { + if (opt == 'C') { + alg = arg->arg(); + } else + usage(); + } + argv = arg->argv(); + args := "auxi/cpuslave"; +# if(ctxt != nil && ctxt.screen != nil) +# args += " -s" + string ctxt.screen.id; +# else + args += " --"; + + mach: string; + case len argv { + 0 => + usage(); + 1 => + mach = hd argv; + args += " " + DEFCMD; + * => + mach = hd argv; + args += " " + str->quoted(tl argv); + } + + user := getuser(); + kd := "/usr/" + user + "/keyring/"; + cert := kd + netmkaddr(mach, "tcp", ""); + if (!exists(cert)) { + cert = kd + "default"; + if (!exists(cert)) { + sys->fprint(stderr, "cpu: cannot find certificate in %s; use getauthinfo\n", kd); + raise "fail:no certificate"; + } + } + + # To make visible remotely + if(!exists("/dev/draw/new")) + sys->bind("#d", "/dev", Sys->MBEFORE); + + (ok, c) := sys->dial(netmkaddr(mach, "net", "rstyx"), nil); + if(ok < 0){ + sys->fprint(stderr, "Error: cpu: dial: %r\n"); + return; + } + + ai := kr->readauthinfo(cert); + + if (alg == nil) + alg = "none"; + err := au->init(); + if(err != nil) { + sys->fprint(stderr, "cpu: cannot initialise auth module: %s\n", err); + raise "fail:auth init failed"; + } + + fd := ref Sys->FD; + #sys->fprint(stderr, "cpu: authenticating using alg '%s'\n", alg); + (fd, err) = au->client(alg, ai, c.dfd); + if(fd == nil) { + sys->fprint(stderr, "cpu: authentication failed: %s\n", err); + raise "fail:authentication failure"; + } + + t := array of byte sys->sprint("%d\n%s\n", len (array of byte args)+1, args); + if(sys->write(fd, t, len t) != len t){ + sys->fprint(stderr, "cpu: export args write error: %r\n"); + raise "fail:write error"; + } + + if(sys->export(fd, "/", sys->EXPWAIT) < 0){ + sys->fprint(stderr, "cpu: export failed: %r\n"); + raise "fail:export error"; + } +} + +exists(file: string): int +{ + (ok, nil) := sys->stat(file); + return ok != -1; +} + +getuser(): string +{ + fd := sys->open("/dev/user", sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "cpu: cannot open /dev/user: %r\n"); + raise "fail:no user id"; + } + + buf := array[50] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0){ + sys->fprint(stderr, "cpu: cannot read /dev/user: %r\n"); + raise "fail:no user id"; + } + + return string buf[0:n]; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/crypt.b b/appl/cmd/crypt.b new file mode 100644 index 00000000..a478abfc --- /dev/null +++ b/appl/cmd/crypt.b @@ -0,0 +1,234 @@ +implement Crypt; + +# encrypt/decrypt from stdin to stdout + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; +include "draw.m"; +include "keyring.m"; + keyring: Keyring; +include "security.m"; + ssl: SSL; +include "arg.m"; + +Crypt: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +Ehungup: con "i/o on hungup channel"; + +ALGSTR: con "alg "; +DEFAULTALG: con "md5/ideacbc"; +usage() +{ + sys->fprint(stderr, "usage: crypt [-?] [-d] [-k secret] [-f secretfile] [-a alg[/alg]]\n"); + sys->fprint(stderr, "available algorithms:\n"); + showalgs(stderr); + fail("bad usage"); +} + +badmodule(m: string) +{ + sys->fprint(stderr, "crypt: cannot load %s: %r\n", m); + fail("bad module"); +} + +headers: con 1; +verbose := 0; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + ssl = load SSL SSL->PATH; + if (ssl == nil) + badmodule(SSL->PATH); + keyring = load Keyring Keyring->PATH; + if (keyring == nil) + badmodule(SSL->PATH); + + arg := load Arg Arg->PATH; + if (arg == nil) + badmodule(SSL->PATH); + + decrypt := 0; + secret: array of byte; + alg := DEFAULTALG; + + arg->init(argv); + while ((opt := arg->opt()) != 0) { + case opt { + 'd' => + decrypt = 1; + 'k' => + if ((s := arg->arg()) == nil) + usage(); + secret = array of byte s; + 'f' => + if ((f := arg->arg()) == nil) + usage(); + secret = readfile(f); + 'a' => + if ((alg = arg->arg()) == nil) + usage(); + '?' => + showalgs(sys->fildes(1)); + return; + 'v' => + verbose = 1; + * => + usage(); + } + } + argv = arg->argv(); + if (argv != nil) + usage(); + if(secret == nil){ + sys->fprint(stderr, "crypt: no secret given\n"); + usage(); + } + sk := array[Keyring->SHA1dlen] of byte; + keyring->sha1(secret, len secret, sk, nil); + if (headers) { + # deal with header - the header encodes the algorithm along with the data. + if (decrypt) { + msg := keyring->getmsg(sys->fildes(0)); + if (msg != nil) + alg = string msg; + if (msg == nil || len alg < len ALGSTR || alg[0:len ALGSTR] != ALGSTR) + error("couldn't get decrypt algorithm"); + alg = alg[len ALGSTR:]; + } else { + msg := array of byte ("alg " + alg); + e := keyring->sendmsg(sys->fildes(1), msg, len msg); + if (e == -1) + error("couldn't write algorithm string"); + } + } + fd := docrypt(decrypt, alg, sk); + if (decrypt) { + # if decrypting, don't use stream, as we want to catch + # decryption or checksum errors when they happen. + buf := array[Sys->ATOMICIO] of byte; + stdout := sys->fildes(1); + while ((n := sys->read(fd, buf, len buf)) > 0) + sys->write(stdout, buf, n); + + if (n == -1) { + err := sys->sprint("%r"); + if (err != Ehungup) + error("decryption failed: " + err); + } + } else { + stream(fd, sys->fildes(1), Sys->ATOMICIO); + } +} + +docrypt(decrypt: int, alg: string, sk: array of byte): ref Sys->FD +{ + if (verbose) + sys->fprint(stderr, "%scrypting with alg %s\n", (array[] of {"en", "de"})[decrypt!=0], alg); + (err, fds, nil, nil) := cryptpipe(decrypt, alg, sk); + if (err != nil) + error(err); + + spawn stream(sys->fildes(0), fds[1], Sys->ATOMICIO); + return fds[0]; +} + +# set up an encrypt/decrypt session; if decrypt is non-zero, then +# decrypt, else encrypt. alg is the algorithm to use; sk is the +# used as the secret key. +# returns tuple (err, fds, cfd, dir) +# where err is non-nil on failure; +# otherwise fds is an array of two fds; writing to fds[1] will make +# crypted/decrypted data available to be read on fds[0]. +# dir is the ssl directory in question. +cryptpipe(decrypt: int, alg: string, sk: array of byte): (string, array of ref Sys->FD, ref Sys->FD, string) +{ + pfd := array[2] of ref Sys->FD; + if (sys->pipe(pfd) == -1) + return ("pipe failed", nil, nil, nil); + + (err, c) := ssl->connect(pfd[1]); + if (err != nil) + return ("could not connect ssl: "+sys->sprint("%r"), nil, nil, nil); + pfd[1] = nil; + err = ssl->secret(c, sk, sk); + if (err != nil) + return ("could not write secret: "+sys->sprint("%r"), nil, nil, nil); + + if (alg != nil) + if (sys->fprint(c.cfd, "alg %s", alg) == -1) + return (sys->sprint("bad algorithm %s: %r", alg), nil, nil, nil); + + fds := array[2] of ref Sys->FD; + if (decrypt) { + fds[1] = pfd[0]; + fds[0] = c.dfd; + } else { + fds[1] = c.dfd; + fds[0] = pfd[0]; + } + return (nil, fds, c.cfd, c.dir); +} + +algnames := array[] of {("crypt", "encalgs"), ("hash", "hashalgs")}; + +# find available algorithms and return as tuple of two lists: +# (err, hashalgs, cryptalgs) +algs(): (string, array of list of string) +{ + (err, nil, nil, dir) := cryptpipe(0, nil, array[100] of byte); + if (err != nil) + return (err, nil); + alglists := array[len algnames] of list of string; + for (i := 0; i < len algnames; i++) { + (nil, f) := algnames[i]; + (nil, alglists[i]) = sys->tokenize(string readfile(dir + "/" + f), " "); + } + return (nil, alglists); +} + +showalgs(fd: ref Sys->FD) +{ + (err, alglists) := algs(); + if (err != nil) + error("cannot get algorithms: " + err); + for (j := 0; j < len alglists; j++) { + (name, nil) := algnames[j]; + sys->fprint(fd, "%s:", name); + for (l := alglists[j]; l != nil; l = tl l) + sys->fprint(fd, " %s", hd l); + sys->fprint(fd, "\n"); + } +} + +stream(src, dst: ref Sys->FD, bufsize: int) +{ + sys->stream(src, dst, bufsize); +} + +readfile(f: string): array of byte +{ + fd := sys->open(f, Sys->OREAD); + if (fd == nil) + error(sys->sprint("cannot read %s: %r", f)); + buf := array[8192] of byte; # >8K key? get real! + n := sys->read(fd, buf, len buf); + if (n <= 0) + return nil; + return buf[0:n]; +} + +error(s: string) +{ + sys->fprint(stderr, "crypt: %s\n", s); + fail("error"); +} + +fail(e: string) +{ + raise "fail: "+e; +} diff --git a/appl/cmd/date.b b/appl/cmd/date.b new file mode 100644 index 00000000..83068e0d --- /dev/null +++ b/appl/cmd/date.b @@ -0,0 +1,71 @@ +implement Date; + +include "sys.m"; + sys: Sys; + +include "draw.m"; +include "daytime.m"; +include "arg.m"; + +Date: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: date [-un] [seconds]\n"); + raise "fail:usage"; +} + +nomod(m: string) +{ + sys->fprint(sys->fildes(2), "date: cannot load %s: %r", m); + raise "fail:load"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + daytime := load Daytime Daytime->PATH; + if (daytime == nil) + nomod(Daytime->PATH); + arg := load Arg Arg->PATH; + if (arg == nil) + nomod(Arg->PATH); + nflag := uflag := 0; + arg->init(argv); + while ((opt := arg->opt()) != 0) { + case opt { + 'n' => + nflag = 1; + 'u' => + uflag = 1; + * => + usage(); + } + } + argv = arg->argv(); + arg = nil; + if (argv != nil && (tl argv != nil || !isnumeric(hd argv))) + usage(); + now: int; + if (argv != nil) + now = int hd argv; + else + now = daytime->now(); + if (nflag) + sys->print("%d\n", now); + else if (uflag) + sys->print("%s\n", daytime->text(daytime->gmt(now))); + else + sys->print("%s\n", daytime->text(daytime->local(now))); +} + +isnumeric(s: string): int +{ + for (i := 0; i < len s; i++) + if (s[i] < '0' || s[i] > '9') + return 0; + return 1; +} diff --git a/appl/cmd/dbfs.b b/appl/cmd/dbfs.b new file mode 100644 index 00000000..9482e8df --- /dev/null +++ b/appl/cmd/dbfs.b @@ -0,0 +1,518 @@ +implement Dbfs; + +# +# Copyright © 1999 Vita Nuova Limited. All rights reserved. +# Revisions copyright © 2002 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + Qid: import Sys; + +include "draw.m"; + +include "arg.m"; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + +include "styxservers.m"; + styxservers: Styxservers; + Fid, Styxserver, Navigator, Navop: import styxservers; + Enotfound, Eperm, Ebadarg: import styxservers; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +Record: adt { + id: int; # file number in directory + x: int; # index in file + dirty: int; # modified but not written + vers: int; # version + data: array of byte; + + new: fn(x: array of byte): ref Record; + print: fn(r: self ref Record, fd: ref Sys->FD); + qid: fn(r: self ref Record): Sys->Qid; +}; + +Database: adt { + name: string; + file: ref Iobuf; + records: array of ref Record; + dirty: int; + vers: int; + nextid: int; + + findrec: fn(db: self ref Database, id: int): ref Record; +}; + +Dbfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Qdir, Qnew, Qdata: con iota; + +clockfd: ref Sys->FD; +stderr: ref Sys->FD; +database: ref Database; +user: string; +Eremoved: con "file removed"; + +usage() +{ + sys->fprint(stderr, "Usage: dbfs [-a|-b|-ac|-bc] [-D] file mountpoint\n"); + raise "fail:usage"; +} + +nomod(s: string) +{ + sys->fprint(stderr, "dbfs: can't load %s: %r\n", s); + raise "fail:load"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + stderr = sys->fildes(2); + styx = load Styx Styx->PATH; + if(styx == nil) + nomod(Styx->PATH); + styx->init(); + styxservers = load Styxservers Styxservers->PATH; + if(styxservers == nil) + nomod(Styxservers->PATH); + styxservers->init(styx); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + nomod(Bufio->PATH); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + flags := Sys->MREPL; + copt := 0; + empty := 0; + while((o := arg->opt()) != 0) + case o { + 'a' => flags = Sys->MAFTER; + 'b' => flags = Sys->MBEFORE; + 'c' => copt = 1; + 'e' => empty = 1; + 'D' => styxservers->traceset(1); + * => usage(); + } + args = arg->argv(); + arg = nil; + + if(len args != 2) + usage(); + if(copt) + flags |= Sys->MCREATE; + file := hd args; + args = tl args; + mountpt := hd args; + + df := bufio->open(file, Sys->OREAD); + if(df == nil && empty){ + (rc, d) := sys->stat(file); + if(rc < 0) + df = bufio->create(file, Sys->OREAD, 8r600); + } + if(df == nil){ + sys->fprint(stderr, "dbfs: can't open %s: %r\n", file); + raise "fail:open"; + } + (db, err) := dbread(ref Database(file, df, nil, 0, 0, 0)); + if(db == nil){ + sys->fprint(stderr, "dbfs: can't read %s: %s\n", file, err); + raise "fail:dbread"; + } + db.file = nil; +# dbprint(db); + database = db; + + sys->pctl(Sys->FORKFD, nil); + + user = rf("/dev/user"); + if(user == nil) + user = "inferno"; + + fds := array[2] of ref Sys->FD; + if(sys->pipe(fds) < 0){ + sys->fprint(stderr, "dbfs: can't create pipe: %r\n"); + raise "fail:pipe"; + } + + navops := chan of ref Navop; + spawn navigator(navops); + + (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qdir); + fds[0] = nil; + + pidc := chan of int; + spawn serveloop(tchan, srv, pidc, navops); + <-pidc; + + if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) { + sys->fprint(stderr, "dbfs: mount failed: %r\n"); + raise "fail:mount"; + } +} + +rf(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd == nil) + return nil; + b := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, b, len b); + if(n < 0) + return nil; + return string b[0:n]; +} + +dbread(db: ref Database): (ref Database, string) +{ + db.file.seek(big 0, Sys->SEEKSTART); + rl: list of ref Record; + n := 0; + for(;;){ + (r, err) := getrec(db); + if(err != nil) + return (nil, err); # could press on without it, or make it the `file' contents + if(r == nil) + break; + rl = r :: rl; + n++; + } + db.nextid = n; + db.records = array[n] of ref Record; + for(; rl != nil; rl = tl rl){ + r := hd rl; + n--; + r.id = n; + r.x = n; + db.records[n] = r; + } + return (db, nil); +} + +# +# a record is (.+\n)*\n +# +getrec(db: ref Database): (ref Record, string) +{ + r := ref Record(-1, -1, 0, 0, nil); + data := ""; + for(;;){ + s := db.file.gets('\n'); + if(s == nil){ + if(data == nil) + return (nil, nil); # BUG: distinguish i/o error from EOF? + break; + } + if(s[len s - 1] != '\n') +# return (nil, "file missing newline"); # possibly truncated + s += "\n"; + if(s == "\n") + break; + data += s; + } + r.data = array of byte data; + return (r, nil); +} + +dbsync(db: ref Database): int +{ + if(db.dirty){ + db.file = bufio->create(db.name, Sys->OWRITE, 8r666); + if(db.file == nil) + return -1; + for(i := 0; i < len db.records; i++){ + r := db.records[i]; + if(r != nil && r.data != nil){ + if(db.file.write(r.data, len r.data) != len r.data) + return -1; + db.file.putc('\n'); + } + } + if(db.file.flush()) + return -1; + db.file = nil; + db.dirty = 0; + } + return 0; +} + +dbprint(db: ref Database) +{ + stdout := sys->fildes(1); + for(i := 0; i < len db.records; i++){ + db.records[i].print(stdout); + sys->print("\n"); + } +} + +Database.findrec(db: self ref Database, id: int): ref Record +{ + for(i:=0; i<len db.records; i++) + if((r := db.records[i]) != nil && r.id == id) + return r; + return nil; +} + +Record.new(fields: array of byte): ref Record +{ + n := len database.records; + r := ref Record(n, n, 0, 0, fields); + a := array[n+1] of ref Record; + if(n) + a[0:] = database.records[0:]; + a[n] = r; + database.records = a; + database.vers++; + return r; +} + +Record.print(r: self ref Record, fd: ref Sys->FD) +{ + if(r.data != nil) + sys->write(fd, r.data, len r.data); +} + +Record.qid(r: self ref Record): Sys->Qid +{ + return Sys->Qid(QPATH(r.x, Qdata), r.vers, Sys->QTFILE); +} + +serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop) +{ + pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::nil); +Serve: + while((gm := <-tchan) != nil){ + pick m := gm { + Readerror => + sys->fprint(stderr, "dbfs: fatal read error: %s\n", m.error); + break Serve; + Open => + c := srv.getfid(m.fid); + if(c == nil || TYPE(c.path) != Qnew){ + srv.open(m); # default action + break; + } + if(c.uname != user) { + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + mode := styxservers->openmode(m.mode); + if(mode < 0) { + srv.reply(ref Rmsg.Error(m.tag, Ebadarg)); + break; + } + # generate new file, change Fid's qid to match + r := Record.new(array[0] of byte); + qid := r.qid(); + c.open(mode, qid); + srv.reply(ref Rmsg.Open(m.tag, qid, srv.iounit())); + Read => + (c, err) := srv.canread(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + if(c.qtype & Sys->QTDIR){ + srv.read(m); # does readdir + break; + } + r := database.records[FILENO(c.path)]; + if(r == nil) + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + else + srv.reply(styxservers->readbytes(m, r.data)); + Write => + (c, merr) := srv.canwrite(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, merr)); + break; + } + (value, err) := data2rec(m.data); + if(err != nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + fno := FILENO(c.path); + r := database.records[fno]; + if(r == nil){ + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + break; + } + r.data = value; + r.vers++; + database.dirty++; + if(dbsync(database) == 0) + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + else + srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); + Clunk => + # a transaction-oriented dbfs could delay updating the record until clunk + srv.clunk(m); + Remove => + c := srv.getfid(m.fid); + if(c == nil || c.qtype & Sys->QTDIR || TYPE(c.path) != Qdata){ + # let it diagnose all the errors + srv.remove(m); + break; + } + r := database.records[FILENO(c.path)]; + if(r != nil) + r.data = nil; + database.dirty++; + srv.delfid(c); + if(dbsync(database) == 0) + srv.reply(ref Rmsg.Remove(m.tag)); + else + srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); + Wstat => + srv.default(gm); # TO DO? + * => + srv.default(gm); + } + } + navops <-= nil; # shut down navigator +} + +dirslot(n: int): int +{ + for(i := 0; i < len database.records; i++){ + r := database.records[i]; + if(r != nil && r.data != nil){ + if(n == 0) + return i; + n--; + } + } + return -1; +} + +dir(qid: Sys->Qid, name: string, length: big, uid: string, perm: int): ref Sys->Dir +{ + d := ref sys->zerodir; + d.qid = qid; + if(qid.qtype & Sys->QTDIR) + perm |= Sys->DMDIR; + d.mode = perm; + d.name = name; + d.uid = uid; + d.gid = uid; + d.length = length; + return d; +} + +dirgen(p: big): (ref Sys->Dir, string) +{ + case TYPE(p) { + Qdir => + return (dir(Qid(QPATH(0, Qdir),database.vers,Sys->QTDIR), "/", big 0, user, 8r700), nil); + Qnew => + return (dir(Qid(QPATH(0, Qnew),0,Sys->QTFILE), "new", big 0, user, 8r600), nil); + * => + n := FILENO(p); + if(n < 0 || n >= len database.records) + return (nil, nil); + r := database.records[n]; + if(r == nil || r.data == nil) + return (nil, Enotfound); + return (dir(r.qid(), sys->sprint("%d", r.id), big len r.data, user, 8r600), nil); + } +} + +navigator(navops: chan of ref Navop) +{ + while((m := <-navops) != nil){ + pick n := m { + Stat => + n.reply <-= dirgen(n.path); + Walk => + if(int n.path != Qdir){ + n.reply <-= (nil, "not a directory"); + break; + } + case n.name { + ".." => + ; # nop + "new" => + n.path = QPATH(0, Qnew); + * => + if(len n.name < 1 || !(n.name[0]>='0' && n.name[0]<='9')){ # weak test for now + n.reply <-= (nil, Enotfound); + continue; + } + r := database.findrec(int n.name); + if(r == nil){ + n.reply <-= (nil, Enotfound); + continue; + } + n.path = QPATH(r.x, Qdata); + } + n.reply <-= dirgen(n.path); + Readdir => + if(int m.path != Qdir){ + n.reply <-= (nil, "not a directory"); + break; + } + i := n.offset; + if(i == 0) + n.reply <-= dirgen(QPATH(0,Qnew)); + for(; --n.count >= 0 && (j := dirslot(i)) >= 0; i++) + n.reply <-= dirgen(QPATH(j,Qdata)); # n² but the file will be small + n.reply <-= (nil, nil); + } + } +} + +QPATH(w, q: int): big +{ + return big ((w<<8)|q); +} + +TYPE(path: big): int +{ + return int path & 16rFF; +} + +FILENO(path: big) : int +{ + return (int path >> 8) & 16rFFFFFF; +} + +# +# a record is (.+\n)*, without final empty line +# +data2rec(data: array of byte): (array of byte, string) +{ + s: string; + for(b := data; len b > 0;){ + (b, s) = getline(b); + if(s == nil || s[len s - 1] != '\n' || s == "\n") + return (nil, "partial or malformed record"); # possibly truncated + } + return (data, nil); +} + +getline(b: array of byte): (array of byte, string) +{ + n := len b; + for(i := 0; i < n; i++){ + (ch, l, nil) := sys->byte2char(b, i); + i += l; + if(l == 0 || ch == '\n') + break; + } + return (b[i:], string b[0:i]); +} diff --git a/appl/cmd/dbm/delete.b b/appl/cmd/dbm/delete.b new file mode 100755 index 00000000..dcdde8c0 --- /dev/null +++ b/appl/cmd/dbm/delete.b @@ -0,0 +1,34 @@ +implement Dbmdelete; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "dbm.m"; + dbm: Dbm; + Datum, Dbf: import dbm; + +Dbmdelete: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + dbm = load Dbm Dbm->PATH; + + dbm->init(); + + args = tl args; + db := Dbf.open(hd args, Sys->ORDWR); + if(db == nil){ + sys->fprint(sys->fildes(2), "dbm/delete: %s: %r\n", hd args); + raise "fail:open"; + } + args = tl args; + key := hd args; + if(db.delete(array of byte key) < 0) + sys->fprint(sys->fildes(2), "not found\n"); +} diff --git a/appl/cmd/dbm/fetch.b b/appl/cmd/dbm/fetch.b new file mode 100755 index 00000000..3ebbc7d6 --- /dev/null +++ b/appl/cmd/dbm/fetch.b @@ -0,0 +1,37 @@ +implement Dbmfetch; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "dbm.m"; + dbm: Dbm; + Datum, Dbf: import dbm; + +Dbmfetch: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + dbm = load Dbm Dbm->PATH; + + dbm->init(); + + args = tl args; + db := Dbf.open(hd args, Sys->OREAD); + if(db == nil){ + sys->fprint(sys->fildes(2), "dbm/fetch: %s: %r\n", hd args); + raise "fail:open"; + } + args = tl args; + key := hd args; + data := db.fetch(array of byte key); + if(data == nil) + sys->fprint(sys->fildes(2), "not found\n"); + else + sys->write(sys->fildes(1), data, len data); +} diff --git a/appl/cmd/dbm/keys.b b/appl/cmd/dbm/keys.b new file mode 100755 index 00000000..750d734c --- /dev/null +++ b/appl/cmd/dbm/keys.b @@ -0,0 +1,32 @@ +implement Dbmkeys; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "dbm.m"; + dbm: Dbm; + Datum, Dbf: import dbm; + +Dbmkeys: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + dbm = load Dbm Dbm->PATH; + + dbm->init(); + + args = tl args; + db := Dbf.open(hd args, Sys->OREAD); + if(db == nil){ + sys->fprint(sys->fildes(2), "dbm/keys: %s: %r\n", hd args); + raise "fail:open"; + } + for(key := db.firstkey(); key != nil; key = db.nextkey(key)) + sys->print("%s\n", string key); +} diff --git a/appl/cmd/dbm/list.b b/appl/cmd/dbm/list.b new file mode 100755 index 00000000..6c0e71f5 --- /dev/null +++ b/appl/cmd/dbm/list.b @@ -0,0 +1,34 @@ +implement Dbmlist; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "dbm.m"; + dbm: Dbm; + Datum, Dbf: import dbm; + +Dbmlist: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + dbm = load Dbm Dbm->PATH; + + dbm->init(); + + args = tl args; + db := Dbf.open(hd args, Sys->OREAD); + if(db == nil){ + sys->fprint(sys->fildes(2), "dbm/list: %s: %r\n", hd args); + raise "fail:open"; + } + for(key := db.firstkey(); key != nil; key = db.nextkey(key)){ + d := db.fetch(key); + sys->print("%s %s\n", string key, string d); + } +} diff --git a/appl/cmd/dbm/mkfile b/appl/cmd/dbm/mkfile new file mode 100644 index 00000000..7ab434c9 --- /dev/null +++ b/appl/cmd/dbm/mkfile @@ -0,0 +1,19 @@ +<../../../mkconfig + +TARG=\ + fetch.dis\ + delete.dis\ + keys.dis\ + list.dis\ + store.dis\ + +SYSMODULES=\ + arg.m\ + sys.m\ + draw.m\ + bufio.m\ + dbm.m\ + +DISBIN=$ROOT/dis/dbm + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/dbm/store.b b/appl/cmd/dbm/store.b new file mode 100755 index 00000000..0587c4b7 --- /dev/null +++ b/appl/cmd/dbm/store.b @@ -0,0 +1,69 @@ +implement Dbmstore; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "dbm.m"; + dbm: Dbm; + Datum, Dbf: import dbm; + +Dbmstore: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + dbm = load Dbm Dbm->PATH; + bufio = load Bufio Bufio->PATH; + + dbm->init(); + + args = tl args; + db := Dbf.open(hd args, Sys->ORDWR); + if(db == nil){ + sys->fprint(sys->fildes(2), "dbm/store: %s: %r\n", hd args); + raise "fail:open"; + } + args = tl args; + if(args == nil){ + err := 0; + f := bufio->fopen(sys->fildes(0), Bufio->OREAD); + while((s := f.gets('\n')) != nil){ + s = s[0:len s-1]; + key: string; + for(i :=0; i < len s; i++) + if(s[i] == ' ' || s[i] == '\t'){ + key = s[0:i]; + s = s[i+1:]; + break; + } + if(key == nil){ + sys->fprint(sys->fildes(2), "dbm/store: bad input\n"); + raise "fail:error"; + } + if(store(db, key, s)) + err = 1; + } + if(err) + raise "fail:store"; + }else if(store(db, hd args, hd tl args)) + raise "fail:store"; +} + +store(db: ref Dbf, key: string, dat: string): int +{ + r := db.store(array of byte key, array of byte dat, 0); + if(r < 0) + sys->fprint(sys->fildes(2), "bad store\n"); + else if(r) + sys->fprint(sys->fildes(2), "%q exists\n", key); + return r; +} diff --git a/appl/cmd/dd.b b/appl/cmd/dd.b new file mode 100644 index 00000000..cba8067c --- /dev/null +++ b/appl/cmd/dd.b @@ -0,0 +1,625 @@ +implement dd; + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; + +include "draw.m"; + +dd: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +BIG: con 2147483647; +LCASE, +UCASE, +SWAB, +NERR , +SYNC : con (1<<iota); + +NULL, +CNULL, +EBCDIC, +IBM, +ASCII, +BLOCK, +UNBLOCK: con iota; + +cflag: int; +ctype: int; + +fflag: int; +arg: string; +ifile: string; +ofile: string; +ibuf: array of byte; +obuf: array of byte; +op: int; +skip: int; +oseekn: int; +iseekn: int; +count: int; +files:= 1; +ibs:= 512; +obs:= 512; +bs: int; +cbs: int; +ibc: int; +obc: int; +cbc: int; +nifr: int; +nipr: int; +nofr: int; +nopr: int; +ntrunc: int; +ibf: ref Sys->FD; +obf: ref Sys->FD; +nspace: int; + +iskey(key:string, s: string): int +{ + return key[0] == '-' && key[1:] == s; +} + +exits(msg: string) +{ + if(msg == nil) + exit; + + raise "fail:"+msg; +} + +perror(msg: string) +{ + sys->fprint(stderr, "%s: %r\n", msg); +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + if(sys == nil) + return; + stderr = sys->fildes(2); + + ctype = NULL; + argv = tl argv; + while(argv != nil) { + key := hd argv; + argv = tl argv; + if(argv == nil){ + sys->fprint(stderr, "dd: arg %s needs a value\n", key); + exits("arg"); + } + arg = hd argv; + argv = tl argv; + if(iskey(key, "ibs")) { + ibs = number(BIG); + continue; + } + if(iskey(key, "obs")) { + obs = number(BIG); + continue; + } + if(iskey(key, "cbs")) { + cbs = number(BIG); + continue; + } + if(iskey(key, "bs")) { + bs = number(BIG); + continue; + } + if(iskey(key, "if")) { + ifile = arg[0:]; + continue; + } + if(iskey(key, "of")) { + ofile = arg[0:]; + continue; + } + if(iskey(key, "skip")) { + skip = number(BIG); + continue; + } + if(iskey(key, "seek") || iskey(key, "oseek")) { + oseekn = number(BIG); + continue; + } + if(iskey(key, "iseek")) { + iseekn = number(BIG); + continue; + } + if(iskey(key, "count")) { + count = number(BIG); + continue; + } + if(iskey(key, "files")) { + files = number(BIG); + continue; + } + if(iskey(key, "conv")) { + do { + if(arg == nil) + break; + if(match(",")) + continue; + if(match("ebcdic")) { + ctype = EBCDIC; + continue; + } + if(match("ibm")) { + ctype = IBM; + continue; + } + if(match("ascii")) { + ctype = ASCII; + continue; + } + if(match("block")) { + ctype = BLOCK; + continue; + } + if(match("unblock")) { + ctype = UNBLOCK; + continue; + } + if(match("lcase")) { + cflag |= LCASE; + continue; + } + if(match("ucase")) { + cflag |= UCASE; + continue; + } + if(match("swab")) { + cflag |= SWAB; + continue; + } + if(match("noerror")) { + cflag |= NERR; + continue; + } + if(match("sync")) { + cflag |= SYNC; + continue; + } + } while(1); + continue; + } + sys->fprint(stderr, "dd: bad arg: %s\n", key); + exits("arg"); + } + if(ctype == NULL && cflag&(LCASE|UCASE)) + ctype = CNULL; + if(ifile != nil) + ibf = sys->open(ifile, Sys->OREAD); + else + ibf = sys->fildes(sys->dup(0, -1)); + + if(ibf == nil) { + sys->fprint(stderr, "dd: open %s: %r\n", ifile); + exits("open"); + } + + if(ofile != nil){ + obf = sys->create(ofile, Sys->OWRITE, 8r664); + if(obf == nil) { + sys->fprint(stderr, "dd: create %s: %r\n", ofile); + exits("create"); + } + }else{ + obf = sys->fildes(sys->dup(1, -1)); + if(obf == nil) { + sys->fprint(stderr, "dd: can't dup file descriptor: %r\n"); + exits("dup"); + } + } + if(bs) + ibs = obs = bs; + if(ibs == obs && ctype == NULL) + fflag++; + if(ibs == 0 || obs == 0) { + sys->fprint(stderr, "dd: counts: cannot be zero\n"); + exits("counts"); + } + ibuf = array[ibs] of byte; + obuf = array[obs] of byte; + + if(fflag) + obuf = ibuf; + + sys->seek(obf, big obs*big oseekn, Sys->SEEKRELA); + sys->seek(ibf, big ibs*big iseekn, Sys->SEEKRELA); + while(skip) { + sys->read(ibf, ibuf, ibs); + skip--; + } + + ibc = 0; + obc = 0; + cbc = 0; + op = 0; + ip := 0; + do { + if(ibc-- == 0) { + ibc = 0; + if(count==0 || nifr+nipr!=count) { + if(cflag&(NERR|SYNC)) + for(ip=0; ip < len ibuf; ip++) + ibuf[ip] = byte 0; + ibc = sys->read(ibf, ibuf, ibs); + } + if(ibc == -1) { + perror("read"); + if((cflag&NERR) == 0) { + flsh(); + term(); + } + ibc = 0; + for(c:=0; c<ibs; c++) + if(ibuf[c] != byte 0) + ibc = c; + stats(); + } + if(ibc == 0 && --files<=0) { + flsh(); + term(); + } + if(ibc != ibs) { + nipr++; + if(cflag&SYNC) + ibc = ibs; + } else + nifr++; + ip = 0; + c := (ibc>>1) & ~1; + if(cflag&SWAB && c) do { + a := ibuf[ip++]; + ibuf[ip-1] = ibuf[ip]; + ibuf[ip++] = a; + } while(--c); + if(fflag) { + obc = ibc; + flsh(); + ibc = 0; + } + continue; + } + c := 0; + c |= int ibuf[ip++]; + c &= 8r377; + conv(c); + } while(1); +} + +conv(c: int) +{ + case ctype { + NULL => null(c); + CNULL => cnull(c); + EBCDIC => ebcdic(c); + IBM => ibm(c); + ASCII => ascii(c); + BLOCK => block(c); + UNBLOCK => unblock(c); + } +} + +flsh() +{ + if(obc) { + if(obc == obs) + nofr++; + else + nopr++; + c := sys->write(obf, obuf, obc); + if(c != obc) { + perror("write"); + term(); + } + obc = 0; + } +} + +match(s: string): int +{ + if(len s > len arg) + return 0; + if(arg[:len s] == s) { + arg = arg[len s:]; + return 1; + } + return 0; +} + + +number(bignum: int): int +{ + n := 0; + i := 0; + while(i < len arg && arg[i] >= '0' && arg[i] <= '9') + n = n*10 + arg[i++] - '0'; + for(;i<len arg; i++) case(arg[i]) { + 'k' => + n *= 1024; + 'b' => + n *= 512; + 'x' => + arg = arg[i:]; + n *= number(BIG); + } + if(n>=bignum || n<0) { + sys->fprint(stderr, "dd: argument out of range\n"); + exits("range"); + } + return n; +} + +cnull(cc: int) +{ + c := cc; + if((cflag&UCASE) && c>='a' && c<='z') + c += 'A'-'a'; + if((cflag&LCASE) && c>='A' && c<='Z') + c += 'a'-'A'; + null(c); +} + +null(c: int) +{ + obuf[op++] = byte c; + if(++obc >= obs) { + flsh(); + op = 0; + } +} + +ascii(cc: int) +{ + c := etoa[cc]; + if(cbs == 0) { + cnull(int c); + return; + } + if(c == byte ' ') + nspace++; + else { + while(nspace > 0) { + null(' '); + nspace--; + } + cnull(int c); + } + + if(++cbc >= cbs) { + null('\n'); + cbc = 0; + nspace = 0; + } +} + +unblock(cc: int) +{ + c := cc & 8r377; + if(cbs == 0) { + cnull(c); + return; + } + if(c == ' ') + nspace++; + else { + while(nspace > 0) { + null(' '); + nspace--; + } + cnull(c); + } + + if(++cbc >= cbs) { + null('\n'); + cbc = 0; + nspace = 0; + } +} + +ebcdic(cc: int) +{ + + c := cc; + if(cflag&UCASE && c>='a' && c<='z') + c += 'A'-'a'; + if(cflag&LCASE && c>='A' && c<='Z') + c += 'a'-'A'; + c = int atoe[c]; + if(cbs == 0) { + null(c); + return; + } + if(cc == '\n') { + while(cbc < cbs) { + null(int atoe[' ']); + cbc++; + } + cbc = 0; + return; + } + if(cbc == cbs) + ntrunc++; + cbc++; + if(cbc <= cbs) + null(c); +} + +ibm(cc: int) +{ + c := cc; + if(cflag&UCASE && c>='a' && c<='z') + c += 'A'-'a'; + if(cflag&LCASE && c>='A' && c<='Z') + c += 'a'-'A'; + c = int atoibm[c] & 8r377; + if(cbs == 0) { + null(c); + return; + } + if(cc == '\n') { + while(cbc < cbs) { + null(int atoibm[' ']); + cbc++; + } + cbc = 0; + return; + } + if(cbc == cbs) + ntrunc++; + cbc++; + if(cbc <= cbs) + null(c); +} + +block(cc: int) +{ + c := cc; + if(cflag&UCASE && c>='a' && c<='z') + c += 'A'-'a'; + if(cflag&LCASE && c>='A' && c<='Z') + c += 'a'-'A'; + c &= 8r377; + if(cbs == 0) { + null(c); + return; + } + if(cc == '\n') { + while(cbc < cbs) { + null(' '); + cbc++; + } + cbc = 0; + return; + } + if(cbc == cbs) + ntrunc++; + cbc++; + if(cbc <= cbs) + null(c); +} + +term() +{ + stats(); + exits(nil); +} + +stats() +{ + sys->fprint(stderr, "%ud+%ud records in\n", nifr, nipr); + sys->fprint(stderr, "%ud+%ud records out\n", nofr, nopr); + if(ntrunc) + sys->fprint(stderr, "%ud truncated records\n", ntrunc); +} + +etoa := array[] of +{ + byte 8r000,byte 8r001,byte 8r002,byte 8r003,byte 8r234,byte 8r011,byte 8r206,byte 8r177, + byte 8r227,byte 8r215,byte 8r216,byte 8r013,byte 8r014,byte 8r015,byte 8r016,byte 8r017, + byte 8r020,byte 8r021,byte 8r022,byte 8r023,byte 8r235,byte 8r205,byte 8r010,byte 8r207, + byte 8r030,byte 8r031,byte 8r222,byte 8r217,byte 8r034,byte 8r035,byte 8r036,byte 8r037, + byte 8r200,byte 8r201,byte 8r202,byte 8r203,byte 8r204,byte 8r012,byte 8r027,byte 8r033, + byte 8r210,byte 8r211,byte 8r212,byte 8r213,byte 8r214,byte 8r005,byte 8r006,byte 8r007, + byte 8r220,byte 8r221,byte 8r026,byte 8r223,byte 8r224,byte 8r225,byte 8r226,byte 8r004, + byte 8r230,byte 8r231,byte 8r232,byte 8r233,byte 8r024,byte 8r025,byte 8r236,byte 8r032, + byte 8r040,byte 8r240,byte 8r241,byte 8r242,byte 8r243,byte 8r244,byte 8r245,byte 8r246, + byte 8r247,byte 8r250,byte 8r133,byte 8r056,byte 8r074,byte 8r050,byte 8r053,byte 8r041, + byte 8r046,byte 8r251,byte 8r252,byte 8r253,byte 8r254,byte 8r255,byte 8r256,byte 8r257, + byte 8r260,byte 8r261,byte 8r135,byte 8r044,byte 8r052,byte 8r051,byte 8r073,byte 8r136, + byte 8r055,byte 8r057,byte 8r262,byte 8r263,byte 8r264,byte 8r265,byte 8r266,byte 8r267, + byte 8r270,byte 8r271,byte 8r174,byte 8r054,byte 8r045,byte 8r137,byte 8r076,byte 8r077, + byte 8r272,byte 8r273,byte 8r274,byte 8r275,byte 8r276,byte 8r277,byte 8r300,byte 8r301, + byte 8r302,byte 8r140,byte 8r072,byte 8r043,byte 8r100,byte 8r047,byte 8r075,byte 8r042, + byte 8r303,byte 8r141,byte 8r142,byte 8r143,byte 8r144,byte 8r145,byte 8r146,byte 8r147, + byte 8r150,byte 8r151,byte 8r304,byte 8r305,byte 8r306,byte 8r307,byte 8r310,byte 8r311, + byte 8r312,byte 8r152,byte 8r153,byte 8r154,byte 8r155,byte 8r156,byte 8r157,byte 8r160, + byte 8r161,byte 8r162,byte 8r313,byte 8r314,byte 8r315,byte 8r316,byte 8r317,byte 8r320, + byte 8r321,byte 8r176,byte 8r163,byte 8r164,byte 8r165,byte 8r166,byte 8r167,byte 8r170, + byte 8r171,byte 8r172,byte 8r322,byte 8r323,byte 8r324,byte 8r325,byte 8r326,byte 8r327, + byte 8r330,byte 8r331,byte 8r332,byte 8r333,byte 8r334,byte 8r335,byte 8r336,byte 8r337, + byte 8r340,byte 8r341,byte 8r342,byte 8r343,byte 8r344,byte 8r345,byte 8r346,byte 8r347, + byte 8r173,byte 8r101,byte 8r102,byte 8r103,byte 8r104,byte 8r105,byte 8r106,byte 8r107, + byte 8r110,byte 8r111,byte 8r350,byte 8r351,byte 8r352,byte 8r353,byte 8r354,byte 8r355, + byte 8r175,byte 8r112,byte 8r113,byte 8r114,byte 8r115,byte 8r116,byte 8r117,byte 8r120, + byte 8r121,byte 8r122,byte 8r356,byte 8r357,byte 8r360,byte 8r361,byte 8r362,byte 8r363, + byte 8r134,byte 8r237,byte 8r123,byte 8r124,byte 8r125,byte 8r126,byte 8r127,byte 8r130, + byte 8r131,byte 8r132,byte 8r364,byte 8r365,byte 8r366,byte 8r367,byte 8r370,byte 8r371, + byte 8r060,byte 8r061,byte 8r062,byte 8r063,byte 8r064,byte 8r065,byte 8r066,byte 8r067, + byte 8r070,byte 8r071,byte 8r372,byte 8r373,byte 8r374,byte 8r375,byte 8r376,byte 8r377, +}; +atoe := array[] of +{ + byte 8r000,byte 8r001,byte 8r002,byte 8r003,byte 8r067,byte 8r055,byte 8r056,byte 8r057, + byte 8r026,byte 8r005,byte 8r045,byte 8r013,byte 8r014,byte 8r015,byte 8r016,byte 8r017, + byte 8r020,byte 8r021,byte 8r022,byte 8r023,byte 8r074,byte 8r075,byte 8r062,byte 8r046, + byte 8r030,byte 8r031,byte 8r077,byte 8r047,byte 8r034,byte 8r035,byte 8r036,byte 8r037, + byte 8r100,byte 8r117,byte 8r177,byte 8r173,byte 8r133,byte 8r154,byte 8r120,byte 8r175, + byte 8r115,byte 8r135,byte 8r134,byte 8r116,byte 8r153,byte 8r140,byte 8r113,byte 8r141, + byte 8r360,byte 8r361,byte 8r362,byte 8r363,byte 8r364,byte 8r365,byte 8r366,byte 8r367, + byte 8r370,byte 8r371,byte 8r172,byte 8r136,byte 8r114,byte 8r176,byte 8r156,byte 8r157, + byte 8r174,byte 8r301,byte 8r302,byte 8r303,byte 8r304,byte 8r305,byte 8r306,byte 8r307, + byte 8r310,byte 8r311,byte 8r321,byte 8r322,byte 8r323,byte 8r324,byte 8r325,byte 8r326, + byte 8r327,byte 8r330,byte 8r331,byte 8r342,byte 8r343,byte 8r344,byte 8r345,byte 8r346, + byte 8r347,byte 8r350,byte 8r351,byte 8r112,byte 8r340,byte 8r132,byte 8r137,byte 8r155, + byte 8r171,byte 8r201,byte 8r202,byte 8r203,byte 8r204,byte 8r205,byte 8r206,byte 8r207, + byte 8r210,byte 8r211,byte 8r221,byte 8r222,byte 8r223,byte 8r224,byte 8r225,byte 8r226, + byte 8r227,byte 8r230,byte 8r231,byte 8r242,byte 8r243,byte 8r244,byte 8r245,byte 8r246, + byte 8r247,byte 8r250,byte 8r251,byte 8r300,byte 8r152,byte 8r320,byte 8r241,byte 8r007, + byte 8r040,byte 8r041,byte 8r042,byte 8r043,byte 8r044,byte 8r025,byte 8r006,byte 8r027, + byte 8r050,byte 8r051,byte 8r052,byte 8r053,byte 8r054,byte 8r011,byte 8r012,byte 8r033, + byte 8r060,byte 8r061,byte 8r032,byte 8r063,byte 8r064,byte 8r065,byte 8r066,byte 8r010, + byte 8r070,byte 8r071,byte 8r072,byte 8r073,byte 8r004,byte 8r024,byte 8r076,byte 8r341, + byte 8r101,byte 8r102,byte 8r103,byte 8r104,byte 8r105,byte 8r106,byte 8r107,byte 8r110, + byte 8r111,byte 8r121,byte 8r122,byte 8r123,byte 8r124,byte 8r125,byte 8r126,byte 8r127, + byte 8r130,byte 8r131,byte 8r142,byte 8r143,byte 8r144,byte 8r145,byte 8r146,byte 8r147, + byte 8r150,byte 8r151,byte 8r160,byte 8r161,byte 8r162,byte 8r163,byte 8r164,byte 8r165, + byte 8r166,byte 8r167,byte 8r170,byte 8r200,byte 8r212,byte 8r213,byte 8r214,byte 8r215, + byte 8r216,byte 8r217,byte 8r220,byte 8r232,byte 8r233,byte 8r234,byte 8r235,byte 8r236, + byte 8r237,byte 8r240,byte 8r252,byte 8r253,byte 8r254,byte 8r255,byte 8r256,byte 8r257, + byte 8r260,byte 8r261,byte 8r262,byte 8r263,byte 8r264,byte 8r265,byte 8r266,byte 8r267, + byte 8r270,byte 8r271,byte 8r272,byte 8r273,byte 8r274,byte 8r275,byte 8r276,byte 8r277, + byte 8r312,byte 8r313,byte 8r314,byte 8r315,byte 8r316,byte 8r317,byte 8r332,byte 8r333, + byte 8r334,byte 8r335,byte 8r336,byte 8r337,byte 8r352,byte 8r353,byte 8r354,byte 8r355, + byte 8r356,byte 8r357,byte 8r372,byte 8r373,byte 8r374,byte 8r375,byte 8r376,byte 8r377, +}; +atoibm := array[] of +{ + byte 8r000,byte 8r001,byte 8r002,byte 8r003,byte 8r067,byte 8r055,byte 8r056,byte 8r057, + byte 8r026,byte 8r005,byte 8r045,byte 8r013,byte 8r014,byte 8r015,byte 8r016,byte 8r017, + byte 8r020,byte 8r021,byte 8r022,byte 8r023,byte 8r074,byte 8r075,byte 8r062,byte 8r046, + byte 8r030,byte 8r031,byte 8r077,byte 8r047,byte 8r034,byte 8r035,byte 8r036,byte 8r037, + byte 8r100,byte 8r132,byte 8r177,byte 8r173,byte 8r133,byte 8r154,byte 8r120,byte 8r175, + byte 8r115,byte 8r135,byte 8r134,byte 8r116,byte 8r153,byte 8r140,byte 8r113,byte 8r141, + byte 8r360,byte 8r361,byte 8r362,byte 8r363,byte 8r364,byte 8r365,byte 8r366,byte 8r367, + byte 8r370,byte 8r371,byte 8r172,byte 8r136,byte 8r114,byte 8r176,byte 8r156,byte 8r157, + byte 8r174,byte 8r301,byte 8r302,byte 8r303,byte 8r304,byte 8r305,byte 8r306,byte 8r307, + byte 8r310,byte 8r311,byte 8r321,byte 8r322,byte 8r323,byte 8r324,byte 8r325,byte 8r326, + byte 8r327,byte 8r330,byte 8r331,byte 8r342,byte 8r343,byte 8r344,byte 8r345,byte 8r346, + byte 8r347,byte 8r350,byte 8r351,byte 8r255,byte 8r340,byte 8r275,byte 8r137,byte 8r155, + byte 8r171,byte 8r201,byte 8r202,byte 8r203,byte 8r204,byte 8r205,byte 8r206,byte 8r207, + byte 8r210,byte 8r211,byte 8r221,byte 8r222,byte 8r223,byte 8r224,byte 8r225,byte 8r226, + byte 8r227,byte 8r230,byte 8r231,byte 8r242,byte 8r243,byte 8r244,byte 8r245,byte 8r246, + byte 8r247,byte 8r250,byte 8r251,byte 8r300,byte 8r117,byte 8r320,byte 8r241,byte 8r007, + byte 8r040,byte 8r041,byte 8r042,byte 8r043,byte 8r044,byte 8r025,byte 8r006,byte 8r027, + byte 8r050,byte 8r051,byte 8r052,byte 8r053,byte 8r054,byte 8r011,byte 8r012,byte 8r033, + byte 8r060,byte 8r061,byte 8r032,byte 8r063,byte 8r064,byte 8r065,byte 8r066,byte 8r010, + byte 8r070,byte 8r071,byte 8r072,byte 8r073,byte 8r004,byte 8r024,byte 8r076,byte 8r341, + byte 8r101,byte 8r102,byte 8r103,byte 8r104,byte 8r105,byte 8r106,byte 8r107,byte 8r110, + byte 8r111,byte 8r121,byte 8r122,byte 8r123,byte 8r124,byte 8r125,byte 8r126,byte 8r127, + byte 8r130,byte 8r131,byte 8r142,byte 8r143,byte 8r144,byte 8r145,byte 8r146,byte 8r147, + byte 8r150,byte 8r151,byte 8r160,byte 8r161,byte 8r162,byte 8r163,byte 8r164,byte 8r165, + byte 8r166,byte 8r167,byte 8r170,byte 8r200,byte 8r212,byte 8r213,byte 8r214,byte 8r215, + byte 8r216,byte 8r217,byte 8r220,byte 8r232,byte 8r233,byte 8r234,byte 8r235,byte 8r236, + byte 8r237,byte 8r240,byte 8r252,byte 8r253,byte 8r254,byte 8r255,byte 8r256,byte 8r257, + byte 8r260,byte 8r261,byte 8r262,byte 8r263,byte 8r264,byte 8r265,byte 8r266,byte 8r267, + byte 8r270,byte 8r271,byte 8r272,byte 8r273,byte 8r274,byte 8r275,byte 8r276,byte 8r277, + byte 8r312,byte 8r313,byte 8r314,byte 8r315,byte 8r316,byte 8r317,byte 8r332,byte 8r333, + byte 8r334,byte 8r335,byte 8r336,byte 8r337,byte 8r352,byte 8r353,byte 8r354,byte 8r355, + byte 8r356,byte 8r357,byte 8r372,byte 8r373,byte 8r374,byte 8r375,byte 8r376,byte 8r377, +}; diff --git a/appl/cmd/dial.b b/appl/cmd/dial.b new file mode 100644 index 00000000..c562a570 --- /dev/null +++ b/appl/cmd/dial.b @@ -0,0 +1,148 @@ +implement Dial; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; +include "keyring.m"; + keyring: Keyring; +include "security.m"; + auth: Auth; +include "sh.m"; + sh: Sh; + Context: import sh; + +Dial: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +badmodule(p: string) +{ + sys->fprint(stderr(), "dial: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +DEFAULTALG := "none"; + +verbose := 0; + +init(drawctxt: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + auth = load Auth Auth->PATH; + if (auth == nil) + badmodule(Auth->PATH); + arg := load Arg Arg->PATH; + if (arg == nil) + badmodule(Arg->PATH); + sh = load Sh Sh->PATH; + if (sh == nil) + badmodule(Sh->PATH); + + auth->init(); + alg: string; + keyfile: string; + doauth := 1; + arg->init(argv); + arg->setusage("dial [-A] [-k keyfile] [-a alg] addr command [arg...]"); + while ((opt := arg->opt()) != 0) { + case opt { + 'A' => + doauth = 0; + 'a' => + alg = arg->earg(); + 'f' or + 'k' => + keyfile = arg->earg(); + if (! (keyfile[0] == '/' || (len keyfile > 2 && keyfile[0:2] == "./"))) + keyfile = "/usr/" + user() + "/keyring/" + keyfile; + 'v' => + verbose = 1; + * => + arg->usage(); + } + } + argv = arg->argv(); + if (len argv < 2) + arg->usage(); + arg = nil; + (addr, shcmd) := (hd argv, tl argv); + + if (doauth && alg == nil) + alg = DEFAULTALG; + + if (alg != nil && keyfile == nil) { + kd := "/usr/" + user() + "/keyring/"; + if (exists(kd + addr)) + keyfile = kd + addr; + else + keyfile = kd + "default"; + } + cert: ref Keyring->Authinfo; + if (alg != nil) { + cert = keyring->readauthinfo(keyfile); + if (cert == nil) { + sys->fprint(stderr(), "dial: cannot read %s: %r\n", keyfile); + raise "fail:bad keyfile"; + } + } + + (ok, c) := sys->dial(addr, nil); + if (ok == -1) { + sys->fprint(stderr(), "dial: cannot dial %s:: %r\n", addr); + raise "fail:errors"; + } + user: string; + if (alg != nil) { + err: string; + (c.dfd, err) = auth->client(alg, cert, c.dfd); + if (c.dfd == nil) { + sys->fprint(stderr(), "dial: authentication failed: %s\n", err); + raise "fail:errors"; + } + user = err; + } + sys->dup(c.dfd.fd, 0); + sys->dup(c.dfd.fd, 1); + c.dfd = c.cfd = nil; + ctxt := Context.new(drawctxt); + if (user != nil) + ctxt.set("user", sh->stringlist2list(user :: nil)); + else + ctxt.set("user", nil); + ctxt.set("net", ref Sh->Listnode(nil, c.dir) :: nil); + ctxt.run(sh->stringlist2list(shcmd), 1); +} + +exists(f: string): int +{ + (ok, nil) := sys->stat(f); + return ok != -1; +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + +user(): string +{ + u := readfile("/dev/user"); + if (u == nil) + return "nobody"; + return u; +} + +readfile(f: string): string +{ + fd := sys->open(f, sys->OREAD); + if(fd == nil) + return nil; + + buf := array[128] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return nil; + + return string buf[0:n]; +} diff --git a/appl/cmd/diff.b b/appl/cmd/diff.b new file mode 100644 index 00000000..4ef3ab32 --- /dev/null +++ b/appl/cmd/diff.b @@ -0,0 +1,858 @@ +implement Diff; + +# diff - differential file comparison +# +# Uses an algorithm due to Harold Stone, which finds +# a pair of longest identical subsequences in the two +# files. +# +# The major goal is to generate the match vector J. +# J[i] is the index of the line in file1 corresponding +# to line i file0. J[i] = 0 if there is no +# such line in file1. +# +# Lines are hashed so as to work in core. All potential +# matches are located by sorting the lines of each file +# on the hash (called value). In particular, this +# collects the equivalence classes in file1 together. +# Subroutine equiv replaces the value of each line in +# file0 by the index of the first element of its +# matching equivalence in (the reordered) file1. +# To save space equiv squeezes file1 into a single +# array member in which the equivalence classes +# are simply concatenated, except that their first +# members are flagged by changing sign. +# +# Next the indices that point into member are unsorted into +# array class according to the original order of file0. +# +# The cleverness lies in routine stone. This marches +# through the lines of file0, developing a vector klist +# of "k-candidates". At step i a k-candidate is a matched +# pair of lines x,y (x in file0 y in file1) such that +# there is a common subsequence of lenght k +# between the first i lines of file0 and the first y +# lines of file1, but there is no such subsequence for +# any smaller y. x is the earliest possible mate to y +# that occurs in such a subsequence. +# +# Whenever any of the members of the equivalence class of +# lines in file1 matable to a line in file0 has serial number +# less than the y of some k-candidate, that k-candidate +# with the smallest such y is replaced. The new +# k-candidate is chained (via pred) to the current +# k-1 candidate so that the actual subsequence can +# be recovered. When a member has serial number greater +# that the y of all k-candidates, the klist is extended. +# At the end, the longest subsequence is pulled out +# and placed in the array J by unravel. +# +# With J in hand, the matches there recorded are +# check'ed against reality to assure that no spurious +# matches have crept in due to hashing. If they have, +# they are broken, and "jackpot " is recorded--a harmless +# matter except that a true match for a spuriously +# mated line may now be unnecessarily reported as a change. +# +# Much of the complexity of the program comes simply +# from trying to minimize core utilization and +# maximize the range of doable problems by dynamically +# allocating what is needed and reusing what is not. +# The core requirements for problems larger than somewhat +# are (in words) 2*length(file0) + length(file1) + +# 3*(number of k-candidates installed), typically about +# 6n words for files of length n. +# +# + +include "sys.m"; + sys: Sys; + +include "bufio.m"; + bufio : Bufio; +Iobuf : import bufio; + +include "draw.m"; + draw: Draw; +include "readdir.m"; + readdir : Readdir; +include "string.m"; + str : String; +include "arg.m"; + +Diff : module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +mode : int; # '\0', 'e', 'f', 'h' +bflag : int; # ignore multiple and trailing blanks +rflag : int; # recurse down directory trees +mflag : int; # pseudo flag: doing multiple files, one dir + +REG, +BIN: con iota; + +HALFINT : con 16; +Usage : con "usage: diff [ -efbwr ] file1 ... file2"; + +cand : adt { + x : int; + y : int; + pred : int; +}; + +line : adt { + serial : int; + value : int; +}; + +out : ref Iobuf; +file := array[2] of array of line; +sfile := array[2] of array of line; # shortened by pruning common prefix and suffix +slen := array[2] of int; +ilen := array[2] of int; +pref, suff, clen : int; # length of prefix and suffix +firstchange : int; +clist : array of cand; # merely a free storage pot for candidates +J : array of int; # will be overlaid on class +ixold, ixnew : array of int; +input := array[2] of ref Iobuf ; +file1, file2 : string; +tmpname := array[] of {"/tmp/diff1", "/tmp/diff2"}; +whichtmp : int; +anychange := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + draw = load Draw Draw->PATH; + bufio = load Bufio Bufio->PATH; + readdir = load Readdir Readdir->PATH; + str = load String String->PATH; + if (bufio==nil) + fatal(sys->sprint("cannot load %s: %r", Bufio->PATH)); + if (readdir==nil) + fatal(sys->sprint("cannot load %s: %r", Readdir->PATH)); + if (str==nil) + fatal(sys->sprint("cannot load %s: %r", String->PATH)); + arg := load Arg Arg->PATH; + if (arg==nil) + fatal(sys->sprint("cannot load %s: %r", Arg->PATH)); + fsb, tsb : Sys->Dir; + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 'e' or 'f' => + mode = o; + 'w' => + bflag = 2; + 'b' => + bflag = 1; + 'r' => + rflag = 1; + 'm' => + mflag = 1; + * => + fatal(Usage); + } + tmp := arg->argv(); + arg = nil; + j := len tmp; + if (j < 2) + fatal(Usage); + arr := array[j] of string; + for(i:=0;i<j;i++){ + arr[i]= hd tmp; + tmp = tl tmp; + } + + (i,tsb)=sys->stat(arr[j-1]); + if (i == -1) + fatal(sys->sprint("can't stat %s: %r", arr[j-1])); + if (j > 2) { + if (!(tsb.qid.qtype&Sys->QTDIR)) + fatal(Usage); + mflag = 1; + } + else { + (i,fsb)=sys->stat(arr[0]); + if (i == -1) + fatal(sys->sprint("can't stat %s: %r", arr[0])); + if ((fsb.qid.qtype&Sys->QTDIR) && (tsb.qid.qtype&Sys->QTDIR)) + mflag = 1; + } + out=bufio->fopen(sys->fildes(1),Bufio->OWRITE); + for (i = 0; i < j-1; i++) { + diff(arr[i], arr[j-1], 0); + rmtmpfiles(); + } + rmtmpfiles(); + out.flush(); + if (anychange) + raise "fail:some"; +} + +############################# diffreg from here .... + +# shellsort CACM #201 + +sort(a : array of line, n : int) +{ + w : line; + j1:=0; + m := 0; + for (i := 1; i <= n; i *= 2) + m = 2*i - 1; + for (m /= 2; m != 0; m /= 2) { + for (j := 1; j <= n-m ; j++) { + ai:=j; + aim:=j+m; + do { + if (a[aim].value > a[ai].value || + a[aim].value == a[ai].value && + a[aim].serial > a[ai].serial) + break; + w = a[ai]; + a[ai] = a[aim]; + a[aim] = w; + aim=ai; + ai-=m; + } while (ai > 0 && aim >= ai); + } + } +} + +unsort(f : array of line, l : int) : array of int +{ + i : int; + a := array[l+1] of int; + for(i=1;i<=l;i++) + a[f[i].serial] = f[i].value; + return a; +} + +prune() +{ + for(pref=0;pref< ilen[0]&&pref< ilen[1]&& + file[0][pref+1].value==file[1][pref+1].value; + pref++ ) ; + for(suff=0;suff< ilen[0]-pref&&suff< ilen[1]-pref&& + file[0][ilen[0]-suff].value==file[1][ilen[1]-suff].value; + suff++) ; + for(j:=0;j<2;j++) { + sfile[j] = file[j][pref:]; + slen[j]= ilen[j]-pref-suff; + for(i:=0;i<=slen[j];i++) + sfile[j][i].serial = i; + } +} + +equiv(a: array of line, n:int , b: array of line, m: int, c : array of int) +{ + i := 1; + j := 1; + while(i<=n && j<=m) { + if(a[i].value < b[j].value) + a[i++].value = 0; + else if(a[i].value == b[j].value) + a[i++].value = j; + else + j++; + } + while(i <= n) + a[i++].value = 0; + b[m+1].value = 0; # huh ? + j = 1; + while(j <= m) { + c[j] = -b[j].serial; + while(b[j+1].value == b[j].value) { + j++; + c[j] = b[j].serial; + } + j++; + } + c[j] = -1; +} + +newcand(x, y, pred : int) : int +{ + if (clen==len clist){ + q := array[clen*2] of cand; + q[0:]=clist; + clist= array[clen*2] of cand; + clist[0:]=q; + q=nil; + } + clist[clen].x=x; + clist[clen].y=y; + clist[clen].pred=pred; + return clen++; +} + +search(c : array of int, k,y : int) : int +{ + if(clist[c[k]].y < y) # quick look for typical case + return k+1; + i := 0; + j := k+1; + while((l:=(i+j)/2) > i) { + t := clist[c[l]].y; + if(t > y) + j = l; + else if(t < y) + i = l; + else + return l; + } + return l+1; +} + +stone(a : array of int ,n : int, b: array of int , c : array of int) : int +{ + oldc, oldl, tc, l ,y : int; + k := 0; + c[0] = newcand(0,0,0); + for(i:=1; i<=n; i++) { + j := a[i]; + if(j==0) + continue; + y = -b[j]; + oldl = 0; + oldc = c[0]; + do { + if(y <= clist[oldc].y) + continue; + l = search(c, k, y); + if(l!=oldl+1) + oldc = c[l-1]; + if(l<=k) { + if(clist[c[l]].y <= y) + continue; + tc = c[l]; + c[l] = newcand(i,y,oldc); + oldc = tc; + oldl = l; + } else { + c[l] = newcand(i,y,oldc); + k++; + break; + } + } while((y=b[j+=1]) > 0); + } + return k; +} + +unravel(p : int) +{ + for(i:=0; i<=ilen[0]; i++) { + if (i <= pref) + J[i] = i; + else if (i > ilen[0]-suff) + J[i] = i+ ilen[1]-ilen[0]; + else + J[i] = 0; + } + for(q:=clist[p];q.y!=0;q=clist[q.pred]) + J[q.x+pref] = q.y+pref; +} + +output() +{ + i1: int; + m := ilen[0]; + J[0] = 0; + J[m+1] = ilen[1]+1; + if (mode != 'e') { + for (i0 := 1; i0 <= m; i0 = i1+1) { + while (i0 <= m && J[i0] == J[i0-1]+1) + i0++; + j0 := J[i0-1]+1; + i1 = i0-1; + while (i1 < m && J[i1+1] == 0) + i1++; + j1 := J[i1+1]-1; + J[i1] = j1; + change(i0, i1, j0, j1); + } + } + else { + for (i0 := m; i0 >= 1; i0 = i1-1) { + while (i0 >= 1 && J[i0] == J[i0+1]-1 && J[i0]) + i0--; + j0 := J[i0+1]-1; + i1 = i0+1; + while (i1 > 1 && J[i1-1] == 0) + i1--; + j1 := J[i1-1]+1; + J[i1] = j1; + change(i1 , i0, j1, j0); + } + } + if (m == 0) + change(1, 0, 1, ilen[1]); + out.flush(); +} + +diffreg(f,t : string) +{ + k : int; + + (b0, b0type) := prepare(0, f); + if (b0==nil) + return; + (b1, b1type) := prepare(1, t); + if (b1==nil) { + b0=nil; + return; + } + if (b0type == BIN || b1type == BIN) { + if (cmp(b0, b1)) { + out.puts(sys->sprint("Binary files %s %s differ\n", f, t)); + anychange = 1; + } + b0 = nil; + b1 = nil; + return; + } + clen=0; + prune(); + file[0]=nil; + file[1]=nil; + sort(sfile[0],slen[0]); + sort(sfile[1],slen[1]); + member := array[slen[1]+2] of int; + equiv(sfile[0], slen[0],sfile[1],slen[1], member); + class:=unsort(sfile[0],slen[0]); + sfile[0]=nil; + sfile[1]=nil; + klist := array[slen[0]+2] of int; + clist = array[1] of cand; + k = stone(class, slen[0], member, klist); + J = array[ilen[0]+2] of int; + unravel(klist[k]); + clist=nil; + klist=nil; + class=nil; + member=nil; + ixold = array[ilen[0]+2] of int; + ixnew = array[ilen[1]+2] of int; + + b0.seek(big 0, 0); + b1.seek(big 0, 0); + check(b0, b1); + output(); + ixold=nil; + ixnew=nil; + b0=nil; + b1=nil; +} + +######################## diffio starts here... + + +# hashing has the effect of +# arranging line in 7-bit bytes and then +# summing 1-s complement in 16-bit hunks + +readhash(bp : ref Iobuf) : int +{ + sum := 1; + shift := 0; + buf := bp.gets('\n'); + if (buf == nil) + return 0; + buf = buf[0:len buf -1]; + p := 0; + case bflag { + # various types of white space handling + 0 => + while (p< len buf) { + sum += (buf[p] << (shift &= (HALFINT-1))); + p++; + shift += 7; + } + 1 => + + # coalesce multiple white-space + + for (space := 0; p< len buf; p++) { + if (buf[p]==' ' || buf[p]=='\t') { + space++; + continue; + } + if (space) { + shift += 7; + space = 0; + } + sum += (buf[p] << (shift &= (HALFINT-1))); + p++; + shift += 7; + } + * => + + # strip all white-space + + while (p< len buf) { + if (buf[p]==' ' || buf[p]=='\t') { + p++; + continue; + } + sum += (buf[p] << (shift &= (HALFINT-1))); + p++; + shift += 7; + } + } + return sum; +} + +prepare(i : int, arg : string) : (ref Iobuf, int) +{ + h : int; + bp := bufio->open(arg,Bufio->OREAD); + if (bp==nil) { + error(sys->sprint("cannot open %s: %r", arg)); + return (nil, 0); + } + buf := array[1024] of byte; + n :=bp.read(buf, len buf); + str1 := string buf[0:n]; + for (j:=0;j<len str1 -2;j++) + if (str1[j] == Sys->UTFerror) + return (bp, BIN); + bp.seek(big 0, Sys->SEEKSTART); + p := array[4] of line; + for (j = 0; h = readhash(bp); p[j].value = h){ + j++; + if (j+3>=len p){ + newp:=array[len p*2] of line; + newp[0:]=p[0:]; + p=array[len p*2] of line; + p=newp; + newp=nil; + } + } + ilen[i]=j; + file[i] = p; + input[i] = bp; + if (i == 0) { + file1 = arg; + firstchange = 0; + } + else + file2 = arg; + return (bp, REG); +} + +squishspace(buf : string) : string +{ + q:=0; + p:=0; + for (space := 0; q<len buf; q++) { + if (buf[q]==' ' || buf[q]=='\t') { + space++; + continue; + } + if (space && bflag == 1) { + buf[p] = ' '; + p++; + space = 0; + } + buf[p]=buf[q]; + p++; + } + buf=buf[0:p]; + return buf; +} + + +# need to fix up for unexpected EOF's + +ftell(b: ref Iobuf): int +{ + return int b.offset(); +} + +check(bf, bt : ref Iobuf) +{ + fbuf, tbuf : string; + f:=1; + t:=1; + ixold[0] = ixnew[0] = 0; + for (; f < ilen[0]; f++) { + fbuf = bf.gets('\n'); + if (fbuf!=nil) + fbuf=fbuf[0:len fbuf -1]; + ixold[f] = ftell(bf); + if (J[f] == 0) + continue; + tbuflen: int; + do { + tbuf = bt.gets('\n'); + if (tbuf!=nil) + tbuf=tbuf[0:len tbuf -1]; + tbuflen = len array of byte tbuf; + ixnew[t] = ftell(bt); + } while (t++ < J[f]); + if (bflag) { + fbuf = squishspace(fbuf); + tbuf = squishspace(tbuf); + } + if (len fbuf != len tbuf || fbuf!=tbuf) + J[f] = 0; + } + while (t < ilen[1]) { + tbuf = bt.gets('\n'); + if (tbuf!=nil) + tbuf=tbuf[0:len tbuf -1]; + ixnew[t] = ftell(bt); + t++; + } +} + +range(a, b : int, separator : string) +{ + if (a>b) + out.puts(sys->sprint("%d", b)); + else + out.puts(sys->sprint("%d", a)); + if (a < b) + out.puts(sys->sprint("%s%d", separator, b)); +} + +fetch(f : array of int, a,b : int , bp : ref Iobuf, s : string) +{ + buf : string; + bp.seek(big f[a-1], 0); + while (a++ <= b) { + buf=bp.gets('\n'); + out.puts(s); + out.puts(buf); + } +} + +change(a, b, c, d : int) +{ + if (a > b && c > d) + return; + anychange = 1; + if (mflag && firstchange == 0) { + out.puts(sys->sprint( "diff %s %s\n", file1, file2)); + firstchange = 1; + } + if (mode != 'f') { + range(a, b, ","); + if (a>b) + out.putc('a'); + else if (c>d) + out.putc('d'); + else + out.putc('c'); + if (mode != 'e') + range(c, d, ","); + } + else { + if (a>b) + out.putc('a'); + else if (c>d) + out.putc('d'); + else + out.putc('c'); + range(a, b, " "); + } + out.putc('\n'); + if (mode == 0) { + fetch(ixold, a, b, input[0], "< "); + if (a <= b && c <= d) + out.puts("---\n"); + } + if (mode==0) + fetch(ixnew, c, d, input[1], "> "); + else + fetch(ixnew, c, d, input[1], ""); + + if (mode != 0 && c <= d) + out.puts(".\n"); +} + + +######################### diffdir starts here ...... + +scandir(name : string) : array of string +{ + (db,nitems):= readdir->init(name,Readdir->NAME); + cp := array[nitems] of string; + for(i:=0;i<nitems;i++) + cp[i]=db[i].name; + return cp; +} + + +diffdir(f, t : string, level : int) +{ + df, dt : array of string; + fb, tb : string; + i:=0; + j:=0; + df = scandir(f); + dt = scandir(t); + while ((i<len df) || (j<len dt)) { + if ((j==len dt) || (i<len df && df[i] < dt[j])) { + if (mode == 0) + out.puts(sys->sprint("Only in %s: %s\n", f, df[i])); + i++; + continue; + } + if ((i==len df) || (j<len dt && df[i] > dt[j])) { + if (mode == 0) + out.puts(sys->sprint("Only in %s: %s\n", t, dt[j])); + j++; + continue; + } + fb=sys->sprint("%s/%s", f, df[i]); + tb=sys->sprint("%s/%s", t, dt[j]); + diff(fb, tb, level+1); + i++; j++; + } +} + +cmp(b0, b1: ref Iobuf): int +{ + b0.seek(big 0, Sys->SEEKSTART); + b1.seek(big 0, Sys->SEEKSTART); + buf0 := array[1024] of byte; + buf1 := array[1024] of byte; + for (;;) { + n0 := b0.read(buf0, len buf0); + n1 := b1.read(buf1, len buf1); + + if (n0 != n1) + return 1; + + if (n0 == 0) + return 0; + + for (i := 0; i < n0; i++) + if (buf0[i] != buf1[i]) + return 1; + } +} + +################## main from here..... + +REGULAR_FILE(s : Sys->Dir) : int +{ + # both pipes and networks contain non-zero-length files + # which are not seekable. + return (s.qid.qtype&Sys->QTDIR) == 0 && + s.dtype != '|' && + s.dtype != 'I'; +# && s.length > 0; device files have zero length. +} + +rmtmpfiles() +{ + while (whichtmp > 0) { + whichtmp--; + sys->remove(tmpname[whichtmp]); + } +} + +mktmpfile(inputf : ref Sys->FD) : (string, Sys->Dir) +{ + i, j : int; + sb : Sys->Dir; + p : string; + buf := array[8192] of byte; + + p = tmpname[whichtmp++]; + fd := sys->create(p, Sys->OWRITE, 8r600); + if (fd == nil) { + error(sys->sprint("cannot create %s: %r", p)); + return (nil, sb); + } + while ((i = sys->read(inputf, buf, len buf)) > 0) { + if ((i = sys->write(fd, buf, i)) < 0) + break; + } + (j,sb)=sys->fstat(fd); + if (i < 0 || j < 0) { + error(sys->sprint("cannot read/write %s: %r", p)); + return (nil, sb); + } + return (p, sb); +} + + +statfile(file : string) : (string,Sys->Dir) +{ + (ret,sb):=sys->stat(file); + if (ret==-1) { + if (file == "-") { + (ret,sb)= sys->fstat(sys->fildes(0)); + if (ret == -1) { + error(sys->sprint("cannot stat %s: %r", file)); + return (nil,sb); + } + } + (file, sb) = mktmpfile(sys->fildes(0)); + } + else if (!REGULAR_FILE(sb) && !(sb.qid.qtype&Sys->QTDIR)) { + if ((i := sys->open(file, Sys->OREAD)) == nil) { + error(sys->sprint("cannot open %s: %r", file)); + return (nil, sb); + } + (file, sb) = mktmpfile(i); + } + return (file,sb); +} + +diff(f, t : string, level : int) +{ + fp,tp,p,rest,fb,tb : string; + fsb, tsb : Sys->Dir; + (fp,fsb) = statfile(f); + if (fp == nil) + return; + (tp,tsb) = statfile(t); + if (tp == nil) + return; + if ((fsb.qid.qtype&Sys->QTDIR) && (tsb.qid.qtype&Sys->QTDIR)) { + if (rflag || level == 0) + diffdir(fp, tp, level); + else + out.puts(sys->sprint("Common subdirectories: %s and %s\n", fp, tp)); + } + else if (REGULAR_FILE(fsb) && REGULAR_FILE(tsb)){ + diffreg(fp, tp); + } else { + if (!(fsb.qid.qtype&Sys->QTDIR)) { + (p,rest)=str->splitr(f,"/"); + if (rest!=nil) + p = rest; + tb=sys->sprint("%s/%s", tp, p); + diffreg(fp, tb); + } + else { + (p,rest)=str->splitr(t,"/"); + if (rest!=nil) + p = rest; + fb=sys->sprint("%s/%s", fp, p); + diffreg(fb, tp); + } + } +} + +fatal(s: string) +{ + sys->fprint(stderr, "diff: %s\n", s); + raise "fail:error"; +} + +error(s: string) +{ + sys->fprint(stderr, "diff: %s\n", s); +} diff --git a/appl/cmd/disdep.b b/appl/cmd/disdep.b new file mode 100644 index 00000000..0a12c617 --- /dev/null +++ b/appl/cmd/disdep.b @@ -0,0 +1,250 @@ +implement Disdep; + +# +# Copyright © 2000 Vita Nuova Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + print, sprint: import sys; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "draw.m"; + +include "string.m"; + str: String; + +include "arg.m"; + arg: Arg; + +include "dis.m"; + dis: Dis; + Mod: import dis; + +include "hash.m"; + hash: Hash; + HashTable, HashVal: import hash; + +Disdep: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +Item: adt { + name: string; + needs: cyclic list of ref Item; + visited: int; + + find: fn(s: string): ref Item; +}; + +bout: ref Iobuf; +pending: list of ref Item; +roots: list of ref Item; +tab: ref HashTable; +aflag := 0; # display all non-recursive dependencies +oflag := 0; # only list the immediate (outer) dependencies +sflag := 0; # include $system modules +pflag := 0; # show dependency sets as pairs, one per line +showdepth := 0; # indent to show the dependency structure + +noload(mod: string) +{ + sys->fprint(sys->fildes(2), "disdep: can't load %s: %r\n", mod); + raise "fail:load"; +} + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: disdep [-a] [-d] [-o] [-p] [-s] file.dis ...\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + noload(Bufio->PATH); + + str = load String String->PATH; + if(str == nil) + noload(String->PATH); + + hash = load Hash Hash->PATH; + if(hash == nil) + noload(Hash->PATH); + + arg = load Arg Arg->PATH; + if(arg == nil) + noload(Arg->PATH); + + dis = load Dis Dis->PATH; + if(dis == nil) + noload(Dis->PATH); + dis->init(); + + arg->init(argv); + while((opt := arg->opt()) != 0) + case opt { + 'a' => aflag = 1; showdepth = 1; + 'o' => oflag = 1; + 's' => sflag = 1; + 'd' => showdepth = 1; + 'p' => pflag = 1; + * => usage(); + } + + argv = arg->argv(); + if(argv == nil) + usage(); + + tab = hash->new(521); + + bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + for(l := rev(argv); l != nil; l = tl l) + roots = Item.find(hd l) :: roots; + pending = roots; + while(pending != nil){ + f := hd pending; + pending = tl pending; + (m, s) := dis->loadobj(f.name); + if(s != nil){ + sys->fprint(sys->fildes(2), "disdep: can't open %s: %s\n", f.name, s); + continue; + } + f.needs = disfind(m); + for(nl := f.needs; nl != nil; nl = tl nl){ + n := hd nl; + if(!n.visited){ + n.visited = 1; + if(!oflag && !isdol(n.name)) + pending = n :: pending; + } + } + } + + if(pflag){ + for(i := 0; i < nextitem; i++){ + f := items[i]; + if(f.needs != nil){ + for(nl := f.needs; nl != nil; nl = tl nl){ + bout.puts(f.name); + bout.putc(' '); + bout.puts((hd nl).name); + bout.putc('\n'); + } + }else{ + bout.puts(f.name); + bout.putc('\n'); + } + } + }else{ + unvisited(); + for(; roots != nil; roots = tl roots){ + if(aflag) + unvisited(); + f := hd roots; + depth := 0; + if(showdepth){ + bout.puts(f.name); + bout.putc('\n'); + depth = 1; + } + prdep(hd roots, depth); + } + } + bout.flush(); +} + +disfind(m: ref Mod): list of ref Item +{ + needs: list of ref Item; + for(d := m.data; d != nil; d = tl d) { + pick dat := hd d { + String => + if(isdisfile(dat.str) || sflag && isdol(dat.str)) + needs = Item.find(dat.str) :: needs; + } + } + return rev(needs); +} + +prdep(f: ref Item, depth: int) +{ + f.visited = 1; # short-circuit self-reference + for(nl := f.needs; nl != nil; nl = tl nl){ + n := hd nl; + if(!n.visited){ + n.visited = 1; + name(n.name, depth); + prdep(n, depth+1); + }else if(aflag) + name(n.name, depth); + } +} + +items := array[100] of ref Item; +nextitem := 0; + +Item.find(name: string): ref Item +{ + k := tab.find(name); + if(k != nil) + return items[k.i]; + if(nextitem >= len items){ + a := array[len items + 100] of ref Item; + a[0:] = items; + items = a; + } + f := ref Item; + f.name = name; + f.visited = 0; + items[nextitem] = f; + tab.insert(name, HashVal(nextitem, 0.0, nil)); + nextitem++; + return f; +} + +unvisited() +{ + for(i := 0; i < nextitem; i++) + items[i].visited = 0; +} + +name(s: string, depth: int) +{ + if(showdepth) + for(i:=0; i<depth; i++) + bout.putc('\t'); + bout.puts(s); + bout.putc('\n'); +} + +isdisfile(s: string): int +{ + if(len s > 4 && s[len s-4:]==".dis"){ # worth a look + for(i := 0; i < len s; i++) + if(s[i] <= ' ' || s[i] == '%') + return 0; + return 1; + } + return 0; +} + +isdol(s: string): int +{ + return len s > 1 && s[0] == '$' && s[1]>='A' && s[1]<='Z'; # reasonable guess +} + +rev[T](l: list of T): list of T +{ + t: list of T; + for(; l != nil; l = tl l) + t = hd l :: t; + return t; +} + diff --git a/appl/cmd/disdump.b b/appl/cmd/disdump.b new file mode 100644 index 00000000..2bc9763f --- /dev/null +++ b/appl/cmd/disdump.b @@ -0,0 +1,52 @@ +implement Disdump; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "dis.m"; + dis: Dis; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +Disdump: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr := sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) { + sys->fprint(stderr, "dis: cannot load %s: %r\n", Bufio->PATH); + raise "fail:bad module"; + } + + dis = load Dis Dis->PATH; + if (dis == nil) { + sys->fprint(stderr, "dis: cannot load %s: %r\n", Dis->PATH); + raise "fail:bad module"; + } + + if (len argv < 2) { + sys->fprint(stderr, "usage: dis module...\n"); + raise "fail:usage"; + } + dis->init(); + out := bufio->fopen(sys->fildes(1), Sys->OWRITE); + errs := 0; + for (argv = tl argv; argv != nil; argv = tl argv) { + (mod, err) := dis->loadobj(hd argv); + if (mod == nil) { + sys->fprint(stderr, "dis: failed to load %s: %s\n", hd argv, err); + errs++; + continue; + } + for (i := 0; i < len mod.inst; i++) + out.puts(dis->inst2s(mod.inst[i])+"\n"); + } + out.close(); + if (errs) + raise "fail:errors"; +} diff --git a/appl/cmd/disk/format.b b/appl/cmd/disk/format.b new file mode 100644 index 00000000..80fee62c --- /dev/null +++ b/appl/cmd/disk/format.b @@ -0,0 +1,755 @@ +implement Format; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "daytime.m"; + daytime: Daytime; + +include "disks.m"; + disks: Disks; + Disk: import disks; + +include "arg.m"; + +Format: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +# +# floppy types (all MFM encoding) +# +Type: adt { + name: string; + bytes: int; # bytes/sector + sectors: int; # sectors/track + heads: int; # number of heads + tracks: int; # tracks/disk + media: int; # media descriptor byte + cluster: int; # default cluster size +}; + +floppytype := array[] of { + Type ( "3½HD", 512, 18, 2, 80, 16rf0, 1 ), + Type ( "3½DD", 512, 9, 2, 80, 16rf9, 2 ), + Type ( "3½QD", 512, 36, 2, 80, 16rf9, 2 ), # invented + Type ( "5¼HD", 512, 15, 2, 80, 16rf9, 1 ), + Type ( "5¼DD", 512, 9, 2, 40, 16rfd, 2 ), + Type ( "hard", 512, 0, 0, 0, 16rf8, 4 ), +}; + +# offsets in DOS boot area +DB_MAGIC : con 0; +DB_VERSION : con 3; +DB_SECTSIZE : con 11; +DB_CLUSTSIZE : con 13; +DB_NRESRV : con 14; +DB_NFATS : con 16; +DB_ROOTSIZE : con 17; +DB_VOLSIZE : con 19; +DB_MEDIADESC: con 21; +DB_FATSIZE : con 22; +DB_TRKSIZE : con 24; +DB_NHEADS : con 26; +DB_NHIDDEN : con 28; +DB_BIGVOLSIZE: con 32; +DB_DRIVENO : con 36; +DB_RESERVED0: con 37; +DB_BOOTSIG : con 38; +DB_VOLID : con 39; +DB_LABEL : con 43; +DB_TYPE : con 54; + +DB_VERSIONSIZE: con 8; +DB_LABELSIZE : con 11; +DB_TYPESIZE : con 8; +DB_SIZE : con 62; + +# offsets in DOS directory +DD_NAME : con 0; +DD_EXT : con 8; +DD_ATTR : con 11; +DD_RESERVED : con 12; +DD_TIME : con 22; +DD_DATE : con 24; +DD_START : con 26; +DD_LENGTH : con 28; + +DD_NAMESIZE : con 8; +DD_EXTSIZE : con 3; +DD_SIZE : con 32; + +DRONLY : con 16r01; +DHIDDEN : con 16r02; +DSYSTEM : con byte 16r04; +DVLABEL : con byte 16r08; +DDIR : con byte 16r10; +DARCH : con byte 16r20; + +# the boot program for the boot sector. +bootprog := array[512] of { +16r000 => + byte 16rEB, byte 16r3C, byte 16r90, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, + byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, +16r03E => + byte 16rFA, byte 16rFC, byte 16r8C, byte 16rC8, byte 16r8E, byte 16rD8, byte 16r8E, byte 16rD0, + byte 16rBC, byte 16r00, byte 16r7C, byte 16rBE, byte 16r77, byte 16r7C, byte 16rE8, byte 16r19, + byte 16r00, byte 16r33, byte 16rC0, byte 16rCD, byte 16r16, byte 16rBB, byte 16r40, byte 16r00, + byte 16r8E, byte 16rC3, byte 16rBB, byte 16r72, byte 16r00, byte 16rB8, byte 16r34, byte 16r12, + byte 16r26, byte 16r89, byte 16r07, byte 16rEA, byte 16r00, byte 16r00, byte 16rFF, byte 16rFF, + byte 16rEB, byte 16rD6, byte 16rAC, byte 16r0A, byte 16rC0, byte 16r74, byte 16r09, byte 16rB4, + byte 16r0E, byte 16rBB, byte 16r07, byte 16r00, byte 16rCD, byte 16r10, byte 16rEB, byte 16rF2, + byte 16rC3, byte 'N', byte 'o', byte 't', byte ' ', byte 'a', byte ' ', byte 'b', + byte 'o', byte 'o', byte 't', byte 'a', byte 'b', byte 'l', byte 'e', byte ' ', + byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'o', byte 'r', byte ' ', + byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'e', byte 'r', byte 'r', + byte 'o', byte 'r', byte '\r', byte '\n', byte 'P', byte 'r', byte 'e', byte 's', + byte 's', byte ' ', byte 'a', byte 'l', byte 'm', byte 'o', byte 's', byte 't', + byte ' ', byte 'a', byte 'n', byte 'y', byte ' ', byte 'k', byte 'e', byte 'y', + byte ' ', byte 't', byte 'o', byte ' ', byte 'r', byte 'e', byte 'b', byte 'o', + byte 'o', byte 't', byte '.', byte '.', byte '.', byte 16r00, byte 16r00, byte 16r00, +16r1F0 => + byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, + byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r55, byte 16rAA, +* => + byte 16r00, +}; + +dev: string; +clustersize := 0; +fat: array of byte; # the fat +fatbits: int; +fatsecs: int; +fatlast: int; # last cluster allocated +clusters: int; +volsecs: int; +root: array of byte; # first block of root +rootsecs: int; +rootfiles: int; +rootnext: int; +chatty := 0; +xflag := 0; +nresrv := 1; +dos := 0; +fflag := 0; +file: string; # output file name +pbs: string; +typ: string; + +Sof: con 1; # start of file +Eof: con 2; # end of file + +stdin, stdout, stderr: ref Sys->FD; + +fatal(str: string) +{ + sys->fprint(stderr, "format: %s\n", str); + if(fflag && file != nil) + sys->remove(file); + raise "fail:error"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + daytime = load Daytime Daytime->PATH; + disks = load Disks Disks->PATH; + arg := load Arg Arg->PATH; + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + disks->init(); + + fflag = 0; + typ = nil; + clustersize = 0; + writepbs := 0; + label := array[DB_LABELSIZE] of {* => byte ' '}; + label[0:] = array of byte "CYLINDRICAL"; + arg->init(args); + arg->setusage("disk/format [-df] [-b bootblock] [-c csize] [-l label] [-r nresrv] [-t type] disk [files ...]"); + while((o := arg->opt()) != 0) + case o { + 'b' => + pbs = arg->earg(); + writepbs = 1; + 'd' => + dos = 1; + writepbs = 1; + 'c' => + clustersize = int arg->earg(); + 'f' => + fflag = 1; + 'l' => + a := array of byte arg->earg(); + if(len a > len label) + a = a[0:len label]; + label[0:] = a; + for(i := len a; i < len label; i++) + label[i] = byte ' '; + 'r' => + nresrv = int arg->earg(); + 't' => + typ = arg->earg(); + 'v' => + chatty = 1; + 'x' => + xflag = 1; + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + dev = hd args; + disk := Disk.open(dev, Sys->ORDWR, 0); + if(disk == nil){ + if(fflag){ + fd := sys->create(dev, Sys->ORDWR, 8r666); + if(fd != nil){ + fd = nil; + disk = Disk.open(dev, Sys->ORDWR, 0); + } + } + if(disk == nil) + fatal(sys->sprint("opendisk %q: %r", dev)); + } + + if(disk.dtype == "file") + fflag = 1; + + if(typ == nil){ + case disk.dtype { + "file" => + typ = "3½HD"; + "floppy" => + sys->seek(disk.ctlfd, big 0, 0); + buf := array[10] of byte; + n := sys->read(disk.ctlfd, buf, len buf); + if(n <= 0 || n >= 10) + fatal("reading floppy type"); + typ = string buf[0:n]; + "sd" => + typ = "hard"; + * => + typ = "unknown"; + } + } + + if(!fflag && disk.dtype == "floppy") + if(sys->fprint(disk.ctlfd, "format %s", typ) < 0) + fatal(sys->sprint("formatting floppy as %s: %r", typ)); + + if(disk.dtype != "floppy" && !xflag) + sanitycheck(disk); + + # check that everything will succeed + dosfs(dos, writepbs, disk, label, tl args, 0); + + # commit + dosfs(dos, writepbs, disk, label, tl args, 1); + + sys->print("used %bd bytes\n", big fatlast*big clustersize*big disk.secsize); + exit; +} + +# +# look for a partition table on sector 1, as would be the +# case if we were erroneously formatting 9fat without -r 2. +# if it's there and nresrv is not big enough, complain and exit. +# i've blown away my partition table too many times. +# +sanitycheck(disk: ref Disk) +{ + buf := array[512] of byte; + bad := 0; + if(dos && nresrv < 2 && sys->seek(disk.fd, big disk.secsize, 0) == big disk.secsize && + sys->read(disk.fd, buf, len buf) >= 5 && string buf[0:5] == "part "){ + sys->fprint(sys->fildes(2), "there's a plan9 partition on the disk\n"+ + "and you didn't specify -r 2 (or greater).\n" + + "either specify -r 2 or -x to disable this check.\n"); + bad = 1; + } + + if(disk.dtype == "sd" && disk.offset == big 0){ + sys->fprint(sys->fildes(2), "you're attempting to format your disk (/dev/sdXX/data)\n"+ + "rather than a partition such as /dev/sdXX/9fat;\n" + + "this is probably a mistake. specify -x to disable this check.\n"); + bad = 1; + } + + if(bad) + raise "fail:failed disk sanity check"; +} + +# +# return the BIOS driver number for the disk. +# 16r80 is the first fixed disk, 16r81 the next, etc. +# We map sdC0=16r80, sdC1=16r81, sdD0=16r82, sdD1=16r83 +# +getdriveno(disk: ref Disk): int +{ + if(disk.dtype != "sd") + return 16r80; # first hard disk + + name := sys->fd2path(disk.fd); + if(len name < 3) + return 16r80; + + # + # The name is of the format #SsdC0/foo + # or /dev/sdC0/foo. + # So that we can just look for /sdC0, turn + # #SsdC0/foo into #/sdC0/foo. + # + if(name[0:1] == "#S") + name[1] = '/'; + + for(p := name; len p >= 4; p = p[1:]) + if(p[0:2] == "sd" && (p[2]=='C' || p[2]=='D') && (p[3]=='0' || p[3]=='1')) + return 16r80 + (p[2]-'c')*2 + (p[3]-'0'); + + return 16r80; +} + +writen(fd: ref Sys->FD, buf: array of byte, n: int): int +{ + # write 8k at a time, to be nice to the disk subsystem + m: int; + for(tot:=0; tot<n; tot+=m){ + m = n - tot; + if(m > 8192) + m = 8192; + if(sys->write(fd, buf[tot:], m) != m) + break; + } + return tot; +} + +dosfs(dofat: int, dopbs: int, disk: ref Disk, label: array of byte, arg: list of string, commit: int) +{ + if(dofat == 0 && dopbs == 0) + return; + + for(i := 0; i < len floppytype; i++) + if(typ == floppytype[i].name) + break; + if(i == len floppytype) + fatal(sys->sprint("unknown floppy type %q", typ)); + + t := floppytype[i]; + if(t.sectors == 0 && typ == "hard"){ + t.sectors = disk.s; + t.heads = disk.h; + t.tracks = disk.c; + } + + if(t.sectors == 0 && dofat) + fatal(sys->sprint("cannot format fat with type %s: geometry unknown", typ)); + + if(fflag){ + disk.size = big (t.bytes*t.sectors*t.heads*t.tracks); + disk.secsize = t.bytes; + disk.secs = disk.size / big disk.secsize; + } + + secsize := disk.secsize; + length := disk.size; + + # + # make disk full size if a file + # + if(fflag && disk.dtype == "file"){ + (ok, d) := sys->fstat(disk.wfd); + if(ok < 0) + fatal(sys->sprint("fstat disk: %r")); + if(commit && d.length < disk.size){ + if(sys->seek(disk.wfd, disk.size-big 1, 0) < big 0) + fatal(sys->sprint("seek to 9: %r")); + if(sys->write(disk.wfd, array[] of {0 => byte '9'}, 1) < 0) + fatal(sys->sprint("writing 9: @%bd %r", sys->seek(disk.wfd, big 0, 1))); + } + } + + buf := array[secsize] of byte; + + # + # start with initial sector from disk + # + if(sys->seek(disk.fd, big 0, 0) < big 0) + fatal(sys->sprint("seek to boot sector: %r")); + if(commit && sys->read(disk.fd, buf, secsize) != secsize) + fatal(sys->sprint("reading boot sector: %r")); + + if(dofat) + memset(buf, 0, DB_SIZE); + + # + # Jump instruction and OEM name + # + b := buf; # hmm. + b[DB_MAGIC+0] = byte 16rEB; + b[DB_MAGIC+1] = byte 16r3C; + b[DB_MAGIC+2] = byte 16r90; + memmove(b[DB_VERSION: ], array of byte "Plan9.00", DB_VERSIONSIZE); + + # + # Add bootstrapping code; assume it starts + # at 16r3E (the destination of the jump we just + # wrote to b[DB_MAGIC] + # + if(dopbs){ + pbsbuf := array[secsize] of byte; + npbs: int; + if(pbs != nil){ + if((sysfd := sys->open(pbs, Sys->OREAD)) == nil) + fatal(sys->sprint("open %s: %r", pbs)); + npbs = sys->read(sysfd, pbsbuf, len pbsbuf); + if(npbs < 0) + fatal(sys->sprint("read %s: %r", pbs)); + if(npbs > secsize-2) + fatal("boot block too large"); + }else{ + pbsbuf[0:] = bootprog; + npbs = len bootprog; + } + if(npbs <= 16r3E) + sys->fprint(sys->fildes(2), "warning: pbs too small\n"); + else + buf[16r3E:] = pbsbuf[16r3E:npbs]; + } + + # + # Add FAT BIOS parameter block + # + if(dofat){ + if(commit){ + sys->print("Initializing FAT file system\n"); + sys->print("type %s, %d tracks, %d heads, %d sectors/track, %d bytes/sec\n", + t.name, t.tracks, t.heads, t.sectors, secsize); + } + + if(clustersize == 0) + clustersize = t.cluster; + # + # the number of fat bits depends on how much disk is left + # over after you subtract out the space taken up by the fat tables. + # try both. what a crock. + # + for(fatbits = 12;;){ + volsecs = int (length/big secsize); + # + # here's a crock inside a crock. even having fixed fatbits, + # the number of fat sectors depends on the number of clusters, + # but of course we don't know yet. maybe iterating will get us there. + # or maybe it will cycle. + # + clusters = 0; + for(i=0;; i++){ + fatsecs = (fatbits*clusters + 8*secsize - 1)/(8*secsize); + rootsecs = volsecs/200; + rootfiles = rootsecs * (secsize/DD_SIZE); + if(rootfiles > 512){ + rootfiles = 512; + rootsecs = rootfiles/(secsize/DD_SIZE); + } + data := nresrv + 2*fatsecs + (rootfiles*DD_SIZE + secsize-1)/secsize; + newclusters := 2 + (volsecs - data)/clustersize; + if(newclusters == clusters) + break; + clusters = newclusters; + if(i > 10) + fatal(sys->sprint("can't decide how many clusters to use (%d? %d?)", clusters, newclusters)); +if(chatty) sys->print("clusters %d\n", clusters); +if(clusters <= 1) raise "trap"; + } + +if(chatty) sys->print("try %d fatbits => %d clusters of %d\n", fatbits, clusters, clustersize); + if(clusters < 4087 || fatbits > 12) + break; + fatbits = 16; + } + if(clusters >= 65527) + fatal("disk too big; implement fat32"); + + putshort(b[DB_SECTSIZE: ], secsize); + b[DB_CLUSTSIZE] = byte clustersize; + putshort(b[DB_NRESRV: ], nresrv); + b[DB_NFATS] = byte 2; + putshort(b[DB_ROOTSIZE: ], rootfiles); + if(volsecs < (1<<16)) + putshort(b[DB_VOLSIZE: ], volsecs); + b[DB_MEDIADESC] = byte t.media; + putshort(b[DB_FATSIZE: ], fatsecs); + putshort(b[DB_TRKSIZE: ], t.sectors); + putshort(b[DB_NHEADS: ], t.heads); + putlong(b[DB_NHIDDEN: ], int disk.offset); + putlong(b[DB_BIGVOLSIZE: ], volsecs); + + # + # Extended BIOS Parameter Block + # + if(t.media == 16rF8) + dno := getdriveno(disk); + else + dno = 0; +if(chatty) sys->print("driveno = %ux\n", dno); + b[DB_DRIVENO] = byte dno; + b[DB_BOOTSIG] = byte 16r29; + x := int (disk.offset + big b[DB_NFATS]*big fatsecs + big nresrv); + putlong(b[DB_VOLID:], x); +if(chatty) sys->print("volid = %ux\n", x); + b[DB_LABEL:] = label; + r := sys->aprint("FAT%d ", fatbits); + if(len r > DB_TYPESIZE) + r = r[0:DB_TYPESIZE]; + b[DB_TYPE:] = r; + } + + b[secsize-2] = byte Disks->Magic0; + b[secsize-1] = byte Disks->Magic1; + + if(commit){ + if(sys->seek(disk.wfd, big 0, 0) < big 0) + fatal(sys->sprint("seek to boot sector: %r\n")); + if(sys->write(disk.wfd, b, secsize) != secsize) + fatal(sys->sprint("writing to boot sector: %r")); + } + + # + # if we were only called to write the PBS, leave now + # + if(dofat == 0) + return; + + # + # allocate an in memory fat + # + if(sys->seek(disk.wfd, big (nresrv*secsize), 0) < big 0) + fatal(sys->sprint("seek to fat: %r")); +if(chatty) sys->print("fat @%buX\n", sys->seek(disk.wfd, big 0, 1)); + fat = array[fatsecs*secsize] of {* => byte 0}; + if(fat == nil) + fatal("out of memory"); + fat[0] = byte t.media; + fat[1] = byte 16rff; + fat[2] = byte 16rff; + if(fatbits == 16) + fat[3] = byte 16rff; + fatlast = 1; + if(sys->seek(disk.wfd, big (2*fatsecs*secsize), 1) < big 0) # 2 fats + fatal(sys->sprint("seek to root: %r")); +if(chatty) sys->print("root @%buX\n", sys->seek(disk.wfd, big 0, 1)); + + # + # allocate an in memory root + # + root = array[rootsecs*secsize] of {* => byte 0}; + if(sys->seek(disk.wfd, big (rootsecs*secsize), 1) < big 0) # rootsecs + fatal(sys->sprint("seek to files: %r")); +if(chatty) sys->print("files @%buX\n", sys->seek(disk.wfd, big 0, 1)); + + # + # Now positioned at the Files Area. + # If we have any arguments, process + # them and write out. + # + for(p := 0; arg != nil; arg = tl arg){ + if(p >= rootsecs*secsize) + fatal("too many files in root"); + # + # Open the file and get its length. + # + if((sysfd := sys->open(hd arg, Sys->OREAD)) == nil) + fatal(sys->sprint("open %s: %r", hd arg)); + (ok, d) := sys->fstat(sysfd); + if(ok < 0) + fatal(sys->sprint("stat %s: %r", hd arg)); + if(d.length >= big 16r7FFFFFFF) + fatal(sys->sprint("file %s too big (%bd bytes)", hd arg, d.length)); + if(commit) + sys->print("Adding file %s, length %bd\n", hd arg, d.length); + + x: int; + length = d.length; + if(length > big 0){ + # + # Allocate a buffer to read the entire file into. + # This must be rounded up to a cluster boundary. + # + # Read the file and write it out to the Files Area. + # + length += big (secsize*clustersize - 1); + length /= big (secsize*clustersize); + length *= big (secsize*clustersize); + fbuf := array[int length] of byte; + if((nr := sys->read(sysfd, fbuf, int d.length)) != int d.length){ + if(nr >= 0) + sys->werrstr("short read"); + fatal(sys->sprint("read %s: %r", hd arg)); + } + for(; nr < len fbuf; nr++) + fbuf[nr] = byte 0; +if(chatty) sys->print("%q @%buX\n", d.name, sys->seek(disk.wfd, big 0, 1)); + if(commit && writen(disk.wfd, fbuf, len fbuf) != len fbuf) + fatal(sys->sprint("write %s: %r", hd arg)); + fbuf = nil; + + # + # Allocate the FAT clusters. + # We're assuming here that where we + # wrote the file is in sync with + # the cluster allocation. + # Save the starting cluster. + # + length /= big (secsize*clustersize); + x = clustalloc(Sof); + for(n := 0; n < int length-1; n++) + clustalloc(0); + clustalloc(Eof); + } + else + x = 0; + + # + # Add the filename to the root. + # +sys->fprint(sys->fildes(2), "add %s at clust %ux\n", d.name, x); + addrname(root[p:], d, hd arg, x); + p += DD_SIZE; + } + + # + # write the fats and root + # + if(commit){ + if(sys->seek(disk.wfd, big (nresrv*secsize), 0) < big 0) + fatal(sys->sprint("seek to fat #1: %r")); + if(sys->write(disk.wfd, fat, fatsecs*secsize) < 0) + fatal(sys->sprint("writing fat #1: %r")); + if(sys->write(disk.wfd, fat, fatsecs*secsize) < 0) + fatal(sys->sprint("writing fat #2: %r")); + if(sys->write(disk.wfd, root, rootsecs*secsize) < 0) + fatal(sys->sprint("writing root: %r")); + } +} + +# +# allocate a cluster +# +clustalloc(flag: int): int +{ + o, x: int; + + if(flag != Sof){ + if (flag == Eof) + x =16rffff; + else + x = fatlast+1; + if(fatbits == 12){ + x &= 16rfff; + o = (3*fatlast)/2; + if(fatlast & 1){ + fat[o] = byte ((int fat[o] & 16r0f) | (x<<4)); + fat[o+1] = byte (x>>4); + } else { + fat[o] = byte x; + fat[o+1] = byte ((int fat[o+1] & 16rf0) | ((x>>8) & 16r0F)); + } + } else { + o = 2*fatlast; + fat[o] = byte x; + fat[o+1] = byte (x>>8); + } + } + + if(flag == Eof) + return 0; + if(++fatlast >= clusters) + fatal(sys->sprint("data does not fit on disk (%d %d)", fatlast, clusters)); + return fatlast; +} + +putname(p: string, buf: array of byte) +{ + memset(buf[DD_NAME: ], ' ', DD_NAMESIZE+DD_EXTSIZE); + for(i := 0; i < DD_NAMESIZE && i < len p && p[i] != '.'; i++){ + c := p[i]; + if(c >= 'a' && c <= 'z') + c += 'A'-'a'; + buf[DD_NAME+i] = byte c; + } + for(i = 0; i < len p; i++) + if(p[i] == '.'){ + p = p[i+1:]; + for(i = 0; i < DD_EXTSIZE && i < len p; i++){ + c := p[i]; + if(c >= 'a' && c <= 'z') + c += 'A'-'a'; + buf[DD_EXT+i] = byte c; + } + break; + } +} + +puttime(buf: array of byte) +{ + t := daytime->local(daytime->now()); + x := (t.hour<<11) | (t.min<<5) | (t.sec>>1); + buf[DD_TIME+0] = byte x; + buf[DD_TIME+1] = byte (x>>8); + x = ((t.year-80)<<9) | ((t.mon+1)<<5) | t.mday; + buf[DD_DATE+0] = byte x; + buf[DD_DATE+1] = byte (x>>8); +} + +addrname(buf: array of byte, dir: Sys->Dir, name: string, start: int) +{ + s := name; + for(i := len s; --i >= 0;) + if(s[i] == '/'){ + s = s[i+1:]; + break; + } + putname(s, buf); + if(s == "9load") + buf[DD_ATTR] = byte DSYSTEM; + else + buf[DD_ATTR] = byte 0; + puttime(buf); + buf[DD_START+0] = byte start; + buf[DD_START+1] = byte (start>>8); + buf[DD_LENGTH+0] = byte dir.length; + buf[DD_LENGTH+1] = byte (dir.length>>8); + buf[DD_LENGTH+2] = byte (dir.length>>16); + buf[DD_LENGTH+3] = byte (dir.length>>24); +} + +memset(d: array of byte, v: int, n: int) +{ + for (i := 0; i < n; i++) + d[i] = byte v; +} + +memmove(d: array of byte, s: array of byte, n: int) +{ + d[0:] = s[0:n]; +} + +putshort(b: array of byte, v: int) +{ + b[1] = byte (v>>8); + b[0] = byte v; +} + +putlong(b: array of byte, v: int) +{ + putshort(b, v); + putshort(b[2: ], v>>16); +} diff --git a/appl/cmd/disk/ftl.b b/appl/cmd/disk/ftl.b new file mode 100644 index 00000000..750defb7 --- /dev/null +++ b/appl/cmd/disk/ftl.b @@ -0,0 +1,911 @@ +# +# basic Flash Translation Layer driver +# see for instance the Intel technical paper +# ``Understanding the Flash Translation Layer (FTL) Specification'' +# Order number 297816-001 (online at www.intel.com) +# +# a public driver by David Hinds, dhinds@allegro.stanford.edu +# further helps with some details. +# +# this driver uses the common simplification of never storing +# the VBM on the medium (a waste of precious flash!) but +# rather building it on the fly as the block maps are read. +# +# Plan 9 driver (c) 1997 by C H Forsyth (forsyth@caldo.demon.co.uk) +# This driver may be used or adapted by anyone for any non-commercial purpose. +# +# adapted for Inferno 1998 by C H Forsyth, Vita Nuova Limited, York, England (byteles@vitanuova.com) +# +# C H Forsyth and Vita Nuova Limited expressly allow Lucent Technologies +# to use this driver freely for any Inferno-related purposes whatever, +# including commercial applications. +# +# TO DO: +# check error handling details for get/put flash +# bad block handling +# reserved space in formatted size +# possibly block size as parameter +# fetch parameters from header on init +# +# Adapted to a ftl formatter for Inferno 2000 by J R Firth, Vita Nuova Limited +# usage : ftl flashsize secsize inputfile outputfile +# outputfile will then be a ftl image of inputfile +# nb assumes the base address is zero +# +# Converted to limbo for Inferno 2000 by JR Firth, Vita Nuova Holdings Limited +# + +implement Ftlimage; + +include "sys.m"; +include "draw.m"; + +sys : Sys; + OREAD, OWRITE, FD, open, create, read, write, print, fprint : import sys; + +Ftlimage : module +{ + init : fn(nil : ref Draw->Context, argv : list of string); +}; + +stderr : ref FD; + +flashsize, secsize : int; +flashm : array of byte; +trace : int = 0; + +Eshift : con 18; # 2^18=256k; log2(eraseunit) +Flashseg : con 1<<Eshift; +Bshift : con 9; # 2^9=512 +Bsize : con 1<<Bshift; +BAMoffset : con 16r100; +Nolimit : con ~0; +USABLEPCT : con 95; # release only this % to client + +FTLDEBUG : con 0; + +# erase unit header (defined by FTL specification) +# offsets into Merase +O_LINKTUPLE : con 0; +O_ORGTUPLE : con 5; +O_NXFER : con 15; +O_NERASE : con 16; +O_ID : con 20; +O_BSHIFT : con 22; +O_ESHIFT : con 23; +O_PSTART : con 24; +O_NUNITS : con 26; +O_PSIZE : con 28; +O_VBMBASE : con 32; +O_NVBM : con 36; +O_FLAGS : con 38; +O_CODE : con 39; +O_SERIAL : con 40; +O_ALTOFFSET : con 44; +O_BAMOFFSET : con 48; +O_RSV2 : con 52; + +ERASEHDRLEN : con 64; + +# special unit IDs +XferID : con 16rffff; +XferBusy : con 16r7fff; + +# special BAM addresses +Bfree : con -1; #16rffffffff +Bwriting : con -2; #16rfffffffe +Bdeleted : con 0; + +# block types +TypeShift : con 7; +BlockType : con (1<<TypeShift)-1; +ControlBlock : con 16r30; +DataBlock : con 16r40; +ReplacePage : con 16r60; +BadBlock : con 16r70; + +BNO(va : int) : int +{ + return va>>Bshift; +} +MKBAM(b : int,t : int) : int +{ + return (b<<Bshift)|t; +} + +Terase : adt { + x : int; + id : int; + offset : int; + bamoffset : int; + nbam : int; + bam : array of byte; + bamx : int; + nfree : int; + nused : int; + ndead : int; + nbad : int; + nerase : int; +}; + +Ftl : adt { + base : int; # base of flash region + size : int; # size of flash region + segsize : int; # size of flash segment (erase unit) + eshift : int; # log2(erase-unit-size) + bshift : int; # log2(bsize) + bsize : int; + nunit : int; # number of segments (erase units) + unit : array of ref Terase; + lastx : int; # index in unit of last allocation + xfer : int; # index in unit of current transfer unit (-1 if none) + nfree : int; # total free space in blocks + nblock : int; # total space in blocks + rwlimit : int; # user-visible block limit (`formatted size') + vbm : array of int; # virtual block map + fstart : int; # address of first block of data in a segment + trace : int; # (debugging) trace of read/write actions + detach : int; # free Ftl on last close + + # scavenging variables + needspace : int; + hasproc : int; +}; + +# Ftl.detach +Detached : con 1; # detach on close +Deferred : con 2; # scavenger must free it + +ftls : ref Ftl; + +ftlstat(sz : int) +{ + print("16r%x:16r%x:16r%x\n", ftls.rwlimit*Bsize, sz, flashsize); + print("%d:%d:%d in 512b blocks\n", ftls.rwlimit, sz>>Bshift, flashsize>>Bshift); +} + +ftlread(buf : array of byte, n : int, offset : int) : int +{ + ftl : ref Ftl; + e : ref Terase; + nb : int; + a : int; + pb : int; + mapb : int; + + if(n <= 0 || n%Bsize || offset%Bsize) { + fprint(stderr, "ftl: bad read\n"); + exit; + } + ftl = ftls; + nb = n/Bsize; + offset /= Bsize; + if(offset >= ftl.rwlimit) + return 0; + if(offset+nb > ftl.rwlimit) + nb = ftl.rwlimit - offset; + a = 0; + for(n = 0; n < nb; n++){ + (mapb, e, pb) = mapblk(ftl, offset+n); + if(mapb) + getflash(ftl, buf[a:], e.offset + pb*Bsize, Bsize); + else + memset(buf[a:], 0, Bsize); + a += Bsize; + } + return a; +} + +ftlwrite(buf : array of byte, n : int, offset : int) : int +{ + ns, nb : int; + a : int; + e, oe : ref Terase; + ob, v : int; + ftl : ref Ftl; + mapb : int; + + if(n <= 0) + return 0; + ftl = ftls; + if(n <= 0 || n%Bsize || offset%Bsize) { + fprint(stderr, "ftl: bad write\n"); + exit; + } + nb = n/Bsize; + offset /= Bsize; + if(offset >= ftl.rwlimit) + return 0; + if(offset+nb > ftl.rwlimit) + nb = ftl.rwlimit - offset; + a = 0; + for(n = 0; n < nb; n++){ + ns = 0; + while((v = allocblk(ftl)) == 0) + if(!scavenge(ftl) || ++ns > 3){ + fprint(stderr, "ftl: flash memory full\n"); + } + (mapb, oe, ob) = mapblk(ftl, offset+n); + if(!mapb) + oe = nil; + e = ftl.unit[v>>16]; + v &= 16rffff; + putflash(ftl, e.offset + v*Bsize, buf[a:], Bsize); + putbam(ftl, e, v, MKBAM(offset+n, DataBlock)); + # both old and new block references exist in this window (can't be closed?) + ftl.vbm[offset+n] = (e.x<<16) | v; + if(oe != nil){ + putbam(ftl, oe, ob, Bdeleted); + oe.ndead++; + } + a += Bsize; + } + return a; +} + +mkftl(fname : string, base : int, size : int, eshift : int, op : string) : ref Ftl +{ + i, j, nov, segblocks : int; + limit : int; + e : ref Terase; + + ftl := ref Ftl; + ftl.lastx = 0; + ftl.detach = 0; + ftl.needspace = 0; + ftl.hasproc = 0; + ftl.trace = 0; + limit = flashsize; + if(size == Nolimit) + size = limit-base; + if(base >= limit || size > limit || base+size > limit || eshift < 8 || (1<<eshift) > size) { + fprint(stderr, "bad flash space parameters"); + exit; + } + if(FTLDEBUG || ftl.trace || trace) + print("%s flash %s #%x:#%x limit #%x\n", op, fname, base, size, limit); + ftl.base = base; + ftl.size = size; + ftl.bshift = Bshift; + ftl.bsize = Bsize; + ftl.eshift = eshift; + ftl.segsize = 1<<eshift; + ftl.nunit = size>>eshift; + nov = ((ftl.segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize; # number of overhead blocks per segment (header, and BAM itself) + ftl.fstart = nov; + segblocks = ftl.segsize/Bsize - nov; + ftl.nblock = ftl.nunit*segblocks; + if(ftl.nblock >= 16r10000) + ftl.nblock = 16r10000; + ftl.vbm = array[ftl.nblock] of int; + ftl.unit = array[ftl.nunit] of ref Terase; + if(ftl.vbm == nil || ftl.unit == nil) { + fprint(stderr, "out of mem"); + exit; + } + for(i=0; i<ftl.nblock; i++) + ftl.vbm[i] = 0; + if(op == "format"){ + for(i=0; i<ftl.nunit-1; i++) + eraseinit(ftl, i*ftl.segsize, i, 1); + eraseinit(ftl, i*ftl.segsize, XferID, 1); + } + ftl.xfer = -1; + for(i=0; i<ftl.nunit; i++){ + e = eraseload(ftl, i, i*ftl.segsize); + if(e == nil){ + fprint(stderr, "ftl: logical segment %d: bad format\n", i); + continue; + } + if(e.id == XferBusy){ + e.nerase++; + eraseinit(ftl, e.offset, XferID, e.nerase); + e.id = XferID; + } + for(j=0; j<ftl.nunit; j++) + if(ftl.unit[j] != nil && ftl.unit[j].id == e.id){ + fprint(stderr, "ftl: duplicate erase unit #%x\n", e.id); + erasefree(e); + e = nil; + break; + } + if(e != nil){ + ftl.unit[e.x] = e; + if(e.id == XferID) + ftl.xfer = e.x; + if (FTLDEBUG || ftl.trace || trace) + fprint(stderr, "ftl: unit %d:#%x used %d free %d dead %d bad %d nerase %d\n", + e.x, e.id, e.nused, e.nfree, e.ndead, e.nbad, e.nerase); + } + } + if(ftl.xfer < 0 && ftl.nunit <= 0 || ftl.xfer >= 0 && ftl.nunit <= 1) { + fprint(stderr, "ftl: no valid flash data units"); + exit; + } + if(ftl.xfer < 0) + fprint(stderr, "ftl: no transfer unit: device is WORM\n"); + else + ftl.nblock -= segblocks; # discount transfer segment + if(ftl.nblock >= 1000) + ftl.rwlimit = ftl.nblock-100; # TO DO: variable reserve + else + ftl.rwlimit = ftl.nblock*USABLEPCT/100; + return ftl; +} + +ftlfree(ftl : ref Ftl) +{ + if(ftl != nil){ + ftl.unit = nil; + ftl.vbm = nil; + ftl = nil; + } +} + +# +# this simple greedy algorithm weighted by nerase does seem to lead +# to even wear of erase units (cf. the eNVy file system) +# + +bestcopy(ftl : ref Ftl) : ref Terase +{ + e, be : ref Terase; + i : int; + + be = nil; + for(i=0; i<ftl.nunit; i++) + if((e = ftl.unit[i]) != nil && e.id != XferID && e.id != XferBusy && e.ndead+e.nbad && + (be == nil || e.nerase <= be.nerase && e.ndead >= be.ndead)) + be = e; + return be; +} + +copyunit(ftl : ref Ftl, from : ref Terase, too : ref Terase) : int +{ + i, nb : int; + id := array[2] of byte; + bam : array of byte; + buf : array of byte; + v, bno : int; + + if(FTLDEBUG || ftl.trace || trace) + print("ftl: copying %d (#%x) to #%x\n", from.id, from.offset, too.offset); + too.nbam = 0; + too.bam = nil; + bam = nil; + buf = array[Bsize] of byte; + if(buf == nil) + return 0; + PUT2(id, XferBusy); + putflash(ftl, too.offset+O_ID, id, 2); + # make new BAM + nb = from.nbam*4; + bam = array[nb] of byte; + memmove(bam, from.bam, nb); + too.nused = 0; + too.nbad = 0; + too.nfree = 0; + too.ndead = 0; + for(i = 0; i < from.nbam; i++) + bv := GET4(bam[4*i:]); + case(bv){ + Bwriting or + Bdeleted or + Bfree => + PUT4(bam[4*i:], Bfree); + too.nfree++; + break; + * => + case(bv&BlockType){ + DataBlock or + ReplacePage => + v = bv; + bno = BNO(v & ~BlockType); + if(i < ftl.fstart || bno >= ftl.nblock){ + print("ftl: unit %d:#%x bad bam[%d]=#%x\n", from.x, from.id, i, v); + too.nfree++; + PUT4(bam[4*i:], Bfree); + break; + } + getflash(ftl, buf, from.offset+i*Bsize, Bsize); + putflash(ftl, too.offset+i*Bsize, buf, Bsize); + too.nused++; + break; + ControlBlock => + too.nused++; + break; + * => + # case BadBlock: # it isn't necessarily bad in this unit + too.nfree++; + PUT4(bam[4*i:], Bfree); + break; + } + } + # for(i=0; i<from.nbam; i++){ + # v = GET4(bam[4*i:]); + # if(v != Bfree && ftl.trace > 1) + # print("to[%d]=#%x\n", i, v); + # PUT4(bam[4*i:], v); + # } + putflash(ftl, too.bamoffset, bam, nb); # BUG: PUT4 ? IS IT ? + # for(i=0; i<from.nbam; i++){ + # v = GET4(bam[4*i:]); + # PUT4(bam[4*i:], v); + # } + too.id = from.id; + PUT2(id, too.id); + putflash(ftl, too.offset+O_ID, id, 2); + too.nbam = from.nbam; + too.bam = bam; + ftl.nfree += too.nfree - from.nfree; + buf = nil; + return 1; +} + +mustscavenge(a : ref Ftl) : int +{ + return a.needspace || a.detach == Deferred; +} + +donescavenge(a : ref Ftl) : int +{ + return a.needspace == 0; +} + +scavengeproc(arg : ref Ftl) +{ + ftl : ref Ftl; + i : int; + e, ne : ref Terase; + + ftl = arg; + if(mustscavenge(ftl)){ + if(ftl.detach == Deferred){ + ftlfree(ftl); + fprint(stderr, "scavenge out of memory\n"); + exit; + } + if(FTLDEBUG || ftl.trace || trace) + print("ftl: scavenge %d\n", ftl.nfree); + e = bestcopy(ftl); + if(e == nil || ftl.xfer < 0 || (ne = ftl.unit[ftl.xfer]) == nil || ne.id != XferID || e == ne) + ; + else if(copyunit(ftl, e, ne)){ + i = ne.x; ne.x = e.x; e.x = i; + ftl.unit[ne.x] = ne; + ftl.unit[e.x] = e; + ftl.xfer = e.x; + e.id = XferID; + e.nbam = 0; + e.bam = nil; + e.bamx = 0; + e.nerase++; + eraseinit(ftl, e.offset, XferID, e.nerase); + } + if(FTLDEBUG || ftl.trace || trace) + print("ftl: end scavenge %d\n", ftl.nfree); + ftl.needspace = 0; + } +} + +scavenge(ftl : ref Ftl) : int +{ + if(ftl.xfer < 0 || bestcopy(ftl) == nil) + return 0; # you worm! + + if(!ftl.hasproc){ + ftl.hasproc = 1; + } + ftl.needspace = 1; + + scavengeproc(ftls); + + return ftl.nfree; +} + +putbam(ftl : ref Ftl, e : ref Terase, n : int, entry : int) +{ + b := array[4] of byte; + + PUT4(e.bam[4*n:], entry); + PUT4(b, entry); + putflash(ftl, e.bamoffset + n*4, b, 4); +} + +allocblk(ftl : ref Ftl) : int +{ + e : ref Terase; + i, j : int; + + i = ftl.lastx; + do{ + e = ftl.unit[i]; + if(e != nil && e.id != XferID && e.nfree){ + ftl.lastx = i; + for(j=e.bamx; j<e.nbam; j++) + if(GET4(e.bam[4*j:])== Bfree){ + putbam(ftl, e, j, Bwriting); + ftl.nfree--; + e.nfree--; + e.bamx = j+1; + return (e.x<<16) | j; + } + e.nfree = 0; + print("ftl: unit %d:#%x nfree %d but not free in BAM\n", e.x, e.id, e.nfree); + } + if(++i >= ftl.nunit) + i = 0; + }while(i != ftl.lastx); + return 0; +} + +mapblk(ftl : ref Ftl, bno : int) : (int, ref Terase, int) +{ + v : int; + x : int; + + if(bno < ftl.nblock){ + v = ftl.vbm[bno]; + if(v == 0 || v == ~0) + return (0, nil, 0); + x = v>>16; + if(x >= ftl.nunit || x == ftl.xfer || ftl.unit[x] == nil){ + print("ftl: corrupt format: bad block mapping %d . unit #%x\n", bno, x); + return (0, nil, 0); + } + return (1, ftl.unit[x], v & 16rFFFF); + } + return (0, nil, 0); +} + +eraseinit(ftl : ref Ftl, offset : int, id : int, nerase : int) +{ + m : array of byte; + bam : array of byte; + i, nov : int; + + nov = ((ftl.segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize; # number of overhead blocks (header, and BAM itself) + if(nov*Bsize >= ftl.segsize) { + fprint(stderr, "ftl -- too small for files"); + exit; + } + eraseflash(ftl, offset); + m = array[ERASEHDRLEN] of byte; + if(m == nil) { + fprint(stderr, "nomem\n"); + exit; + } + memset(m, 16rFF, len m); + m[O_LINKTUPLE+0] = byte 16r13; + m[O_LINKTUPLE+1] = byte 16r3; + memmove(m[O_LINKTUPLE+2:], array of byte "CIS", 3); + m[O_ORGTUPLE+0] = byte 16r46; + m[O_ORGTUPLE+1] = byte 16r57; + m[O_ORGTUPLE+2] = byte 16r00; + memmove(m[O_ORGTUPLE+3:], array of byte "FTL100\0", 7); + m[O_NXFER] = byte 1; + PUT4(m[O_NERASE:], nerase); + PUT2(m[O_ID:], id); + m[O_BSHIFT] = byte ftl.bshift; + m[O_ESHIFT] = byte ftl.eshift; + PUT2(m[O_PSTART:], 0); + PUT2(m[O_NUNITS:], ftl.nunit); + PUT4(m[O_PSIZE:], ftl.size - nov*Bsize); + PUT4(m[O_VBMBASE:], -1); # we always calculate the VBM (16rffffffff) + PUT2(m[O_NVBM:], 0); + m[O_FLAGS] = byte 0; + m[O_CODE] = byte 16rFF; + memmove(m[O_SERIAL:], array of byte "Inf1", 4); + PUT4(m[O_ALTOFFSET:], 0); + PUT4(m[O_BAMOFFSET:], BAMoffset); + putflash(ftl, offset, m, ERASEHDRLEN); + m = nil; + if(id == XferID) + return; + nov *= 4; # now bytes of BAM + bam = array[nov] of byte; + if(bam == nil) { + fprint(stderr, "nomem"); + exit; + } + for(i=0; i<nov; i += 4) + PUT4(bam[i:], ControlBlock); # reserve them + putflash(ftl, offset+BAMoffset, bam, nov); + bam = nil; +} + +eraseload(ftl : ref Ftl, x : int, offset : int) : ref Terase +{ + m : array of byte; + e : ref Terase; + i, nbam : int; + bno, v : int; + + m = array[ERASEHDRLEN] of byte; + if(m == nil) { + fprint(stderr, "nomem"); + exit; + } + getflash(ftl, m, offset, ERASEHDRLEN); + if(memcmp(m[O_ORGTUPLE+3:], array of byte "FTL100\0", 7) != 0 || + memcmp(m[O_SERIAL:], array of byte "Inf1", 4) != 0){ + m = nil; + return nil; + } + e = ref Terase; + if(e == nil){ + m = nil; + fprint(stderr, "nomem"); + exit; + } + e.x = x; + e.id = GET2(m[O_ID:]); + e.offset = offset; + e.bamoffset = GET4(m[O_BAMOFFSET:]); + e.nerase = GET4(m[O_NERASE:]); + e.bamx = 0; + e.nfree = 0; + e.nused = 0; + e.ndead = 0; + e.nbad = 0; + m = nil; + if(e.bamoffset != BAMoffset){ + e = nil; + return nil; + } + e.bamoffset += offset; + if(e.id == XferID || e.id == XferBusy){ + e.bam = nil; + e.nbam = 0; + return e; + } + nbam = ftl.segsize/Bsize; + e.bam = array[4*nbam] of byte; + e.nbam = nbam; + getflash(ftl, e.bam, e.bamoffset, nbam*4); + # scan BAM to build VBM + e.bamx = 0; + for(i=0; i<nbam; i++){ + v = GET4(e.bam[4*i:]); + if(v == Bwriting || v == Bdeleted) + e.ndead++; + else if(v == Bfree){ + if(e.bamx == 0) + e.bamx = i; + e.nfree++; + ftl.nfree++; + }else{ + case(v & BlockType){ + ControlBlock => + break; + DataBlock => + # add to VBM + if(v & (1<<31)) + break; # negative => VBM page, ignored + bno = BNO(v & ~BlockType); + if(i < ftl.fstart || bno >= ftl.nblock){ + print("ftl: unit %d:#%x bad bam[%d]=#%x\n", e.x, e.id, i, v); + e.nbad++; + break; + } + ftl.vbm[bno] = (e.x<<16) | i; + e.nused++; + break; + ReplacePage => + # replacement VBM page; ignored + break; + BadBlock => + e.nbad++; + break; + * => + print("ftl: unit %d:#%x bad bam[%d]=%x\n", e.x, e.id, i, v); + } + } + } + return e; +} + +erasefree(e : ref Terase) +{ + e.bam = nil; + e = nil; +} + +eraseflash(ftl : ref Ftl, offset : int) +{ + offset += ftl.base; + if(FTLDEBUG || ftl.trace || trace) + print("ftl: erase seg @#%x\n", offset); + memset(flashm[offset:], 16rff, secsize); +} + +putflash(ftl : ref Ftl, offset : int, buf : array of byte, n : int) +{ + offset += ftl.base; + if(ftl.trace || trace) + print("ftl: write(#%x, %d)\n", offset, n); + memmove(flashm[offset:], buf, n); +} + +getflash(ftl : ref Ftl, buf : array of byte, offset : int, n : int) +{ + offset += ftl.base; + if(ftl.trace || trace) + print("ftl: read(#%x, %d)\n", offset, n); + memmove(buf, flashm[offset:], n); +} + +BUFSIZE : con 8192; + +main(argv : list of string) +{ + k, r, sz, offset : int = 0; + buf, buf1 : array of byte; + fd1, fd2 : ref FD; + + if (len argv != 5) { + fprint(stderr, "usage: %s flashsize secsize kfsfile flashfile\n", hd argv); + exit; + } + flashsize = atoi(hd tl argv); + secsize = atoi(hd tl tl argv); + fd1 = open(hd tl tl tl argv, OREAD); + fd2 = create(hd tl tl tl tl argv, OWRITE, 8r644); + if (fd1 == nil || fd2 == nil) { + fprint(stderr, "bad io files\n"); + exit; + } + if(secsize == 0 || secsize > flashsize || secsize&(secsize-1) || 0&(secsize-1) || flashsize == 0 || flashsize != Nolimit && flashsize&(secsize-1)) { + fprint(stderr, "ftl: bad sizes\n"); + exit; + } + for(k=0; k<32 && (1<<k) != secsize; k++) + ; + flashm = array[flashsize] of byte; + buf = array[BUFSIZE] of byte; + if (flashm == nil) { + fprint(stderr, "ftl: no mem for flash\n"); + exit; + } + ftls = mkftl("FLASH", 0, Nolimit, k, "format"); + for (;;) { + r = read(fd1, buf, BUFSIZE); + if (r <= 0) + break; + if (ftlwrite(buf, r, offset) != r) { + fprint(stderr, "ftl: ftlwrite failed - input file too big\n"); + exit; + } + offset += r; + } + write(fd2, flashm, flashsize); + fd1 = fd2 = nil; + ftlstat(offset); + # ftls = mkftl("FLASH", 0, Nolimit, k, "init"); + sz = offset; + offset = 0; + buf1 = array[BUFSIZE] of byte; + fd1 = open(hd tl tl tl argv, OREAD); + for (;;) { + r = read(fd1, buf1, BUFSIZE); + if (r <= 0) + break; + if (ftlread(buf, r, offset) != r) { + fprint(stderr, "ftl: ftlread failed\n"); + exit; + } + if (memcmp(buf, buf1, r) != 0) { + fprint(stderr, "ftl: bad read\n"); + exit; + } + offset += r; + } + fd1 = nil; + if (offset != sz) { + fprint(stderr, "ftl: bad final offset\n"); + exit; + } + exit; +} + +init(nil : ref Draw->Context, argl : list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + main(argl); +} + +memset(d : array of byte, v : int, n : int) +{ + for (i := 0; i < n; i++) + d[i] = byte v; +} + +memmove(d : array of byte, s : array of byte, n : int) +{ + d[0:] = s[0:n]; +} + +memcmp(s1 : array of byte, s2 : array of byte, n : int) : int +{ + for (i := 0; i < n; i++) { + if (s1[i] < s2[i]) + return -1; + if (s1[i] > s2[i]) + return 1; + } + return 0; +} + +atoi(s : string) : int +{ + v : int; + base := 10; + n := len s; + neg := 0; + + for (i := 0; i < n && (s[i] == ' ' || s[i] == '\t'); i++) + ; + if (s[i] == '+' || s[i] == '-') { + if (s[i] == '-') + neg = 1; + i++; + } + if (n-i >= 2 && s[i] == '0' && s[i+1] == 'x') { + base = 16; + i += 2; + } + else if (n-i >= 1 && s[i] == '0') { + base = 8; + i++; + } + m := 0; + for(; i < n; i++) { + c := s[i]; + case c { + 'a' to 'z' => + v = c - 'a' + 10; + 'A' to 'Z' => + v = c - 'A' + 10; + '0' to '9' => + v = c - '0'; + * => + fprint(stderr, "ftl: bad character in number %s\n", s); + exit; + } + if(v >= base) { + fprint(stderr, "ftl: character too big for base in %s\n", s); + exit; + } + m = m * base + v; + } + if(neg) + m = -m; + return m; +} + +# little endian + +GET2(b : array of byte) : int +{ + return ((int b[1]) << 8) | (int b[0]); +} + +GET4(b : array of byte) : int +{ + return ((int b[3]) << 24) | ((int b[2]) << 16) | ((int b[1]) << 8) | (int b[0]); +} + +PUT2(b : array of byte, v : int) +{ + b[1] = byte (v>>8); + b[0] = byte v; +} + +PUT4(b : array of byte, v : int) +{ + b[3] = byte (v>>24); + b[2] = byte (v>>16); + b[1] = byte (v>>8); + b[0] = byte v; +} diff --git a/appl/cmd/disk/kfs.b b/appl/cmd/disk/kfs.b new file mode 100644 index 00000000..56440205 --- /dev/null +++ b/appl/cmd/disk/kfs.b @@ -0,0 +1,3842 @@ +implement Kfs; + +# +# Copyright © 1991-2003 Lucent Technologies Inc. +# Limbo version Copyright © 2004 Vita Nuova Holdings Limited +# + +# +# TO DO: +# - sync proc; Bmod; process structure +# - swiz? + +include "sys.m"; + sys: Sys; + Qid, Dir: import Sys; + DMEXCL, DMAPPEND, DMDIR: import Sys; + QTEXCL, QTAPPEND, QTDIR: import Sys; + +include "draw.m"; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + NOFID, OEXEC, ORCLOSE, OREAD, OWRITE, ORDWR, OTRUNC: import Styx; + IOHDRSZ: import Styx; + +include "daytime.m"; + daytime: Daytime; + now: import daytime; + +include "arg.m"; + +Kfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +MAXBUFSIZE: con 16*1024; + +# +# fundamental constants +# +NAMELEN: con 28; # size of names, including null byte +NDBLOCK: con 6; # number of direct blocks in Dentry +MAXFILESIZE: con big 16r7FFFFFFF; # Plan 9's limit (kfs's size is signed) + +SUPERADDR: con 1; +ROOTADDR: con 2; + +QPDIR: con int (1<<31); +QPNONE: con 0; +QPROOT: con 1; +QPSUPER: con 2; + +# +# don't change, these are the mode bits on disc +# +DALLOC: con 16r8000; +DDIR: con 16r4000; +DAPND: con 16r2000; +DLOCK: con 16r1000; +DREAD: con 4; +DWRITE: con 2; +DEXEC: con 1; + +# +# other constants +# + +MINUTE: con 60; +TLOCK: con 5*MINUTE; +NTLOCK: con 200; # number of active file locks + +Buffering: con 1; + +FID1, FID2, FID3: con 1+iota; + +None: con 0; # user ID for "none" +Noworld: con 9999; # conventional id for "noworld" group + +Lock: adt +{ + c: chan of int; + new: fn(): ref Lock; + lock: fn(c: self ref Lock); + canlock: fn(c: self ref Lock): int; + unlock: fn(c: self ref Lock); +}; + +Dentry: adt +{ + name: string; + uid: int; + gid: int; + muid: int; # not set by plan 9's kfs + mode: int; # mode bits on disc: DALLOC etc + qid: Qid; # 9p1 format on disc + size: big; # only 32-bits on disc, and Plan 9 limits it to signed + atime: int; + mtime: int; + + iob: ref Iobuf; # locked block containing directory entry, when in memory + buf: array of byte; # pointer into block to packed directory entry, when in memory + mod: int; # bits of buf that need updating + + unpack: fn(a: array of byte): ref Dentry; + get: fn(p: ref Iobuf, slot: int): ref Dentry; + geta: fn(d: ref Device, addr: int, slot: int, qpath: int, mode: int): (ref Dentry, string); + getd: fn(f: ref File, mode: int): (ref Dentry, string); + put: fn(d: self ref Dentry); + access: fn(d: self ref Dentry, f: int, uid: int); + change: fn(d: self ref Dentry, f: int); + release: fn(d: self ref Dentry); + getblk: fn(d: self ref Dentry, a: int, tag: int): ref Iobuf; + getblk1: fn(d: self ref Dentry, a: int, tag: int): ref Iobuf; + rel2abs: fn(d: self ref Dentry, a: int, tag: int, putb: int): int; + trunc: fn(d: self ref Dentry, uid: int); + update: fn(d: self ref Dentry); + print: fn(d: self ref Dentry); +}; + +Uname, Uids, Umode, Uqid, Usize, Utime: con 1<<iota; # Dentry.mod + +# +# disc structure: +# Tag: pad[2] tag[2] path[4] +Tagsize: con 2+2+4; + +Tag: adt +{ + tag: int; + path: int; + + unpack: fn(a: array of byte): Tag; + pack: fn(t: self Tag, a: array of byte); +}; + +Superb: adt +{ + iob: ref Iobuf; + + fstart: int; + fsize: int; + tfree: int; + qidgen: int; # generator for unique ids + + fsok: int; + + fbuf: array of byte; # nfree[4] free[FEPERBLK*4]; aliased into containing block + + get: fn(dev: ref Device, flags: int): ref Superb; + touched: fn(s: self ref Superb); + put: fn(s: self ref Superb); + print: fn(s: self ref Superb); + + pack: fn(s: self ref Superb, a: array of byte); + unpack: fn(a: array of byte): ref Superb; +}; + +Device: adt +{ + fd: ref Sys->FD; + ronly: int; + # could put locks here if necessary + # partitioning by ds(3) +}; + +# +# one for each locked qid +# +Tlock: adt +{ + dev: ref Device; + time: int; + qpath: int; + file: cyclic ref File; # TO DO: probably not needed +}; + +File: adt +{ + qlock: chan of int; + qid: Qid; + wpath: ref Wpath; + tlock: cyclic ref Tlock; # if file is locked + fs: ref Device; + addr: int; + slot: int; + lastra: int; # read ahead address + fid: int; + uid: int; + open: int; + cons: int; # if opened by console + doffset: big; # directory reading + dvers: int; + dslot: int; + + new: fn(fid: int): ref File; + access: fn(f: self ref File, d: ref Dentry, mode: int): int; + lock: fn(f: self ref File); + unlock: fn(f: self ref File); +}; + +FREAD, FWRITE, FREMOV, FWSTAT: con 1<<iota; # File.open + +Chan: adt +{ + fd: ref Sys->FD; # fd request came in on +# rlock, wlock: QLock; # lock for reading/writing messages on cp + flags: int; + flist: list of ref File; # active files + fqlock: chan of int; +# reflock: RWLock; # lock for Tflush + msize: int; # version + + new: fn(fd: ref Sys->FD): ref Chan; + getfid: fn(c: self ref Chan, fid: int, flag: int): ref File; + putfid: fn(c: self ref Chan, f: ref File); + flock: fn(nil: self ref Chan); + funlock: fn(nil: self ref Chan); +}; + +Hiob: adt +{ + link: ref Iobuf; # TO DO: eliminate circular list + lk: ref Lock; + niob: int; + + newbuf: fn(h: self ref Hiob): ref Iobuf; +}; + +Iobuf: adt +{ + qlock: chan of int; + dev: ref Device; + fore: cyclic ref Iobuf; # lru hash chain + back: cyclic ref Iobuf; # for lru + iobuf: array of byte; # only active while locked + xiobuf: array of byte; # "real" buffer pointer + addr: int; + flags: int; + + get: fn(dev: ref Device, addr: int, flags: int):ref Iobuf; + put: fn(iob: self ref Iobuf); + lock: fn(iob: self ref Iobuf); + canlock: fn(iob: self ref Iobuf): int; + unlock: fn(iob: self ref Iobuf); + + checktag: fn(iob: self ref Iobuf, tag: int, qpath: int): int; + settag: fn(iob: self ref Iobuf, tag: int, qpath: int); +}; + +Wpath: adt +{ + up: cyclic ref Wpath; # pointer upwards in path + addr: int; # directory entry addr + slot: int; # directory entry slot +}; + +# +# error codes generated from the file server +# +Eaccess: con "access permission denied"; +Ealloc: con "phase error -- directory entry not allocated"; +Eauth: con "authentication failed"; +Eauthmsg: con "kfs: authentication not required"; +Ebadspc: con "attach -- bad specifier"; +Ebadu: con "attach -- privileged user"; +Ebroken: con "close/read/write -- lock is broken"; +Echar: con "bad character in directory name"; +Econvert: con "protocol botch"; +Ecount: con "read/write -- count too big"; +Edir1: con "walk -- in a non-directory"; +Edir2: con "create -- in a non-directory"; +Edot: con "create -- . and .. illegal names"; +Eempty: con "remove -- directory not empty"; +Eentry: con "directory entry not found"; +Eexist: con "create -- file exists"; +Efid: con "unknown fid"; +Efidinuse: con "fid already in use"; +Efull: con "file system full"; +Elocked: con "open/create -- file is locked"; +Emode: con "open/create -- unknown mode"; +Ename: con "create/wstat -- bad character in file name"; +Enotd: con "wstat -- attempt to change directory"; +Enotg: con "wstat -- not in group"; +Enotl: con "wstat -- attempt to change length"; +Enotm: con "wstat -- unknown type/mode"; +Enotu: con "wstat -- not owner"; +Eoffset: con "read/write -- offset negative"; +Eopen: con "read/write -- on non open fid"; +Ephase: con "phase error -- cannot happen"; +Eqid: con "phase error -- qid does not match"; +Eqidmode: con "wstat -- qid.qtype/dir.mode mismatch"; +Eronly: con "file system read only"; +Ersc: con "it's russ's fault. bug him."; +Esystem: con "kfs system error"; +Etoolong: con "name too long"; +Etoobig: con "write -- file size limit"; +Ewalk: con "walk -- too many (system wide)"; + +# +# tags on block +# +Tnone, +Tsuper, # the super block +Tdir, # directory contents +Tind1, # points to blocks +Tind2, # points to Tind1 +Tfile, # file contents +Tfree, # in free list +Tbuck, # cache fs bucket +Tvirgo, # fake worm virgin bits +Tcache, # cw cache things +MAXTAG: con iota; + +# +# flags to Iobuf.get +# + Bread, # read the block if miss + Bprobe, # return null if miss + Bmod, # set modified bit in buffer + Bimm, # set immediate bit in buffer + Bres: # never renamed + con 1<<iota; + +# +# check flags +# + Crdall, # read all files + Ctag, # rebuild tags + Cpfile, # print files + Cpdir, # print directories + Cfree, # rebuild free list + Cream, # clear all bad tags + Cbad, # clear all bad blocks + Ctouch, # touch old dir and indir + Cquiet: # report just nasty things + con 1<<iota; + +# +# buffer size variables, determined by RBUFSIZE +# +RBUFSIZE: int; +BUFSIZE: int; +DIRPERBUF: int; +INDPERBUF: int; +INDPERBUF2: int; +FEPERBUF: int; + +emptyblock: array of byte; + +wrenfd: ref Sys->FD; +thedevice: ref Device; +devnone: ref Device; +wstatallow := 0; +writeallow := 0; +writegroup := 0; + +ream := 0; +readonly := 0; +noatime := 0; +localfs: con 1; +conschan: ref Chan; +consuid := -1; +consgid := -1; +debug := 0; +kfsname: string; +consoleout: chan of string; +mainlock: ref Lock; +pids: list of int; + +noqid: Qid; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + styx = load Styx Styx->PATH; + daytime = load Daytime Daytime->PATH; + + styx->init(); + + + arg := load Arg Arg->PATH; + if(arg == nil) + error(sys->sprint("can't load %s: %r", Arg->PATH)); + arg->init(args); + arg->setusage("disk/kfs [-r [-b bufsize]] [-cADPRW] [-n name] kfsfile"); + bufsize := 1024; + nocheck := 0; + while((o := arg->opt()) != 0) + case o { + 'c' => nocheck = 1; + 'r' => ream = 1; + 'b' => bufsize = int arg->earg(); + 'D' => debug = !debug; + 'P' => writeallow = 1; + 'W' => wstatallow = 1; + 'R' => readonly = 1; + 'A' => noatime = 1; # mainly useful for flash + 'n' => kfsname = arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + devnone = ref Device(nil, 1); + mainlock = Lock.new(); + + conschan = Chan.new(nil); + conschan.msize = Styx->MAXRPC; + + mode := Sys->ORDWR; + if(readonly) + mode = Sys->OREAD; + wrenfd = sys->open(hd args, mode); + if(wrenfd == nil) + error(sys->sprint("can't open %s: %r", hd args)); + thedevice = ref Device(wrenfd, readonly); + if(ream){ + if(bufsize <= 0 || bufsize % 512 || bufsize > MAXBUFSIZE) + error(sys->sprint("invalid block size %d", bufsize)); + RBUFSIZE = bufsize; + wrenream(thedevice); + }else{ + if(!wreninit(thedevice)) + error("kfs magic in trouble"); + } + BUFSIZE = RBUFSIZE - Tagsize; + DIRPERBUF = BUFSIZE / Dentrysize; + INDPERBUF = BUFSIZE / 4; + INDPERBUF2 = INDPERBUF * INDPERBUF; + FEPERBUF = (BUFSIZE - Super1size - 4) / 4; + emptyblock = array[RBUFSIZE] of {* => byte 0}; + + iobufinit(30); + + if(ream){ + superream(thedevice, SUPERADDR); + rootream(thedevice, ROOTADDR); + wstatallow = writeallow = 1; + } + if(wrencheck(wrenfd)) + error("kfs super/root in trouble"); + + if(!ream && !superok(0)){ + sys->print("kfs needs check\n"); + if(!nocheck) + check(thedevice, Cquiet|Cfree); + } + + (d, e) := Dentry.geta(thedevice, ROOTADDR, 0, QPROOT, Bread); + if(d != nil && !(d.mode & DDIR)) + e = "not a directory"; + if(e != nil) + error("bad root: "+e); + if(debug) + d.print(); + d.put(); + + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + + sys->pctl(Sys->NEWFD, wrenfd.fd :: 0 :: 1 :: 2 :: nil); + wrenfd = sys->fildes(wrenfd.fd); + thedevice.fd = wrenfd; + + c := chan of int; + + if(Buffering){ + spawn syncproc(c); + pid := <-c; + if(pid) + pids = pid :: pids; + } + spawn consinit(c); + pid := <- c; + if(pid) + pids = pid :: pids; + + spawn kfs(sys->fildes(0)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "kfs: %s\n", s); + for(; pids != nil; pids = tl pids) + kill(hd pids); + raise "fail:error"; +} + +panic(s: string) +{ + sys->fprint(sys->fildes(2), "kfs: panic: %s\n", s); + for(; pids != nil; pids = tl pids) + kill(hd pids); + raise "panic"; +} + +syncproc(c: chan of int) +{ + c <-= 0; +} + +shutdown() +{ + for(; pids != nil; pids = tl pids) + kill(hd pids); + # TO DO: when Bmod deferred, must sync + # sync super block + if(superok(1)){ + # ; + } + iobufclear(); +} + +kill(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +# +# limited file system support for console +# +kattach(fid: int): string +{ + return applycons(ref Tmsg.Attach(1, fid, NOFID, "adm", "")).t1; +} + +kopen(oldfid: int, newfid: int, names: array of string, mode: int): string +{ + (r1, e1) := applycons(ref Tmsg.Walk(1, oldfid, newfid, names)); + if(r1 != nil){ + pick m := r1 { + Walk => + if(len m.qids != len names){ + kclose(newfid); + cprint(Eexist); + return Eexist; + } + * => + return "unexpected reply"; + } + (r1, e1) = applycons(ref Tmsg.Open(1, newfid, mode)); + if(e1 != nil){ + kclose(newfid); + cprint(sys->sprint("open: %s", e1)); + } + } + return e1; +} + +kread(fid: int, offset: int, nbytes: int): (array of byte, string) +{ + (r, e) := applycons(ref Tmsg.Read(1, fid, big offset, nbytes)); + if(r != nil){ + pick m := r { + Read => + return (m.data, nil); + * => + return (nil, "unexpected reply"); + } + } + cprint(sys->sprint("read error: %s", e)); + return (nil, e); +} + +kclose(fid: int) +{ + applycons(ref Tmsg.Clunk(1, fid)); +} + +applycons(t: ref Tmsg): (ref Rmsg, string) +{ + r := apply(conschan, t); + pick m := r { + Error => + if(debug) + cprint(sys->sprint("%s: %s\n", t.text(), m.ename)); + return (nil, m.ename); + } + return (r, nil); +} + +# +# always reads /adm/users in userinit(), then +# optionally serves the command file, if used. +# +Req: adt { + nbytes: int; + rc: chan of (array of byte, string); +}; + +consinit(c: chan of int) +{ + kattach(FID1); + userinit(); + if(kfsname == nil){ + c <-= 0; + exit; + } + cfname := "kfs."+kfsname+".cmd"; + sys->bind("#s", "/chan", Sys->MBEFORE); + file := sys->file2chan("/chan", cfname); + if(file == nil) + error(sys->sprint("can't create /chan/%s: %r", cfname)); + c <-= sys->pctl(0, nil); + consc := chan of string; + checkend := chan of int; + cdata: array of byte; + pending: ref Req; + cfid := -1; + for(;;) alt{ + (nil, nbytes, fid, rc) := <-file.read => + if(rc == nil) + break; + if(cfid == -1) + cfid = fid; + if(fid != cfid || pending != nil){ + rc <-= (nil, "kfs.cmd is busy"); + break; + } + if(cdata != nil){ + cdata = reply(rc, nbytes, cdata); + break; + } + if(nbytes <= 0 || consoleout == nil){ + rc <-= (nil, nil); + break; + } + pending = ref Req(nbytes, rc); + consc = consoleout; + (nil, data, fid, wc) := <-file.write => + if(cfid == -1) + cfid = fid; + if(wc == nil){ + if(fid == cfid){ + cfid = -1; + pending = nil; + cdata = nil; # discard unread data from last command + if((consc = consoleout) == nil) + consc = chan of string; + } + break; + } + if(fid != cfid){ + wc <-= (0, "kfs.cmd is busy"); + break; + } + (nf, fld) := sys->tokenize(string data, " \t\n\r"); + if(nf < 1){ + wc <-= (0, "illegal kfs request"); + break; + } + case hd fld { + "check" => + if(consoleout != nil){ + wc <-= (0, "check in progress"); + break; + } + f := 0; + if(nf > 1){ + f = checkflags(hd tl fld); + if(f < 0){ + wc <-= (0, "illegal check flag: "+hd tl fld); + break; + } + } + consoleout = chan of string; + spawn checkproc(checkend, f); + wc <-= (len data, nil); + consc = consoleout; + "users" or "user" => + cmd_users(); + wc <-= (len data, nil); + "sync" => + # nothing TO DO until writes are buffered + wc <-= (len data, nil); + "allow" => + wstatallow = writeallow = 1; + wc <-= (len data, nil); + "allowoff" or "disallow" => + wstatallow = writeallow = 0; + wc <-= (len data, nil); + * => + wc <-= (0, "unknown kfs request"); + continue; + } + <-checkend => + consoleout = nil; + consc = chan of string; + s := <-consc => + #sys->print("<-%s\n", s); + req := pending; + pending = nil; + if(req != nil) + cdata = reply(req.rc, req.nbytes, array of byte s); + else + cdata = array of byte s; + if(cdata != nil && cfid != -1) + consc = chan of string; + } +} + +reply(rc: chan of (array of byte, string), nbytes: int, a: array of byte): array of byte +{ + if(len a < nbytes) + nbytes = len a; + rc <-= (a[0:nbytes], nil); + if(nbytes == len a) + return nil; + return a[nbytes:]; +} + +checkproc(c: chan of int, flags: int) +{ + mainlock.lock(); + check(thedevice, flags); + mainlock.unlock(); + c <-= 1; +} + +# +# normal kfs service +# +kfs(rfd: ref Sys->FD) +{ + cp := Chan.new(rfd); + while((t := Tmsg.read(rfd, cp.msize)) != nil){ + if(debug) + sys->print("<- %s\n", t.text()); + r := apply(cp, t); + pick m := r { + Error => + r.tag = t.tag; + } + if(debug) + sys->print("-> %s\n", r.text()); + rbuf := r.pack(); + if(rbuf == nil) + panic("Rmsg.pack"); + if(sys->write(rfd, rbuf, len rbuf) != len rbuf) + panic("mount write"); + } + shutdown(); +} + +apply(cp: ref Chan, t: ref Tmsg): ref Rmsg +{ + mainlock.lock(); # TO DO: this is just to keep console and kfs from colliding + r: ref Rmsg; + pick m := t { + Readerror => + error(sys->sprint("mount read error: %s", m.error)); + Version => + r = rversion(cp, m); + Auth => + r = rauth(cp, m); + Flush => + r = rflush(cp, m); + Attach => + r = rattach(cp, m); + Walk => + r = rwalk(cp, m); + Open => + r = ropen(cp, m); + Create => + r = rcreate(cp, m); + Read => + r = rread(cp, m); + Write => + r = rwrite(cp, m); + Clunk => + r = rclunk(cp, m); + Remove => + r = rremove(cp, m); + Stat => + r = rstat(cp, m); + Wstat => + r = rwstat(cp, m); + * => + panic("Styx mtype"); + return nil; + } + mainlock.unlock(); + return r; +} + +rversion(cp: ref Chan, t: ref Tmsg.Version): ref Rmsg +{ + cp.msize = RBUFSIZE+IOHDRSZ; + if(cp.msize < Styx->MAXRPC) + cp.msize = Styx->MAXRPC; + (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION); + if(msize < 256) + return ref Rmsg.Error(t.tag, "message size too small"); + return ref Rmsg.Version(t.tag, msize, version); +} + +rauth(nil: ref Chan, t: ref Tmsg.Auth): ref Rmsg +{ + return ref Rmsg.Error(t.tag, Eauthmsg); +} + +rflush(nil: ref Chan, t: ref Tmsg.Flush): ref Rmsg +{ + # runlock(cp.reflock); + # wlock(cp.reflock); + # wunlock(cp.reflock); + # rlock(cp.reflock); + return ref Rmsg.Flush(t.tag); +} + +err(t: ref Tmsg, s: string): ref Rmsg.Error +{ + return ref Rmsg.Error(t.tag, s); +} + +ferr(t: ref Tmsg, s: string, file: ref File, p: ref Iobuf): ref Rmsg.Error +{ + if(p != nil) + p.put(); + if(file != nil) + file.unlock(); + return ref Rmsg.Error(t.tag, s); +} + +File.new(fid: int): ref File +{ + f := ref File; + f.qlock = chan[1] of int; + f.fid = fid; + f.cons = 0; + f.tlock = nil; + f.wpath = nil; + f.doffset = big 0; + f.dvers = 0; + f.dslot = 0; + f.uid = None; + f.cons = 0; +# f.cuid = None; + return f; +} + +# +# returns a locked file structure +# + +Chan.getfid(cp: self ref Chan, fid: int, flag: int): ref File +{ + if(fid == NOFID) + return nil; + cp.flock(); + for(l := cp.flist; l != nil; l = tl l){ + f := hd l; + if(f.fid == fid){ + cp.funlock(); + if(flag) + return nil; # fid in use + f.lock(); + if(f.fid == fid) + return f; + f.unlock(); + cp.flock(); + } + } + if(flag == 0){ + sys->print("kfs: cannot find %H.%ud", cp, fid); + cp.funlock(); + return nil; + } + f := File.new(fid); + f.lock(); + cp.flist = f :: cp.flist; + cp.funlock(); + return f; +} + +Chan.putfid(cp: self ref Chan, f: ref File) +{ + cp.flock(); + nl: list of ref File; + for(x := cp.flist; x != nil; x = tl x) + if(hd x != f) + nl = hd x :: nl; + cp.flist = nl; + cp.funlock(); + f.unlock(); +} + +File.lock(f: self ref File) +{ + f.qlock <-= 1; +} + +File.unlock(f: self ref File) +{ + <-f.qlock; +} + +Chan.new(fd: ref Sys->FD): ref Chan +{ + c := ref Chan; + c.fd = fd; + c.fqlock = chan[1] of int; +# rlock, wlock: QLock; # lock for reading/writing messages on cp + c.flags = 0; +# reflock: RWLock; # lock for Tflush + c.msize = 0; # set by rversion + return c; +} + +Chan.flock(c: self ref Chan) +{ + c.fqlock <-= 1; +} + +Chan.funlock(c: self ref Chan) +{ + <-c.fqlock; +} + +rattach(cp: ref Chan, t: ref Tmsg.Attach): ref Rmsg +{ + if(t.aname != "" && t.aname != "main") + return err(t, Ebadspc); + file := cp.getfid(t.fid, 1); + if(file == nil) + return err(t, Efidinuse); + p := Iobuf.get(thedevice, ROOTADDR, Bread); + if(p == nil){ + cp.putfid(file); + return err(t, "can't access root block"); + } + d := Dentry.get(p, 0); + if(d == nil || p.checktag(Tdir, QPROOT) || (d.mode & DALLOC) == 0 || (d.mode & DDIR) == 0){ + p.put(); + cp.putfid(file); + return err(t, Ealloc); + } + if(file.access(d, DEXEC)){ + p.put(); + cp.putfid(file); + return err(t, Eaccess); + } + d.access(FREAD, file.uid); + file.fs = thedevice; + file.qid = d.qid; + file.addr = p.addr; + file.slot = 0; + file.open = 0; + file.uid = strtouid(t.uname); + file.wpath = nil; + p.put(); + qid := file.qid; + file.unlock(); + return ref Rmsg.Attach(t.tag, qid); +} + +clone(nfile: ref File, file: ref File) +{ + nfile.qid = file.qid; + nfile.wpath = file.wpath; + nfile.fs = file.fs; + nfile.addr = file.addr; + nfile.slot = file.slot; + nfile.uid = file.uid; +# nfile.cuid = None; + nfile.open = file.open & ~FREMOV; +} + +walkname(file: ref File, wname: string): (string, Qid) +{ + # + # File must not have been opened for I/O by an open + # or create message and must represent a directory. + # + if(file.open != 0) + return (Emode, noqid); + + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return (e, noqid); + if(!(d.mode & DDIR)){ + d.put(); + return (Edir1, noqid); + } + + # + # For walked elements the implied user must + # have permission to search the directory. + # + if(file.access(d, DEXEC)){ + d.put(); + return (Eaccess, noqid); + } + d.access(FREAD, file.uid); + + if(wname == "." || wname == ".." && file.wpath == nil){ + d.put(); + return (nil, file.qid); + } + + d1: ref Dentry; # entry for wname, if found + slot: int; + + if(wname == ".."){ + d.put(); + addr := file.wpath.addr; + slot = file.wpath.slot; + (d1, e) = Dentry.geta(file.fs, addr, slot, QPNONE, Bread); + if(d1 == nil) + return (e, noqid); + file.wpath = file.wpath.up; + }else{ + + Search: + for(addr := 0; ; addr++){ + if(d.iob == nil){ + (d, e) = Dentry.getd(file, Bread); + if(d == nil) + return (e, noqid); + } + p1 := d.getblk1(addr, 0); + if(p1 == nil || p1.checktag(Tdir, int d.qid.path)){ + if(p1 != nil) + p1.put(); + return (Eentry, noqid); + } + for(slot = 0; slot < DIRPERBUF; slot++){ + d1 = Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)) + continue; + if(wname != d1.name) + continue; + # + # update walk path + # + file.wpath = ref Wpath(file.wpath, file.addr, file.slot); + slot += DIRPERBUF*addr; + break Search; + } + p1.put(); + } + d.put(); + } + + file.addr = d1.iob.addr; + file.slot = slot; + file.qid = d1.qid; + d1.put(); + return (nil, file.qid); +} + +rwalk(cp: ref Chan, t: ref Tmsg.Walk): ref Rmsg +{ + nfile, tfile: ref File; + q: Qid; + + # The file identified by t.fid must be valid in the + # current session and must not have been opened for I/O + # by an open or create message. + + if((file := cp.getfid(t.fid, 0)) == nil) + return err(t, Efid); + if(file.open != 0) + return ferr(t, Emode, file, nil); + + # If newfid is not the same as fid, allocate a new file; + # a side effect is checking newfid is not already in use (error); + # if there are no names to walk this will be equivalent to a + # simple 'clone' operation. + # Otherwise, fid and newfid are the same and if there are names + # to walk make a copy of 'file' to be used during the walk as + # 'file' must only be updated on success. + # Finally, it's a no-op if newfid is the same as fid and t.nwname + # is 0. + + nwqid := 0; + if(t.newfid != t.fid){ + if((nfile = cp.getfid(t.newfid, 1)) == nil) + return ferr(t, Efidinuse, file, nil); + } + else if(len t.names != 0) + nfile = tfile = File.new(NOFID); + else{ + file.unlock(); + return ref Rmsg.Walk(t.tag, nil); + } + clone(nfile, file); + + r := ref Rmsg.Walk(t.tag, array[len t.names] of Qid); + error: string; + for(nwname := 0; nwname < len t.names; nwname++){ + (error, q) = walkname(nfile, t.names[nwname]); + if(error != nil) + break; + r.qids[nwqid++] = q; + } + + if(len t.names == 0){ + + # Newfid must be different to fid (see above) + # so this is a simple 'clone' operation - there's + # nothing to do except unlock unless there's + # an error. + + nfile.unlock(); + if(error != nil) + cp.putfid(nfile); + }else if(nwqid < len t.names){ + # + # Didn't walk all elements, 'clunk' nfile + # and leave 'file' alone. + # Clear error if some of the elements were + # walked OK. + # + if(nfile != tfile) + cp.putfid(nfile); + if(nwqid != 0) + error = nil; + r.qids = r.qids[0:nwqid]; + }else{ + # + # Walked all elements. If newfid is the same + # as fid must update 'file' from the temporary + # copy used during the walk. + # Otherwise just unlock (when using tfile there's + # no need to unlock as it's a local). + # + if(nfile == tfile){ + file.qid = nfile.qid; + file.wpath = nfile.wpath; + file.addr = nfile.addr; + file.slot = nfile.slot; + }else + nfile.unlock(); + } + file.unlock(); + + if(error != nil) + return err(t, error); + return r; +} + +ropen(cp: ref Chan, f: ref Tmsg.Open): ref Rmsg +{ + wok := cp == conschan || writeallow; + + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + + # + # if remove on close, check access here + # + ro := isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup)); + if(f.mode & ORCLOSE){ + if(ro) + return ferr(f, Eronly, file, nil); + # + # check on parent directory of file to be deleted + # + if(file.wpath == nil || file.wpath.addr == file.addr) + return ferr(f, Ephase, file, nil); + p := Iobuf.get(file.fs, file.wpath.addr, Bread); + if(p == nil || p.checktag(Tdir, QPNONE)) + return ferr(f, Ephase, file, p); + if((d := Dentry.get(p, file.wpath.slot)) == nil || !(d.mode & DALLOC)) + return ferr(f, Ephase, file, p); + if(file.access(d, DWRITE)) + return ferr(f, Eaccess, file, p); + p.put(); + } + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + p := d.iob; + qid := d.qid; + fmod: int; + case f.mode & 7 { + + OREAD => + if(file.access(d, DREAD) && !wok) + return ferr(f, Eaccess, file, p); + fmod = FREAD; + + OWRITE => + if((d.mode & DDIR) || (file.access(d, DWRITE) && !wok)) + return ferr(f, Eaccess, file, p); + if(ro) + return ferr(f, Eronly, file, p); + fmod = FWRITE; + + ORDWR => + if((d.mode & DDIR) + || (file.access(d, DREAD) && !wok) + || (file.access(d, DWRITE) && !wok)) + return ferr(f, Eaccess, file, p); + if(ro) + return ferr(f, Eronly, file, p); + fmod = FREAD+FWRITE; + + OEXEC => + if((d.mode & DDIR) || (file.access(d, DEXEC) && !wok)) + return ferr(f, Eaccess, file, p); + fmod = FREAD; + + * => + return ferr(f, Emode, file, p); + } + if(f.mode & OTRUNC){ + if((d.mode & DDIR) || (file.access(d, DWRITE) && !wok)) + return ferr(f, Eaccess, file, p); + if(ro) + return ferr(f, Eronly, file, p); + } + if(d.mode & DLOCK){ + if((t := tlocked(file, d)) == nil) + return ferr(f, Elocked, file, p); + file.tlock = t; + t.file = file; + } + if(f.mode & ORCLOSE) + fmod |= FREMOV; + file.open = fmod; + if((f.mode & OTRUNC) && !(d.mode & DAPND)){ + d.trunc(file.uid); + qid.vers = d.qid.vers; + } + file.lastra = 1; + p.put(); + file.unlock(); + return ref Rmsg.Open(f.tag, qid, cp.msize-IOHDRSZ); +} + +rcreate(cp: ref Chan, f: ref Tmsg.Create): ref Rmsg +{ + wok := cp == conschan || writeallow; + + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup))) + return ferr(f, Eronly, file, nil); + + (d, e) := Dentry.getd(file, Bread); + if(e != nil) + return ferr(f, e, file, nil); + p := d.iob; + if(!(d.mode & DDIR)) + return ferr(f, Edir2, file, p); + if(file.access(d, DWRITE) && !wok) + return ferr(f, Eaccess, file, p); + d.access(FREAD, file.uid); + + # + # Check the name is valid and will fit in an old + # directory entry. + # + if((l := checkname9p2(f.name)) == 0) + return ferr(f, Ename, file, p); + if(l+1 > NAMELEN) + return ferr(f, Etoolong, file, p); + if(f.name == "." || f.name == "..") + return ferr(f, Edot, file, p); + + addr1 := 0; # block with first empty slot, if any + slot1 := 0; + for(addr := 0; ; addr++){ + if((p1 := d.getblk(addr, 0)) == nil){ + if(addr1 != 0) + break; + p1 = d.getblk(addr, Tdir); + } + if(p1 == nil) + return ferr(f, Efull, file, p); + if(p1.checktag(Tdir, int d.qid.path)){ + p1.put(); + return ferr(f, Ephase, file, p); + } + for(slot := 0; slot < DIRPERBUF; slot++){ + d1 := Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)){ + if(addr1 == 0){ + addr1 = p1.addr; + slot1 = slot + addr*DIRPERBUF; + } + continue; + } + if(f.name == d1.name){ + p1.put(); + return ferr(f, Eexist, file, p); + } + } + p1.put(); + } + + fmod: int; + + case f.mode & 7 { + OEXEC or + OREAD => # seems only useful to make directories + fmod = FREAD; + + OWRITE => + fmod = FWRITE; + + ORDWR => + fmod = FREAD+FWRITE; + + * => + return ferr(f, Emode, file, p); + } + if(f.perm & DMDIR) + if((f.mode & OTRUNC) || (f.perm & DMAPPEND) || (fmod & FWRITE)) + return ferr(f, Eaccess, file, p); + + # do it + + path := qidpathgen(file.fs); + if((p1 := Iobuf.get(file.fs, addr1, Bread|Bimm|Bmod)) == nil) + return ferr(f, Ephase, file, p); + d1 := Dentry.get(p1, slot1); + if(d1 == nil || p1.checktag(Tdir, int d.qid.path)){ + p.put(); + return ferr(f, Ephase, file, p1); + } + if(d1.mode & DALLOC){ + p.put(); + return ferr(f, Ephase, file, p1); + } + + d1.name = f.name; + if(cp == conschan){ + d1.uid = consuid; + d1.gid = consgid; + } + else{ + d1.uid = file.uid; + d1.gid = d.gid; + f.perm &= d.mode | ~8r666; + if(f.perm & DMDIR) + f.perm &= d.mode | ~8r777; + } + d1.qid.path = big path; + d1.qid.vers = 0; + d1.mode = DALLOC | (f.perm & 8r777); + if(f.perm & DMDIR) + d1.mode |= DDIR; + if(f.perm & DMAPPEND) + d1.mode |= DAPND; + t: ref Tlock; + if(f.perm & DMEXCL){ + d1.mode |= DLOCK; + t = tlocked(file, d1); + # if nil, out of tlock structures + } + d1.access(FWRITE, file.uid); + d1.change(~0); + d1.update(); + qid := mkqid(path, 0, d1.mode); + p1.put(); + d.change(~0); + d.access(FWRITE, file.uid); + d.update(); + p.put(); + + # + # do a walk to new directory entry + # + file.wpath = ref Wpath(file.wpath, file.addr, file.slot); + file.qid = qid; + file.tlock = t; + if(t != nil) + t.file = file; + file.lastra = 1; + if(f.mode & ORCLOSE) + fmod |= FREMOV; + file.open = fmod; + file.addr = addr1; + file.slot = slot1; + file.unlock(); + return ref Rmsg.Create(f.tag, qid, cp.msize-IOHDRSZ); +} + +dirread(cp: ref Chan, f: ref Tmsg.Read, file: ref File, d: ref Dentry): ref Rmsg +{ + p1: ref Iobuf; + d1: ref Dentry; + + count := f.count; + data := array[count] of byte; + offset := f.offset; + iounit := cp.msize-IOHDRSZ; + + # Pick up where we left off last time if nothing has changed, + # otherwise must scan from the beginning. + + addr, slot: int; + start: big; + + if(offset == file.doffset){ # && file.qid.vers == file.dvers + addr = file.dslot/DIRPERBUF; + slot = file.dslot%DIRPERBUF; + start = offset; + } + else{ + addr = 0; + slot = 0; + start = big 0; + } + + nread := 0; +Dread: + for(;;){ + if(d.iob == nil){ + # + # This is just a check to ensure the entry hasn't + # gone away during the read of each directory block. + # + e: string; + (d, e) = Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + } + p1 = d.getblk1(addr, 0); + if(p1 == nil) + break; + if(p1.checktag(Tdir, QPNONE)) + return ferr(f, Ephase, file, p1); + + for(; slot < DIRPERBUF; slot++){ + d1 = Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)) + continue; + dir := dir9p2(d1); + n := styx->packdirsize(dir); + if(n > count-nread){ + p1.put(); + break Dread; + } + data[nread:] = styx->packdir(dir); + start += big n; + if(start < offset) + continue; + if(count < n){ + p1.put(); + break Dread; + } + count -= n; + nread += n; + offset += big n; + } + p1.put(); + slot = 0; + addr++; + } + + file.doffset = offset; + file.dvers = file.qid.vers; + file.dslot = slot+DIRPERBUF*addr; + + d.put(); + file.unlock(); + return ref Rmsg.Read(f.tag, data[0:nread]); +} + +rread(cp: ref Chan, f: ref Tmsg.Read): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + if(!(file.open & FREAD)) + return ferr(f, Eopen, file, nil); + count := f.count; + iounit := cp.msize-IOHDRSZ; + if(count < 0 || count > iounit) + return ferr(f, Ecount, file, nil); + offset := f.offset; + if(offset < big 0) + return ferr(f, Eoffset, file, nil); + + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + if((t := file.tlock) != nil){ + tim := now(); + if(t.time < tim || t.file != file){ + d.put(); + return ferr(f, Ebroken, file, nil); + } + # renew the lock + t.time = tim + TLOCK; + } + d.access(FREAD, file.uid); + if(d.mode & DDIR) + return dirread(cp, f, file, d); + + if(offset+big count > d.size) + count = int (d.size - offset); + if(count < 0) + count = 0; + data := array[count] of byte; + nread := 0; + while(count > 0){ + if(d.iob == nil){ + # must check and reacquire entry + (d, e) = Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + } + addr := int (offset / big BUFSIZE); + if(addr == file.lastra+1) + ; # dbufread(p, d, addr+1); + file.lastra = addr; + o := int (offset % big BUFSIZE); + n := BUFSIZE - o; + if(n > count) + n = count; + p1 := d.getblk1(addr, 0); + if(p1 != nil){ + if(p1.checktag(Tfile, QPNONE)){ + p1.put(); + return ferr(f, Ephase, file, nil); + } + data[nread:] = p1.iobuf[o:o+n]; + p1.put(); + }else + data[nread:] = emptyblock[0:n]; + count -= n; + nread += n; + offset += big n; + } + d.put(); + file.unlock(); + return ref Rmsg.Read(f.tag, data[0:nread]); +} + +rwrite(cp: ref Chan, f: ref Tmsg.Write): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + if(!(file.open & FWRITE)) + return ferr(f, Eopen, file, nil); + if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup))) + return ferr(f, Eronly, file, nil); + count := len f.data; + if(count < 0 || count > cp.msize-IOHDRSZ) + return ferr(f, Ecount, file, nil); + offset := f.offset; + if(offset < big 0) + return ferr(f, Eoffset, file, nil); + + (d, e) := Dentry.getd(file, Bread|Bmod); + if(d == nil) + return ferr(f, e, file, nil); + if((t := file.tlock) != nil){ + tim := now(); + if(t.time < tim || t.file != file){ + d.put(); + return ferr(f, Ebroken, file, nil); + } + # renew the lock + t.time = tim + TLOCK; + } + d.access(FWRITE, file.uid); + if(d.mode & DAPND) + offset = d.size; + end := offset + big count; + if(end > d.size){ + if(end > MAXFILESIZE) + return ferr(f, Etoobig, file, nil); + d.size = end; + d.change(Usize); + } + d.update(); + + nwrite := 0; + while(count > 0){ + if(d.iob == nil){ + # must check and reacquire entry + (d, e) = Dentry.getd(file, Bread|Bmod); + if(d == nil) + return ferr(f, e, file, nil); + } + addr := int (offset / big BUFSIZE); + o := int (offset % big BUFSIZE); + n := BUFSIZE - o; + if(n > count) + n = count; + qpath := int d.qid.path; + p1 := d.getblk1(addr, Tfile); + if(p1 == nil) + return ferr(f, Efull, file, nil); + if(p1.checktag(Tfile, qpath)){ + p1.put(); + return ferr(f, Ealloc, file, nil); + } + p1.iobuf[o:] = f.data[nwrite:nwrite+n]; + p1.flags |= Bmod; + p1.put(); + count -= n; + nwrite += n; + offset += big n; + } + d.put(); + file.unlock(); + return ref Rmsg.Write(f.tag, nwrite); +} + +doremove(f: ref File, iscon: int): string +{ + if(isro(f.fs) || f.cons == 0 && (writegroup && !ingroup(f.uid, writegroup))) + return Eronly; + # + # check permission on parent directory of file to be deleted + # + if(f.wpath == nil || f.wpath.addr == f.addr) + return Ephase; + (d1, e1) := Dentry.geta(f.fs, f.wpath.addr, f.wpath.slot, QPNONE, Bread); + if(e1 != nil) + return e1; + if(!iscon && f.access(d1, DWRITE)){ + d1.put(); + return Eaccess; + } + d1.access(FWRITE, f.uid); + d1.put(); + + # + # check on file to be deleted + # + (d, e) := Dentry.getd(f, Bread); + if(e != nil) + return e; + + # + # if deleting a directory, make sure it is empty + # + if(d.mode & DDIR) + for(addr:=0; (p1 := d.getblk(addr, 0)) != nil; addr++){ + if(p1.checktag(Tdir, int d.qid.path)){ + p1.put(); + d.put(); + return Ephase; + } + for(slot:=0; slot<DIRPERBUF; slot++){ + d1 = Dentry.get(p1, slot); + if(!(d1.mode & DALLOC)) + continue; + p1.put(); + d.put(); + return Eempty; + } + p1.put(); + } + + # + # do it + # + d.trunc(f.uid); + d.buf[0:] = emptyblock[0:Dentrysize]; + d.put(); + return nil; +} + +clunk(cp: ref Chan, file: ref File, remove: int, wok: int): string +{ + if((t := file.tlock) != nil){ + if(t.file == file) + t.time = 0; # free the lock + file.tlock = nil; + } + if(remove) + error := doremove(file, wok); + file.open = 0; + file.wpath = nil; + cp.putfid(file); + + return error; +} + +rclunk(cp: ref Chan, t: ref Tmsg.Clunk): ref Rmsg +{ + if((file := cp.getfid(t.fid, 0)) == nil) + return err(t, Efid); + clunk(cp, file, file.open & FREMOV, 0); + return ref Rmsg.Clunk(t.tag); +} + +rremove(cp: ref Chan, t: ref Tmsg.Remove): ref Rmsg +{ + if((file := cp.getfid(t.fid, 0)) == nil) + return err(t, Efid); + e := clunk(cp, file, 1, cp == conschan); + if(e != nil) + return err(t, e); + return ref Rmsg.Remove(t.tag); +} + +rstat(cp: ref Chan, f: ref Tmsg.Stat): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, nil); + dir := dir9p2(d); + if(d.qid.path == big QPROOT) # stat of root gives time + dir.atime = now(); + d.put(); + if(styx->packdirsize(dir) > cp.msize-IOHDRSZ) + return ferr(f, Ersc, file, nil); + file.unlock(); + + return ref Rmsg.Stat(f.tag, dir); +} + +rwstat(cp: ref Chan, f: ref Tmsg.Wstat): ref Rmsg +{ + if((file := cp.getfid(f.fid, 0)) == nil) + return err(f, Efid); + + # if user none, can't do anything unless in allow mode + + if(file.uid == None && !wstatallow) + return ferr(f, Eaccess, file, nil); + + if(isro(file.fs) || (writegroup && !ingroup(file.uid, writegroup))) + return ferr(f, Eronly, file, nil); + + # + # first get parent + # + p1: ref Iobuf; + d1: ref Dentry; + if(file.wpath != nil){ + p1 = Iobuf.get(file.fs, file.wpath.addr, Bread); + if(p1 == nil) + return ferr(f, Ephase, file, p1); + d1 = Dentry.get(p1, file.wpath.slot); + if(d1 == nil || p1.checktag(Tdir, QPNONE) || !(d1.mode & DALLOC)) + return ferr(f, Ephase, file, p1); + } + + # + # now the file + # + (d, e) := Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, e, file, p1); + + # + # Convert the message and fix up + # fields not to be changed. + # + dir := f.stat; + if(dir.uid == nil) + uid := d.uid; + else + uid = strtouid(dir.uid); + if(dir.gid == nil) + gid := d.gid; + else + gid = strtouid(dir.gid); + if(dir.name == nil) + dir.name = d.name; + else{ + if((l := checkname9p2(dir.name)) == 0){ + d.put(); + return ferr(f, Ename, file, p1); + } + if(l+1 > NAMELEN){ + d.put(); + return ferr(f, Etoolong, file, p1); + } + } + + # Before doing sanity checks, find out what the + # new 'mode' should be: + # if 'type' and 'mode' are both defaults, take the + # new mode from the old directory entry; + # else if 'type' is the default, use the new mode entry; + # else if 'mode' is the default, create the new mode from + # 'type' or'ed with the old directory mode; + # else neither are defaults, use the new mode but check + # it agrees with 'type'. + + if(dir.qid.qtype == 16rFF && dir.mode == ~0){ + dir.mode = d.mode & 8r777; + if(d.mode & DLOCK) + dir.mode |= DMEXCL; + if(d.mode & DAPND) + dir.mode |= DMAPPEND; + if(d.mode & DDIR) + dir.mode |= DMDIR; + } + else if(dir.qid.qtype == 16rFF){ + # nothing to do + } + else if(dir.mode == ~0) + dir.mode = (dir.qid.qtype<<24)|(d.mode & 8r777); + else if(dir.qid.qtype != ((dir.mode>>24) & 16rFF)){ + d.put(); + return ferr(f, Eqidmode, file, p1); + } + + # Check for unknown type/mode bits + # and an attempt to change the directory bit. + + if(dir.mode & ~(DMDIR|DMAPPEND|DMEXCL|8r777)){ + d.put(); + return ferr(f, Enotm, file, p1); + } + if(d.mode & DDIR) + mode := DMDIR; + else + mode = 0; + if((dir.mode^mode) & DMDIR){ + d.put(); + return ferr(f, Enotd, file, p1); + } + + if(dir.mtime == ~0) + dir.mtime = d.mtime; + if(dir.length == ~big 0) + dir.length = big d.size; + + + # Currently, can't change length. + + if(dir.length != big d.size){ + d.put(); + return ferr(f, Enotl, file, p1); + } + + + # if chown, + # must be god + # wstatallow set to allow chown during boot + + if(uid != d.uid && !wstatallow){ + d.put(); + return ferr(f, Enotu, file, p1); + } + + # if chgroup, + # must be either + # a) owner and in new group + # b) leader of both groups + # wstatallow and writeallow are set to allow chgrp during boot + + while(gid != d.gid){ + if(wstatallow || writeallow) + break; + if(d.uid == file.uid && ingroup(file.uid, gid)) + break; + if(leadgroup(file.uid, gid)) + if(leadgroup(file.uid, d.gid)) + break; + d.put(); + return ferr(f, Enotg, file, p1); + } + + # if rename, + # must have write permission in parent + + while(d.name != dir.name){ + + # drop entry to prevent deadlock, then + # check that destination name is valid and unique + + d.put(); + if(checkname9p2(dir.name) == 0 || d1 == nil) + return ferr(f, Ename, file, p1); + if(dir.name == "." || dir.name == "..") + return ferr(f, Edot, file, p1); + + + for(addr := 0; ; addr++){ + if((p := d1.getblk(addr, 0)) == nil) + break; + if(p.checktag(Tdir, int d1.qid.path)){ + p.put(); + continue; + } + for(slot := 0; slot < DIRPERBUF; slot++){ + d = Dentry.get(p, slot); + if(!(d.mode & DALLOC)) + continue; + if(dir.name == d.name){ + p.put(); + return ferr(f, Eexist, file, p1); + } + } + p.put(); + } + + # reacquire entry + + (d, nil) = Dentry.getd(file, Bread); + if(d == nil) + return ferr(f, Ephase, file, p1); + + if(wstatallow || writeallow) # set to allow rename during boot + break; + if(d1 == nil || file.access(d1, DWRITE)){ + d.put(); + return ferr(f, Eaccess, file, p1); + } + break; + } + + # if mode/time, either + # a) owner + # b) leader of either group + + mode = dir.mode & 8r777; + if(dir.mode & DMAPPEND) + mode |= DAPND; + if(dir.mode & DMEXCL) + mode |= DLOCK; + while(d.mtime != dir.mtime || ((d.mode^mode) & (DAPND|DLOCK|8r777))){ + if(wstatallow) # set to allow chmod during boot + break; + if(d.uid == file.uid) + break; + if(leadgroup(file.uid, gid)) + break; + if(leadgroup(file.uid, d.gid)) + break; + d.put(); + return ferr(f, Enotu, file, p1); + } + d.mtime = dir.mtime; + d.uid = uid; + d.gid = gid; + d.mode = (mode & (DAPND|DLOCK|8r777)) | (d.mode & (DALLOC|DDIR)); + + d.name = dir.name; + d.access(FWSTAT, file.uid); + d.change(~0); + d.put(); + + if(p1 != nil) + p1.put(); + file.unlock(); + + return ref Rmsg.Wstat(f.tag); +} + +superok(set: int): int +{ + sb := Superb.get(thedevice, Bread|Bmod|Bimm); + ok := sb.fsok; + sb.fsok = set; + if(debug) + sb.print(); + sb.touched(); + sb.put(); + return ok; +} + +# little-endian +get2(a: array of byte, o: int): int +{ + return (int a[o+1]<<8) | int a[o]; +} + +get2s(a: array of byte, o: int): int +{ + v := (int a[o+1]<<8) | int a[o]; + if(v & 16r8000) + v |= ~0 << 8; + return v; +} + +get4(a: array of byte, o: int): int +{ + return (int a[o+3]<<24) | (int a[o+2] << 16) | (int a[o+1]<<8) | int a[o]; +} + +put2(a: array of byte, o: int, v: int) +{ + a[o] = byte v; + a[o+1] = byte (v>>8); +} + +put4(a: array of byte, o: int, v: int) +{ + a[o] = byte v; + a[o+1] = byte (v>>8); + a[o+2] = byte (v>>16); + a[o+3] = byte (v>>24); +} + +Tag.unpack(a: array of byte): Tag +{ + return Tag(get2(a,2), get4(a,4)); +} + +Tag.pack(t: self Tag, a: array of byte) +{ + put2(a, 0, 0); + put2(a, 2, t.tag); + if(t.path != QPNONE) + put4(a, 4, t.path & ~QPDIR); +} + +Superb.get(dev: ref Device, flags: int): ref Superb +{ + p := Iobuf.get(dev, SUPERADDR, flags); + if(p == nil) + return nil; + if(p.checktag(Tsuper, QPSUPER)){ + p.put(); + return nil; + } + sb := Superb.unpack(p.iobuf); + sb.iob = p; + return sb; +} + +Superb.touched(s: self ref Superb) +{ + s.iob.flags |= Bmod; +} + +Superb.put(sb: self ref Superb) +{ + if(sb.iob == nil) + return; + if(sb.iob.flags & Bmod) + sb.pack(sb.iob.iobuf); + sb.iob.put(); + sb.iob = nil; +} + +# this is the disk structure +# Superb: +# Super1; +# Fbuf fbuf; +# Fbuf: +# nfree[4] +# free[] # based on BUFSIZE +# Super1: +# long fstart; +# long fsize; +# long tfree; +# long qidgen; # generator for unique ids +# long fsok; # file system ok +# long roraddr; # dump root addr +# long last; # last super block addr +# long next; # next super block addr + +Ofstart: con 0; +Ofsize: con Ofstart+4; +Otfree: con Ofsize+4; +Oqidgen: con Otfree+4; +Ofsok: con Oqidgen+4; +Ororaddr: con Ofsok+4; +Olast: con Ororaddr+4; +Onext: con Olast+4; +Super1size: con Onext+4; + +Superb.unpack(a: array of byte): ref Superb +{ + s := ref Superb; + s.fstart = get4(a, Ofstart); + s.fsize = get4(a, Ofsize); + s.tfree = get4(a, Otfree); + s.qidgen = get4(a, Oqidgen); + s.fsok = get4(a, Ofsok); + s.fbuf = a[Super1size:]; + return s; +} + +Superb.pack(s: self ref Superb, a: array of byte) +{ + put4(a, Ofstart, s.fstart); + put4(a, Ofsize, s.fsize); + put4(a, Otfree, s.tfree); + put4(a, Oqidgen, s.qidgen); + put4(a, Ofsok, s.fsok); +} + +Superb.print(sb: self ref Superb) +{ + sys->print("fstart=%ud fsize=%ud tfree=%ud qidgen=%ud fsok=%d\n", + sb.fstart, sb.fsize, sb.tfree, sb.qidgen, sb.fsok); +} + +Dentry.get(p: ref Iobuf, slot: int): ref Dentry +{ + if(p == nil) + return nil; + buf := p.iobuf[(slot%DIRPERBUF)*Dentrysize:]; + d := Dentry.unpack(buf); + d.iob = p; + d.buf = buf; + return d; +} + +Dentry.geta(fs: ref Device, addr: int, slot: int, qpath: int, mode: int): (ref Dentry, string) +{ + p := Iobuf.get(fs, addr, mode); + if(p == nil || p.checktag(Tdir, qpath)){ + if(p != nil) + p.put(); + return (nil, Ealloc); + } + d := Dentry.get(p, slot); + if(d == nil || !(d.mode & DALLOC)){ + p.put(); + return (nil, Ealloc); + } + return (d, nil); +} + +Dentry.getd(file: ref File, mode: int): (ref Dentry, string) +{ + (d, e) := Dentry.geta(file.fs, file.addr, file.slot, QPNONE, mode); # QPNONE should be file.wpath's path + if(e != nil) + return (nil, e); + if(file.qid.path != d.qid.path || (file.qid.qtype&QTDIR) != (d.qid.qtype&QTDIR)){ + d.put(); + return (nil, Eqid); + } + return (d, nil); +} + +# this is the disk structure: +# char name[NAMELEN]; +# short uid; +# short gid; [2*2] +# ushort mode; +# #define DALLOC 0x8000 +# #define DDIR 0x4000 +# #define DAPND 0x2000 +# #define DLOCK 0x1000 +# #define DREAD 0x4 +# #define DWRITE 0x2 +# #define DEXEC 0x1 +# [ushort muid] [2*2] +# Qid.path; [4] +# Qid.version; [4] +# long size; [4] +# long dblock[NDBLOCK]; +# long iblock; +# long diblock; +# long atime; +# long mtime; + +Oname: con 0; +Ouid: con Oname+NAMELEN; +Ogid: con Ouid+2; +Omode: con Ogid+2; +Omuid: con Omode+2; +Opath: con Omuid+2; +Overs: con Opath+4; +Osize: con Overs+4; +Odblock: con Osize+4; +Oiblock: con Odblock+NDBLOCK*4; +Odiblock: con Oiblock+4; +Oatime: con Odiblock+4; +Omtime: con Oatime+4; +Dentrysize: con Omtime+4; + +Dentry.unpack(a: array of byte): ref Dentry +{ + d := ref Dentry; + for(i:=0; i<NAMELEN; i++) + if(int a[i] == 0) + break; + d.name = string a[0:i]; + d.uid = get2s(a, Ouid); + d.gid = get2s(a, Ogid); + d.mode = get2(a, Omode); + d.muid = get2(a, Omuid); # note: not set by Plan 9's kfs + d.qid = mkqid(get4(a, Opath), get4(a, Overs), d.mode); + d.size = big get4(a, Osize) & big 16rFFFFFFFF; + d.atime = get4(a, Oatime); + d.mtime = get4(a, Omtime); + d.mod = 0; + return d; +} + +Dentry.change(d: self ref Dentry, f: int) +{ + d.mod |= f; +} + +Dentry.update(d: self ref Dentry) +{ + f := d.mod; + d.mod = 0; + if(d.iob == nil || (d.iob.flags & Bmod) == 0){ + if(f != 0) + panic("Dentry.update"); + return; + } + a := d.buf; + if(f & Uname){ + b := array of byte d.name; + for(i := 0; i < NAMELEN; i++) + if(i < len b) + a[i] = b[i]; + else + a[i] = byte 0; + } + if(f & Uids){ + put2(a, Ouid, d.uid); + put2(a, Ogid, d.gid); + } + if(f & Umode) + put2(a, Omode, d.mode); + if(f & Uqid){ + path := int d.qid.path; + if(d.mode & DDIR) + path |= QPDIR; + put4(a, Opath, path); + put4(a, Overs, d.qid.vers); + } + if(f & Usize) + put4(a, Osize, int d.size); + if(f & Utime){ + put4(a, Omtime, d.mtime); + put4(a, Oatime, d.atime); + } + d.iob.flags |= Bmod; +} + +Dentry.access(d: self ref Dentry, f: int, uid: int) +{ + if((p := d.iob) != nil && !readonly){ + if((f & (FWRITE|FWSTAT)) == 0 && noatime) + return; + if(f & (FREAD|FWRITE|FWSTAT)){ + d.atime = now(); + put4(d.buf, Oatime, d.atime); + p.flags |= Bmod; + } + if(f & FWRITE){ + d.mtime = now(); + put4(d.buf, Omtime, d.mtime); + d.muid = uid; + put2(d.buf, Omuid, uid); + d.qid.vers++; + put4(d.buf, Overs, d.qid.vers); + p.flags |= Bmod; + } + } +} + +# +# release the directory entry buffer and thus the +# lock on both buffer and entry, typically during i/o, +# to be reacquired later if needed +# +Dentry.release(d: self ref Dentry) +{ + if(d.iob != nil){ + d.update(); + d.iob.put(); + d.iob = nil; + d.buf = nil; + } +} + +Dentry.getblk(d: self ref Dentry, a: int, tag: int): ref Iobuf +{ + addr := d.rel2abs(a, tag, 0); + if(addr == 0) + return nil; + return Iobuf.get(thedevice, addr, Bread); +} + +# +# same as Dentry.buf but calls d.release +# to reduce interference. +# +Dentry.getblk1(d: self ref Dentry, a: int, tag: int): ref Iobuf +{ + addr := d.rel2abs(a, tag, 1); + if(addr == 0) + return nil; + return Iobuf.get(thedevice, addr, Bread); +} + +Dentry.rel2abs(d: self ref Dentry, a: int, tag: int, putb: int): int +{ + if(a < 0){ + sys->print("Dentry.rel2abs: neg\n"); + return 0; + } + p := d.iob; + if(p == nil || d.buf == nil) + panic("nil iob"); + data := d.buf; + qpath := int d.qid.path; + dev := p.dev; + if(a < NDBLOCK){ + addr := get4(data, Odblock+a*4); + if(addr == 0 && tag){ + addr = balloc(dev, tag, qpath); + put4(data, Odblock+a*4, addr); + p.flags |= Bmod|Bimm; + } + if(putb) + d.release(); + return addr; + } + a -= NDBLOCK; + if(a < INDPERBUF){ + addr := get4(data, Oiblock); + if(addr == 0 && tag){ + addr = balloc(dev, Tind1, qpath); + put4(data, Oiblock, addr); + p.flags |= Bmod|Bimm; + } + if(putb) + d.release(); + return indfetch(dev, qpath, addr, a, Tind1, tag); + } + a -= INDPERBUF; + if(a < INDPERBUF2){ + addr := get4(data, Odiblock); + if(addr == 0 && tag){ + addr = balloc(dev, Tind2, qpath); + put4(data, Odiblock, addr); + p.flags |= Bmod|Bimm; + } + if(putb) + d.release(); + addr = indfetch(dev, qpath, addr, a/INDPERBUF, Tind2, Tind1); + return indfetch(dev, qpath, addr, a%INDPERBUF, Tind1, tag); + } + if(putb) + d.release(); + sys->print("Dentry.buf: trip indirect\n"); + return 0; +} + +indfetch(dev: ref Device, path: int, addr: int, a: int, itag: int, tag: int): int +{ + if(addr == 0) + return 0; + bp := Iobuf.get(dev, addr, Bread); + if(bp == nil){ + sys->print("ind fetch bp = nil\n"); + return 0; + } + if(bp.checktag(itag, path)){ + sys->print("ind fetch tag\n"); + bp.put(); + return 0; + } + addr = get4(bp.iobuf, a*4); + if(addr == 0 && tag){ + addr = balloc(dev, tag, path); + if(addr != 0){ + put4(bp.iobuf, a*4, addr); + bp.flags |= Bmod; + if(localfs || tag == Tdir) + bp.flags |= Bimm; + bp.settag(itag, path); + } + } + bp.put(); + return addr; +} + +balloc(dev: ref Device, tag: int, qpath: int): int +{ + # TO DO: cache superblock to reduce pack/unpack + sb := Superb.get(dev, Bread|Bmod); + if(sb == nil) + panic("balloc: super block"); + n := get4(sb.fbuf, 0); + n--; + sb.tfree--; + if(n < 0 || n >= FEPERBUF) + panic("balloc: bad freelist"); + a := get4(sb.fbuf, 4+n*4); + if(n == 0){ + if(a == 0){ + sb.tfree = 0; + sb.touched(); + sb.put(); + return 0; + } + bp := Iobuf.get(dev, a, Bread); + if(bp == nil || bp.checktag(Tfree, QPNONE)){ + if(bp != nil) + bp.put(); + sb.put(); + return 0; + } + sb.fbuf[0:] = bp.iobuf[0:(FEPERBUF+1)*4]; + sb.touched(); + bp.put(); + }else{ + put4(sb.fbuf, 0, n); + sb.touched(); + } + bp := Iobuf.get(dev, a, Bmod); + bp.iobuf[0:] = emptyblock; + bp.settag(tag, qpath); + if(tag == Tind1 || tag == Tind2 || tag == Tdir) + bp.flags |= Bimm; + bp.put(); + sb.put(); + return a; +} + +bfree(dev: ref Device, addr: int, d: int) +{ + if(addr == 0) + return; + if(d > 0){ + d--; + p := Iobuf.get(dev, addr, Bread); + if(p != nil){ + for(i:=INDPERBUF-1; i>=0; i--){ + a := get4(p.iobuf, i*4); + bfree(dev, a, d); + } + p.put(); + } + } + + # stop outstanding i/o + p := Iobuf.get(dev, addr, Bprobe); + if(p != nil){ + p.flags &= ~(Bmod|Bimm); + p.put(); + } + + s := Superb.get(dev, Bread|Bmod); + if(s == nil) + panic("bfree: super block"); + addfree(dev, addr, s); + s.put(); +} + +addfree(dev: ref Device, addr: int, sb: ref Superb) +{ + if(addr >= sb.fsize){ + sys->print("addfree: bad addr %ud\n", addr); + return; + } + n := get4(sb.fbuf, 0); + if(n < 0 || n > FEPERBUF) + panic("addfree: bad freelist"); + if(n >= FEPERBUF){ + p := Iobuf.get(dev, addr, Bmod); + if(p == nil) + panic("addfree: Iobuf.get"); + p.iobuf[0:] = sb.fbuf[0:(1+FEPERBUF)*4]; + sb.fbuf[0:] = emptyblock[0:(1+FEPERBUF)*4]; # clear it for debugging + p.settag(Tfree, QPNONE); + p.put(); + n = 0; + } + put4(sb.fbuf, 4+n*4, addr); + put4(sb.fbuf, 0, n+1); + sb.tfree++; + if(addr >= sb.fsize) + sb.fsize = addr+1; + sb.touched(); +} + +qidpathgen(dev: ref Device): int +{ + sb := Superb.get(dev, Bread|Bmod); + if(sb == nil) + panic("qidpathgen: super block"); + sb.qidgen++; + path := sb.qidgen; + sb.touched(); + sb.put(); + return path; +} + +Dentry.trunc(d: self ref Dentry, uid: int) +{ + p := d.iob; + data := d.buf; + bfree(p.dev, get4(data, Odiblock), 2); + put4(data, Odiblock, 0); + bfree(p.dev, get4(data, Oiblock), 1); + put4(data, Oiblock, 0); + for(i:=NDBLOCK-1; i>=0; i--){ + bfree(p.dev, get4(data, Odblock+i*4), 0); + put4(data, Odblock+i*4, 0); + } + d.size = big 0; + d.change(Usize); + p.flags |= Bmod|Bimm; + d.access(FWRITE, uid); + d.update(); +} + +Dentry.put(d: self ref Dentry) +{ + p := d.iob; + if(p == nil || d.buf == nil) + return; + d.update(); + p.put(); + d.iob = nil; + d.buf = nil; +} + +Dentry.print(d: self ref Dentry) +{ + sys->print("name=%#q uid=%d gid=%d mode=#%8.8ux qid.path=#%bux qid.vers=%ud size=%bud\n", + d.name, d.uid, d.gid, d.mode, d.qid.path, d.qid.vers, d.size); + p := d.iob; + if(p != nil && (data := p.iobuf) != nil){ + sys->print("\tdblock="); + for(i := 0; i < NDBLOCK; i++) + sys->print(" %d", get4(data, Odblock+i*4)); + sys->print(" iblock=%ud diblock=%ud\n", get4(data, Oiblock), get4(data, Odiblock)); + } +} + +HWidth: con 5; # buffers per line + +hiob: array of ref Hiob; + +iobufinit(niob: int) +{ + nhiob := niob/HWidth; + while(!prime(nhiob)) + nhiob++; + hiob = array[nhiob] of {* => ref Hiob(nil, Lock.new(), 0)}; + # allocate the buffers now + for(i := 0; i < len hiob; i++){ + h := hiob[i]; + while(h.niob < HWidth) + h.newbuf(); + } +} + +iobufclear() +{ + # eliminate the cyclic references + for(i := 0; i < len hiob; i++){ + h := hiob[i]; + while(--h.niob >= 0){ + p := hiob[i].link; + hiob[i].link = p.fore; + p.fore = p.back = nil; + p = nil; + } + } +} + +prime(n: int): int +{ + if((n%2) == 0) + return 0; + for(i:=3;; i+=2) { + if((n%i) == 0) + return 0; + if(i*i >= n) + return 1; + } +} + +Hiob.newbuf(hb: self ref Hiob): ref Iobuf +{ + # hb must be locked + p := ref Iobuf; + p.qlock = chan[1] of int; + q := hb.link; + if(q != nil){ + p.fore = q; + p.back = q.back; + q.back = p; + p.back.fore = p; + }else{ + hb.link = p; + p.fore = p; + p.back = p; + } + p.dev = devnone; + p.addr = -1; + p.flags = 0; + p.xiobuf = array[RBUFSIZE] of byte; + hb.niob++; + return p; +} + +Iobuf.get(dev: ref Device, addr: int, flags: int): ref Iobuf +{ + hb := hiob[addr%len hiob]; + p: ref Iobuf; +Search: + for(;;){ + hb.lk.lock(); + s := hb.link; + + # see if it's active + p = s; + do{ + if(p.addr == addr && p.dev == dev){ + if(p != s){ + p.back.fore = p.fore; + p.fore.back = p.back; + p.fore = s; + p.back = s.back; + s.back = p; + p.back.fore = p; + hb.link = p; + } + hb.lk.unlock(); + p.lock(); + if(p.addr != addr || p.dev != dev){ + # lost race + p.unlock(); + continue Search; + } + p.flags |= flags; + p.iobuf = p.xiobuf; + return p; + } + }while((p = p.fore) != s); + if(flags == Bprobe){ + hb.lk.unlock(); + return nil; + } + + # steal the oldest unlocked buffer + do{ + p = s.back; + if(p.canlock()){ + # TO DO: if Bmod, write it out and restart Hashed + # for now we needn't because Iobuf.put is synchronous + if(p.flags & Bmod) + sys->print("Bmod unexpected (%ud)\n", p.addr); + hb.link = p; + p.dev = dev; + p.addr = addr; + p.flags = flags; + break Search; + } + s = p; + }while(p != hb.link); + + # no unlocked blocks available; add a new one + p = hb.newbuf(); + p.lock(); # return it locked + break; + } + + p.dev = dev; + p.addr = addr; + p.flags = flags; + hb.lk.unlock(); + p.iobuf = p.xiobuf; + if(flags & Bread){ + if(wrenread(dev.fd, addr, p.iobuf)){ + eprint(sys->sprint("error reading block %ud: %r", addr)); + p.flags = 0; + p.dev = devnone; + p.addr = -1; + p.iobuf = nil; + p.unlock(); + return nil; + } + } + return p; +} + +Iobuf.put(p: self ref Iobuf) +{ + if(p.flags & Bmod) + p.flags |= Bimm; # temporary; see comment in Iobuf.get + if(p.flags & Bimm){ + if(!(p.flags & Bmod)) + eprint(sys->sprint("imm and no mod (%d)", p.addr)); + if(!wrenwrite(p.dev.fd, p.addr, p.iobuf)) + p.flags &= ~(Bmod|Bimm); + else + panic(sys->sprint("error writing block %ud: %r", p.addr)); + } + p.iobuf = nil; + p.unlock(); +} + +Iobuf.lock(p: self ref Iobuf) +{ + p.qlock <-= 1; +} + +Iobuf.canlock(p: self ref Iobuf): int +{ + alt{ + p.qlock <-= 1 => + return 1; + * => + return 0; + } +} + +Iobuf.unlock(p: self ref Iobuf) +{ + <-p.qlock; +} + +File.access(f: self ref File, d: ref Dentry, m: int): int +{ + if(wstatallow) + return 0; + + # none gets only other permissions + + if(f.uid != None){ + if(f.uid == d.uid) # owner + if((m<<6) & d.mode) + return 0; + if(ingroup(f.uid, d.gid)) # group membership + if((m<<3) & d.mode) + return 0; + } + + # + # other access for everyone except members of group "noworld" + # + if(m & d.mode){ + # + # walk directories regardless. + # otherwise it's impossible to get + # from the root to noworld's directories. + # + if((d.mode & DDIR) && (m == DEXEC)) + return 0; + if(!ingroup(f.uid, Noworld)) + return 0; + } + return 1; +} + +tagname(t: int): string +{ + case t { + Tnone => return "Tnone"; + Tsuper => return "Tsuper"; + Tdir => return "Tdir"; + Tind1 => return "Tind1"; + Tind2 => return "Tind2"; + Tfile => return "Tfile"; + Tfree => return "Tfree"; + Tbuck => return "Tbuck"; + Tvirgo => return "Tvirgo"; + Tcache => return "Tcache"; + * => return sys->sprint("%d", t); + } +} + +Iobuf.checktag(p: self ref Iobuf, tag: int, qpath: int): int +{ + t := Tag.unpack(p.iobuf[BUFSIZE:]); + if(t.tag != tag){ + if(1) + eprint(sys->sprint(" tag = %s; expected %s; addr = %ud\n", + tagname(t.tag), tagname(tag), p.addr)); + return 2; + } + if(qpath != QPNONE){ + qpath &= ~QPDIR; + if(qpath != t.path){ + if(qpath == (t.path&~QPDIR)) # old bug + return 0; + if(1) + eprint(sys->sprint(" tag/path = %ux; expected %s/%ux\n", + t.path, tagname(tag), qpath)); + return 1; + } + } + return 0; +} + +Iobuf.settag(p: self ref Iobuf, tag: int, qpath: int) +{ + Tag(tag, qpath).pack(p.iobuf[BUFSIZE:]); + p.flags |= Bmod; +} + +badmagic := 0; +wmagic := "kfs wren device\n"; + +wrenream(dev: ref Device) +{ + if(RBUFSIZE % 512) + panic(sys->sprint("kfs: bad buffersize(%d): restart a multiple of 512", RBUFSIZE)); + if(RBUFSIZE > MAXBUFSIZE) + panic(sys->sprint("kfs: bad buffersize(%d): must be at most %d", RBUFSIZE, MAXBUFSIZE)); + sys->print("kfs: reaming the file system using %d byte blocks\n", RBUFSIZE); + buf := array[RBUFSIZE] of {* => byte 0}; + buf[256:] = sys->aprint("%s%d\n", wmagic, RBUFSIZE); + if(sys->seek(dev.fd, big 0, 0) < big 0 || sys->write(dev.fd, buf, len buf) != len buf) + panic("can't ream disk"); +} + +wreninit(dev: ref Device): int +{ + (ok, nil) := sys->fstat(dev.fd); + if(ok < 0) + return 0; + buf := array[MAXBUFSIZE] of byte; + sys->seek(dev.fd, big 0, 0); + n := sys->read(dev.fd, buf, len buf); + if(n < len buf) + return 0; + badmagic = 0; + RBUFSIZE = 1024; + if(string buf[256:256+len wmagic] != wmagic){ + badmagic = 1; + return 0; + } + RBUFSIZE = int string buf[256+len wmagic:256+len wmagic+12]; + if(RBUFSIZE % 512) + error("bad block size"); + return 1; +} + +wrenread(fd: ref Sys->FD, addr: int, a: array of byte): int +{ + return sys->pread(fd, a, len a, big addr * big RBUFSIZE) != len a; +} + +wrenwrite(fd: ref Sys->FD, addr: int, a: array of byte): int +{ + return sys->pwrite(fd, a, len a, big addr * big RBUFSIZE) != len a; +} + +wrentag(buf: array of byte, tag: int, qpath: int): int +{ + t := Tag.unpack(buf[BUFSIZE:]); + return t.tag != tag || (qpath&~QPDIR) != t.path; +} + +wrencheck(fd: ref Sys->FD): int +{ + if(badmagic) + return 1; + buf := array[RBUFSIZE] of byte; + if(wrenread(fd, SUPERADDR, buf) || wrentag(buf, Tsuper, QPSUPER) || + wrenread(fd, ROOTADDR, buf) || wrentag(buf, Tdir, QPROOT)) + return 1; + d0 := Dentry.unpack(buf); + if(d0.mode & DALLOC) + return 0; + return 1; +} + +wrensize(dev: ref Device): int +{ + (ok, d) := sys->fstat(dev.fd); + if(ok < 0) + return -1; + return int (d.length / big RBUFSIZE); +} + +checkname9p2(s: string): int +{ + for(i := 0; i < len s; i++) + if(s[i] <= 8r40) + return 0; + return styx->utflen(s); +} + +isro(d: ref Device): int +{ + return d == nil || d.ronly; +} + +tlocks: list of ref Tlock; + +tlocked(f: ref File, d: ref Dentry): ref Tlock +{ + tim := now(); + path := int d.qid.path; + t1: ref Tlock; + for(l := tlocks; l != nil; l = tl l){ + t := hd l; + if(t.qpath == path && t.time >= tim && t.dev == f.fs) + return nil; # it's locked + if(t.file == nil || t1 == nil && t.time < tim) + t1 = t; + } + t := t1; + if(t == nil) + t = ref Tlock; + t.dev = f.fs; + t.qpath = path; + t.time = tim + TLOCK; + tlocks = t :: tlocks; + return t; +} + +mkqid(path: int, vers: int, mode: int): Qid +{ + qid: Qid; + + qid.path = big (path & ~QPDIR); + qid.vers = vers; + qid.qtype = 0; + if(mode & DDIR) + qid.qtype |= QTDIR; + if(mode & DAPND) + qid.qtype |= QTAPPEND; + if(mode & DLOCK) + qid.qtype |= QTEXCL; + return qid; +} + +dir9p2(d: ref Dentry): Sys->Dir +{ + dir: Sys->Dir; + + dir.name = d.name; + dir.uid = uidtostr(d.uid); + dir.gid = uidtostr(d.gid); + dir.muid = uidtostr(d.muid); + dir.qid = d.qid; + dir.mode = d.mode & 8r777; + if(d.mode & DDIR) + dir.mode |= DMDIR; + if(d.mode & DAPND) + dir.mode |= DMAPPEND; + if(d.mode & DLOCK) + dir.mode |= DMEXCL; + dir.atime = d.atime; + dir.mtime = d.mtime; + dir.length = big d.size; + dir.dtype = 0; + dir.dev = 0; + return dir; +} + +rootream(dev: ref Device, addr: int) +{ + p := Iobuf.get(dev, addr, Bmod|Bimm); + p.iobuf[0:] = emptyblock; + p.settag(Tdir, QPROOT); + d := Dentry.get(p, 0); + d.name = "/"; + d.uid = -1; + d.gid = -1; + d.mode = DALLOC | DDIR | + ((DREAD|DWRITE|DEXEC) << 6) | + ((DREAD|DWRITE|DEXEC) << 3) | + ((DREAD|DWRITE|DEXEC) << 0); + d.qid.path = big QPROOT; + d.qid.vers = 0; + d.qid.qtype = QTDIR; + d.atime = now(); + d.mtime = d.atime; + d.change(~0); + d.access(FREAD|FWRITE, -1); + d.update(); + p.put(); +} + +superream(dev: ref Device, addr: int) +{ + fsize := wrensize(dev); + if(fsize <= 0) + panic("file system device size"); + p := Iobuf.get(dev, addr, Bmod|Bimm); + p.iobuf[0:] = emptyblock; + p.settag(Tsuper, QPSUPER); + sb := ref Superb; + sb.iob = p; + sb.fstart = 1; + sb.fsize = fsize; + sb.qidgen = 10; + sb.tfree = 0; + sb.fsok = 0; + sb.fbuf = p.iobuf[Super1size:]; + put4(sb.fbuf, 0, 1); # nfree = 1 + for(i := fsize-1; i>=addr+2; i--) + addfree(dev, i, sb); + sb.put(); +} + +eprint(s: string) +{ + sys->print("kfs: %s\n", s); +} + +# +# /adm/users +# +# uid:user:leader:members[,...] + +User: adt { + uid: int; + name: string; + leader: int; + mem: list of int; +}; + +users: list of ref User; + +admusers := array[] of { + (-1, "adm", "adm"), + (None, "none", "adm"), + (Noworld, "noworld", nil), + (10000, "sys", nil), + (10001, "upas", "upas"), + (10002, "bootes", "bootes"), + (10006, "inferno", nil), +}; + +userinit() +{ + if(!cmd_users() && users == nil){ + cprint("initializing minimal user table"); + defaultusers(); + } + writegroup = strtouid("write"); +} + +cmd_users(): int +{ + if(kopen(FID1, FID2, array[] of {"adm", "users"}, OREAD) != nil) + return 0; + buf: array of byte; + for(off := 0;;){ + (a, e) := kread(FID2, off, Styx->MAXFDATA); + if(e != nil){ + cprint("/adm/users read error: "+e); + return 0; + } + if(len a == 0) + break; + off += len a; + if(buf != nil){ + c := array[len buf + len a] of byte; + if(buf != nil) + c[0:] = buf; + c[len buf:] = a; + buf = c; + }else + buf = a; + } + kclose(FID2); + + # (uid:name:lead:mem,...\n)+ + (nl, lines) := sys->tokenize(string buf, "\n"); + if(nl == 0){ + cprint("empty /adm/users"); + return 0; + } + oldusers := users; + users = nil; + + # first pass: enter id:name + for(l := lines; l != nil; l = tl l){ + uid, name, r: string; + s := hd l; + if(s == "" || s[0] == '#') + continue; + (uid, r) = field(s, ':'); + (name, r) = field(r, ':'); + if(uid == nil || name == nil || string int uid != uid){ + cprint("invalid /adm/users line: "+hd l); + users = oldusers; + return 0; + } + adduser(int uid, name, nil, nil); + } + + # second pass: groups and leaders + for(l = lines; l != nil; l = tl l){ + s := hd l; + if(s == "" || s[0] == '#') + continue; + name, lead, mem, r: string; + (nil, r) = field(s, ':'); # skip id + (name, r) = field(r, ':'); + (lead, mem) = field(r, ':'); + (nil, mems) := sys->tokenize(mem, ",\n"); + if(name == nil || lead == nil && mems == nil) + continue; + u := finduname(name); + if(lead != nil){ + lu := strtouid(lead); + if(lu != None) + u.leader = lu; + else if(lead != nil) + u.leader = u.uid; # mimic kfs not fs + } + mids: list of int = nil; + for(; mems != nil; mems = tl mems){ + lu := strtouid(hd mems); + if(lu != None) + mids = lu :: mids; + } + u.mem = mids; + } + + if(debug) + for(x := users; x != nil; x = tl x){ + u := hd x; + sys->print("%d : %q : %d :", u.uid, u.name, u.leader); + for(y := u.mem; y != nil; y = tl y) + sys->print(" %d", hd y); + sys->print("\n"); + } + return 1; +} + +field(s: string, c: int): (string, string) +{ + for(i := 0; i < len s; i++) + if(s[i] == c) + return (s[0:i], s[i+1:]); + return (s, nil); +} + +defaultusers() +{ + for(i := 0; i < len admusers; i++){ + (id, name, leader) := admusers[i]; + adduser(id, name, leader, nil); + } +} + +finduname(s: string): ref User +{ + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.name == s) + return u; + } + return nil; +} + +uidtostr(id: int): string +{ + if(id == None) + return "none"; + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == id) + return u.name; + } + return sys->sprint("#%d", id); +} + +leadgroup(ui: int, gi: int): int +{ + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == gi){ + if(u.leader == ui) + return 1; + if(u.leader == 0) + return ingroup(ui, gi); + return 0; + } + } + return 0; +} + +strtouid(s: string): int +{ + if(s == "none") + return None; + u := finduname(s); + if(u != nil) + return u.uid; + return 0; +} + +ingroup(uid: int, gid: int): int +{ + if(uid == gid) + return 1; + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == gid){ + for(m := u.mem; m != nil; m = tl m) + if(hd m == uid) + return 1; + return 0; + } + } + return 0; +} + +baduname(s: string): int +{ + n := checkname9p2(s); + if(n == 0 || n+1 > NAMELEN || s == "." || s == ".."){ + sys->print("kfs: illegal user name %q\n", s); + return 1; + } + return 0; +} + +adduser(id: int, name: string, leader: string, mem: list of string) +{ + if(baduname(name)) + return; + for(l := users; l != nil; l = tl l){ + u := hd l; + if(u.uid == id){ + sys->print("kfs: duplicate user ID %d (name %q)\n", id, u.name); + return; + }else if(u.name == name){ + sys->print("kfs: duplicate user name %q (id %d)\n", name, u.uid); + return; + } + } + if(name == leader) + lid := id; + else if(leader == nil) + lid = 0; + else if(!baduname(leader)) + lid = strtouid(leader); + else + return; + memid: list of int; + for(; mem != nil; mem = tl mem){ + if(baduname(hd mem)) + return; + x := strtouid(hd mem); + if(x != 0) + memid = x :: memid; + } + u := ref User(id, name, lid, memid); + users = u :: users; +} + +Lock.new(): ref Lock +{ + return ref Lock(chan[1] of int); +} + +Lock.lock(l: self ref Lock) +{ + l.c <-= 1; +} + +Lock.canlock(l: self ref Lock): int +{ + alt{ + l.c <-= 1 => + return 1; + * => + return 0; + } +} + +Lock.unlock(l: self ref Lock) +{ + <-l.c; +} + +# +# kfs check, could be a separate module if that seemed important +# + +MAXDEPTH: con 100; +MAXNAME: con 4000; + +Map: adt { + lo, hi: int; + bits: array of byte; + nbad: int; + ndup: int; + nmark: int; + + new: fn(lo, hi: int): ref Map; + isset: fn(b: self ref Map, a: int): int; + mark: fn(b: self ref Map, a: int): string; +}; + +Check: adt { + dev: ref Device; + + amap: ref Map; + qmap: ref Map; + + name: string; + nfiles: int; + maxq: int; + + mod: int; + flags: int; + oldblock: int; + + depth: int; + maxdepth: int; + + check: fn(c: self ref Check); + touch: fn(c: self ref Check, a: int): int; + checkdir: fn(c: self ref Check, a: int, qpath: int): int; + checkindir: fn(c: self ref Check, a: int, d: ref Dentry, qpath: int): int; + maked: fn(c: self ref Check, a: int, s: int, qpath: int): ref Dentry; + modd: fn(c: self ref Check, a: int, s: int, d: ref Dentry); + fsck: fn(c: self ref Check, d: ref Dentry): int; + xread: fn(c: self ref Check, a: int, qpath: int); + xtag: fn(c: self ref Check, a: int, tag: int, qpath: int): ref Iobuf; + ckfreelist: fn(c: self ref Check, sb: ref Superb); + mkfreelist: fn(c: self ref Check, sb: ref Superb); + amark: fn(c: self ref Check, a: int): int; + fmark: fn(c: self ref Check, a: int): int; + missing: fn(c: self ref Check, sb: ref Superb); + qmark: fn(c: self ref Check, q: int); +}; + +check(dev: ref Device, flag: int) +{ + #mainlock.wlock(); + #mainlock.wunlock(); + c := ref Check; + c.dev = dev; + c.nfiles = 0; + c.maxq = 0; + c.mod = 0; + c.flags = flag; + c.oldblock = 0; + c.depth = 0; + c.maxdepth = 0; + c.check(); +} + +checkflags(s: string): int +{ + f := 0; + for(i := 0; i < len s; i++) + case s[i] { + 'r' => f |= Crdall; + 't' => f |= Ctag; + 'P' => f |= Cpfile; + 'p' => f |= Cpdir; + 'f' => f |= Cfree; + 'c' => f |= Cream; + 'd' => f |= Cbad; + 'w' => f |= Ctouch; + 'q' => f |= Cquiet; + 'v' => ; # old verbose flag; ignored + * => return -1; + } + return f; +} + +Check.check(c: self ref Check) +{ + sbaddr := SUPERADDR; + p := c.xtag(sbaddr, Tsuper, QPSUPER); + if(p == nil){ + cprint(sys->sprint("bad superblock")); + return; + } + sb := Superb.unpack(p.iobuf); + sb.iob = p; + + fstart := sb.fstart; + if(fstart != 1){ + cprint(sys->sprint("invalid superblock")); + return; + } + fsize := sb.fsize; + if(fsize < fstart || fsize > wrensize(c.dev)){ + cprint(sys->sprint("invalid size in superblock")); + return; + } + c.amap = Map.new(fstart, fsize); + + nqid := sb.qidgen+100; # not as much of a botch + if(nqid > 1024*1024*8) + nqid = 1024*1024*8; + if(nqid < 64*1024) + nqid = 64*1024; + c.qmap = Map.new(0, nqid); + + c.mod = 0; + c.depth = 0; + c.maxdepth = 0; + + if(c.amark(sbaddr)) + {} + + if(!(c.flags & Cquiet)) + cprint(sys->sprint("checking file system: %s", "main")); + c.nfiles = 0; + c.maxq = 0; + + d := c.maked(ROOTADDR, 0, QPROOT); + if(d != nil){ + if(c.amark(ROOTADDR)) + {} + if(c.fsck(d)) + c.modd(ROOTADDR, 0, d); + if(--c.depth != 0) + cprint("depth not zero on return"); + } + if(sb.qidgen < c.maxq) + cprint(sys->sprint("qid generator low path=%d maxq=%d", sb.qidgen, c.maxq)); + + nqbad := c.qmap.nbad + c.qmap.ndup; + c.qmap = nil; # could use to implement resequence + + ndup := c.amap.ndup; + nused := c.amap.nmark; + + c.amap.ndup = c.amap.nmark = 0; # reset for free list counts + if(c.flags & Cfree){ + c.name = "free list"; + c.mkfreelist(sb); + sb.qidgen = c.maxq; + p.settag(Tsuper, QPNONE); + }else + c.ckfreelist(sb); + + nbad := c.amap.nbad; + nfdup := c.amap.ndup; + nfree := c.amap.nmark; + # leave amap for missing, below + + if(c.mod){ + cprint("file system was modified"); + p.settag(Tsuper, QPNONE); + } + + if(!(c.flags & Cquiet)){ + cprint(sys->sprint("%8d files", c.nfiles)); + cprint(sys->sprint("%8d blocks in the file system", fsize-fstart)); + cprint(sys->sprint("%8d used blocks", nused)); + cprint(sys->sprint("%8d free blocks", sb.tfree)); + } + if(!(c.flags & Cfree)){ + if(nfree != sb.tfree) + cprint(sys->sprint("%8d free blocks found", nfree)); + if(nfdup) + cprint(sys->sprint("%8d blocks duplicated in the free list", nfdup)); + if(fsize-fstart-nused-nfree) + cprint(sys->sprint("%8d missing blocks", fsize-fstart-nused-nfree)); + } + if(ndup) + cprint(sys->sprint("%8d address duplications", ndup)); + if(nbad) + cprint(sys->sprint("%8d bad block addresses", nbad)); + if(nqbad) + cprint(sys->sprint("%8d bad qids", nqbad)); + if(!(c.flags & Cquiet)) + cprint(sys->sprint("%8d maximum qid path", c.maxq)); + c.missing(sb); + + sb.put(); +} + +Check.touch(c: self ref Check, a: int): int +{ + if((c.flags&Ctouch) && a){ + p := Iobuf.get(c.dev, a, Bread|Bmod); + if(p != nil) + p.put(); + return 1; + } + return 0; +} + +Check.checkdir(c: self ref Check, a: int, qpath: int): int +{ + ns := len c.name; + dmod := c.touch(a); + for(i:=0; i<DIRPERBUF; i++){ + nd := c.maked(a, i, qpath); + if(nd == nil) + break; + if(c.fsck(nd)){ + c.modd(a, i, nd); + dmod++; + } + c.depth--; + c.name = c.name[0:ns]; + } + c.name = c.name[0:ns]; + return dmod; +} + +Check.checkindir(c: self ref Check, a: int, d: ref Dentry, qpath: int): int +{ + dmod := c.touch(a); + p := c.xtag(a, Tind1, qpath); + if(p == nil) + return dmod; + for(i:=0; i<INDPERBUF; i++){ + a = get4(p.iobuf, i*4); + if(a == 0) + continue; + if(c.amark(a)){ + if(c.flags & Cbad){ + put4(p.iobuf, i*4, 0); + p.flags |= Bmod; + } + continue; + } + if(d.mode & DDIR) + dmod += c.checkdir(a, qpath); + else if(c.flags & Crdall) + c.xread(a, qpath); + } + p.put(); + return dmod; +} + +Check.fsck(c: self ref Check, d: ref Dentry): int +{ + p: ref Iobuf; + i: int; + a, qpath: int; + + if(++c.depth >= c.maxdepth){ + c.maxdepth = c.depth; + if(c.maxdepth >= MAXDEPTH){ + cprint(sys->sprint("max depth exceeded: %s", c.name)); + return 0; + } + } + dmod := 0; + if(!(d.mode & DALLOC)) + return 0; + c.nfiles++; + + ns := len c.name; + i = styx->utflen(d.name); + if(i >= NAMELEN){ + d.name[NAMELEN-1] = 0; # TO DO: not quite right + cprint(sys->sprint("%q.name (%q) not terminated", c.name, d.name)); + return 0; + } + ns += i; + if(ns >= MAXNAME){ + cprint(sys->sprint("%q.name (%q) name too large", c.name, d.name)); + return 0; + } + c.name += d.name; + + if(d.mode & DDIR){ + if(ns > 1) + c.name += "/"; + if(c.flags & Cpdir) + cprint(sys->sprint("%s", c.name)); + } else if(c.flags & Cpfile) + cprint(sys->sprint("%s", c.name)); + + qpath = int d.qid.path & ~QPDIR; + c.qmark(qpath); + if(qpath > c.maxq) + c.maxq = qpath; + for(i=0; i<NDBLOCK; i++){ + a = get4(d.buf, Odblock+i*4); + if(a == 0) + continue; + if(c.amark(a)){ + put4(d.buf, Odblock+i*4, 0); + dmod++; + continue; + } + if(d.mode & DDIR) + dmod += c.checkdir(a, qpath); + else if(c.flags & Crdall) + c.xread(a, qpath); + } + a = get4(d.buf, Oiblock); + if(a){ + if(c.amark(a)){ + put4(d.buf, Oiblock, 0); + dmod++; + } + else + dmod += c.checkindir(a, d, qpath); + } + + a = get4(d.buf, Odiblock); + if(a && c.amark(a)){ + put4(d.buf, Odiblock, 0); + return dmod + 1; + } + dmod += c.touch(a); + p = c.xtag(a, Tind2, qpath); + if(p != nil){ + for(i=0; i<INDPERBUF; i++){ + a = get4(p.iobuf, i*4); + if(a == 0) + continue; + if(c.amark(a)){ + if(c.flags & Cbad){ + put4(p.iobuf, i*4, 0); + p.flags |= Bmod; + } + continue; + } + dmod += c.checkindir(a, d, qpath); + } + p.put(); + } + return dmod; +} + +Check.ckfreelist(c: self ref Check, sb: ref Superb) +{ + c.name = "free list"; + cprint(sys->sprint("check %s", c.name)); + fb := sb.fbuf; + a := SUPERADDR; + p: ref Iobuf; + lo := 0; + hi := 0; + for(;;){ + n := get4(fb, 0); # nfree + if(n < 0 || n > FEPERBUF){ + cprint(sys->sprint("check: nfree bad %d", a)); + break; + } + for(i:=1; i<n; i++){ + a = get4(fb, 4+i*4); # free[i] + if(a && !c.fmark(a)){ + if(!lo || lo > a) + lo = a; + if(!hi || hi < a) + hi = a; + } + } + a = get4(fb, 4); # free[0] + if(a == 0) + break; + if(c.fmark(a)) + break; + if(!lo || lo > a) + lo = a; + if(!hi || hi < a) + hi = a; + if(p != nil) + p.put(); + p = c.xtag(a, Tfree, QPNONE); + if(p == nil) + break; + fb = p.iobuf; + } + if(p != nil) + p.put(); + cprint(sys->sprint("lo = %d; hi = %d", lo, hi)); +} + +# +# make freelist from scratch +# +Check.mkfreelist(c: self ref Check, sb: ref Superb) +{ + sb.fbuf[0:] = emptyblock[0:(FEPERBUF+1)*4]; + sb.tfree = 0; + put4(sb.fbuf, 0, 1); # nfree = 1 + for(a:=sb.fsize-sb.fstart-1; a >= 0; a--){ + i := a>>3; + if(i < 0 || i >= len c.amap.bits) + continue; + b := byte (1 << (a&7)); + if((c.amap.bits[i] & b) != byte 0) + continue; + addfree(c.dev, sb.fstart+a, sb); + c.amap.bits[i] |= b; + } + sb.iob.flags |= Bmod; +} + +# +# makes a copy of a Dentry's representation on disc so that +# the rest of the much larger iobuf can be freed. +# +Check.maked(c: self ref Check, a: int, s: int, qpath: int): ref Dentry +{ + p := c.xtag(a, Tdir, qpath); + if(p == nil) + return nil; + d := Dentry.get(p, s); + if(d == nil) + return nil; + copy := array[len d.buf] of byte; + copy[0:] = d.buf; + d.put(); + d.buf = copy; + return d; +} + +Check.modd(c: self ref Check, a: int, s: int, d1: ref Dentry) +{ + if(!(c.flags & Cbad)) + return; + p := Iobuf.get(c.dev, a, Bread); + d := Dentry.get(p, s); + if(d == nil){ + if(p != nil) + p.put(); + return; + } + d.buf[0:] = d1.buf; + p.flags |= Bmod; + p.put(); +} + +Check.xread(c: self ref Check, a: int, qpath: int) +{ + p := c.xtag(a, Tfile, qpath); + if(p != nil) + p.put(); +} + +Check.xtag(c: self ref Check, a: int, tag: int, qpath: int): ref Iobuf +{ + if(a == 0) + return nil; + p := Iobuf.get(c.dev, a, Bread); + if(p == nil){ + cprint(sys->sprint("check: \"%s\": xtag: p null", c.name)); + if(c.flags & (Cream|Ctag)){ + p = Iobuf.get(c.dev, a, Bmod); + if(p != nil){ + p.iobuf[0:] = emptyblock; + p.settag(tag, qpath); + c.mod++; + return p; + } + } + return nil; + } + if(p.checktag(tag, qpath)){ + cprint(sys->sprint("check: \"%s\": xtag: checktag", c.name)); + if(c.flags & Cream) + p.iobuf[0:] = emptyblock; + if(c.flags & (Cream|Ctag)){ + p.settag(tag, qpath); + c.mod++; + } + return p; + } + return p; +} + +Check.amark(c: self ref Check, a: int): int +{ + e := c.amap.mark(a); + if(e != nil){ + cprint(sys->sprint("check: \"%s\": %s %d", c.name, e, a)); + return e != "dup"; # don't clear dup blocks because rm might repair + } + return 0; +} + +Check.fmark(c: self ref Check,a: int): int +{ + e := c.amap.mark(a); + if(e != nil){ + cprint(sys->sprint("check: \"%s\": %s %d", c.name, e, a)); + return 1; + } + return 0; +} + +Check.missing(c: self ref Check, sb: ref Superb) +{ + n := 0; + for(a:=sb.fsize-sb.fstart-1; a>=0; a--){ + i := a>>3; + b := byte (1 << (a&7)); + if((c.amap.bits[i] & b) == byte 0){ + cprint(sys->sprint("missing: %d", sb.fstart+a)); + n++; + } + if(n > 10){ + cprint(sys->sprint(" ...")); + break; + } + } +} + +Check.qmark(c: self ref Check, qpath: int) +{ + e := c.qmap.mark(qpath); + if(e != nil){ + if(c.qmap.nbad+c.qmap.ndup < 20) + cprint(sys->sprint("check: \"%s\": qid %s 0x%ux", c.name, e, qpath)); + } +} + +Map.new(lo, hi: int): ref Map +{ + m := ref Map; + n := (hi-lo+7)>>3; + m.bits = array[n] of {* => byte 0}; + m.lo = lo; + m.hi = hi; + m.nbad = 0; + m.ndup = 0; + m.nmark = 0; + return m; +} + +Map.isset(m: self ref Map, i: int): int +{ + if(i < m.lo || i >= m.hi) + return -1; # hard to say + i -= m.lo; + return (m.bits[i>>3] & byte (1<<(i&7))) != byte 0; +} + +Map.mark(m: self ref Map, i: int): string +{ + if(i < m.lo || i >= m.hi){ + m.nbad++; + return "out of range"; + } + i -= m.lo; + b := byte (1 << (i&7)); + i >>= 3; + if((m.bits[i] & b) != byte 0){ + m.ndup++; + return "dup"; + } + m.bits[i] |= b; + m.nmark++; + return nil; +} + +cprint(s: string) +{ + if(consoleout != nil) + consoleout <-= s+"\n"; + else + eprint(s); +} diff --git a/appl/cmd/disk/kfscmd.b b/appl/cmd/disk/kfscmd.b new file mode 100644 index 00000000..e1b023a9 --- /dev/null +++ b/appl/cmd/disk/kfscmd.b @@ -0,0 +1,53 @@ +implement Kfscmd; + +include "sys.m"; + sys: Sys; + +include "draw.m"; +include "arg.m"; + +Kfscmd: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + arg := load Arg Arg->PATH; + if (arg == nil) + err(sys->sprint("can't load %s: %r", Arg->PATH)); + + cfs := "main"; + arg->init(args); + arg->setusage("disk/kfscmd [-n fsname] cmd ..."); + while((c := arg->opt()) != 0) + case c { + 'n' => + cfs = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + arg = nil; + + ctlf := "/chan/kfs."+cfs+".cmd"; + ctl := sys->open(ctlf, Sys->ORDWR); + if(ctl == nil) + err(sys->sprint("can't open %s: %r", ctlf)); + for(; args != nil; args = tl args){ + if(sys->fprint(ctl, "%s", hd args) > 0){ + buf := array[1024] of byte; + while((n := sys->read(ctl, buf, len buf)) > 0) + sys->write(sys->fildes(1), buf, n); + }else + err(sys->sprint("%q: %r", hd args)); + } +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "kfscmd: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/disk/mbr.b b/appl/cmd/disk/mbr.b new file mode 100644 index 00000000..9d51c945 --- /dev/null +++ b/appl/cmd/disk/mbr.b @@ -0,0 +1,134 @@ +implement Mbr; + +# +# install new master boot record boot code on PC disk. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "disks.m"; + disks: Disks; + Disk, PCpart, Toffset: import disks; + +include "arg.m"; + +Mbr: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + + + +# +# Default boot block prints an error message and reboots. +# +ndefmbr := Toffset; +defmbr := array[512] of { + byte 16rEB, byte 16r3C, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, + byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, byte 16r00, +16r03E => byte 16rFA, byte 16rFC, byte 16r8C, byte 16rC8, byte 16r8E, byte 16rD8, byte 16r8E, byte 16rD0, + byte 16rBC, byte 16r00, byte 16r7C, byte 16rBE, byte 16r77, byte 16r7C, byte 16rE8, byte 16r19, + byte 16r00, byte 16r33, byte 16rC0, byte 16rCD, byte 16r16, byte 16rBB, byte 16r40, byte 16r00, + byte 16r8E, byte 16rC3, byte 16rBB, byte 16r72, byte 16r00, byte 16rB8, byte 16r34, byte 16r12, + byte 16r26, byte 16r89, byte 16r07, byte 16rEA, byte 16r00, byte 16r00, byte 16rFF, byte 16rFF, + byte 16rEB, byte 16rD6, byte 16rAC, byte 16r0A, byte 16rC0, byte 16r74, byte 16r09, byte 16rB4, + byte 16r0E, byte 16rBB, byte 16r07, byte 16r00, byte 16rCD, byte 16r10, byte 16rEB, byte 16rF2, + byte 16rC3, byte 'N', byte 'o', byte 't', byte ' ', byte 'a', byte ' ', byte 'b', + byte 'o', byte 'o', byte 't', byte 'a', byte 'b', byte 'l', byte 'e', byte ' ', + byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'o', byte 'r', byte ' ', + byte 'd', byte 'i', byte 's', byte 'c', byte ' ', byte 'e', byte 'r', byte 'r', + byte 'o', byte 'r', byte '\r', byte '\n', byte 'P', byte 'r', byte 'e', byte 's', + byte 's', byte ' ', byte 'a', byte 'l', byte 'm', byte 'o', byte 's', byte 't', + byte ' ', byte 'a', byte 'n', byte 'y', byte ' ', byte 'k', byte 'e', byte 'y', + byte ' ', byte 't', byte 'o', byte ' ', byte 'r', byte 'e', byte 'b', byte 'o', + byte 'o', byte 't', byte '.', byte '.', byte '.', byte 16r00, byte 16r00, byte 16r00, +}; + +init(nil: ref Draw->Context, args: list of string) +{ + flag9 := 0; + mbrfile: string; + sys = load Sys Sys->PATH; + disks = load Disks Disks->PATH; + + sys->pctl(Sys->FORKFD, nil); + disks->init(); + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("disk/mbr [-m mbrfile] disk"); + while((o := arg->opt()) != 0) + case o { + '9' => + flag9 = 1; + 'm' => + mbrfile = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + if(len args != 1) + arg->usage(); + arg = nil; + + disk := Disk.open(hd args, Sys->ORDWR, 0); + if(disk == nil) + fatal(sys->sprint("opendisk %s: %r", hd args)); + + if(disk.dtype == "floppy") + fatal(sys->sprint("will not install mbr on floppy")); + if(disk.secsize != 512) + fatal(sys->sprint("secsize %d invalid: must be 512", disk.secsize)); + + secsize := disk.secsize; + mbr := array[secsize*disk.s] of {* => byte 0}; + + # + # Start with initial sector from disk. + # + if(sys->seek(disk.fd, big 0, 0) < big 0) + fatal(sys->sprint("seek to boot sector: %r\n")); + if(sys->read(disk.fd, mbr, secsize) != secsize) + fatal(sys->sprint("reading boot sector: %r")); + + nmbr: int; + if(mbrfile == nil){ + nmbr = ndefmbr; + mbr[0:] = defmbr; + } else { + buf := array[secsize*(disk.s+1)] of {* => byte 0}; + if((sysfd := sys->open(mbrfile, Sys->OREAD)) == nil) + fatal(sys->sprint("open %s: %r", mbrfile)); + if((nmbr = sys->read(sysfd, buf, secsize*(disk.s+1))) < 0) + fatal(sys->sprint("read %s: %r", mbrfile)); + if(nmbr > secsize*disk.s) + fatal(sys->sprint("master boot record too large %d > %d", nmbr, secsize*disk.s)); + if(nmbr < secsize) + nmbr = secsize; + sysfd = nil; + buf[Toffset:] = mbr[Toffset:secsize]; + mbr[0:] = buf[0:nmbr]; + } + + if(flag9){ + for(i := Toffset; i < secsize; i++) + mbr[i] = byte 0; + mbr[Toffset:] = PCpart(0, Disks->Type9, big 0, big disk.s, disk.secs-big disk.s).bytes(disk); + } + mbr[secsize-2] = byte Disks->Magic0; + mbr[secsize-1] = byte Disks->Magic1; + nmbr = (nmbr+secsize-1)&~(secsize-1); + if(sys->seek(disk.wfd, big 0, 0) < big 0) + fatal(sys->sprint("seek to MBR sector: %r\n")); + if(sys->write(disk.wfd, mbr, nmbr) != nmbr) + fatal(sys->sprint("writing MBR: %r")); +} + +fatal(s: string) +{ + sys->fprint(sys->fildes(2), "disk/mbr: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/disk/mkext.b b/appl/cmd/disk/mkext.b new file mode 100644 index 00000000..fc13f2fe --- /dev/null +++ b/appl/cmd/disk/mkext.b @@ -0,0 +1,377 @@ +implement Mkext; + +include "sys.m"; + sys: Sys; + Dir, sprint, fprint: import sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "arg.m"; + arg: Arg; + +Mkext: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +LEN: con Sys->ATOMICIO; +NFLDS: con 6; # filename, modes, uid, gid, mtime, bytes + +bin: ref Iobuf; +uflag := 0; +tflag := 0; +hflag := 0; +vflag := 0; +fflag := 0; +qflag := 1; +stderr: ref Sys->FD; +bout: ref Iobuf; +argv0 := "mkext"; + +usage() +{ + fprint(stderr, "Usage: mkext [-h] [-u] [-v] [-f] [-t] [-q] [-d dest-fs] [file ...]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + error(sys->sprint("cannot load %s: %r\n", Bufio->PATH)); + + str = load String String->PATH; + if(str == nil) + error(sys->sprint("cannot load %s: %r\n", String->PATH)); + + arg = load Arg Arg->PATH; + if(arg == nil) + error(sys->sprint("cannot load %s: %r\n", Arg->PATH)); + + destdir := ""; + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'd' => + destdir = arg->arg(); + if(destdir == nil) + error("destination directory name missing"); + 'f' => + fflag = 1; + + 'h' => + hflag = 1; + bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + if(bout == nil) + error(sys->sprint("can't access standard output: %r")); + 'u' => + uflag = 1; + 't' => + tflag = 1; + 'v' => + vflag = 1; + 'q' => + qflag = 0; + * => + usage(); + } + args = arg->argv(); + + bin = bufio->fopen(sys->fildes(0), Sys->OREAD); + if(bin == nil) + error(sys->sprint("can't access standard input: %r")); + while((p := bin.gets('\n')) != nil){ + if(p == "end of archive\n"){ + fprint(stderr, "done\n"); + quit(nil); + } + fields: list of string; + nf: int; + if(qflag){ + fields = str->unquoted(p); + nf = len fields; + }else + (nf, fields) = sys->tokenize(p, " \t\n"); + if(nf != NFLDS){ + warn("too few fields in file header"); + continue; + } + name := hd fields; + fields = tl fields; + (mode, nil) := str->toint(hd fields, 8); + fields = tl fields; + uid := hd fields; + fields = tl fields; + gid := hd fields; + fields = tl fields; + (mtime, nil) := str->toint(hd fields, 10); + fields = tl fields; + (bytes, nil) := str->tobig(hd fields, 10); + if(args != nil){ + if(!selected(name, args)){ + if(bytes != big 0) + seekpast(bytes); + continue; + } + mkdirs(destdir, name); + } + name = destdir+name; + if(hflag){ + bout.puts(sys->sprint("%s %s %s %s %ud %bd\n", + quoted(name), octal(mode), uid, gid, mtime, bytes)); + if(bytes != big 0) + seekpast(bytes); + continue; + } + if(mode & Sys->DMDIR) + mkdir(name, mode, mtime, uid, gid); + else + extract(name, mode, mtime, uid, gid, bytes); + } + fprint(stderr, "premature end of archive\n"); + quit("eof"); +} + +quit(s: string) +{ + if(bout != nil) + bout.flush(); + if(s != nil) + raise "fail: "+s; + exit; +} + +fileprefix(prefix, s: string): int +{ + n := len prefix; + m := len s; + if(n > m || !str->prefix(prefix, s)) + return 0; + if(m > n && s[n] != '/') + return 0; + return 1; +} + +selected(s: string, args: list of string): int +{ + for(; args != nil; args = tl args) + if(fileprefix(hd args, s)) + return 1; + return 0; +} + +mkdirs(basedir, name: string) +{ + (nil, names) := sys->tokenize(name, "/"); + while(names != nil) { + #sys->print("mkdir %s\n", basedir); + create(basedir, Sys->OREAD, 8r775|Sys->DMDIR); + + if(tl names == nil) + break; + basedir = basedir + "/" + hd names; + names = tl names; + } +} + +mkdir(name: string, mode: int, mtime: int, uid: string, gid: string) +{ + d: Dir; + i: int; + + fd := create(name, Sys->OREAD, mode); + if(fd == nil){ + (i, d) = sys->stat(name); + if(i < 0 || !(d.mode & Sys->DMDIR)){ + warn(sys->sprint("can't make directory %s: %r", name)); + return; + } + }else{ + (i, d) = sys->fstat(fd); + if(i < 0) + warn(sys->sprint("can't stat %s: %r", name)); + fd = nil; + } + + d = sys->nulldir; + (nil, p) := str->splitr(name, "/"); + if(p == nil) + p = name; + d.name = p; + if(tflag) + d.mtime = mtime; + if(uflag){ + d.uid = uid; + d.gid = gid; + d.mtime = mtime; + } + d.mode = mode; + if(sys->wstat(name, d) < 0) + warn(sys->sprint("can't set modes for %s: %r", name)); + if(uflag){ + (i, d) = sys->stat(name); + if(i < 0) + warn(sys->sprint("can't reread modes for %s: %r", name)); + if(d.mtime != mtime) + warn(sys->sprint("%s: time mismatch %ud %ud\n", name, mtime, d.mtime)); + if(uid != d.uid) + warn(sys->sprint("%s: uid mismatch %s %s", name, uid, d.uid)); + if(gid != d.gid) + warn(sys->sprint("%s: gid mismatch %s %s", name, gid, d.gid)); + } +} + +extract(name: string, mode: int, mtime: int, uid: string, gid: string, bytes: big) +{ + n: int; + + if(vflag) + sys->print("x %s %bd bytes\n", name, bytes); + + sfd := create(name, Sys->OWRITE, mode); + if(sfd == nil) { + if(!fflag || sys->remove(name) == -1 || + (sfd = create(name, Sys->OWRITE, mode)) == nil) { + warn(sys->sprint("can't make file %s: %r", name)); + seekpast(bytes); + return; + } + } + b := bufio->fopen(sfd, Bufio->OWRITE); + if (b == nil) { + warn(sys->sprint("can't open file %s for bufio : %r", name)); + seekpast(bytes); + return; + } + buf := array [LEN] of byte; + for(tot := big 0; tot < bytes; tot += big n){ + n = len buf; + if(tot + big n > bytes) + n = int(bytes - tot); + n = bin.read(buf, n); + if(n <= 0) + error(sys->sprint("premature eof reading %s", name)); + if(b.write(buf, n) != n) + warn(sys->sprint("error writing %s: %r", name)); + } + + (i, nil) := sys->fstat(b.fd); + if(i < 0) + warn(sys->sprint("can't stat %s: %r", name)); + d := sys->nulldir; + (nil, p) := str->splitr(name, "/"); + if(p == nil) + p = name; + d.name = p; + if(tflag || uflag) + d.mtime = mtime; + if(uflag){ + d.uid = uid; + d.gid = gid; + } + d.mode = mode; + if(b.flush() == Bufio->ERROR) + warn(sys->sprint("error writing %s: %r", name)); + if(sys->fwstat(b.fd, d) < 0) + warn(sys->sprint("can't set modes for %s: %r", name)); + if(uflag){ + (i, d) = sys->fstat(b.fd); + if(i < 0) + warn(sys->sprint("can't reread modes for %s: %r", name)); + if(d.mtime != mtime) + warn(sys->sprint("%s: time mismatch %ud %ud\n", name, mtime, d.mtime)); + if(d.uid != uid) + warn(sys->sprint("%s: uid mismatch %s %s", name, uid, d.uid)); + if(d.gid != gid) + warn(sys->sprint("%s: gid mismatch %s %s", name, gid, d.gid)); + } + b.close(); +} + +seekpast(bytes: big) +{ + n: int; + + buf := array [LEN] of byte; + for(tot := big 0; tot < bytes; tot += big n){ + n = len buf; + if(tot + big n > bytes) + n = int(bytes - tot); + n = bin.read(buf, n); + if(n <= 0) + error("premature eof"); + } +} + +error(s: string) +{ + fprint(stderr, "%s: %s\n", argv0, s); + quit("error"); +} + +warn(s: string) +{ + fprint(stderr, "%s: %s\n", argv0, s); +} + +octal(i: int): string +{ + s := ""; + do { + t: string; + t[0] = '0' + (i&7); + s = t+s; + } while((i = (i>>3)&~(7<<29)) != 0); + return s; +} + +parent(name : string) : string +{ + slash := -1; + for (i := 0; i < len name; i++) + if (name[i] == '/') + slash = i; + if (slash > 0) + return name[0:slash]; + return "/"; +} + +create(name : string, rw : int, mode : int) : ref Sys->FD +{ + fd := sys->create(name, rw, mode); + if (fd == nil) { + p := parent(name); + (ok, d) := sys->stat(p); + if (ok < 0) + return nil; + omode := d.mode; + d = sys->nulldir; + d.mode = omode | 8r222; # ensure parent is writable + if(sys->wstat(p, d) < 0) { + warn(sys->sprint("can't set modes for %s: %r", p)); + return nil; + } + fd = sys->create(name, rw, mode); + d.mode = omode; + sys->wstat(p, d); + } + return fd; +} + +quoted(s: string): string +{ + if(qflag) + for(i:=0; i<len s; i++) + if((c := s[i]) == ' ' || c == '\t' || c == '\n' || c == '\'') + return str->quoted(s :: nil); + return s; +} diff --git a/appl/cmd/disk/mkfile b/appl/cmd/disk/mkfile new file mode 100644 index 00000000..46b7f067 --- /dev/null +++ b/appl/cmd/disk/mkfile @@ -0,0 +1,25 @@ +<../../../mkconfig + +DIRS=\ + prep\ + +TARG=\ + kfs.dis\ + mbr.dis\ + mkext.dis\ + mkfs.dis\ + kfscmd.dis\ + format.dis\ + ftl.dis\ + +SYSMODULES=\ + arg.m\ + sys.m\ + draw.m\ + bufio.m\ + string.m\ + +DISBIN=$ROOT/dis/disk + +<$ROOT/mkfiles/mkdis +<$ROOT/mkfiles/mksubdirs diff --git a/appl/cmd/disk/mkfs.b b/appl/cmd/disk/mkfs.b new file mode 100644 index 00000000..8b07aa8f --- /dev/null +++ b/appl/cmd/disk/mkfs.b @@ -0,0 +1,778 @@ +implement Mkfs; + +include "sys.m"; + sys: Sys; + Dir, sprint, fprint: import sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "arg.m"; + arg: Arg; + +Mkfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +LEN: con Sys->ATOMICIO; +HUNKS: con 128; + +Kfs, Fs, Archive: con iota; # types of destination file sytems + +File: adt { + new: string; + elem: string; + old: string; + uid: string; + gid: string; + mode: int; +}; + +b: ref Iobuf; +bout: ref Iobuf; # stdout when writing archive +newfile: string; +oldfile: string; +proto: string; +cputype: string; +users: string; +oldroot: string; +newroot: string; +prog := "mkfs"; +lineno := 0; +buf: array of byte; +zbuf: array of byte; +buflen := 1024-8; +indent: int; +verb: int; +modes: int; +ream: int; +debug: int; +xflag: int; +qflag := 1; +sfd: ref Sys->FD; +fskind: int; # Kfs, Fs, Archive +user: string; +stderr: ref Sys->FD; +usrid, grpid : string; +setuid: int; + +usage() +{ + fprint(stderr, "usage: %s [-apqrvx] [-d root] [-n kfsname] [-s src-fs] [-u userfile] [-z n] proto ...\n", prog); + quit("usage"); +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + str = load String String->PATH; + arg = load Arg Arg->PATH; + + sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil); + + stderr = sys->fildes(2); + if(arg == nil) + error(sys->sprint("can't load %s: %r", Arg->PATH)); + + user = getuser(); + if(user == nil) + user = "none"; + name := ""; + file := ref File; + file.new = ""; + file.old = nil; + file.mode = 0; + oldroot = ""; + newroot = "/n/kfs"; + users = nil; + fskind = Kfs; # i suspect Inferno default should be different + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'a' => + fskind = Archive; + newroot = ""; + bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + if(bout == nil) + error(sys->sprint("can't open standard output for archive: %r")); + 'd' => + fskind = Fs; + newroot = reqarg("destination directory (-d)"); + 'D' => + debug = 1; + 'n' => + name = reqarg("kfs instance name (-n)"); + 'p' => + modes = 1; + 'q' => + qflag = 0; + 'r' => + ream = 1; + 's' => + oldroot = reqarg("source directory (-d)"); + 'u' => + users = reqarg("/adm/users file (-u)"); + 'v' => + verb = 1; + 'x' => + xflag = 1; + 'z' => + (buflen, nil) = str->toint(reqarg("buffer length (-z)"), 10); + buflen -= 8; # qid.path and tag at end of each kfs block + 'U' => + usrid = reqarg("user name (-U)"); + 'G' => + grpid = reqarg("group name (-G)"); + 'S' => + setuid = 1; + * => + usage(); + } + + args = arg->argv(); + if(args == nil) + usage(); + + buf = array [buflen] of byte; + zbuf = array [buflen] of { * => byte 0 }; + + mountkfs(name); + kfscmd("allow"); + proto = "users"; + setusers(); + cputype = getenv("cputype"); + if(cputype == nil) + cputype = "dis"; + + errs := 0; + for(; args != nil; args = tl args){ + proto = hd args; + fprint(stderr, "processing %s\n", proto); + + b = bufio->open(proto, Sys->OREAD); + if(b == nil){ + fprint(stderr, "%s: can't open %s: %r: skipping\n", prog, proto); + errs++; + continue; + } + + lineno = 0; + indent = 0; + mkfs(file, -1); + b.close(); + } + fprint(stderr, "file system made\n"); + kfscmd("disallow"); + kfscmd("sync"); + if(errs) + quit("skipped protos"); + if(fskind == Archive){ + bout.puts("end of archive\n"); + if(bout.flush() == Bufio->ERROR) + error(sys->sprint("write error: %r")); + } +} + +quit(why: string) +{ + if(bout != nil) + bout.flush(); + if(why != nil) + raise "fail:"+why; + exit; +} + +reqarg(what: string): string +{ + if((o := arg->arg()) == nil){ + sys->fprint(stderr, "%s: missing %s\n", prog, what); + quit("usage"); + } + return o; +} + +mkfs(me: ref File, level: int) +{ + (child, fp) := getfile(me); + if(child == nil) + return; + if(child.elem == "+" || child.elem == "*" || child.elem == "%"){ + rec := child.elem[0] == '+'; + filesonly := child.elem[0] == '%'; + child.new = me.new; + setnames(child); + mktree(child, rec, filesonly); + (child, fp) = getfile(me); + } + while(child != nil && indent > level){ + if(mkfile(child)) + mkfs(child, indent); + (child, fp) = getfile(me); + } + if(child != nil){ + b.seek(fp, 0); + lineno--; + } +} + +mktree(me: ref File, rec: int, filesonly: int) +{ + fd := sys->open(oldfile, Sys->OREAD); + if(fd == nil){ + warn(sys->sprint("can't open %s: %r", oldfile)); + return; + } + + child := ref *me; + r := ref Rec(nil, 0); + for(;;){ + (n, d) := sys->dirread(fd); + if(n <= 0) + break; + for(i := 0; i < n; i++) + if (!recall(d[i].name, r)) { + if(filesonly && d[i].mode & Sys->DMDIR) + continue; + child.new = mkpath(me.new, d[i].name); + if(me.old != nil) + child.old = mkpath(me.old, d[i].name); + child.elem = d[i].name; + setnames(child); + if(copyfile(child, ref d[i], 1) && rec) + mktree(child, rec, filesonly); + } + } +} + +# Recall namespace fix +# -- remove duplicates (could use Readdir->init(,Readdir->COMPACT)) +# obc + +Rec: adt +{ + ad: array of string; + l: int; +}; + +AL : con HUNKS; +recall(e : string, r : ref Rec) : int +{ + if (r.ad == nil) r.ad = array[AL] of string; + # double array + if (r.l >= len r.ad) { + nar := array[2*(len r.ad)] of string; + nar[0:] = r.ad; + r.ad = nar; + } + for(i := 0; i < r.l; i++) + if (r.ad[i] == e) return 1; + r.ad[r.l++] = e; + return 0; +} + +mkfile(f: ref File): int +{ + (i, dir) := sys->stat(oldfile); + if(i < 0){ + warn(sys->sprint("can't stat file %s: %r", oldfile)); + skipdir(); + return 0; + } + return copyfile(f, ref dir, 0); +} + +copyfile(f: ref File, d: ref Dir, permonly: int): int +{ + mode: int; + + if(xflag && bout != nil){ + bout.puts(sys->sprint("%s\t%d\t%bd\n", quoted(f.new), d.mtime, d.length)); + return (d.mode & Sys->DMDIR) != 0; + } + d.name = f.elem; + if(d.dtype != 'M' && d.dtype != 'U'){ # hmm... Indeed! + d.uid = "inferno"; + d.gid = "inferno"; + mode = (d.mode >> 6) & 7; + d.mode |= mode | (mode << 3); + } + if(f.uid != "-") + d.uid = f.uid; + if(f.gid != "-") + d.gid = f.gid; + if(fskind == Fs && !setuid){ # new system: set to nil + d.uid = user; + d.gid = user; + } + if (usrid != nil) + d.uid = usrid; + if (grpid != nil) + d.gid = grpid; + if(f.mode != ~0){ + if(permonly) + d.mode = (d.mode & ~8r666) | (f.mode & 8r666); + else if((d.mode&Sys->DMDIR) != (f.mode&Sys->DMDIR)) + warn(sys->sprint("inconsistent mode for %s", f.new)); + else + d.mode = f.mode; + } + if(!uptodate(d, newfile)){ + if(d.mode & Sys->DMDIR) + mkdir(d); + else { + if(verb) + fprint(stderr, "%s\n", f.new); + copy(d); + } + }else if(modes){ + nd := sys->nulldir; + nd.mode = d.mode; + nd.mtime = d.mtime; + nd.gid = d.gid; + if(sys->wstat(newfile, nd) < 0) + warn(sys->sprint("can't set modes for %s: %r", f.new)); + # do the uid separately since different file systems object + nd = sys->nulldir; + nd.uid = d.uid; + sys->wstat(newfile, nd); + } + return (d.mode & Sys->DMDIR) != 0; +} + + +# check if file to is up to date with +# respect to the file represented by df + +uptodate(df: ref Dir, newf: string): int +{ + if(fskind == Archive || ream) + return 0; + (i, dt) := sys->stat(newf); + if(i < 0) + return 0; + return dt.mtime >= df.mtime; +} + +copy(d: ref Dir) +{ + t: ref Sys->FD; + n: int; + + f := sys->open(oldfile, Sys->OREAD); + if(f == nil){ + warn(sys->sprint("can't open %s: %r", oldfile)); + return; + } + t = nil; + if(fskind == Archive) + arch(d); + else{ + (dname, fname) := str->splitr(newfile, "/"); + if(fname == nil) + error(sys->sprint("internal temporary file error (%s)", dname)); + cptmp := dname+"__mkfstmp"; + t = sys->create(cptmp, Sys->OWRITE, 8r666); + if(t == nil){ + warn(sys->sprint("can't create %s: %r", newfile)); + return; + } + } + + for(tot := big 0;; tot += big n){ + n = sys->read(f, buf, buflen); + if(n < 0){ + warn(sys->sprint("can't read %s: %r", oldfile)); + break; + } + if(n == 0) + break; + if(fskind == Archive){ + if(bout.write(buf, n) != n) + error(sys->sprint("write error: %r")); + }else if(buf[0:buflen] == zbuf[0:buflen]){ + if(sys->seek(t, big buflen, 1) < big 0) + error(sys->sprint("can't write zeros to %s: %r", newfile)); + }else if(sys->write(t, buf, n) < n) + error(sys->sprint("can't write %s: %r", newfile)); + } + f = nil; + if(tot != d.length){ + warn(sys->sprint("wrong number bytes written to %s (was %bd should be %bd)", + newfile, tot, d.length)); + if(fskind == Archive){ + warn("seeking to proper position"); + bout.seek(d.length - tot, 1); + } + } + if(fskind == Archive) + return; + sys->remove(newfile); + nd := sys->nulldir; + nd.name = d.name; + nd.mode = d.mode; + nd.mtime = d.mtime; + if(sys->fwstat(t, nd) < 0) + error(sys->sprint("can't move tmp file to %s: %r", newfile)); + nd = sys->nulldir; + nd.gid = d.gid; + if(sys->fwstat(t, nd) < 0) + warn(sys->sprint("can't set group id of %s to %s: %r", newfile, d.gid)); + nd.gid = nil; + nd.uid = d.uid; + sys->fwstat(t, nd); +} + +mkdir(d: ref Dir) +{ + if(fskind == Archive){ + arch(d); + return; + } + fd := sys->create(newfile, Sys->OREAD, d.mode); + nd := sys->nulldir; + nd.mode = d.mode; + nd.gid = d.gid; + nd.mtime = d.mtime; + if(fd == nil){ + (i, d1) := sys->stat(newfile); + if(i < 0 || !(d1.mode & Sys->DMDIR)) + error(sys->sprint("can't create %s", newfile)); + if(sys->wstat(newfile, nd) < 0) + warn(sys->sprint("can't set modes for %s: %r", newfile)); + nd = sys->nulldir; + nd.uid = d.uid; + sys->wstat(newfile, nd); + return; + } + if(sys->fwstat(fd, nd) < 0) + warn(sys->sprint("can't set modes for %s: %r", newfile)); + nd = sys->nulldir; + nd.uid = d.uid; + sys->fwstat(fd, nd); +} + +arch(d: ref Dir) +{ + bout.puts(sys->sprint("%s %s %s %s %ud %bd\n", + quoted(newfile), octal(d.mode), d.uid, d.gid, d.mtime, d.length)); +} + +mkpath(prefix, elem: string): string +{ + return sys->sprint("%s/%s", prefix, elem); +} + +setnames(f: ref File) +{ + newfile = newroot+f.new; + if(f.old != nil){ + if(f.old[0] == '/') + oldfile = oldroot+f.old; + else + oldfile = f.old; + }else + oldfile = oldroot+f.new; +} + +# +# skip all files in the proto that +# could be in the current dir +# +skipdir() +{ + if(indent < 0) + return; + level := indent; + for(;;){ + indent = 0; + fp := b.offset(); + p := b.gets('\n'); + lineno++; + if(p == nil){ + indent = -1; + return; + } + for(j := 0; (c := p[j++]) != '\n';) + if(c == ' ') + indent++; + else if(c == '\t') + indent += 8; + else + break; + if(indent <= level){ + b.seek(fp, 0); + lineno--; + return; + } + } +} + +getfile(old: ref File): (ref File, big) +{ + f: ref File; + p, elem: string; + c: int; + + if(indent < 0) + return (nil, big 0); + fp := b.offset(); + do { + indent = 0; + p = b.gets('\n'); + lineno++; + if(p == nil){ + indent = -1; + return (nil, big 0); + } + for(; (c = p[0]) != '\n'; p = p[1:]) + if(c == ' ') + indent++; + else if(c == '\t') + indent += 8; + else + break; + } while(c == '\n' || c == '#'); + f = ref File; + (elem, p) = getname(p); + if(debug) + fprint(stderr, "getfile: %s root %s\n", elem, old.new); + f.new = mkpath(old.new, elem); + (nil, f.elem) = str->splitr(f.new, "/"); + if(f.elem == nil) + error(sys->sprint("can't find file name component of %s", f.new)); + (f.mode, p) = getmode(p); + (f.uid, p) = getname(p); + if(f.uid == nil) + f.uid = "-"; + (f.gid, p) = getname(p); + if(f.gid == nil) + f.gid = "-"; + f.old = getpath(p); + if(f.old == "-") + f.old = nil; + setnames(f); + + if(debug) + printfile(f); + + return (f, fp); +} + +getpath(p: string): string +{ + for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:]) + ; + for(n := 0; (c = p[n]) != '\n' && c != ' ' && c != '\t'; n++) + ; + return p[0:n]; +} + +getname(p: string): (string, string) +{ + for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:]) + ; + i := 0; + s := ""; + quoted := 0; + for(; (c = p[0]) != '\n' && (c != ' ' && c != '\t' || quoted); p = p[1:]){ + if(quoted && c == '\'' && p[1] == '\'') + p = p[1:]; + else if(c == '\'' && qflag){ + quoted = !quoted; + continue; + } + s[i++] = c; + } + if(len s > 0 && s[0] == '$'){ + s = getenv(s[1:]); + if(s == nil) + error(sys->sprint("can't read environment variable %s", s)); + } + return (s, p); +} + +getenv(s: string): string +{ + if(s == "user") + return getuser(); + return readfile("/env/"+s); +} + +getuser(): string +{ + return readfile("/dev/user"); +} + +readfile(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd != nil){ + a := array[256] of byte; + n := sys->read(fd, a, len a); + if(n > 0) + return string a[0:n]; + } + return nil; +} + +getmode(p: string): (int, string) +{ + s: string; + + (s, p) = getname(p); + if(s == nil || s == "-") + return (~0, p); + os := s; + m := 0; + if(s[0] == 'd'){ + m |= Sys->DMDIR; + s = s[1:]; + } + if(s[0] == 'a'){ + m |= Sys->DMAPPEND; + s = s[1:]; + } + if(s[0] == 'l'){ + m |= Sys->DMEXCL; + s = s[1:]; + } + + for(i:=0; i<len s || i < 3; i++) + if(i >= len s || !(s[i]>='0' && s[i]<='7')){ + warn(sys->sprint("bad mode specification %s", os)); + return (~0, p); + } + (v, nil) := str->toint(s, 8); + return (m|v, p); +} + +quoted(s: string): string +{ + if(qflag) + return sys->sprint("%q", s); + return s; +} + +setusers() +{ + if(fskind != Kfs) + return; + file := ref File; + m := modes; + modes = 1; + file.uid = "adm"; + file.gid = "adm"; + file.mode = Sys->DMDIR|8r775; + file.new = "/adm"; + file.elem = "adm"; + file.old = nil; + setnames(file); + mkfile(file); + file.new = "/adm/users"; + file.old = users; + file.elem = "users"; + file.mode = 8r664; + setnames(file); + mkfile(file); + kfscmd("user"); + mkfile(file); + file.mode = Sys->DMDIR|8r775; + file.new = "/adm"; + file.old = "/adm"; + file.elem = "adm"; + setnames(file); + mkfile(file); + modes = m; +} + +# this isn't right for the current #K +mountkfs(name: string) +{ + kname: string; + + if(fskind != Kfs) + return; + if(name != nil) + kname = sys->sprint("/srv/kfs.%s", name); + else + kname = "/srv/kfs"; + fd := sys->open(kname, Sys->ORDWR); + if(fd == nil){ + fprint(stderr, "%s: can't open %s: %r\n", prog, kname); + quit("open kfs"); + } + if(sys->mount(fd, nil, "/n/kfs", Sys->MREPL|Sys->MCREATE, "") < 0){ + fprint(stderr, "%s: can't mount kfs on /n/kfs: %r\n", prog); + quit("mount kfs"); + } + kname += ".cmd"; + sfd = sys->open(kname, Sys->ORDWR); + if(sfd == nil){ + fprint(stderr, "%s: can't open %s: %r\n", prog, kname); + quit("open kfscmd"); + } +} + +kfscmd(cmd: string) +{ + if(fskind != Kfs || sfd == nil) + return; + a := array of byte cmd; + if(sys->write(sfd, a, len a) != len a){ + fprint(stderr, "%s: error writing %s: %r", prog, cmd); + return; + } + for(;;){ + reply := array[4*1024] of byte; + n := sys->read(sfd, reply, len reply); + if(n <= 0) + return; + s := string reply[0:n]; + if(s == "done" || s == "success") + return; + if(s == "unknown command"){ + fprint(stderr, "%s: command %s not recognized\n", prog, cmd); + return; + } + } +} + +error(s: string) +{ + fprint(stderr, "%s: %s: %d: %s\n", prog, proto, lineno, s); + kfscmd("disallow"); + kfscmd("sync"); + quit("error"); +} + +warn(s: string) +{ + fprint(stderr, "%s: %s: %d: %s\n", prog, proto, lineno, s); +} + +printfile(f: ref File) +{ + if(f.old != nil) + fprint(stderr, "%s from %s %s %s %s\n", f.new, f.old, f.uid, f.gid, octal(f.mode)); + else + fprint(stderr, "%s %s %s %s\n", f.new, f.uid, f.gid, octal(f.mode)); +} + +octal(i: int): string +{ + s := ""; + do { + t: string; + t[0] = '0' + (i&7); + s = t+s; + } while((i = (i>>3)&~(7<<29)) != 0); + return s; +} diff --git a/appl/cmd/disk/prep/calc.tab.b b/appl/cmd/disk/prep/calc.tab.b new file mode 100644 index 00000000..25f81487 --- /dev/null +++ b/appl/cmd/disk/prep/calc.tab.b @@ -0,0 +1,454 @@ +implement Calc; + +#line 2 "calc.y" +# +# from Plan 9. subject to the Lucent Public License 1.02 +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + + NUM, + DOT, + DOLLAR, + ADD, + SUB, + MUL, + DIV, + FRAC, + NEG: con iota; + +Exp: adt { + ty: int; + n: big; + e1, e2: cyclic ref Exp; +}; + +YYSTYPE: adt { + e: ref Exp; +}; +yyexp: ref Exp; + +YYLEX: adt { + s: string; + n: int; + lval: YYSTYPE; + lex: fn(l: self ref YYLEX): int; + error: fn(l: self ref YYLEX, msg: string); +}; +Calc: module { + + parseexpr: fn(s: string, a, b, c: big): (big, string); + init: fn(nil: ref Draw->Context, nil: list of string); +NUMBER: con 57346; +UNARYMINUS: con 57347; + +}; +YYEOFCODE: con 1; +YYERRCODE: con 2; +YYMAXDEPTH: con 200; + +#line 68 "calc.y" + + +mkNUM(x: big): ref Exp +{ + return ref Exp(NUM, x, nil, nil); +} + +mkOP(ty: int, e1: ref Exp, e2: ref Exp): ref Exp +{ + return ref Exp(ty, big 0, e1, e2); +} + +dot, size, dollar: big; + +YYLEX.lex(l: self ref YYLEX): int +{ + while(l.n < len l.s && isspace(l.s[l.n])) + l.n++; + + if(l.n == len l.s) + return -1; + + if(isdigit(l.s[l.n])){ + for(o := l.n; o < len l.s && isdigit(l.s[o]); o++) + ; + l.lval.e = mkNUM(big l.s[l.n:o]); + l.n = o; + return NUMBER; + } + + return l.s[l.n++]; +} + +isdigit(c: int): int +{ + return c >= '0' && c <= '9'; +} + +isspace(c: int): int +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'; +} + +YYLEX.error(nil: self ref YYLEX, s: string) +{ + raise s; +} + +eval(e: ref Exp): big +{ + case e.ty { + NUM => + return e.n; + DOT => + return dot; + DOLLAR => + return dollar; + ADD => + return eval(e.e1)+eval(e.e2); + SUB => + return eval(e.e1)-eval(e.e2); + MUL => + return eval(e.e1)*eval(e.e2); + DIV => + i := eval(e.e2); + if(i == big 0) + raise "division by zero"; + return eval(e.e1)/i; + FRAC => + return (size*eval(e.e1))/big 100; + NEG => + return -eval(e.e1); + * => + raise "invalid operator"; + } +} + +parseexpr(s: string, xdot: big, xdollar: big, xsize: big): (big, string) +{ + dot = xdot; + size = xsize; + dollar = xdollar; + l := ref YYLEX(s, 0, YYSTYPE(nil)); + { + yyparse(l); + if(yyexp == nil) + return (big 0, "nil yylval?"); + return (eval(yyexp), nil); + }exception e{ + "*" => + return (big 0, e); + } +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + while((args = tl args) != nil){ + (r, e) := parseexpr(hd args, big 1000, big 1000000, big 1000000); + if(e != nil) + sys->print("%s\n", e); + else + sys->print("%bd\n", r); + } +} + +yyexca := array[] of {-1, 1, + 1, -1, + -2, 0, +}; +YYNPROD: con 12; +YYPRIVATE: con 57344; +yytoknames: array of string; +yystates: array of string; +yydebug: con 0; +YYLAST: con 30; +yyact := array[] of { + 8, 9, 10, 11, 3, 12, 7, 2, 12, 19, + 1, 4, 5, 6, 13, 14, 15, 16, 17, 18, + 8, 9, 10, 11, 0, 12, 10, 11, 0, 12, +}; +yypact := array[] of { + 0,-1000, 15,-1000,-1000,-1000, 0, 0, 0, 0, + 0, 0,-1000, -5,-1000, 19, 19, -2, -2,-1000, +}; +yypgo := array[] of { + 0, 7, 10, +}; +yyr1 := array[] of { + 0, 2, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, +}; +yyr2 := array[] of { + 0, 1, 1, 1, 1, 3, 3, 3, 3, 3, + 2, 2, +}; +yychk := array[] of { +-1000, -2, -1, 4, 11, 12, 13, 6, 5, 6, + 7, 8, 10, -1, -1, -1, -1, -1, -1, 14, +}; +yydef := array[] of { + 0, -2, 1, 2, 3, 4, 0, 0, 0, 0, + 0, 0, 10, 0, 11, 6, 7, 8, 9, 5, +}; +yytok1 := array[] of { + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 12, 10, 3, 3, + 13, 14, 7, 5, 3, 6, 11, 8, +}; +yytok2 := array[] of { + 2, 3, 4, 9, +}; +yytok3 := array[] of { + 0 +}; + +YYSys: module +{ + FD: adt + { + fd: int; + }; + fildes: fn(fd: int): ref FD; + fprint: fn(fd: ref FD, s: string, *): int; +}; + +yysys: YYSys; +yystderr: ref YYSys->FD; + +YYFLAG: con -1000; + +# parser for yacc output + +yytokname(yyc: int): string +{ + if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil) + return yytoknames[yyc-1]; + return "<"+string yyc+">"; +} + +yystatname(yys: int): string +{ + if(yys >= 0 && yys < len yystates && yystates[yys] != nil) + return yystates[yys]; + return "<"+string yys+">\n"; +} + +yylex1(yylex: ref YYLEX): int +{ + c : int; + yychar := yylex.lex(); + if(yychar <= 0) + c = yytok1[0]; + else if(yychar < len yytok1) + c = yytok1[yychar]; + else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2) + c = yytok2[yychar-YYPRIVATE]; + else{ + n := len yytok3; + c = 0; + for(i := 0; i < n; i+=2) { + if(yytok3[i+0] == yychar) { + c = yytok3[i+1]; + break; + } + } + if(c == 0) + c = yytok2[1]; # unknown char + } + if(yydebug >= 3) + yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c)); + return c; +} + +YYS: adt +{ + yyv: YYSTYPE; + yys: int; +}; + +yyparse(yylex: ref YYLEX): int +{ + if(yydebug >= 1 && yysys == nil) { + yysys = load YYSys "$Sys"; + yystderr = yysys->fildes(2); + } + + yys := array[YYMAXDEPTH] of YYS; + + yyval: YYSTYPE; + yystate := 0; + yychar := -1; + yynerrs := 0; # number of errors + yyerrflag := 0; # error recovery flag + yyp := -1; + yyn := 0; + +yystack: + for(;;){ + # put a state and value onto the stack + if(yydebug >= 4) + yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + + yyp++; + if(yyp >= len yys) + yys = (array[len yys * 2] of YYS)[0:] = yys; + yys[yyp].yys = yystate; + yys[yyp].yyv = yyval; + + for(;;){ + yyn = yypact[yystate]; + if(yyn > YYFLAG) { # simple state + if(yychar < 0) + yychar = yylex1(yylex); + yyn += yychar; + if(yyn >= 0 && yyn < YYLAST) { + yyn = yyact[yyn]; + if(yychk[yyn] == yychar) { # valid shift + yychar = -1; + yyp++; + if(yyp >= len yys) + yys = (array[len yys * 2] of YYS)[0:] = yys; + yystate = yyn; + yys[yyp].yys = yystate; + yys[yyp].yyv = yylex.lval; + if(yyerrflag > 0) + yyerrflag--; + if(yydebug >= 4) + yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + continue; + } + } + } + + # default state action + yyn = yydef[yystate]; + if(yyn == -2) { + if(yychar < 0) + yychar = yylex1(yylex); + + # look through exception table + for(yyxi:=0;; yyxi+=2) + if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate) + break; + for(yyxi += 2;; yyxi += 2) { + yyn = yyexca[yyxi]; + if(yyn < 0 || yyn == yychar) + break; + } + yyn = yyexca[yyxi+1]; + if(yyn < 0){ + yyn = 0; + break yystack; + } + } + + if(yyn != 0) + break; + + # error ... attempt to resume parsing + if(yyerrflag == 0) { # brand new error + yylex.error("syntax error"); + yynerrs++; + if(yydebug >= 1) { + yysys->fprint(yystderr, "%s", yystatname(yystate)); + yysys->fprint(yystderr, "saw %s\n", yytokname(yychar)); + } + } + + if(yyerrflag != 3) { # incompletely recovered error ... try again + yyerrflag = 3; + + # find a state where "error" is a legal shift action + while(yyp >= 0) { + yyn = yypact[yys[yyp].yys] + YYERRCODE; + if(yyn >= 0 && yyn < YYLAST) { + yystate = yyact[yyn]; # simulate a shift of "error" + if(yychk[yystate] == YYERRCODE) + continue yystack; + } + + # the current yyp has no shift onn "error", pop stack + if(yydebug >= 2) + yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n", + yys[yyp].yys, yys[yyp-1].yys ); + yyp--; + } + # there is no state on the stack with an error shift ... abort + yyn = 1; + break yystack; + } + + # no shift yet; clobber input char + if(yydebug >= 2) + yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar)); + if(yychar == YYEOFCODE) { + yyn = 1; + break yystack; + } + yychar = -1; + # try again in the same state + } + + # reduction by production yyn + if(yydebug >= 2) + yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); + + yypt := yyp; + yyp -= yyr2[yyn]; +# yyval = yys[yyp+1].yyv; + yym := yyn; + + # consult goto table to find next state + yyn = yyr1[yyn]; + yyg := yypgo[yyn]; + yyj := yyg + yys[yyp].yys + 1; + + if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) + yystate = yyact[yyg]; + case yym { + +1=> +#line 54 "calc.y" +{ yyexp = yys[yypt-0].yyv.e; return 0; } +2=> +yyval.e = yys[yyp+1].yyv.e; +3=> +#line 57 "calc.y" +{ yyval.e = mkOP(DOT, nil, nil); } +4=> +#line 58 "calc.y" +{ yyval.e = mkOP(DOLLAR, nil, nil); } +5=> +#line 59 "calc.y" +{ yyval.e = yys[yypt-1].yyv.e; } +6=> +#line 60 "calc.y" +{ yyval.e = mkOP(ADD, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); } +7=> +#line 61 "calc.y" +{ yyval.e = mkOP(SUB, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); } +8=> +#line 62 "calc.y" +{ yyval.e = mkOP(MUL, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); } +9=> +#line 63 "calc.y" +{ yyval.e = mkOP(DIV, yys[yypt-2].yyv.e, yys[yypt-0].yyv.e); } +10=> +#line 64 "calc.y" +{ yyval.e = mkOP(FRAC, yys[yypt-1].yyv.e, nil); } +11=> +#line 65 "calc.y" +{ yyval.e = mkOP(NEG, yys[yypt-0].yyv.e, nil); } + } + } + + return yyn; +} diff --git a/appl/cmd/disk/prep/calc.tab.m b/appl/cmd/disk/prep/calc.tab.m new file mode 100644 index 00000000..fa531c74 --- /dev/null +++ b/appl/cmd/disk/prep/calc.tab.m @@ -0,0 +1,7 @@ +Calc: module { + + parseexpr: fn(s: string, a, b, c: big): (big, string); + init: fn(nil: ref Draw->Context, nil: list of string); +NUMBER: con 57346; +UNARYMINUS: con 57347; +}; diff --git a/appl/cmd/disk/prep/calc.y b/appl/cmd/disk/prep/calc.y new file mode 100644 index 00000000..7ce56049 --- /dev/null +++ b/appl/cmd/disk/prep/calc.y @@ -0,0 +1,174 @@ +%{ +# +# from Plan 9. subject to the Lucent Public License 1.02 +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + + NUM, + DOT, + DOLLAR, + ADD, + SUB, + MUL, + DIV, + FRAC, + NEG: con iota; + +Exp: adt { + ty: int; + n: big; + e1, e2: cyclic ref Exp; +}; + +YYSTYPE: adt { + e: ref Exp; +}; +yyexp: ref Exp; + +YYLEX: adt { + s: string; + n: int; + lval: YYSTYPE; + lex: fn(l: self ref YYLEX): int; + error: fn(l: self ref YYLEX, msg: string); +}; +%} +%module Calc +{ + parseexpr: fn(s: string, a, b, c: big): (big, string); + init: fn(nil: ref Draw->Context, nil: list of string); +} + +%token <e> NUMBER + +%type <e> expr + +%left '+' '-' +%left '*' '/' +%left UNARYMINUS '%' +%% +top: expr { yyexp = $1; return 0; } + +expr: NUMBER + | '.' { $$ = mkOP(DOT, nil, nil); } + | '$' { $$ = mkOP(DOLLAR, nil, nil); } + | '(' expr ')' { $$ = $2; } + | expr '+' expr { $$ = mkOP(ADD, $1, $3); } + | expr '-' expr { $$ = mkOP(SUB, $1, $3); } + | expr '*' expr { $$ = mkOP(MUL, $1, $3); } + | expr '/' expr { $$ = mkOP(DIV, $1, $3); } + | expr '%' { $$ = mkOP(FRAC, $1, nil); } + | '-' expr %prec UNARYMINUS { $$ = mkOP(NEG, $2, nil); } + ; + +%% + +mkNUM(x: big): ref Exp +{ + return ref Exp(NUM, x, nil, nil); +} + +mkOP(ty: int, e1: ref Exp, e2: ref Exp): ref Exp +{ + return ref Exp(ty, big 0, e1, e2); +} + +dot, size, dollar: big; + +YYLEX.lex(l: self ref YYLEX): int +{ + while(l.n < len l.s && isspace(l.s[l.n])) + l.n++; + + if(l.n == len l.s) + return -1; + + if(isdigit(l.s[l.n])){ + for(o := l.n; o < len l.s && isdigit(l.s[o]); o++) + ; + l.lval.e = mkNUM(big l.s[l.n:o]); + l.n = o; + return NUMBER; + } + + return l.s[l.n++]; +} + +isdigit(c: int): int +{ + return c >= '0' && c <= '9'; +} + +isspace(c: int): int +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f'; +} + +YYLEX.error(nil: self ref YYLEX, s: string) +{ + raise s; +} + +eval(e: ref Exp): big +{ + case e.ty { + NUM => + return e.n; + DOT => + return dot; + DOLLAR => + return dollar; + ADD => + return eval(e.e1)+eval(e.e2); + SUB => + return eval(e.e1)-eval(e.e2); + MUL => + return eval(e.e1)*eval(e.e2); + DIV => + i := eval(e.e2); + if(i == big 0) + raise "division by zero"; + return eval(e.e1)/i; + FRAC => + return (size*eval(e.e1))/big 100; + NEG => + return -eval(e.e1); + * => + raise "invalid operator"; + } +} + +parseexpr(s: string, xdot: big, xdollar: big, xsize: big): (big, string) +{ + dot = xdot; + size = xsize; + dollar = xdollar; + l := ref YYLEX(s, 0, YYSTYPE(nil)); + { + yyparse(l); + if(yyexp == nil) + return (big 0, "nil yylval?"); + return (eval(yyexp), nil); + }exception e{ + "*" => + return (big 0, e); + } +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + while((args = tl args) != nil){ + (r, e) := parseexpr(hd args, big 1000, big 1000000, big 1000000); + if(e != nil) + sys->print("%s\n", e); + else + sys->print("%bd\n", r); + } +} + diff --git a/appl/cmd/disk/prep/fdisk.b b/appl/cmd/disk/prep/fdisk.b new file mode 100644 index 00000000..00ecbb36 --- /dev/null +++ b/appl/cmd/disk/prep/fdisk.b @@ -0,0 +1,925 @@ +implement Fdisk; + +# +# fdisk - edit dos disk partition table +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "disks.m"; + disks: Disks; + Disk, PCpart: import disks; + NTentry, Toffset, TentrySize: import Disks; + Magic0, Magic1: import Disks; + readn: import disks; + +include "pedit.m"; + pedit: Pedit; + Edit, Part: import pedit; + +include "arg.m"; + +Fdisk: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Mpart: con 64; + +blank := 0; +dowrite := 0; +file := 0; +rdonly := 0; +doauto := 0; +mbroffset := big 0; +printflag := 0; +printchs := 0; +sec2cyl := big 0; +written := 0; + +edit: ref Edit; +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + disks = load Disks Disks->PATH; + pedit = load Pedit Pedit->PATH; + + sys->pctl(Sys->FORKFD, nil); + disks->init(); + pedit->init(); + + edit = Edit.mk("cylinder"); + + edit.add = cmdadd; + edit.del = cmddel; + edit.okname = cmdokname; + edit.ext = cmdext; + edit.help = cmdhelp; + edit.sum = cmdsum; + edit.write = cmdwrite; + edit.printctl = cmdprintctl; + + stderr = sys->fildes(2); + + secsize := 0; + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("disk/fdisk [-abfprvw] [-s sectorsize] /dev/sdC0/data"); + while((o := arg->opt()) != 0) + case o { + 'a' => + doauto++; + 'b' => + blank++; + 'f' => + file++; + 'p' => + printflag++; + 'r' => + rdonly++; + 's' => + secsize = int arg->earg(); + 'v' => + printchs++; + 'w' => + dowrite++; + * => + arg->usage(); + } + args = arg->argv(); + if(len args != 1) + arg->usage(); + arg = nil; + + mode := Sys->ORDWR; + if(rdonly) + mode = Sys->OREAD; + edit.disk = Disk.open(hd args, mode, file); + if(edit.disk == nil) { + sys->fprint(stderr, "cannot open disk: %r\n"); + exits("opendisk"); + } + + if(secsize != 0) { + edit.disk.secsize = secsize; + edit.disk.secs = edit.disk.size / big secsize; + } + + sec2cyl = big (edit.disk.h * edit.disk.s); + edit.end = edit.disk.secs / sec2cyl; + + findmbr(edit); + + if(blank) + blankpart(edit); + else + rdpart(edit, big 0, big 0); + + if(doauto) + autopart(edit); + + { + if(dowrite) + edit.runcmd("w"); + + if(printflag) + edit.runcmd("P"); + + if(dowrite || printflag) + exits(nil); + + sys->fprint(stderr, "cylinder = %bd bytes\n", sec2cyl*big edit.disk.secsize); + edit.runcmd("p"); + for(;;) { + sys->fprint(stderr, ">>> "); + edit.runcmd(edit.getline()); + } + }exception e{ + "*" => + sys->fprint(stderr, "fdisk: exception %q\n", e); + if(written) + recover(edit); + } +} + +Active: con 16r80; # partition is active +Primary: con 16r01; # internal flag + +TypeBB: con 16rFF; + +TypeEMPTY: con 16r00; +TypeFAT12: con 16r01; +TypeXENIX: con 16r02; # root +TypeXENIXUSR: con 16r03; # usr +TypeFAT16: con 16r04; +TypeEXTENDED: con 16r05; +TypeFATHUGE: con 16r06; +TypeHPFS: con 16r07; +TypeAIXBOOT: con 16r08; +TypeAIXDATA: con 16r09; +TypeOS2BOOT: con 16r0A; # OS/2 Boot Manager +TypeFAT32: con 16r0B; # FAT 32 +TypeFAT32LBA: con 16r0C; # FAT 32 needing LBA support +TypeEXTHUGE: con 16r0F; # FAT 32 extended partition +TypeUNFORMATTED: con 16r16; # unformatted primary partition (OS/2 FDISK)? +TypeHPFS2: con 16r17; +TypeIBMRecovery: con 16r1C; # really hidden fat +TypeCPM0: con 16r52; +TypeDMDDO: con 16r54; # Disk Manager Dynamic Disk Overlay +TypeGB: con 16r56; # ???? +TypeSPEEDSTOR: con 16r61; +TypeSYSV386: con 16r63; # also HURD? +TypeNETWARE: con 16r64; +TypePCIX: con 16r75; +TypeMINIX13: con 16r80; # Minix v1.3 and below +TypeMINIX: con 16r81; # Minix v1.5+ +TypeLINUXSWAP: con 16r82; +TypeLINUX: con 16r83; +TypeLINUXEXT: con 16r85; +TypeAMOEBA: con 16r93; +TypeAMOEBABB: con 16r94; +TypeBSD386: con 16rA5; +TypeBSDI: con 16rB7; +TypeBSDISWAP: con 16rB8; +TypeOTHER: con 16rDA; +TypeCPM: con 16rDB; +TypeDellRecovery: con 16rDE; +TypeSPEEDSTOR12: con 16rE1; +TypeSPEEDSTOR16: con 16rE4; +TypeLANSTEP: con 16rFE; + +Type9: con Disks->Type9; + +TableSize: con TentrySize*NTentry; +Omagic: con TableSize; + +Type: adt { + desc: string; + name: string; +}; + +Dospart: adt { + p: ref Part; + pc: ref PCpart; + primary: int; + lba: big; # absolute address + size: big; +}; + +Recover: adt { + table: array of byte; # [TableSize+2] copy of table and magic + lba: big; # where it came from +}; + +types: array of Type = array[256] of { + TypeEMPTY => ( "EMPTY", "" ), + TypeFAT12 => ( "FAT12", "dos" ), + TypeFAT16 => ( "FAT16", "dos" ), + TypeFAT32 => ( "FAT32", "dos" ), + TypeFAT32LBA => ( "FAT32LBA", "dos" ), + TypeEXTHUGE => ( "EXTHUGE", "" ), + TypeIBMRecovery => ( "IBMRECOVERY", "ibm" ), + TypeEXTENDED => ( "EXTENDED", "" ), + TypeFATHUGE => ( "FATHUGE", "dos" ), + TypeBB => ( "BB", "bb" ), + + TypeXENIX => ( "XENIX", "xenix" ), + TypeXENIXUSR => ( "XENIX USR", "xenixusr" ), + TypeHPFS => ( "HPFS", "ntfs" ), + TypeAIXBOOT => ( "AIXBOOT", "aixboot" ), + TypeAIXDATA => ( "AIXDATA", "aixdata" ), + TypeOS2BOOT => ( "OS/2BOOT", "os2boot" ), + TypeUNFORMATTED => ( "UNFORMATTED", "" ), + TypeHPFS2 => ( "HPFS2", "hpfs2" ), + TypeCPM0 => ( "CPM0", "cpm0" ), + TypeDMDDO => ( "DMDDO", "dmdd0" ), + TypeGB => ( "GB", "gb" ), + TypeSPEEDSTOR => ( "SPEEDSTOR", "speedstor" ), + TypeSYSV386 => ( "SYSV386", "sysv386" ), + TypeNETWARE => ( "NETWARE", "netware" ), + TypePCIX => ( "PCIX", "pcix" ), + TypeMINIX13 => ( "MINIXV1.3", "minix13" ), + TypeMINIX => ( "MINIXV1.5", "minix15" ), + TypeLINUXSWAP => ( "LINUXSWAP", "linuxswap" ), + TypeLINUX => ( "LINUX", "linux" ), + TypeLINUXEXT => ( "LINUXEXTENDED", "" ), + TypeAMOEBA => ( "AMOEBA", "amoeba" ), + TypeAMOEBABB => ( "AMOEBABB", "amoebaboot" ), + TypeBSD386 => ( "BSD386", "bsd386" ), + TypeBSDI => ( "BSDI", "bsdi" ), + TypeBSDISWAP => ( "BSDISWAP", "bsdiswap" ), + TypeOTHER => ( "OTHER", "other" ), + TypeCPM => ( "CPM", "cpm" ), + TypeDellRecovery => ( "DELLRECOVERY", "dell" ), + TypeSPEEDSTOR12 => ( "SPEEDSTOR12", "speedstor" ), + TypeSPEEDSTOR16 => ( "SPEEDSTOR16", "speedstor" ), + TypeLANSTEP => ( "LANSTEP", "lanstep" ), + + Type9 => ( "PLAN9", "plan9" ), + + * => (nil, nil), +}; + +dosparts: list of ref Dospart; + +tag2part(p: ref Part): ref Dospart +{ + for(l := dosparts; l != nil; l = tl l) + if((hd l).p.tag == p.tag) + return hd l; + raise "tag2part: cannot happen"; +} + +typestr0(ptype: int): string +{ + if(ptype < 0 || ptype >= len types || types[ptype].desc == nil) + return sys->sprint("type %d", ptype); + return types[ptype].desc; +} + +gettable(disk: ref Disk, addr: big, mbr: int): array of byte +{ + table := array[TableSize+2] of {* => byte 0}; + diskread(disk, table, len table, addr, Toffset); + if(mbr){ + # the informal specs say all must have this but apparently not, only mbr + if(int table[Omagic] != Magic0 || int table[Omagic+1] != Magic1) + sysfatal("did not find master boot record"); + } + return table; +} + +diskread(disk: ref Disk, data: array of byte, ndata: int, sec: big, off: int) +{ + a := sec*big disk.secsize + big off; + if(sys->seek(disk.fd, a, 0) != a) + sysfatal(sys->sprint("diskread seek %bud.%ud: %r", sec, off)); + if(readn(disk.fd, data, ndata) != ndata) + sysfatal(sys->sprint("diskread %ud at %bud.%ud: %r", ndata, sec, off)); +} + +puttable(disk: ref Disk, table: array of byte, sec: big): int +{ + return diskwrite(disk, table, len table, sec, Toffset); +} + +diskwrite(disk: ref Disk, data: array of byte, ndata: int, sec: big, off: int): int +{ + written = 1; + a := sec*big disk.secsize + big off; + if(sys->seek(disk.wfd, a, 0) != a || + sys->write(disk.wfd, data, ndata) != ndata){ + sys->fprint(stderr, "write %d bytes at %bud.%ud failed: %r\n", ndata, sec, off); + return -1; + } + return 0; +} + +partgen := 0; +parttag := 0; + +mkpart(name: string, primary: int, lba: big, size: big, pcpart: ref PCpart): ref Dospart +{ + p := ref Dospart; + if(name == nil){ + if(primary) + c := 'p'; + else + c = 's'; + name = sys->sprint("%c%d", c, ++partgen); + } + + if(pcpart != nil) + p.pc = pcpart; + else + p.pc = ref PCpart(0, 0, big 0, big 0, big 0); + + p.primary = primary; + p.p = ref Part; # TO DO + p.p.name = name; + p.p.start = lba/sec2cyl; + p.p.end = (lba+size)/sec2cyl; + p.p.ctlstart = lba; + p.p.ctlend = lba+size; + p.p.tag = ++parttag; + p.lba = lba; # absolute lba + p.size = size; + dosparts = p :: dosparts; + return p; +} + +# +# Recovery takes care of remembering what the various tables +# looked like when we started, attempting to restore them when +# we are finished. +# +rtabs: list of ref Recover; + +addrecover(t: array of byte, lba: big) +{ + tc := array[TableSize+2] of byte; + tc[0:] = t[0:len tc]; + rtabs = ref Recover(tc, lba) :: rtabs; +} + +recover(edit: ref Edit) +{ + err := 0; + for(rl := rtabs; rl != nil; rl = tl rl){ + r := hd rl; + if(puttable(edit.disk, r.table, r.lba) < 0) + err = 1; + } + if(err) { + sys->fprint(stderr, "warning: some writes failed during restoration of old partition tables\n"); + exits("inconsistent"); + } else + sys->fprint(stderr, "restored old partition tables\n"); + + ctlfd := edit.disk.ctlfd; + if(ctlfd != nil){ + offset := edit.disk.offset; + for(i:=0; i<len edit.part; i++) + if(edit.part[i].ctlname != nil && sys->fprint(ctlfd, "delpart %s", edit.part[i].ctlname)<0) + sys->fprint(stderr, "delpart failed: %s: %r", edit.part[i].ctlname); + for(i=0; i<len edit.ctlpart; i++) + if(edit.part[i].name != nil && sys->fprint(ctlfd, "delpart %s", edit.ctlpart[i].name)<0) + sys->fprint(stderr, "delpart failed: %s: %r", edit.ctlpart[i].name); + for(i=0; i<len edit.ctlpart; i++){ + if(sys->fprint(ctlfd, "part %s %bd %bd", edit.ctlpart[i].name, + edit.ctlpart[i].start+offset, edit.ctlpart[i].end+offset) < 0){ + sys->fprint(stderr, "restored disk partition table but not kernel; reboot\n"); + exits("inconsistent"); + } + } + } + exits("restored"); +} + +# +# Read the partition table (including extended partition tables) +# from the disk into the part array. +# +rdpart(edit: ref Edit, lba: big, xbase: big) +{ + if(xbase == big 0) + xbase = lba; # extended partition in mbr sets the base + + table := gettable(edit.disk, mbroffset+lba, lba == big 0); + addrecover(table, mbroffset+lba); + + for(tp := 0; tp<TableSize; tp += TentrySize){ + dp := PCpart.extract(table[tp:], edit.disk); + case dp.ptype { + TypeEMPTY => + ; + TypeEXTENDED or + TypeEXTHUGE or + TypeLINUXEXT => + rdpart(edit, xbase+dp.offset, xbase); + * => + p := mkpart(nil, lba==big 0, lba+dp.offset, dp.size, ref dp); + if((err := edit.addpart(p.p)) != nil) + sys->fprint(stderr, "error adding partition: %s\n", err); + } + } +} + +blankpart(edit: ref Edit) +{ + edit.changed = 1; +} + +findmbr(edit: ref Edit) +{ + table := gettable(edit.disk, big 0, 1); + for(tp := 0; tp < TableSize; tp += TentrySize){ + p := PCpart.extract(table[tp:], edit.disk); + if(p.ptype == TypeDMDDO) + mbroffset = big edit.disk.s; + } +} + +haveroom(edit: ref Edit, primary: int, start: big): int +{ + if(primary) { + # + # must be open primary slot. + # primary slots are taken by primary partitions + # and runs of secondary partitions. + # + n := 0; + lastsec := 0; + for(i:=0; i<len edit.part; i++) { + p := tag2part(edit.part[i]); + if(p.primary){ + n++; + lastsec = 0; + }else if(!lastsec){ + n++; + lastsec = 1; + } + } + return n<4; + } + + # + # secondary partitions can be inserted between two primary + # partitions only if there is an empty primary slot. + # otherwise, we can put a new secondary partition next + # to a secondary partition no problem. + # + n := 0; + for(i:=0; i<len edit.part; i++){ + p := tag2part(edit.part[i]); + if(p.primary) + n++; + pend := p.p.end; + q: ref Dospart; + qstart: big; + if(i+1<len edit.part){ + q = tag2part(edit.part[i+1]); + qstart = q.p.start; + }else{ + qstart = edit.end; + q = nil; + } + if(start < pend || start >= qstart) + continue; + # we go between these two + if(p.primary==0 || (q != nil && q.primary==0)) + return 1; + } + # not next to a secondary, need a new primary + return n<4; +} + +autopart(edit: ref Edit) +{ + for(i:=0; i<len edit.part; i++) + if(tag2part(edit.part[i]).pc.ptype == Type9) + return; + + # look for the biggest gap in which we can put a primary partition + start := big 0; + bigsize := big 0; + bigstart := big 0; + for(i=0; i<len edit.part; i++) { + p := tag2part(edit.part[i]); + if(p.p.start > start && p.p.start - start > bigsize && haveroom(edit, 1, start)) { + bigsize = p.p.start - start; + bigstart = start; + } + start = p.p.end; + } + + if(edit.end - start > bigsize && haveroom(edit, 1, start)) { + bigsize = edit.end - start; + bigstart = start; + } + if(bigsize < big 1) { + sys->fprint(stderr, "couldn't find space or partition slot for plan 9 partition\n"); + return; + } + + # set new partition active only if no others are + active := Active; + for(i=0; i<len edit.part; i++){ + p := tag2part(edit.part[i]); + if(p.primary && p.pc.active & Active) + active = 0; + } + + # add new plan 9 partition + bigsize *= sec2cyl; + bigstart *= sec2cyl; + if(bigstart == big 0) { + bigstart += big edit.disk.s; + bigsize -= big edit.disk.s; + } + p := mkpart(nil, 1, bigstart, bigsize, nil); + p.p.changed = 1; + p.pc.active = active; + p.pc.ptype = Type9; + edit.changed = 1; + if((err := edit.addpart(p.p)) != nil){ + sys->fprint(stderr, "error adding plan9 partition: %s\n", err); + return; + } +} + +namelist: list of string; + +plan9print(part: ref Dospart, fd: ref Sys->FD) +{ + vname := types[part.pc.ptype].name; + if(vname==nil) { + part.p.ctlname = ""; + return; + } + + start := mbroffset+part.lba; + end := start+part.size; + + # avoid names like plan90 + i := len vname - 1; + if(isdigit(vname[i])) + sep := "."; + else + sep = ""; + + i = 0; + name := sys->sprint("%s", vname); + ok: int; + do { + ok = 1; + for(nl := namelist; nl != nil; nl = tl nl) + if(name == hd nl) { + i++; + name = sys->sprint("%s%s%d", vname, sep, i); + ok = 0; + } + } while(ok == 0); + + namelist = name :: namelist; + part.p.ctlname = name; + + if(fd != nil) + sys->print("part %s %bd %bd\n", name, start, end); +} + +cmdprintctl(edit: ref Edit, ctlfd: ref Sys->FD) +{ + namelist = nil; + for(i:=0; i<len edit.part; i++) + plan9print(tag2part(edit.part[i]), nil); + edit.ctldiff(ctlfd); +} + +cmdokname(nil: ref Edit, name: string): string +{ + if(name[0] != 'p' && name[0] != 's' || len name < 2) + return "name must be pN or sN"; + for(i := 1; i < len name; i++) + if(!isdigit(name[i])) + return "name must be pN or sN"; + + return nil; +} + +KB: con big 1024; +MB: con KB*KB; +GB: con KB*MB; + +cmdsum(edit: ref Edit, vp: ref Part, a, b: big) +{ + if(vp != nil) + p := tag2part(vp); + + qual: string; + if(p != nil && p.p.changed) + qual += "'"; + else + qual += " "; + if(p != nil && p.pc.active&Active) + qual += "*"; + else + qual += " "; + + if(p != nil) + name := p.p.name; + else + name = "empty"; + if(p != nil) + ty := " "+typestr0(p.pc.ptype); + else + ty = ""; + + sz := (b-a)*big edit.disk.secsize*sec2cyl; + suf := "B"; + div := big 1; + if(sz >= big 1*GB){ + suf = "GB"; + div = GB; + }else if(sz >= big 1*MB){ + suf = "MB"; + div = MB; + }else if(sz >= big 1*KB){ + suf = "KB"; + div = KB; + } + + if(div == big 1) + sys->print("%s %-12s %*bd %-*bd (%bd cylinders, %bd %s)%s\n", qual, name, + edit.disk.width, a, edit.disk.width, b, b-a, sz, suf, ty); + else + sys->print("%s %-12s %*bd %-*bd (%bd cylinders, %bd.%.2d %s)%s\n", qual, name, + edit.disk.width, a, edit.disk.width, b, b-a, + sz/div, int(((sz%div)*big 100)/div), suf, ty); +} + +cmdadd(edit: ref Edit, name: string, start: big, end: big): string +{ + if(!haveroom(edit, name[0]=='p', start)) + return "no room for partition"; + start *= sec2cyl; + end *= sec2cyl; + if(start == big 0 || name[0] != 'p') + start += big edit.disk.s; + p := mkpart(name, name[0]=='p', start, end-start, nil); + p.p.changed = 1; + p.pc.ptype = Type9; + return edit.addpart(p.p); +} + +cmddel(edit: ref Edit, p: ref Part): string +{ + return edit.delpart(p); +} + +cmdwrite(edit: ref Edit): string +{ + wrpart(edit); + return nil; +} + +help: con + "A name - set partition active\n"+ + "P - sys->print table in ctl format\n"+ + "R - restore disk back to initial configuration and exit\n"+ + "e - show empty dos partitions\n"+ + "t name [type] - set partition type\n"; + +cmdhelp(nil: ref Edit): string +{ + sys->print("%s\n", help); + return nil; +} + +cmdactive(edit: ref Edit, f: array of string): string +{ + if(len f != 2) + return "args"; + + if(f[1][0] != 'p') + return "cannot set secondary partition active"; + + if((p := tag2part(edit.findpart(f[1]))) == nil) + return "unknown partition"; + + for(i:=0; i<len edit.part; i++) { + ip := tag2part(edit.part[i]); + if(ip.pc.active & Active) { + ip.pc.active &= ~Active; + ip.p.changed = 1; + edit.changed = 1; + } + } + + if((p.pc.active & Active) == 0) { + p.pc.active |= Active; + p.p.changed = 1; + edit.changed = 1; + } + + return nil; +} + +strupr(s: string): string +{ + for(i := 0; i < len s; i++) + if(s[i] >= 'a' && s[i] <= 'z') + s[i] += 'A' - 'a'; + return s; +} + +dumplist() +{ + n := 0; + for(i:=0; i<len types; i++) { + if(types[i].desc != nil) { + sys->print("%-16s", types[i].desc); + if(n++%4 == 3) + sys->print("\n"); + } + } + if(n%4) + sys->print("\n"); +} + +cmdtype(edit: ref Edit, f: array of string): string +{ + if(len f < 2) + return "args"; + + if((p := tag2part(edit.findpart(f[1]))) == nil) + return "unknown partition"; + + q: string; + if(len f == 2) { + for(;;) { + sys->fprint(stderr, "new partition type [? for list]: "); + q = edit.getline(); + if(q[0] == '?') + dumplist(); + else + break; + } + } else + q = f[2]; + + q = strupr(q); + for(i:=0; i<len types; i++) + if(types[i].desc != nil && types[i].desc == q) + break; + if(i < len types && p.pc.ptype != i) { + p.pc.ptype = i; + p.p.changed = 1; + edit.changed = 1; + } + return nil; +} + +cmdext(edit: ref Edit, f: array of string): string +{ + case f[0][0] { + 'A' => + return cmdactive(edit, f); + 't' => + return cmdtype(edit, f); + 'R' => + recover(edit); + return nil; + * => + return "unknown command"; + } +} + +wrextend(edit: ref Edit, i: int, xbase: big, startlba: big): (int, big) +{ + if(i == len edit.part){ + endlba := edit.disk.secs; + if(startlba < endlba) + wrzerotab(edit.disk, mbroffset+startlba); + return (i, endlba); + } + + p := tag2part(edit.part[i]); + if(p.primary){ + endlba := p.p.start*sec2cyl; + if(startlba < endlba) + wrzerotab(edit.disk, mbroffset+startlba); + return (i, endlba); + } + + disk := edit.disk; + table := gettable(disk, mbroffset+startlba, 0); + + (ni, endlba) := wrextend(edit, i+1, xbase, p.p.end*sec2cyl); + + tp := wrtentry(disk, table[0:], p.pc.active, p.pc.ptype, startlba, startlba+big disk.s, p.p.end*sec2cyl); + if(p.p.end*sec2cyl != endlba) + tp += wrtentry(disk, table[tp:], 0, TypeEXTENDED, xbase, p.p.end*sec2cyl, endlba); + + for(; tp<TableSize; tp++) + table[tp] = byte 0; + + table[Omagic] = byte Magic0; + table[Omagic+1] = byte Magic1; + + if(puttable(edit.disk, table, mbroffset+startlba) < 0) + recover(edit); + return (ni, endlba); +} + +wrzerotab(disk: ref Disk, addr: big) +{ + table := array[TableSize+2] of {Omagic => byte Magic0, Omagic+1 => byte Magic1, * => byte 0}; + if(puttable(disk, table, addr) < 0) + recover(edit); +} + +wrpart(edit: ref Edit) +{ + disk := edit.disk; + + table := gettable(disk, mbroffset, 0); + + tp := 0; + for(i:=0; i<len edit.part && tp<TableSize; ) { + p := tag2part(edit.part[i]); + if(p.p.start == big 0) + s := big disk.s; + else + s = p.p.start*sec2cyl; + if(p.primary) { + tp += wrtentry(disk, table[tp:], p.pc.active, p.pc.ptype, big 0, s, p.p.end*sec2cyl); + i++; + }else{ + (ni, endlba) := wrextend(edit, i, p.p.start*sec2cyl, p.p.start*sec2cyl); + if(endlba >= big 1024*sec2cyl) + t := TypeEXTHUGE; + else + t = TypeEXTENDED; + tp += wrtentry(disk, table[tp:], 0, t, big 0, s, endlba); + i = ni; + } + } + for(; tp<TableSize; tp++) + table[tp] = byte 0; + + if(i != len edit.part) + raise "wrpart: cannot happen #1"; + + if(puttable(disk, table, mbroffset) < 0) + recover(edit); + + # bring parts up to date + namelist = nil; + for(i=0; i<len edit.part; i++) + plan9print(tag2part(edit.part[i]), nil); + + if(edit.ctldiff(disk.ctlfd) < 0) + sys->fprint(stderr, "?warning: partitions could not be updated in devsd\n"); +} + +isdigit(c: int): int +{ + return c >= '0' && c <= '9'; +} + +sysfatal(s: string) +{ + sys->fprint(stderr, "fdisk: %s\n", s); + raise "fail:error"; +} + +exits(s: string) +{ + if(s != nil) + raise "fail:"+s; + exit; +} + +assert(i: int) +{ + if(!i) + raise "assertion failed"; +} + +wrtentry(disk: ref Disk, entry: array of byte, active: int, ptype: int, xbase: big, lba: big, end: big): int +{ + pc: PCpart; + pc.active = active; + pc.ptype = ptype; + pc.base = xbase; + pc.offset = lba-xbase; + pc.size = end-lba; + entry[0:] = pc.bytes(disk); + return TentrySize; +} diff --git a/appl/cmd/disk/prep/mkfile b/appl/cmd/disk/prep/mkfile new file mode 100644 index 00000000..714c26f8 --- /dev/null +++ b/appl/cmd/disk/prep/mkfile @@ -0,0 +1,26 @@ +<../../../../mkconfig + +TARG=\ + fdisk.dis\ + pedit.dis\ + prep.dis\ + calc.tab.dis\ + +MODULES=\ + pedit.m\ + +SYSMODULES=\ + arg.m\ + sys.m\ + draw.m\ + disks.m\ + bufio.m\ + string.m\ + +DISBIN=$ROOT/dis/disk + +<$ROOT/mkfiles/mkdis + +# calc +calc.tab.b: + yacc -s calc -d calc.y diff --git a/appl/cmd/disk/prep/pedit.b b/appl/cmd/disk/prep/pedit.b new file mode 100644 index 00000000..f55bcaff --- /dev/null +++ b/appl/cmd/disk/prep/pedit.b @@ -0,0 +1,504 @@ +implement Pedit; + +# +# disk partition editor +# + +include "sys.m"; + sys: Sys; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "disks.m"; + disks: Disks; + Disk: import disks; + readn: import disks; + +include "draw.m"; +include "calc.tab.m"; + calc: Calc; + +include "pedit.m"; + +Cmd: adt { + c: int; + f: ref fn(e: ref Edit, a: array of string): string; +}; + +cmds: array of Cmd; + +bin: ref Iobuf; + +init() +{ + sys = load Sys Sys->PATH; + calc = load Calc "/dis/disk/calc.tab.dis"; + bufio = load Bufio Bufio->PATH; + disks = load Disks Disks->PATH; + disks->init(); + + bin = bufio->fopen(sys->fildes(0), Bufio->OREAD); + cmds = array[] of { + ('.', editdot), + ('a', editadd), + ('d', editdel), + ('?', edithelp), + ('h', edithelp), + ('P', editctlprint), + ('p', editprint), + ('w', editwrite), + ('q', editquit), + }; +} + +Edit.mk(unit: string): ref Edit +{ + e := ref Edit; + e.unit = unit; + e.dot = big 0; + e.end = big 0; + e.changed = 0; + e.warned = 0; + e.lastcmd = 0; + return e; +} + +Edit.getline(edit: self ref Edit): string +{ + p := bin.gets('\n'); + if(p == nil){ + if(edit.changed) + sys->fprint(sys->fildes(2), "?warning: changes not written\n"); + exit; + } + for(i := 0; i < len p; i++) + if(!isspace(p[i])) + break; + if(i) + return p[i:]; + return p; +} + +Edit.findpart(edit: self ref Edit, name: string): ref Part +{ + for(i:=0; i<len edit.part; i++) + if(edit.part[i].name == name) + return edit.part[i]; + return nil; +} + +okname(edit: ref Edit, name: string): string +{ + if(name[0] == '\0') + return "partition has no name"; + + for(i:=0; i<len edit.part; i++) { + if(name == edit.part[i].name) + return sys->sprint("already have partition with name '%s'", name); + } + return nil; +} + +Edit.addpart(edit: self ref Edit, p: ref Part): string +{ + if((err := okname(edit, p.name)) != nil) + return err; + + for(i:=0; i<len edit.part; i++) { + if(p.start < edit.part[i].end && edit.part[i].start < p.end) { + msg := sys->sprint("\"%s\" %bd-%bd overlaps with \"%s\" %bd-%bd", + p.name, p.start, p.end, + edit.part[i].name, edit.part[i].start, edit.part[i].end); + # return msg; + } + } + + if(len edit.part >= Maxpart) + return "too many partitions"; + + pa := array[i+1] of ref Part; + pa[0:] = edit.part; + edit.part = pa; + + edit.part[i] = p; + for(; i > 0 && p.start < edit.part[i-1].start; i--) { + edit.part[i] = edit.part[i-1]; + edit.part[i-1] = p; + } + + if(p.changed) + edit.changed = 1; + return nil; +} + +Edit.delpart(edit: self ref Edit, p: ref Part): string +{ + n := len edit.part; + for(i:=0; i<n; i++) + if(edit.part[i] == p) + break; + if(i >= n) + raise "internal error: Part not found"; + n--; + pa := array[n] of ref Part; + if(n){ + pa[0:] = edit.part[0:i]; + if(i != n) + pa[i:] = edit.part[i+1:]; + } + edit.part = pa; + edit.changed = 1; + return nil; +} + +editdot(edit: ref Edit, argv: array of string): string +{ + if(len argv == 1) { + sys->print("\t. %bd\n", edit.dot); + return nil; + } + + if(len argv > 2) + return "args"; + + (ndot, err) := calc->parseexpr(argv[1], edit.dot, edit.end, edit.end); + if(err != nil) + return err; + + edit.dot = ndot; + return nil; +} + +editadd(edit: ref Edit, argv: array of string): string +{ + if(len argv < 2) + return "args"; + + name := argv[1]; + if((err := okname(edit, name)) != nil || edit.okname != nil && (err = edit.okname(edit, name)) != nil) + return err; + + if(len argv >= 3) + q := argv[2]; + else { + sys->fprint(sys->fildes(2), "start %s: ", edit.unit); + q = edit.getline(); + } + start: big; + (start, err) = calc->parseexpr(q, edit.dot, edit.end, edit.end); + if(err != nil) + return err; + + if(start < big 0 || start >= edit.end) + return "start out of range"; + + for(i:=0; i < len edit.part; i++) { + if(edit.part[i].start <= start && start < edit.part[i].end) + return sys->sprint("start %s in partition '%s'", edit.unit, edit.part[i].name); + } + + maxend := edit.end; + for(i=0; i < len edit.part; i++) + if(start < edit.part[i].start && edit.part[i].start < maxend) + maxend = edit.part[i].start; + + if(len argv >= 4) + q = argv[3]; + else { + sys->fprint(sys->fildes(2), "end [%bd..%bd] ", start, maxend); + q = edit.getline(); + } + end: big; + (end, err) = calc->parseexpr(q, edit.dot, maxend, edit.end); + if(err != nil) + return err; + + if(start == end) + return "size zero partition"; + + if(end <= start || end > maxend) + return "end out of range"; + + if(len argv > 4) + return "args"; + + if((err = edit.add(edit, name, start, end)) != nil) + return err; + + edit.dot = end; + return nil; +} + +editdel(edit: ref Edit, argv: array of string): string +{ + if(len argv != 2) + return "args"; + + if((p := edit.findpart(argv[1])) == nil) + return "no such partition"; + + return edit.del(edit, p); +} + +helptext := + ". [newdot] - display or set value of dot\n"+ + "a name [start [end]] - add partition\n"+ + "d name - delete partition\n"+ + "h - sys->print help message\n"+ + "p - sys->print partition table\n"+ + "P - sys->print commands to update sd(3) device\n"+ + "w - write partition table\n"+ + "q - quit\n"; + +edithelp(edit: ref Edit, nil: array of string): string +{ + sys->print("%s", helptext); + if(edit.help != nil) + return edit.help(edit); + return nil; +} + +editprint(edit: ref Edit, argv: array of string): string +{ + if(len argv != 1) + return "args"; + + lastend := big 0; + part := edit.part; + for(i:=0; i<len edit.part; i++) { + if(lastend < part[i].start) + edit.sum(edit, nil, lastend, part[i].start); + edit.sum(edit, part[i], part[i].start, part[i].end); + lastend = part[i].end; + } + if(lastend < edit.end) + edit.sum(edit, nil, lastend, edit.end); + return nil; +} + +editwrite(edit: ref Edit, argv: array of string): string +{ + if(len argv != 1) + return "args"; + + if(edit.disk.rdonly) + return "read only"; + + err := edit.write(edit); + if(err != nil) + return err; + for(i:=0; i<len edit.part; i++) + edit.part[i].changed = 0; + edit.changed = 0; + return nil; +} + +editquit(edit: ref Edit, argv: array of string): string +{ + if(len argv != 1) { + edit.warned = 0; + return "args"; + } + + if(edit.changed && (!edit.warned || edit.lastcmd != 'q')) { + edit.warned = 1; + return "changes unwritten"; + } + + exit; +} + +editctlprint(edit: ref Edit, argv: array of string): string +{ + if(len argv != 1) + return "args"; + + if(edit.printctl != nil) + edit.printctl(edit, sys->fildes(1)); + else + edit.ctldiff(sys->fildes(1)); + return nil; +} + +Edit.runcmd(edit: self ref Edit, cmd: string) +{ + (nf, fl) := sys->tokenize(cmd, " \t\n\r"); + if(nf < 1) + return; + f := array[nf] of string; + for(nf = 0; fl != nil; fl = tl fl) + f[nf++] = hd fl; + if(len f[0] != 1) { + sys->fprint(sys->fildes(2), "?\n"); + return; + } + + err := ""; + for(i:=0; i<len cmds; i++) { + if(cmds[i].c == f[0][0]) { + op := cmds[i].f; + err = op(edit, f); + break; + } + } + if(i == len cmds){ + if(edit.ext != nil) + err = edit.ext(edit, f); + else + err = "unknown command"; + } + if(err != nil) + sys->fprint(sys->fildes(2), "?%s\n", err); + edit.lastcmd = f[0][0]; +} + +isspace(c: int): int +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +ctlmkpart(name: string, start: big, end: big, changed: int): ref Part +{ + p := ref Part; + p.name = name; + p.ctlname = name; + p.start = start; + p.end = end; + p.ctlstart = big 0; + p.ctlend = big 0; + p.changed = changed; + return p; +} + +rdctlpart(edit: ref Edit) +{ + disk := edit.disk; + edit.ctlpart = array[0] of ref Part; + sys->seek(disk.ctlfd, big 0, 0); + buf := array[4096] of byte; + if(readn(disk.ctlfd, buf, len buf) <= 0) + return; + for(i := 0; i < len buf; i++) + if(buf[i] == byte 0) + break; + + (nline, lines) := sys->tokenize(string buf[0:i], "\n\r"); + edit.ctlpart = array[nline] of ref Part; # upper bound + npart := 0; + for(i=0; i<nline; i++){ + line := hd lines; + lines = tl lines; + if(len line < 5 || line[0:5] != "part ") + continue; + + (nf, f) := sys->tokenize(line, " \t"); + if(nf != 4 || hd f != "part") + break; + + a := big hd tl tl f; + b := big hd tl tl tl f; + + if(a >= b) + break; + + # only gather partitions contained in the disk partition we are editing + if(a < disk.offset || disk.offset+disk.secs < b) + continue; + + a -= disk.offset; + b -= disk.offset; + + # the partition we are editing does not count + if(hd tl f == disk.part) + continue; + + edit.ctlpart[npart++] = ctlmkpart(hd tl f, a, b, 0); + } + if(npart != len edit.ctlpart) + edit.ctlpart = edit.ctlpart[0:npart]; +} + +ctlstart(p: ref Part): big +{ + if(p.ctlstart != big 0) + return p.ctlstart; + return p.start; +} + +ctlend(p: ref Part): big +{ + if(p.ctlend != big 0) + return p.ctlend; + return p.end; +} + +areequiv(p: ref Part, q: ref Part): int +{ + if(p.ctlname == nil || q.ctlname == nil) + return 0; + return p.ctlname == q.ctlname && + ctlstart(p) == ctlstart(q) && ctlend(p) == ctlend(q); +} + +unchange(edit: ref Edit, p: ref Part) +{ + for(i:=0; i<len edit.ctlpart; i++) { + q := edit.ctlpart[i]; + if(p.start <= q.start && q.end <= p.end) + q.changed = 0; + } + if(p.changed) + raise "internal error: Part unchanged"; +} + +Edit.ctldiff(edit: self ref Edit, ctlfd: ref Sys->FD): int +{ + rdctlpart(edit); + + # everything is bogus until we prove otherwise + for(i:=0; i<len edit.ctlpart; i++) + edit.ctlpart[i].changed = 1; + + # + # partitions with same info have not changed, + # and neither have partitions inside them. + # + for(i=0; i<len edit.ctlpart; i++) + for(j:=0; j<len edit.part; j++) + if(areequiv(edit.ctlpart[i], edit.part[j])) { + unchange(edit, edit.ctlpart[i]); + break; + } + + waserr := 0; + # + # delete all the changed partitions except data (we'll add them back if necessary) + # + for(i=0; i<len edit.ctlpart; i++) { + p := edit.ctlpart[i]; + if(p.changed) + if(sys->fprint(ctlfd, "delpart %s\n", p.ctlname)<0) { + sys->fprint(sys->fildes(2), "delpart failed: %s: %r\n", p.ctlname); + waserr = -1; + } + } + + # + # add all the partitions from the real list; + # this is okay since adding a partition with + # information identical to what is there is a no-op. + # + offset := edit.disk.offset; + for(i=0; i<len edit.part; i++) { + p := edit.part[i]; + if(p.ctlname != nil) { + if(sys->fprint(ctlfd, "part %s %bd %bd\n", p.ctlname, offset+ctlstart(p), offset+ctlend(p)) < 0) { + sys->fprint(sys->fildes(2), "adding part failed: %s: %r\n", p.ctlname); + waserr = -1; + } + } + } + return waserr; +} diff --git a/appl/cmd/disk/prep/pedit.m b/appl/cmd/disk/prep/pedit.m new file mode 100644 index 00000000..2b0d142d --- /dev/null +++ b/appl/cmd/disk/prep/pedit.m @@ -0,0 +1,53 @@ +Pedit: module +{ + PATH: con "/dis/disk/pedit.dis"; + + Part: adt { + name: string; + ctlname: string; + start: big; + end: big; + ctlstart: big; + ctlend: big; + changed: int; + tag: int; + }; + + Maxpart: con 32; + + Edit: adt { + disk: ref Disks->Disk; + + ctlpart: array of ref Part; + part: array of ref Part; + + # to do: replace by channels + add: ref fn(e: ref Edit, s: string, a, b: big): string; + del: ref fn(e: ref Edit, p: ref Part): string; + ext: ref fn(e: ref Edit, f: array of string): string; + help: ref fn(e: ref Edit): string; + okname: ref fn(e: ref Edit, s: string): string; + sum: ref fn(e: ref Edit, p: ref Part, a, b: big); + write: ref fn(e: ref Edit): string; + printctl: ref fn(e: ref Edit, x: ref Sys->FD); + + unit: string; + dot: big; + end: big; + + # do not use fields below this line + changed: int; + warned: int; + lastcmd: int; + + mk: fn(unit: string): ref Edit; + getline: fn(e: self ref Edit): string; + runcmd: fn(e: self ref Edit, c: string); + findpart: fn(e: self ref Edit, n: string): ref Part; + addpart: fn(e: self ref Edit, p: ref Part): string; + delpart: fn(e: self ref Edit, p: ref Part): string; + ctldiff: fn(e: self ref Edit, ctlfd: ref Sys->FD): int; + }; + + init: fn(); +}; diff --git a/appl/cmd/disk/prep/prep.b b/appl/cmd/disk/prep/prep.b new file mode 100644 index 00000000..fa4c60a1 --- /dev/null +++ b/appl/cmd/disk/prep/prep.b @@ -0,0 +1,509 @@ +implement Prep; + +# +# prepare plan 9/inferno disk partition +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "disks.m"; + disks: Disks; + Disk: import disks; + readn: import disks; + +include "pedit.m"; + pedit: Pedit; + Edit, Part: import pedit; + +include "arg.m"; + +Prep: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +blank := 0; +file := 0; +doauto := 0; +printflag := 0; +opart: array of ref Part; +secbuf: array of byte; +osecbuf: array of byte; +zeroes: array of byte; +rdonly := 0; +dowrite := 0; + +Prepedit: type Edit[string]; + +edit: ref Edit; + +Auto: adt +{ + name: string; + min: big; + max: big; + weight: int; + alloc: int; + size: big; +}; + +KB: con big 1024; +MB: con KB*KB; +GB: con KB*MB; + +# +# Order matters -- this is the layout order on disk. +# +auto: array of Auto = array[] of { + ("9fat", big 10*MB, big 100*MB, 10, 0, big 0), + ("nvram", big 512, big 512, 1, 0, big 0), + ("fscfg", big 512, big 512, 1, 0, big 0), + ("fs", big 200*MB, big 0, 10, 0, big 0), + ("fossil", big 200*MB, big 0, 4, 0, big 0), + ("arenas", big 500*MB, big 0, 20, 0, big 0), + ("isect", big 25*MB, big 0, 1, 0, big 0), + ("other", big 200*MB, big 0, 4, 0, big 0), + ("swap", big 100*MB, big 512*MB, 1, 0, big 0), + ("cache", big 50*MB, big 1*GB, 2, 0, big 0), +}; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + disks = load Disks Disks->PATH; + pedit = load Pedit Pedit->PATH; + + sys->pctl(Sys->FORKFD, nil); + disks->init(); + pedit->init(); + + edit = Edit.mk("sector"); + + edit.add = cmdadd; + edit.del = cmddel; + edit.okname = cmdokname; + edit.sum = cmdsum; + edit.write = cmdwrite; + + stderr = sys->fildes(2); + secsize := 0; + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("disk/prep [-bfprw] [-a partname]... [-s sectorsize] /dev/sdC0/plan9"); + while((o := arg->opt()) != 0) + case o { + 'a' => + p := arg->earg(); + for(i:=0; i<len auto; i++){ + if(p == auto[i].name){ + if(auto[i].alloc){ + sys->fprint(stderr, "you said -a %s more than once.\n", p); + arg->usage(); + } + auto[i].alloc = 1; + break; + } + } + if(i == len auto){ + sys->fprint(stderr, "don't know how to create automatic partition %s\n", p); + arg->usage(); + } + doauto = 1; + 'b' => + blank++; + 'f' => + file++; + 'p' => + printflag++; + rdonly++; + 'r' => + rdonly++; + 's' => + secsize = int arg->earg(); + 'w' => + dowrite++; + * => + arg->usage(); + } + args = arg->argv(); + if(len args != 1) + arg->usage(); + arg = nil; + + mode := Sys->ORDWR; + if(rdonly) + mode = Sys->OREAD; + disk := Disk.open(hd args, mode, file); + if(disk == nil) { + sys->fprint(stderr, "cannot open disk: %r\n"); + exits("opendisk"); + } + + if(secsize != 0) { + disk.secsize = secsize; + disk.secs = disk.size / big secsize; + } + edit.end = disk.secs; + + checkfat(disk); + + secbuf = array[disk.secsize+1] of byte; + osecbuf = array[disk.secsize+1] of byte; + zeroes = array[disk.secsize+1] of {* => byte 0}; + edit.disk = disk; + + if(blank == 0) + rdpart(edit); + + # save old partition table + opart = array[len edit.part] of ref Part; + opart[0:] = edit.part; + + if(printflag) { + edit.runcmd("P"); + exits(nil); + } + + if(doauto) + autopart(edit); + + if(dowrite) { + edit.runcmd("w"); + exits(nil); + } + + edit.runcmd("p"); + for(;;) { + sys->fprint(stderr, ">>> "); + edit.runcmd(edit.getline()); + } +} + +cmdsum(edit: ref Edit, p: ref Part, a: big, b: big) +{ + c := ' '; + name := "empty"; + if(p != nil){ + if(p.changed) + c = '\''; + name = p.name; + } + + sz := (b-a)*big edit.disk.secsize; + suf := "B "; + div := big 1; + if(sz >= big 1*GB){ + suf = "GB"; + div = GB; + }else if(sz >= big 1*MB){ + suf = "MB"; + div = MB; + }else if(sz >= big 1*KB){ + suf = "KB"; + div = KB; + } + + if(div == big 1) + sys->print("%c %-12s %*bd %-*bd (%bd sectors, %bd %s)\n", c, name, + edit.disk.width, a, edit.disk.width, b, b-a, sz, suf); + else + sys->print("%c %-12s %*bd %-*bd (%bd sectors, %bd.%.2d %s)\n", c, name, + edit.disk.width, a, edit.disk.width, b, b-a, + sz/div, int (((sz%div)*big 100)/div), suf); +} + +cmdadd(edit: ref Edit, name: string, start: big, end: big): string +{ + if(start < big 2 && name == "9fat") + return "overlaps with the pbs and/or the partition table"; + + return edit.addpart(mkpart(name, start, end, 1)); +} + +cmddel(edit: ref Edit, p: ref Part): string +{ + return edit.delpart(p); +} + +cmdwrite(edit: ref Edit): string +{ + wrpart(edit); + return nil; +} + +isfrog := array[256] of { + byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # NUL + byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # BKS + byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # DLE + byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, byte 1, # CAN + ' ' => byte 1, + '/' => byte 1, + 16r7f=> byte 1, + * => byte 0 +}; + +cmdokname(nil: ref Edit, elem: string): string +{ + for(i := 0; i < len elem; i++) + if(int isfrog[elem[i]]) + return "bad character in name"; + return nil; +} + +mkpart(name: string, start: big, end: big, changed: int): ref Part +{ + p := ref Part; + p.name = name; + p.ctlname = name; + p.start = start; + p.end = end; + p.changed = changed; + p.ctlstart = big 0; + p.ctlend = big 0; + return p; +} + +# plan9 partition table is first sector of the disk + +rdpart(edit: ref Edit) +{ + disk := edit.disk; + sys->seek(disk.fd, big disk.secsize, 0); + if(readn(disk.fd, osecbuf, disk.secsize) != disk.secsize) + return; + osecbuf[disk.secsize] = byte 0; + secbuf[0:] = osecbuf; + + for(i := 0; i < disk.secsize; i++) + if(secbuf[i] == byte 0) + break; + + tab := string secbuf[0:i]; + if(len tab < 4 || tab[0:4] != "part"){ + sys->fprint(stderr, "no plan9 partition table found\n"); + return; + } + + waserr := 0; + (nline, lines) := sys->tokenize(tab, "\n"); + for(i=0; i<nline; i++){ + line := hd lines; + lines = tl lines; + if(len line < 4 || line[0:4] != "part"){ + waserr = 1; + continue; + } + + (nf, f) := sys->tokenize(line, " \t\r"); + if(nf != 4 || hd f != "part"){ + waserr = 1; + continue; + } + + a := big hd tl tl f; + b := big hd tl tl tl f; + if(a >= b){ + waserr = 1; + continue; + } + + if((err := edit.addpart(mkpart(hd tl f, a, b, 0))) != nil) { + sys->fprint(stderr, "?%s: not continuing\n", err); + exits("partition"); + } + } + if(waserr) + sys->fprint(stderr, "syntax error reading partition\n"); +} + +min(a, b: big): big +{ + if(a < b) + return a; + return b; +} + +autopart(edit: ref Edit) +{ + if(len edit.part > 0) { + if(doauto) + sys->fprint(stderr, "partitions already exist; not repartitioning\n"); + return; + } + + secs := edit.disk.secs; + secsize := big edit.disk.secsize; + for(;;){ + # compute total weights + totw := 0; + for(i:=0; i<len auto; i++){ + if(auto[i].alloc==0 || auto[i].size != big 0) + continue; + totw += auto[i].weight; + } + if(totw == 0) + break; + + if(secs <= big 0){ + sys->fprint(stderr, "ran out of disk space during autopartition.\n"); + return; + } + + # assign any minimums for small disks + futz := 0; + for(i=0; i<len auto; i++){ + if(auto[i].alloc==0 || auto[i].size != big 0) + continue; + s := (secs*big auto[i].weight)/big totw; + if(s < big auto[i].min/secsize){ + auto[i].size = big auto[i].min/secsize; + secs -= auto[i].size; + futz = 1; + break; + } + } + if(futz) + continue; + + # assign any maximums for big disks + futz = 0; + for(i=0; i<len auto; i++){ + if(auto[i].alloc==0 || auto[i].size != big 0) + continue; + s := (secs*big auto[i].weight)/big totw; + if(auto[i].max != big 0 && s > auto[i].max/secsize){ + auto[i].size = auto[i].max/secsize; + secs -= auto[i].size; + futz = 1; + break; + } + } + if(futz) + continue; + + # finally, assign partition sizes according to weights + for(i=0; i<len auto; i++){ + if(auto[i].alloc==0 || auto[i].size != big 0) + continue; + s := (secs*big auto[i].weight)/big totw; + auto[i].size = s; + + # use entire disk even in face of rounding errors + secs -= auto[i].size; + totw -= auto[i].weight; + } + } + + for(i:=0; i<len auto; i++) + if(auto[i].alloc) + sys->print("%s %bud\n", auto[i].name, auto[i].size); + + s := big 0; + for(i=0; i<len auto; i++){ + if(auto[i].alloc == 0) + continue; + if((err := edit.addpart(mkpart(auto[i].name, s, s+auto[i].size, 1))) != nil) + sys->fprint(stderr, "addpart %s: %s\n", auto[i].name, err); + s += auto[i].size; + } +} + +restore(edit: ref Edit, ctlfd: ref Sys->FD) +{ + offset := edit.disk.offset; + sys->fprint(stderr, "attempting to restore partitions to previous state\n"); + if(sys->seek(edit.disk.wfd, big edit.disk.secsize, 0) != big 0){ + sys->fprint(stderr, "cannot restore: error seeking on disk: %r\n"); + exits("inconsistent"); + } + + if(sys->write(edit.disk.wfd, osecbuf, edit.disk.secsize) != edit.disk.secsize){ + sys->fprint(stderr, "cannot restore: couldn't write old partition table to disk: %r\n"); + exits("inconsistent"); + } + + if(ctlfd != nil){ + for(i:=0; i<len edit.part; i++) + sys->fprint(ctlfd, "delpart %s", edit.part[i].name); + for(i=0; i<len opart; i++){ + if(sys->fprint(ctlfd, "part %s %bd %bd", opart[i].name, opart[i].start+offset, opart[i].end+offset) < 0){ + sys->fprint(stderr, "restored disk partition table but not kernel table; reboot\n"); + exits("inconsistent"); + } + } + } + exits("restored"); +} + +wrpart(edit: ref Edit) +{ + disk := edit.disk; + + secbuf[0:] = zeroes; + n := 0; + for(i:=0; i<len edit.part; i++){ + a := sys->aprint("part %s %bd %bd\n", + edit.part[i].name, edit.part[i].start, edit.part[i].end); + if(n + len a > disk.secsize){ + sys->fprint(stderr, "partition table bigger than sector (%d bytes)\n", disk.secsize); + exits("overflow"); + } + secbuf[n:] = a; + n += len a; + } + + if(sys->seek(disk.wfd, big disk.secsize, 0) != big disk.secsize){ + sys->fprint(stderr, "error seeking to %d on disk: %r\n", disk.secsize); + exits("seek"); + } + + if(sys->write(disk.wfd, secbuf, disk.secsize) != disk.secsize){ + sys->fprint(stderr, "error writing partition table to disk: %r\n"); + restore(edit, nil); + } + + if(edit.ctldiff(disk.ctlfd) < 0) + sys->fprint(stderr, "?warning: partitions could not be updated in devsd\n"); +} + +# +# Look for a boot sector in sector 1, as would be +# the case if editing /dev/sdC0/data when that +# was really a bootable disk. +# +checkfat(disk: ref Disk) +{ + buf := array[32] of byte; + + if(sys->seek(disk.fd, big disk.secsize, 0) != big disk.secsize || + sys->read(disk.fd, buf, len buf) < len buf) + return; + + if(buf[0] != byte 16rEB || buf[1] != byte 16r3C || buf[2] != byte 16r90) + return; + + sys->fprint(stderr, + "there's a fat partition where the\n"+ + "plan9 partition table would go.\n"+ + "if you really want to overwrite it, zero\n"+ + "the second sector of the disk and try again\n"); + + exits("fat partition"); +} + +exits(s: string) +{ + if(s != nil) + raise "fail:"+s; + exit; +} diff --git a/appl/cmd/dossrv.b b/appl/cmd/dossrv.b new file mode 100644 index 00000000..aefe7948 --- /dev/null +++ b/appl/cmd/dossrv.b @@ -0,0 +1,3432 @@ +implement Dossrv; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "arg.m"; + +include "daytime.m"; + daytime: Daytime; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + +Dossrv: module +{ + init: fn(ctxt: ref Draw->Context, args: list of string); + system: fn(ctxt: ref Draw->Context, args: list of string): string; +}; + +arg0 := "dossrv"; + +deffile: string; +pflag := 0; +debug := 0; + +usage(iscmd: int): string +{ + sys->fprint(sys->fildes(2), "usage: %s [-v] [-s] [-F] [-c] [-S secpertrack] [-f devicefile] [-m mountpoint]\n", arg0); + if(iscmd) + raise "fail:usage"; + return "usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + e := init2(nil, args, 1); + if(e != nil){ + sys->fprint(sys->fildes(2), "%s: %s\n", arg0, e); + raise "fail:error"; + } +} + +system(nil: ref Draw->Context, args: list of string): string +{ + e := init2(nil, args, 0); + if(e != nil) + sys->fprint(sys->fildes(2), "%s: %s\n", arg0, e); + return e; +} + +nomod(s: string): string +{ + return sys->sprint("can't load %s: %r", s); +} + +init2(nil: ref Draw->Context, args: list of string, iscmd: int): string +{ + sys = load Sys Sys->PATH; + + pipefd := array[2] of ref Sys->FD; + + srvfile := "/n/dos"; + deffile = ""; # no default, for safety + sectors := 0; + stdin := 0; + + arg := load Arg Arg->PATH; + if(arg == nil) + return nomod(Arg->PATH); + arg->init(args); + arg0 = arg->progname(); + while((o := arg->opt()) != 0) { + case o { + 'v' => + if(debug & STYX_MESS) + debug |= VERBOSE; + debug |= STYX_MESS; + 'F' => + debug |= FAT_INFO; + 'c' => + debug |= CLUSTER_INFO; + iodebug = 1; + 'S' => + s := arg->arg(); + if(s != nil && s[0]>='0' && s[0]<='9') + sectors = int s; + else + return usage(iscmd); + 's' => + stdin = 1; + 'f' => + deffile = arg->arg(); + if(deffile == nil) + return usage(iscmd); + 'm' => + srvfile = arg->arg(); + if(srvfile == nil) + return usage(iscmd); + 'p' => + pflag++; + * => + return usage(iscmd); + } + } + args = arg->argv(); + arg = nil; + + if(deffile == "" || !stdin && srvfile == "") + return usage(iscmd); + + styx = load Styx Styx->PATH; + if(styx == nil) + return nomod(Styx->PATH); + styx->init(); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + return nomod(Daytime->PATH); + + iotrackinit(sectors); + + if(!stdin) { + if(sys->pipe(pipefd) < 0) + return sys->sprint("can't create pipe: %r"); + }else{ + pipefd[0] = nil; + pipefd[1] = sys->fildes(1); + } + + dossetup(); + + spawn dossrv(pipefd[1]); + + if(!stdin) { + if(sys->mount(pipefd[0], nil, srvfile, sys->MREPL|sys->MCREATE, deffile) < 0) + return sys->sprint("mount %s: %r", srvfile); + } + + return nil; +} + +# +# Styx server +# + + Enevermind, + Eformat, + Eio, + Enomem, + Enonexist, + Enotdir, + Enofid, + Efidopen, + Efidinuse, + Eexist, + Eperm, + Enofilsys, + Eauth, + Econtig, + Efull, + Eopen, + Ephase: con iota; + +errmsg := array[] of { + Enevermind => "never mind", + Eformat => "unknown format", + Eio => "I/O error", + Enomem => "server out of memory", + Enonexist => "file does not exist", + Enotdir => "not a directory", + Enofid => "no such fid", + Efidopen => "fid already open", + Efidinuse => "fid in use", + Eexist => "file exists", + Eperm => "permission denied", + Enofilsys => "no file system device specified", + Eauth => "authentication failed", + Econtig => "out of contiguous disk space", + Efull => "file system full", + Eopen => "invalid open mode", + Ephase => "phase error -- directory entry not found", +}; + +e(n: int): ref Rmsg.Error +{ + if(n < 0 || n >= len errmsg) + return ref Rmsg.Error(0, "it's thermal problems"); + return ref Rmsg.Error(0, errmsg[n]); +} + +dossrv(rfd: ref Sys->FD) +{ + sys->pctl(Sys->NEWFD, rfd.fd :: 2 :: nil); + rfd = sys->fildes(rfd.fd); + data := array[Styx->MAXRPC] of byte; + while((t := Tmsg.read(rfd, 0)) != nil){ + if(debug & STYX_MESS) + chat(sys->sprint("%s...", t.text())); + + r: ref Rmsg; + pick m := t { + Readerror => + panic(sys->sprint("mount read error: %s", m.error)); + Version => + r = rversion(m); + Auth => + r = rauth(m); + Flush => + r = rflush(m); + Attach => + r = rattach(m); + Walk => + r = rwalk(m); + Open => + r = ropen(m); + Create => + r = rcreate(m); + Read => + r = rread(m); + Write => + r = rwrite(m); + Clunk => + r = rclunk(m); + Remove => + r = rremove(m); + Stat => + r = rstat(m); + Wstat => + r = rwstat(m); + * => + panic("Styx mtype"); + } + pick m := r { + Error => + r.tag = t.tag; + } + rbuf := r.pack(); + if(rbuf == nil) + panic("Rmsg.pack"); + if(debug & STYX_MESS) + chat(sys->sprint("%s\n", r.text())); + if(styx->write(rfd, rbuf, len rbuf) != len rbuf) + panic("mount write"); + } + + if(debug & STYX_MESS) + chat("server EOF\n"); +} + +rversion(t: ref Tmsg.Version): ref Rmsg +{ + (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION); + return ref Rmsg.Version(t.tag, msize, version); +} + +rauth(t: ref Tmsg.Auth): ref Rmsg +{ + return ref Rmsg.Error(t.tag, "authentication not required"); +} + +rflush(t: ref Tmsg.Flush): ref Rmsg +{ + return ref Rmsg.Flush(t.tag); +} + +rattach(t: ref Tmsg.Attach): ref Rmsg +{ + root := xfile(t.fid, Clean); + if(root == nil) + return e(Eio); + if(t.aname == nil) + t.aname = deffile; + (xf, ec) := getxfs(t.aname); + root.xf = xf; + if(xf == nil) { + if(root!=nil) + xfile(t.fid, Clunk); + return ref Rmsg.Error(t.tag, ec); + } + if(xf.fmt == 0 && dosfs(xf) < 0){ + if(root!=nil) + xfile(t.fid, Clunk); + return e(Eformat); + } + + root.qid = Sys->Qid(big 0, 0, Sys->QTDIR); + root.xf.rootqid = root.qid; + return ref Rmsg.Attach(t.tag, root.qid); +} + +clone(ofl: ref Xfile, newfid: int): ref Xfile +{ + nfl := xfile(newfid, Clean); + next := nfl.next; + *nfl = *ofl; + nfl.ptr = nil; + nfl.next = next; + nfl.fid = newfid; + refxfs(nfl.xf, 1); + if(ofl.ptr != nil){ + dp := ref *ofl.ptr; + dp.p = nil; + dp.d = nil; + nfl.ptr = dp; + } + return nfl; +} + +walk1(f: ref Xfile, name: string): ref Rmsg.Error +{ + if((f.qid.qtype & Sys->QTDIR) == 0){ + if(debug) + chat(sys->sprint("qid.path=0x%bx...", f.qid.path)); + return e(Enotdir); + } + + if(name == ".") # can't happen + return nil; + + if(name== "..") { + if(f.qid.path == f.xf.rootqid.path) { + if (debug) + chat("walkup from root..."); + return nil; + } + (r,dp) := walkup(f); + if(r < 0) + return e(Enonexist); + + f.ptr = dp; + if(dp.addr == 0) { + f.qid.path = f.xf.rootqid.path; + f.qid.qtype = Sys->QTFILE; + } else { + f.qid.path = QIDPATH(dp); + f.qid.qtype = Sys->QTDIR; + } + } else { + if(getfile(f) < 0) + return e(Enonexist); + (r,dp) := searchdir(f, name, 0,1); + putfile(f); + if(r < 0) + return e(Enonexist); + + f.ptr = dp; + f.qid.path = QIDPATH(dp); + f.qid.qtype = Sys->QTFILE; + if(dp.addr == 0) + f.qid.path = f.xf.rootqid.path; + else { + d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); + if((int d.attr & DDIR) != 0) + f.qid.qtype = Sys->QTDIR; + } + putfile(f); + } + return nil; +} + +rwalk(t: ref Tmsg.Walk): ref Rmsg +{ + f := xfile(t.fid, Asis); + if(f==nil) { + if(debug) + chat("no xfile..."); + return e(Enofid); + } + nf: ref Xfile; + if(t.newfid != t.fid) + f = nf = clone(f, t.newfid); + qids: array of Sys->Qid; + if(len t.names > 0){ + savedqid := f.qid; + savedptr := f.ptr; + qids = array[len t.names] of Sys->Qid; + for(i := 0; i < len t.names; i++){ + e := walk1(f, t.names[i]); + if(e != nil){ + f.qid = savedqid; + f.ptr = savedptr; + if(nf != nil) + xfile(t.newfid, Clunk); + if(i == 0) + return e; + return ref Rmsg.Walk(t.tag, qids[0:i]); + } + qids[i] = f.qid; + } + } + return ref Rmsg.Walk(t.tag, qids); +} + +ropen(t: ref Tmsg.Open): ref Rmsg +{ + attr: int; + + omode := 0; + f := xfile(t.fid, Asis); + if(f == nil) + return e(Enofid); + if((f.flags&Omodes) != 0) + return e(Efidopen); + + dp := f.ptr; + if(dp.paddr && (t.mode & Styx->ORCLOSE) != 0) { + # check on parent directory of file to be deleted + p := getsect(f.xf, dp.paddr); + if(p == nil) + return e(Eio); + # 11 is the attr byte offset in a FAT directory entry + attr = int p.iobuf[dp.poffset+11]; + putsect(p); + if((attr & int DRONLY) != 0) + return e(Eperm); + omode |= Orclose; + } else if(t.mode & Styx->ORCLOSE) + omode |= Orclose; + + if(getfile(f) < 0) + return e(Enonexist); + + if(dp.addr != 0) { + d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); + attr = int d.attr; + } else + attr = int DDIR; + + case t.mode & 7 { + Styx->OREAD or + Styx->OEXEC => + omode |= Oread; + Styx->ORDWR => + omode |= Oread; + omode |= Owrite; + if(attr & int (DRONLY|DDIR)) { + putfile(f); + return e(Eperm); + } + Styx->OWRITE => + omode |= Owrite; + if(attr & int (DRONLY|DDIR)) { + putfile(f); + return e(Eperm); + } + * => + putfile(f); + return e(Eopen); + } + + if(t.mode & Styx->OTRUNC) { + if((attr & int DDIR)!=0 || (attr & int DRONLY) != 0) { + putfile(f); + return e(Eperm); + } + + if(truncfile(f) < 0) { + putfile(f); + return e(Eio); + } + } + + f.flags |= omode; + putfile(f); + return ref Rmsg.Open(t.tag, f.qid, Styx->MAXFDATA); +} + +mkdentry(xf: ref Xfs, ndp: ref Dosptr, name: string, sname: string, islong: int, nattr: byte, start: array of byte, length: array of byte): int +{ + ndp.p = getsect(xf, ndp.addr); + if(ndp.p == nil) + return Eio; + if(islong && (r := putlongname(xf, ndp, name, sname)) < 0){ + putsect(ndp.p); + if(r == -2) + return Efull; + return Eio; + } + + nd := ref Dosdir(". "," ",byte 0,array[10] of { * => byte 0}, + array[2] of { * => byte 0}, array[2] of { * => byte 0}, + array[2] of { * => byte 0},array[4] of { * => byte 0}); + + nd.attr = nattr; + puttime(nd); + nd.start[0: ] = start[0: 2]; + nd.length[0: ] = length[0: 4]; + + if(islong) + putname(sname[0:8]+"."+sname[8:11], nd); + else + putname(name, nd); + ndp.p.iobuf[ndp.offset: ] = Dosdir.Dd2arr(nd); + ndp.p.flags |= BMOD; + return 0; +} + +rcreate(t: ref Tmsg.Create): ref Rmsg +{ + bp: ref Dosbpb; + omode:=0; + start:=0; + sname := ""; + islong :=0; + + f := xfile(t.fid, Asis); + if(f == nil) + return e(Enofid); + if((f.flags&Omodes) != 0) + return e(Efidopen); + if(getfile(f)<0) + return e(Eio); + + pdp := f.ptr; + if(pdp.addr != 0) + pd := Dosdir.arr2Dd(pdp.p.iobuf[pdp.offset:pdp.offset+DOSDIRSIZE]); + else + pd = nil; + + if(pd != nil) + attr := int pd.attr; + else + attr = DDIR; + + if(!(attr & DDIR) || (attr & DRONLY)) { + putfile(f); + return e(Eperm); + } + + if(t.mode & Styx->ORCLOSE) + omode |= Orclose; + + case (t.mode & 7) { + Styx->OREAD or + Styx->OEXEC => + omode |= Oread; + Styx->OWRITE or + Styx->ORDWR => + if ((t.mode & 7) == Styx->ORDWR) + omode |= Oread; + omode |= Owrite; + if(t.perm & Sys->DMDIR){ + putfile(f); + return e(Eperm); + } + * => + putfile(f); + return e(Eopen); + } + + if(t.name=="." || t.name=="..") { + putfile(f); + return e(Eperm); + } + + (r,ndp) := searchdir(f, t.name, 1, 1); + if(r < 0) { + putfile(f); + if(r == -2) + return e(Efull); + return e(Eexist); + } + + nds := name2de(t.name); + if(nds > 0) { + # long file name, find "new" short name + i := 1; + for(;;) { + sname = long2short(t.name, i); + (r1, tmpdp) := searchdir(f, sname, 0, 0); + if(r1 < 0) + break; + putsect(tmpdp.p); + i++; + } + islong = 1; + } + + # allocate first cluster, if making directory + if(t.perm & Sys->DMDIR) { + bp = f.xf.ptr; + start = falloc(f.xf); + if(start <= 0) { + putfile(f); + return e(Efull); + } + } + + # now we're committed + if(pd != nil) { + puttime(pd); + pdp.p.flags |= BMOD; + } + + f.ptr = ndp; + ndp.p = getsect(f.xf, ndp.addr); + if(ndp.p == nil || + islong && putlongname(f.xf, ndp, t.name, sname) < 0){ + putsect(pdp.p); + if(ndp.p != nil) + putsect(ndp.p); + return e(Eio); + } + + nd := ref Dosdir(". "," ",byte 0,array[10] of { * => byte 0}, + array[2] of { * => byte 0}, array[2] of { * => byte 0}, + array[2] of { * => byte 0},array[4] of { * => byte 0}); + + if((t.perm & 8r222) == 0) + nd.attr |= byte DRONLY; + + puttime(nd); + nd.start[0] = byte start; + nd.start[1] = byte (start>>8); + + if(islong) + putname(sname[0:8]+"."+sname[8:11], nd); + else + putname(t.name, nd); + + f.qid.path = QIDPATH(ndp); + if(t.perm & Sys->DMDIR) { + nd.attr |= byte DDIR; + f.qid.qtype |= Sys->QTDIR; + xp := getsect(f.xf, bp.dataaddr+(start-2)*bp.clustsize); + if(xp == nil) { + if(ndp.p!=nil) + putfile(f); + putsect(pdp.p); + return e(Eio); + } + xd := ref *nd; + xd.name = ". "; + xd.ext = " "; + xp.iobuf[0:] = Dosdir.Dd2arr(xd); + if(pd!=nil) + xd = ref *pd; + else{ + xd = ref Dosdir(".. "," ",byte 0, + array[10] of { * => byte 0}, + array[2] of { * => byte 0}, + array[2] of { * => byte 0}, + array[2] of { * => byte 0}, + array[4] of { * => byte 0}); + + puttime(xd); + xd.attr = byte DDIR; + } + xd.name=".. "; + xd.ext=" "; + xp.iobuf[DOSDIRSIZE:] = Dosdir.Dd2arr(xd); + xp.flags |= BMOD; + putsect(xp); + }else + f.qid.qtype = Sys->QTFILE; + + ndp.p.flags |= BMOD; + tmp := Dosdir.Dd2arr(nd); + ndp.p.iobuf[ndp.offset:]= tmp; + putfile(f); + putsect(pdp.p); + + f.flags |= omode; + return ref Rmsg.Create(t.tag, f.qid, Styx->MAXFDATA); +} + +rread(t: ref Tmsg.Read): ref Rmsg +{ + r: int; + data: array of byte; + + if(((f:=xfile(t.fid, Asis))==nil) || + (f.flags&Oread == 0)) + return e(Eio); + + if((f.qid.qtype & Sys->QTDIR) != 0) { + if(getfile(f) < 0) + return e(Eio); + (r, data) = readdir(f, int t.offset, t.count); + } else { + if(getfile(f) < 0) + return e(Eio); + (r,data) = readfile(f, int t.offset, t.count); + } + putfile(f); + + if(r < 0) + return e(Eio); + return ref Rmsg.Read(t.tag, data[0:r]); +} + +rwrite(t: ref Tmsg.Write): ref Rmsg +{ + if(((f:=xfile(t.fid, Asis))==nil) || + !(f.flags&Owrite)) + return e(Eio); + if(getfile(f) < 0) + return e(Eio); + r := writefile(f, t.data, int t.offset, len t.data); + putfile(f); + if(r < 0){ + if(r == -2) + return e(Efull); + return e(Eio); + } + return ref Rmsg.Write(t.tag, r); +} + +rclunk(t: ref Tmsg.Clunk): ref Rmsg +{ + xfile(t.fid, Clunk); + sync(); + return ref Rmsg.Clunk(t.tag); +} + +doremove(f: ref Xfs, dp: ref Dosptr) +{ + dp.p.iobuf[dp.offset] = byte DOSEMPTY; + dp.p.flags |= BMOD; + for(prevdo := dp.offset-DOSDIRSIZE; prevdo >= 0; prevdo-=DOSDIRSIZE){ + if (dp.p.iobuf[prevdo+11] != byte DLONG) + break; + dp.p.iobuf[prevdo] = byte DOSEMPTY; + } + + if (prevdo <= 0 && dp.prevaddr != -1){ + p := getsect(f,dp.prevaddr); + for(prevdo = f.ptr.sectsize-DOSDIRSIZE; prevdo >= 0; prevdo-=DOSDIRSIZE) { + if(p.iobuf[prevdo+11] != byte DLONG) + break; + p.iobuf[prevdo] = byte DOSEMPTY; + p.flags |= BMOD; + } + putsect(p); + } +} + +rremove(t: ref Tmsg.Remove): ref Rmsg +{ + f := xfile(t.fid, Asis); + if(f == nil) + return e(Enofid); + + if(!f.ptr.addr) { + if(debug) + chat("root..."); + xfile(t.fid, Clunk); + sync(); + return e(Eperm); + } + + # check on parent directory of file to be deleted + parp := getsect(f.xf, f.ptr.paddr); + if(parp == nil) { + xfile(t.fid, Clunk); + sync(); + return e(Eio); + } + + pard := Dosdir.arr2Dd(parp.iobuf[f.ptr.poffset:f.ptr.poffset+DOSDIRSIZE]); + if(f.ptr.paddr && (int pard.attr & DRONLY)) { + if(debug) + chat("parent read-only..."); + putsect(parp); + xfile(t.fid, Clunk); + sync(); + return e(Eperm); + } + + if(getfile(f) < 0){ + if(debug) + chat("getfile failed..."); + putsect(parp); + xfile(t.fid, Clunk); + sync(); + return e(Eio); + } + + dattr := int f.ptr.p.iobuf[f.ptr.offset+11]; + if(dattr & DDIR && emptydir(f) < 0){ + if(debug) + chat("non-empty dir..."); + putfile(f); + putsect(parp); + xfile(t.fid, Clunk); + sync(); + return e(Eperm); + } + if(f.ptr.paddr == 0 && dattr&DRONLY) { + if(debug) + chat("read-only file in root directory..."); + putfile(f); + putsect(parp); + xfile(t.fid, Clunk); + sync(); + return e(Eperm); + } + + doremove(f.xf, f.ptr); + + if(f.ptr.paddr) { + puttime(pard); + parp.flags |= BMOD; + } + + parp.iobuf[f.ptr.poffset:] = Dosdir.Dd2arr(pard); + putsect(parp); + err := 0; + if(truncfile(f) < 0) + err = Eio; + + putfile(f); + xfile(t.fid, Clunk); + sync(); + if(err) + return e(err); + return ref Rmsg.Remove(t.tag); +} + +rstat(t: ref Tmsg.Stat): ref Rmsg +{ + f := xfile(t.fid, Asis); + if(f == nil) + return e(Enofid); + if(getfile(f) < 0) + return e(Eio); + dir := dostat(f); + putfile(f); + return ref Rmsg.Stat(t.tag, *dir); +} + +dostat(f: ref Xfile): ref Sys->Dir +{ + islong :=0; + prevdo: int; + longnamebuf:=""; + + # get file info. + dir := getdir(f.ptr.p.iobuf[f.ptr.offset:f.ptr.offset+DOSDIRSIZE], + f.ptr.addr, f.ptr.offset); + # get previous entry + if(f.ptr.prevaddr == -1) { + # maybe extended, but will never cross sector boundary... + # short filename at beginning of sector.. + if(f.ptr.offset!=0) { + for(prevdo = f.ptr.offset-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE) { + prevdattr := f.ptr.p.iobuf[prevdo+11]; + if(prevdattr != byte DLONG) + break; + islong = 1; + longnamebuf += getnamesect(f.ptr.p.iobuf[prevdo:prevdo+DOSDIRSIZE]); + } + } + } else { + # extended and will cross sector boundary. + for(prevdo = f.ptr.offset-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE) { + prevdattr := f.ptr.p.iobuf[prevdo+11]; + if(prevdattr != byte DLONG) + break; + islong = 1; + longnamebuf += getnamesect(f.ptr.p.iobuf[prevdo:prevdo+DOSDIRSIZE]); + } + if (prevdo < 0) { + p := getsect(f.xf,f.ptr.prevaddr); + for(prevdo = f.xf.ptr.sectsize-DOSDIRSIZE; prevdo >=0; prevdo-=DOSDIRSIZE){ + prevdattr := p.iobuf[prevdo+11]; + if(prevdattr != byte DLONG) + break; + islong = 1; + longnamebuf += getnamesect(p.iobuf[prevdo:prevdo+DOSDIRSIZE]); + } + putsect(p); + } + } + if(islong) + dir.name = longnamebuf; + return dir; +} + +nameok(elem: string): int +{ + isfrog := array[256] of { + # NUL + 1, 1, 1, 1, 1, 1, 1, 1, + # BKS + 1, 1, 1, 1, 1, 1, 1, 1, + # DLE + 1, 1, 1, 1, 1, 1, 1, 1, + # CAN + 1, 1, 1, 1, 1, 1, 1, 1, +# ' ' => 1, + '/' => 1, 16r7f => 1, * => 0 + }; + + for(i:=0; i < len elem; i++) { + if(isfrog[elem[i]]) + return -1; + } + return 0; +} + +rwstat(t: ref Tmsg.Wstat): ref Rmsg +{ + f := xfile(t.fid, Asis); + if(f == nil) + return e(Enofid); + + if(getfile(f) < 0) + return e(Eio); + + dp := f.ptr; + + if(dp.addr == 0){ # root + putfile(f); + return e(Eperm); + } + + changes := 0; + dir := dostat(f); + wdir := ref t.stat; + + if(dir.uid != wdir.uid || dir.gid != wdir.gid){ + putfile(f); + return e(Eperm); + } + + if(dir.mtime != wdir.mtime || ((dir.mode^wdir.mode) & 8r777)) + changes = 1; + + if((wdir.mode & 7) != ((wdir.mode >> 3) & 7) + || (wdir.mode & 7) != ((wdir.mode >> 6) & 7)){ + putfile(f); + return e(Eperm); + } + + if(dir.name != wdir.name){ + # temporarily disable this + # g.errno = Eperm; + # putfile(f); + # return; + + # + # grab parent directory of file to be changed and check for write perm + # rename also disallowed for read-only files in root directory + # + parp := getsect(f.xf, dp.paddr); + if(parp == nil){ + putfile(f); + return e(Eio); + } + # pard := Dosdir.arr2Dd(parp.iobuf[dp.poffset: dp.poffset+DOSDIRSIZE]); + pardattr := int parp.iobuf[dp.poffset+11]; + dpd := Dosdir.arr2Dd(dp.p.iobuf[dp.offset: dp.offset+DOSDIRSIZE]); + if(dp.paddr != 0 && int pardattr & DRONLY + || dp.paddr == 0 && int dpd.attr & DRONLY){ + putsect(parp); + putfile(f); + return e(Eperm); + } + + # + # retrieve info from old entry + # + oaddr := dp.addr; + ooffset := dp.offset; + d := dpd; + od := *d; + # start := getstart(f.xf, d); + start := d.start; + length := d.length; + attr := d.attr; + + # + # temporarily release file to allow other directory ops: + # walk to parent, validate new name + # then remove old entry + # + putfile(f); + pf := ref *f; + pdp := ref Dosptr(dp.paddr, dp.poffset, 0, 0, 0, 0, -1, -1, parp, nil); + # if(pdp.addr != 0) + # pdpd := Dosdir.arr2Dd(parp.iobuf[pdp.offset: pdp.offset+DOSDIRSIZE]); + # else + # pdpd = nil; + pf.ptr = pdp; + if(wdir.name == "." || wdir.name == ".."){ + putsect(parp); + return e(Eperm); + } + islong := 0; + sname := ""; + nds := name2de(wdir.name); + if(nds > 0) { + # long file name, find "new" short name + i := 1; + for(;;) { + sname = long2short(wdir.name, i); + (r1, tmpdp) := searchdir(f, sname, 0, 0); + if(r1 < 0) + break; + putsect(tmpdp.p); + i++; + } + islong = 1; + }else{ + (b, e) := dosname(wdir.name); + sname = b+e; + } + # (r, ndp) := searchdir(pf, wdir.name, 1, 1); + # if(r < 0){ + # putsect(parp); + # g.errno = Eperm; + # return; + # } + if(getfile(f) < 0){ + putsect(parp); + return e(Eio); + } + doremove(f.xf, dp); + putfile(f); + + # + # search for dir entry again, since we may be able to use the old slot, + # and we need to set up the naddr field if a long name spans the block. + # create new entry. + # + r := 0; + (r, dp) = searchdir(pf, sname, 1, islong); + if(r < 0){ + putsect(parp); + return e(Ephase); + } + if((r = mkdentry(pf.xf, dp, wdir.name, sname, islong, attr, start, length)) != 0){ + putsect(parp); + return e(r); + } + putsect(parp); + + # + # relocate up other fids to the same file, if it moved + # + f.ptr = dp; + f.qid.path = QIDPATH(dp); + if(oaddr != dp.addr || ooffset != dp.offset) + dosptrreloc(f, dp, oaddr, ooffset); + changes = 1; + # f = nil; + } + + if(changes){ + d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); + putdir(d, wdir); + dp.p.iobuf[dp.offset: ] = Dosdir.Dd2arr(d); + dp.p.flags |= BMOD; + } + if(f != nil) + putfile(f); + sync(); + return ref Rmsg.Wstat(t.tag); +} + +# +# FAT file system format +# + +Dospart: adt { + active: byte; + hstart: byte; + cylstart: array of byte; + typ: byte; + hend: byte; + cylend: array of byte; + start: array of byte; + length: array of byte; +}; + +Dosboot: adt { + arr2Db: fn(arr: array of byte): ref Dosboot; + magic: array of byte; + version: array of byte; + sectsize: array of byte; + clustsize: byte; + nresrv: array of byte; + nfats: byte; + rootsize: array of byte; + volsize: array of byte; + mediadesc: byte; + fatsize: array of byte; + trksize: array of byte; + nheads: array of byte; + nhidden: array of byte; + bigvolsize: array of byte; + driveno: byte; + bootsig: byte; + volid: array of byte; + label: array of byte; +}; + +Dosbpb: adt { + sectsize: int; # in bytes + clustsize: int; # in sectors + nresrv: int; # sectors + nfats: int; # usually 2 + rootsize: int; # number of entries + volsize: int; # in sectors + mediadesc: int; + fatsize: int; # in sectors + fatclusters: int; + fatbits: int; # 12 or 16 + fataddr: int; #big; # sector number + rootaddr: int; #big; + dataaddr: int; #big; + freeptr: int; #big; # next free cluster candidate +}; + +Dosdir: adt { + Dd2arr: fn(d: ref Dosdir): array of byte; + arr2Dd: fn(arr: array of byte): ref Dosdir; + name: string; + ext: string; + attr: byte; + reserved: array of byte; + time: array of byte; + date: array of byte; + start: array of byte; + length: array of byte; +}; + +Dosptr: adt { + addr: int; # of file's directory entry + offset: int; + paddr: int; # of parent's directory entry + poffset: int; + iclust: int; # ordinal within file + clust: int; + prevaddr: int; + naddr: int; + p: ref Iosect; + d: ref Dosdir; +}; + +Asis, Clean, Clunk: con iota; + +FAT12: con byte 16r01; +FAT16: con byte 16r04; +FATHUGE: con byte 16r06; +DMDDO: con 16r54; +DRONLY: con 16r01; +DHIDDEN: con 16r02; +DSYSTEM: con 16r04; +DVLABEL: con 16r08; +DDIR: con 16r10; +DARCH: con 16r20; +DLONG: con DRONLY | DHIDDEN | DSYSTEM | DVLABEL; +DMLONG: con DLONG | DDIR | DARCH; + +DOSDIRSIZE: con 32; +DOSEMPTY: con 16rE5; +DOSRUNES: con 13; + +FATRESRV: con 2; + +Oread: con 1; +Owrite: con 2; +Orclose: con 4; +Omodes: con 3; + +VERBOSE, STYX_MESS, FAT_INFO, CLUSTER_INFO: con (1 << iota); + +nowt, nowt1: int; +tzoff: int; + +# +# because we map all incoming short names from all upper to all lower case, +# and FAT cannot store mixed case names in short name form, +# we'll declare upper case as unacceptable to decide whether a long name +# is needed on output. thus, long names are always written in the case +# in the system call, and are always read back as written; short names +# are produced by the common case of writing all lower case letters +# +isdos := array[256] of { + 'a' to 'z' => 1, 'A' to 'Z' => 0, '0' to '9' => 1, + ' ' => 1, '$' => 1, '%' => 1, '"' => 1, '-' => 1, '_' => 1, '@' => 1, + '~' => 1, '`' => 1, '!' => 1, '(' => 1, ')' => 1, '{' => 1, '}' => 1, '^' => 1, + '#' => 1, '&' => 1, + * => 0 +}; + +dossetup() +{ + nowt = daytime->now(); + nowt1 = sys->millisec(); + tzoff = daytime->local(0).tzoff; +} + +# make xf into a Dos file system... or die trying to. +dosfs(xf: ref Xfs): int +{ + mbroffset := 0; + i: int; + p: ref Iosect; + +Dmddo: + for(;;) { + for(i=2; i>0; i--) { + p = getsect(xf, 0); + if(p == nil) + return -1; + + if((mbroffset == 0) && (p.iobuf[0] == byte 16re9)) + break; + + # Check if the jump displacement (magic[1]) is too + # short for a FAT. DOS 4.0 MBR has a displacement of 8. + if(p.iobuf[0] == byte 16reb && + p.iobuf[2] == byte 16r90 && + p.iobuf[1] != byte 16r08) + break; + + if(i < 2 || + p.iobuf[16r1fe] != byte 16r55 || + p.iobuf[16r1ff] != byte 16raa) { + i = 0; + break; + } + + dp := 16r1be; + for(j:=4; j>0; j--) { + if(debug) { + chat(sys->sprint("16r%2.2ux (%d,%d) 16r%2.2ux (%d,%d) %d %d...", + int p.iobuf[dp], int p.iobuf[dp+1], + bytes2short(p.iobuf[dp+2: dp+4]), + int p.iobuf[dp+4], int p.iobuf[dp+5], + bytes2short(p.iobuf[dp+6: dp+8]), + bytes2int(p.iobuf[dp+8: dp+12]), + bytes2int(p.iobuf[dp+12:dp+16]))); + } + + # Check for a disc-manager partition in the MBR. + # Real MBR is at lba 63. Unfortunately it starts + # with 16rE9, hence the check above against magic. + if(int p.iobuf[dp+4] == DMDDO) { + mbroffset = 63*Sectorsize; + putsect(p); + purgebuf(xf); + xf.offset += mbroffset; + break Dmddo; + } + + # Make sure it really is the right type, other + # filesystems can look like a FAT + # (e.g. OS/2 BOOT MANAGER). + if(p.iobuf[dp+4] == FAT12 || + p.iobuf[dp+4] == FAT16 || + p.iobuf[dp+4] == FATHUGE) + break; + dp+=16; + } + + if(j <= 0) { + if(debug) + chat("no active partition..."); + putsect(p); + return -1; + } + + offset := bytes2int(p.iobuf[dp+8:dp+12])* Sectorsize; + putsect(p); + purgebuf(xf); + xf.offset = mbroffset+offset; + } + break; + } + if(i <= 0) { + if(debug) + chat("bad magic..."); + putsect(p); + return -1; + } + + b := Dosboot.arr2Db(p.iobuf); + if(debug & FAT_INFO) + bootdump(b); + + bp := ref Dosbpb; + xf.ptr = bp; + xf.fmt = 1; + + bp.sectsize = bytes2short(b.sectsize); + bp.clustsize = int b.clustsize; + bp.nresrv = bytes2short(b.nresrv); + bp.nfats = int b.nfats; + bp.rootsize = bytes2short(b.rootsize); + bp.volsize = bytes2short(b.volsize); + if(bp.volsize == 0) + bp.volsize = bytes2int(b.bigvolsize); + bp.mediadesc = int b.mediadesc; + bp.fatsize = bytes2short(b.fatsize); + + bp.fataddr = int bp.nresrv; + bp.rootaddr = bp.fataddr + bp.nfats*bp.fatsize; + i = bp.rootsize*DOSDIRSIZE + bp.sectsize-1; + i /= bp.sectsize; + bp.dataaddr = bp.rootaddr + i; + bp.fatclusters = FATRESRV+(bp.volsize - bp.dataaddr)/bp.clustsize; + if(bp.fatclusters < 4087) + bp.fatbits = 12; + else + bp.fatbits = 16; + bp.freeptr = 2; + if(debug & FAT_INFO){ + chat(sys->sprint("fatbits=%d (%d clusters)...", + bp.fatbits, bp.fatclusters)); + for(i=0; i< int b.nfats; i++) + chat(sys->sprint("fat %d: %d...", + i, bp.fataddr+i*bp.fatsize)); + chat(sys->sprint("root: %d...", bp.rootaddr)); + chat(sys->sprint("data: %d...", bp.dataaddr)); + } + putsect(p); + return 0; +} + +QIDPATH(dp: ref Dosptr): big +{ + return big (dp.addr*(Sectorsize/DOSDIRSIZE) + dp.offset/DOSDIRSIZE); +} + +isroot(addr: int): int +{ + return addr == 0; +} + +getfile(f: ref Xfile): int +{ + dp := f.ptr; + if(dp.p!=nil) + panic("getfile"); + if(dp.addr < 0) + panic("getfile address"); + p := getsect(f.xf, dp.addr); + if(p == nil) + return -1; + + dp.d = nil; + if(!isroot(dp.addr)) { + if(f.qid.path != QIDPATH(dp)){ + if(debug) { + chat(sys->sprint("qid mismatch f=0x%x d=0x%x...", + int f.qid.path, int QIDPATH(dp))); + } + putsect(p); + return -1; + } + # dp.d = Dosdir.arr2Dd(p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); + } + dp.p = p; + return 0; +} + +putfile(f: ref Xfile) +{ + dp := f.ptr; + if(dp.p==nil) + panic("putfile"); + putsect(dp.p); + dp.p = nil; + dp.d = nil; +} + +getstart(nil: ref Xfs, d: ref Dosdir): int +{ + start := bytes2short(d.start); +# if(xf.isfat32) +# start |= bytes2short(d.hstart)<<16; + return start; +} + +putstart(nil: ref Xfs, d: ref Dosdir, start: int) +{ + d.start[0] = byte start; + d.start[1] = byte (start>>8); +# if(xf.isfat32){ +# d.hstart[0] = start>>16; +# d.hstart[1] = start>>24; +# } +} + +# +# return the disk cluster for the iclust cluster in f +# +fileclust(f: ref Xfile, iclust: int, cflag: int): int +{ + + bp := f.xf.ptr; + dp := f.ptr; + if(isroot(dp.addr)) + return -1; # root directory for old FAT format does not start on a cluster boundary + d := dp.d; + if(d == nil){ + if(dp.p == nil) + panic("fileclust"); + d = Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); + } + next := 0; + start := getstart(f.xf, d); + if(start == 0) { + if(!cflag) + return -1; + start = falloc(f.xf); + if(start <= 0) + return -1; + puttime(d); + putstart(f.xf, d, start); + dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d); + dp.p.flags |= BMOD; + dp.clust = 0; + } + + clust, nskip: int; + if(dp.clust == 0 || iclust < dp.iclust) { + clust = start; + nskip = iclust; + } else { + clust = dp.clust; + nskip = iclust - dp.iclust; + } + + if(debug & CLUSTER_INFO && nskip > 0) + chat(sys->sprint("clust %d, skip %d...", clust, nskip)); + + if(clust <= 0) + return -1; + + if(nskip > 0) { + while(--nskip >= 0) { + next = getfat(f.xf, clust); + if(debug & CLUSTER_INFO) + chat(sys->sprint(".%d", next)); + if(next <= 0){ + if(!cflag) + break; + next = falloc(f.xf); + if(next <= 0) + return -1; + putfat(f.xf, clust, next); + } + clust = next; + } + if(next <= 0) + return -1; + dp.clust = clust; + dp.iclust = iclust; + } + if(debug & CLUSTER_INFO) + chat(sys->sprint(" clust(%d)=0x%x...", iclust, clust)); + return clust; +} + +# +# return the disk sector for the isect disk sector in f, +# allocating space if necessary and cflag is set +# +fileaddr(f: ref Xfile, isect: int, cflag: int): int +{ + bp := f.xf.ptr; + dp := f.ptr; + if(isroot(dp.addr)) { + if(isect*bp.sectsize >= bp.rootsize*DOSDIRSIZE) + return -1; + return bp.rootaddr + isect; + } + clust := fileclust(f, isect/bp.clustsize, cflag); + if(clust < 0) + return -1; + return clust2sect(bp, clust) + isect%bp.clustsize; +} + +# +# look for a directory entry matching name +# always searches for long names which match a short name +# +# if creating (cflag is set), set address of available slot and allocate next cluster if necessary +# +searchdir(f: ref Xfile, name: string, cflag: int, lflag: int): (int, ref Dosptr) +{ + xf := f.xf; + bp := xf.ptr; + addr1 := -1; + addr2 := -1; + prevaddr1 := -1; + o1 := 0; + dp := ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil); # prevaddr and naddr are -1 + dp.paddr = f.ptr.addr; + dp.poffset = f.ptr.offset; + islong :=0; + buf := ""; + + need := 1; + if(lflag && cflag) + need += name2de(name); + if(!lflag) { + name = name[0:8]+"."+name[8:11]; + i := len name -1; + while(i >= 0 && (name[i]==' ' || name[i] == '.')) + i--; + name = name[0:i+1]; + } + + addr := -1; + prevaddr: int; + have := 0; + for(isect:=0;; isect++) { + prevaddr = addr; + addr = fileaddr(f, isect, cflag); + if(addr < 0) + break; + p := getsect(xf, addr); + if(p == nil) + break; + for(o:=0; o<bp.sectsize; o+=DOSDIRSIZE) { + dattr := int p.iobuf[o+11]; + dname0 := p.iobuf[o]; + if(dname0 == byte 16r00) { + if(debug) + chat("end dir(0)..."); + putsect(p); + if(!cflag) + return (-1, nil); + + # + # addr1 and o1 are the start of the dirs + # addr2 is the optional second cluster used if the long name + # entry does not fit within the addr1 cluster + # have tells us the number of contiguous free dirs + # starting at addr1.o1; need is the number needed to hold the long name + # + if(addr1 < 0){ + addr1 = addr; + prevaddr1 = prevaddr; + o1 = o; + } + nleft := (bp.sectsize-o)/DOSDIRSIZE; + if(addr2 < 0 && nleft+have < need){ + addr2 = fileaddr(f, isect+1, cflag); + if(addr2 < 0){ + if(debug) + chat("end dir(2)..."); + return (-2, nil); + } + }else if(addr2 < 0) + addr2 = addr; + if(addr2 == addr1) + addr2 = -1; + if(debug) + chat(sys->sprint("allocate addr1=%d,%d addr2=%d for %s nleft=%d have=%d need=%d", addr1, o1, addr2, name, nleft, have, need)); + dp.addr = addr1; + dp.offset = o1; + dp.prevaddr = prevaddr1; + dp.naddr = addr2; + return (0, dp); + } + + if(dname0 == byte DOSEMPTY) { + if(debug) + chat("empty..."); + have++; + if(addr1 == -1){ + addr1 = addr; + o1 = o; + prevaddr1 = prevaddr; + } + if(addr2 == -1 && have >= need) + addr2 = addr; + continue; + } + have = 0; + if(addr2 == -1) + addr1 = -1; + + if(0 && lflag && debug) + dirdump(p.iobuf[o:o+DOSDIRSIZE],addr,o); + + if((dattr & DMLONG) == DLONG) { + if(!islong) + buf = ""; + islong = 1; + buf = getnamesect(p.iobuf[o:o+DOSDIRSIZE]) + buf; # getnamesect should return sum + continue; + } + if(dattr & DVLABEL) { + islong = 0; + continue; + } + + if(!islong || !lflag) + buf = getname(p.iobuf[o:o+DOSDIRSIZE]); + islong = 0; + + if(debug) + chat(sys->sprint("cmp: [%s] [%s]", buf, name)); + if(mystrcmp(buf, name) != 0) { + buf=""; + continue; + } + if(debug) + chat("found\n"); + + if(cflag) { + putsect(p); + return (-1,nil); + } + + dp.addr = addr; + dp.prevaddr = prevaddr; + dp.offset = o; + dp.p = p; + #dp.d = Dosdir.arr2Dd(p.iobuf[o:o+DOSDIRSIZE]); + return (0, dp); + } + putsect(p); + } + if(debug) + chat("end dir(1)..."); + if(!cflag) + return (-1, nil); + # + # end of root directory or end of non-root directory on cluster boundary + # + if(addr1 < 0){ + addr1 = fileaddr(f, isect, 1); + if(addr1 < 0) + return (-2, nil); + prevaddr1 = prevaddr; + o1 = 0; + }else{ + if(addr2 < 0 && have < need){ + addr2 = fileaddr(f, isect, 1); + if(addr2 < 0) + return (-2, nil); + } + } + if(addr2 == addr1) + addr2 = -1; + dp.addr = addr1; + dp.offset = o1; + dp.prevaddr = prevaddr1; + dp.naddr = addr2; + return (0, dp); +} + +emptydir(f: ref Xfile): int +{ + for(isect:=0;; isect++) { + addr := fileaddr(f, isect, 0); + if(addr < 0) + break; + + p := getsect(f.xf, addr); + if(p == nil) + return -1; + + for(o:=0; o<f.xf.ptr.sectsize; o+=DOSDIRSIZE) { + dname0 := p.iobuf[o]; + dattr := int p.iobuf[o+11]; + + if(dname0 == byte 16r00) { + putsect(p); + return 0; + } + + if(dname0 == byte DOSEMPTY || dname0 == byte '.') + continue; + + if(dattr & DVLABEL) + continue; # ignore any long name entries: it's empty if there are no short ones + + putsect(p); + return -1; + } + putsect(p); + } + return 0; +} + +readdir(f:ref Xfile, offset: int, count: int): (int, array of byte) +{ + xf := f.xf; + bp := xf.ptr; + rcnt := 0; + buf := array[Styx->MAXFDATA] of byte; + islong :=0; + longnamebuf:=""; + + if(count <= 0) + return (0, nil); + +Read: + for(isect:=0;; isect++) { + addr := fileaddr(f, isect, 0); + if(addr < 0) + break; + p := getsect(xf, addr); + if(p == nil) + return (-1,nil); + + for(o:=0; o<bp.sectsize; o+=DOSDIRSIZE) { + dname0 := int p.iobuf[o]; + dattr := int p.iobuf[o+11]; + + if(dname0 == 16r00) { + putsect(p); + break Read; + } + + if(dname0 == DOSEMPTY) + continue; + + if(dname0 == '.') { + dname1 := int p.iobuf[o+1]; + if(dname1 == ' ' || dname1 == 0) + continue; + dname2 := int p.iobuf[o+2]; + if(dname1 == '.' && + (dname2 == ' ' || dname2 == 0)) + continue; + } + + if((dattr & DMLONG) == DLONG) { + if(!islong) + longnamebuf = ""; + longnamebuf = getnamesect(p.iobuf[o:o+DOSDIRSIZE]) + longnamebuf; + islong = 1; + continue; + } + if(dattr & DVLABEL) { + islong = 0; + continue; + } + + dir := getdir(p.iobuf[o:o+DOSDIRSIZE], addr, o); + if(islong) { + dir.name = longnamebuf; + longnamebuf = ""; + islong = 0; + } + d := styx->packdir(*dir); + if(offset > 0) { + offset -= len d; + islong = 0; + continue; + } + if(rcnt+len d > count){ + putsect(p); + break Read; + } + buf[rcnt:] = d; + rcnt += len d; + if(rcnt >= count) { + putsect(p); + break Read; + } + } + putsect(p); + } + + return (rcnt, buf[0:rcnt]); +} + +walkup(f: ref Xfile): (int, ref Dosptr) +{ + bp := f.xf.ptr; + dp := f.ptr; + o: int; + ndp:= ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil); + ndp.addr = dp.paddr; + ndp.offset = dp.poffset; + + if(debug) + chat(sys->sprint("walkup: paddr=0x%x...", dp.paddr)); + + if(dp.paddr == 0) + return (0,ndp); + + p := getsect(f.xf, dp.paddr); + if(p == nil) + return (-1,nil); + + if(debug) + dirdump(p.iobuf[dp.poffset:dp.poffset+DOSDIRSIZE],dp.paddr,dp.poffset); + + xd := Dosdir.arr2Dd(p.iobuf[dp.poffset:dp.poffset+DOSDIRSIZE]); + start := getstart(f.xf, xd); + if(debug & CLUSTER_INFO) + if(debug) + chat(sys->sprint("start=0x%x...", start)); + putsect(p); + if(start == 0) + return (-1,nil); + + # + # check that parent's . points to itself + # + p = getsect(f.xf, bp.dataaddr + (start-2)*bp.clustsize); + if(p == nil) + return (-1,nil); + + if(debug) + dirdump(p.iobuf,0,0); + + xd = Dosdir.arr2Dd(p.iobuf); + if(p.iobuf[0]!= byte '.' || + p.iobuf[1]!= byte ' ' || + start != getstart(f.xf, xd)) { + if(p!=nil) + putsect(p); + return (-1,nil); + } + + if(debug) + dirdump(p.iobuf[DOSDIRSIZE:],0,0); + + # + # parent's .. is the next entry, and has start of parent's parent + # + xd = Dosdir.arr2Dd(p.iobuf[DOSDIRSIZE:]); + if(p.iobuf[32] != byte '.' || p.iobuf[33] != byte '.') { + if(p != nil) + putsect(p); + return (-1,nil); + } + + # + # we're done if parent is root + # + pstart := getstart(f.xf, xd); + putsect(p); + if(pstart == 0) + return (0, ndp); + + # + # check that parent's . points to itself + # + p = getsect(f.xf, clust2sect(bp, pstart)); + if(p == nil) { + if(debug) + chat(sys->sprint("getsect %d failed\n", pstart)); + return (-1,nil); + } + if(debug) + dirdump(p.iobuf,0,0); + xd = Dosdir.arr2Dd(p.iobuf); + if(p.iobuf[0]!= byte '.' || + p.iobuf[1]!=byte ' ' || + pstart!=getstart(f.xf, xd)) { + if(p != nil) + putsect(p); + return (-1,nil); + } + + # + # parent's parent's .. is the next entry, and has start of parent's parent's parent + # + if(debug) + dirdump(p.iobuf[DOSDIRSIZE:],0,0); + + xd = Dosdir.arr2Dd(p.iobuf[DOSDIRSIZE:]); + if(xd.name[0] != '.' || xd.name[1] != '.') { + if(p != nil) + putsect(p); + return (-1,nil); + } + ppstart :=getstart(f.xf, xd); + putsect(p); + + # + # open parent's parent's parent, and walk through it until parent's paretn is found + # need this to find parent's parent's addr and offset + # + ppclust := ppstart; + # TO DO: FAT32 + if(ppclust != 0) + k := clust2sect(bp, ppclust); + else + k = bp.rootaddr; + p = getsect(f.xf, k); + if(p == nil) { + if(debug) + chat(sys->sprint("getsect %d failed\n", k)); + return (-1,nil); + } + + if(debug) + dirdump(p.iobuf,0,0); + + if(ppstart) { + xd = Dosdir.arr2Dd(p.iobuf); + if(p.iobuf[0]!= byte '.' || + p.iobuf[1]!= byte ' ' || + ppstart!=getstart(f.xf, xd)) { + if(p!=nil) + putsect(p); + return (-1,nil); + } + } + + for(so:=1; ;so++) { + for(o=0; o<bp.sectsize; o+=DOSDIRSIZE) { + xdname0 := p.iobuf[o]; + if(xdname0 == byte 16r00) { + if(debug) + chat("end dir\n"); + if(p != nil) + putsect(p); + return (-1,nil); + } + + if(xdname0 == byte DOSEMPTY) + continue; + + #xd = Dosdir.arr2Dd(p.iobuf[o:o+DOSDIRSIZE]); + xdstart:= p.iobuf[o+26:o+28]; # TO DO: getstart + if(bytes2short(xdstart) == pstart) { + putsect(p); + ndp.paddr = k; + ndp.poffset = o; + return (0,ndp); + } + } + if(ppclust) { + if(so%bp.clustsize == 0) { + ppstart = getfat(f.xf, ppstart); + if(ppstart < 0){ + if(debug) + chat(sys->sprint("getfat %d fail\n", + ppstart)); + if(p != nil) + putsect(p); + return (-1,nil); + } + } + k = clust2sect(bp, ppclust) + + so%bp.clustsize; + } + else { + if(so*bp.sectsize >= bp.rootsize*DOSDIRSIZE) { + if(p != nil) + putsect(p); + return (-1,nil); + } + k = bp.rootaddr + so; + } + putsect(p); + p = getsect(f.xf, k); + if(p == nil) { + if(debug) + chat(sys->sprint("getsect %d failed\n", k)); + return (-1,nil); + } + } + putsect(p); + ndp.paddr = k; + ndp.poffset = o; + return (0,ndp); +} + +readfile(f: ref Xfile, offset: int, count: int): (int, array of byte) +{ + xf := f.xf; + bp := xf.ptr; + dp := f.ptr; + + length := bytes2int(dp.p.iobuf[dp.offset+28:dp.offset+32]); + rcnt := 0; + if(offset >= length) + return (0,nil); + buf := array[Styx->MAXFDATA] of byte; + if(offset+count >= length) + count = length - offset; + isect := offset/bp.sectsize; + o := offset%bp.sectsize; + while(count > 0) { + addr := fileaddr(f, isect++, 0); + if(addr < 0) + break; + c := bp.sectsize - o; + if(c > count) + c = count; + p := getsect(xf, addr); + if(p == nil) + return (-1, nil); + buf[rcnt:] = p.iobuf[o:o+c]; + putsect(p); + count -= c; + rcnt += c; + o = 0; + } + return (rcnt, buf[0:rcnt]); +} + +writefile(f: ref Xfile, buf: array of byte, offset,count: int): int +{ + xf := f.xf; + bp := xf.ptr; + dp := f.ptr; + addr := 0; + c: int; + rcnt := 0; + p: ref Iosect; + + d := dp.d; + if(d == nil) + d = Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); + isect := offset/bp.sectsize; + + o := offset%bp.sectsize; + while(count > 0) { + addr = fileaddr(f, isect++, 1); + if(addr < 0) + break; + c = bp.sectsize - o; + if(c > count) + c = count; + if(c == bp.sectsize){ + p = getosect(xf, addr); + if(p == nil) + return -1; + p.flags = 0; + }else{ + p = getsect(xf, addr); + if(p == nil) + return -1; + } + p.iobuf[o:] = buf[rcnt:rcnt+c]; + p.flags |= BMOD; + putsect(p); + count -= c; + rcnt += c; + o = 0; + } + if(rcnt <= 0 && addr < 0) + return -2; + length := 0; + dlen := bytes2int(d.length); + if(rcnt > 0) + length = offset+rcnt; + else if(dp.addr && dp.clust) { + c = bp.clustsize*bp.sectsize; + if(dp.iclust > (dlen+c-1)/c) + length = c*dp.iclust; + } + if(length > dlen) { + d.length[0] = byte length; + d.length[1] = byte (length>>8); + d.length[2] = byte (length>>16); + d.length[3] = byte (length>>24); + } + puttime(d); + dp.p.flags |= BMOD; + dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d); + return rcnt; +} + +truncfile(f: ref Xfile): int +{ + xf := f.xf; + bp := xf.ptr; + dp := f.ptr; + d := Dosdir.arr2Dd(dp.p.iobuf[dp.offset:dp.offset+DOSDIRSIZE]); + + clust := getstart(f.xf, d); + putstart(f.xf, d, 0); + while(clust > 0) { + next := getfat(xf, clust); + putfat(xf, clust, 0); + clust = next; + } + + d.length[0] = byte 0; + d.length[1] = byte 0; + d.length[2] = byte 0; + d.length[3] = byte 0; + + dp.p.iobuf[dp.offset:] = Dosdir.Dd2arr(d); + dp.iclust = 0; + dp.clust = 0; + dp.p.flags |= BMOD; + + return 0; +} + +getdir(arr: array of byte, addr,offset: int) :ref Sys->Dir +{ + dp := ref Sys->Dir; + + if(arr == nil || addr == 0) { + dp.name = ""; + dp.qid.path = big 0; + dp.qid.qtype = Sys->QTDIR; + dp.length = big 0; + dp.mode = Sys->DMDIR|8r777; + } + else { + dp.name = getname(arr); + for(i:=0; i < len dp.name; i++) + if(dp.name[i]>='A' && dp.name[i]<='Z') + dp.name[i] = dp.name[i]-'A'+'a'; + + # dp.qid.path = bytes2short(d.start); + dp.qid.path = big (addr*(Sectorsize/DOSDIRSIZE) + offset/DOSDIRSIZE); + dattr := int arr[11]; + + if(dattr & DRONLY) + dp.mode = 8r444; + else + dp.mode = 8r666; + + dp.atime = gtime(arr); + dp.mtime = dp.atime; + if(dattr & DDIR) { + dp.length = big 0; + dp.qid.qtype |= Styx->QTDIR; + dp.mode |= Sys->DMDIR|8r111; + } + else + dp.length = big bytes2int(arr[28:32]); + + if(dattr & DSYSTEM){ + dp.mode |= Styx->DMEXCL; + dp.qid.qtype |= Styx->QTEXCL; + } + } + + dp.qid.vers = 0; + dp.dtype = 0; + dp.dev = 0; + dp.uid = "dos"; + dp.gid = "srv"; + + return dp; +} + +putdir(d: ref Dosdir, dp: ref Sys->Dir) +{ + if(dp.mode & 2) + d.attr &= byte ~DRONLY; + else + d.attr |= byte DRONLY; + + if(dp.mode & Styx->DMEXCL) + d.attr |= byte DSYSTEM; + else + d.attr &= byte ~DSYSTEM; + xputtime(d, dp.mtime); +} + +getname(arr: array of byte): string +{ + p: string; + for(i:=0; i<8; i++) { + c := int arr[i]; + if(c == 0 || c == ' ') + break; + if(i == 0 && c == 16r05) + c = 16re5; + p[len p] = c; + } + for(i=8; i<11; i++) { + c := int arr[i]; + if(c == 0 || c == ' ') + break; + if(i == 8) + p[len p] = '.'; + p[len p] = c; + } + + return p; +} + +dosname(p: string): (string, string) +{ + name := " "; + for(i := 0; i < len p && i < 8; i++) { + c := p[i]; + if(c >= 'a' && c <= 'z') + c += 'A'-'a'; + else if(c == '.') + break; + name[i] = c; + } + ext := " "; + for(j := len p - 1; j >= i; j--) { + if(p[j] == '.') { + q := 0; + for(j++; j < len p && q < 3; j++) { + c := p[j]; + if(c >= 'a' && c <= 'z') + c += 'A'-'a'; + ext[q++] = c; + } + break; + } + } + return (name, ext); +} + +putname(p: string, d: ref Dosdir) +{ + if ((int d.attr & DLONG) == DLONG) + panic("putname of long name"); + (d.name, d.ext) = dosname(p); +} + +mystrcmp(s1, s2: string): int +{ + n := len s1; + if(n != len s2) + return 1; + + for(i := 0; i < n; i++) { + c := s1[i]; + if(c >= 'A' && c <= 'Z') + c -= 'A'-'a'; + d := s2[i]; + if(d >= 'A' && d <= 'Z') + d -= 'A'-'a'; + if(c != d) + return 1; + } + return 0; +} + +# +# return the length of a long name in directory +# entries or zero if it's normal dos +# +name2de(p: string): int +{ + ext := 0; + name := 0; + + for(end := len p; --end >= 0 && p[end] != '.';) + ext++; + + if(end > 0) { + name = end; + for(i := 0; i < end; i++) { + if(p[i] == '.') + return (len p+DOSRUNES-1)/DOSRUNES; + } + } + else { + name = ext; + ext = 0; + } + + if(name <= 8 && ext <= 3 && isvalidname(p)) + return 0; + + return (len p+DOSRUNES-1)/DOSRUNES; +} + +isvalidname(s: string): int +{ + dot := 0; + for(i := 0; i < len s; i++) + if(s[i] == '.') { + if(++dot > 1 || i == len s-1) + return 0; + } else if(s[i] > len isdos || isdos[s[i]] == 0) + return 0; + return 1; +} + +getnamesect(arr: array of byte): string +{ + s: string; + c: int; + + for(i := 1; i < 11; i += 2) { + c = int arr[i] | (int arr[i+1] << 8); + if(c == 0) + return s; + s[len s] = c; + } + for(i = 14; i < 26; i += 2) { + c = int arr[i] | (int arr[i+1] << 8); + if(c == 0) + return s; + s[len s] = c; + } + for(i = 28; i < 32; i += 2) { + c = int arr[i] | (int arr[i+1] << 8); + if(c == 0) + return s; + s[len s] = c; + } + return s; +} + +# takes a long filename and converts to a short dos name, with a tag number. +long2short(src: string,val: int): string +{ + dst :=" "; + skip:=0; + xskip:=0; + ext:=len src-1; + while(ext>=0 && src[ext]!='.') + ext--; + + if (ext < 0) + ext=len src -1; + + # convert name eliding periods + j:=0; + for(name := 0; name < ext && j<8; name++){ + c := src[name]; + if(c!='.' && c!=' ' && c!='\t') { + if(c>='a' && c<='z') + dst[j++] = c-'a'+'A'; + else + dst[j++] = c; + } + else + skip++; + } + + # convert extension + j=8; + for(xname := ext+1; xname < len src && j<11; xname++) { + c := src[xname]; + if(c!=' ' && c!='\t'){ + if (c>='a' && c<='z') + dst[j++] = c-'a'+'A'; + else + dst[j++] = c; + }else + xskip++; + } + + # add tag number + j =1; + for(i:=val; i > 0; i/=10) + j++; + + if (8-j<name) + name = 8-j; + else + name -= skip; + + dst[name]='~'; + for(; val > 0; val /= 10) + dst[name+ --j] = (val%10)+'0'; + + if(debug) + chat(sys->sprint("returning dst [%s] src [%s]\n",dst,src)); + + return dst; +} + +getfat(xf: ref Xfs, n: int): int +{ + bp := xf.ptr; + k := 0; + + if(n < 2 || n >= bp.fatclusters) + return -1; + fb := bp.fatbits; + k = (fb*n) >> 3; + if(k < 0 || k >= bp.fatsize*bp.sectsize) + panic("getfat"); + + sect := k/bp.sectsize + bp.fataddr; + o := k%bp.sectsize; + p := getsect(xf, sect); + if(p == nil) + return -1; + k = int p.iobuf[o++]; + if(o >= bp.sectsize) { + putsect(p); + p = getsect(xf, sect+1); + if(p == nil) + return -1; + o = 0; + } + k |= int p.iobuf[o++]<<8; + if(fb == 32){ + # fat32 is really fat28 + k |= int p.iobuf[o++] << 16; + k |= (int p.iobuf[o] & 16r0F) << 24; + fb = 28; + } + putsect(p); + if(fb == 12) { + if(n&1) + k >>= 4; + else + k &= 16rfff; + } + + if(debug & FAT_INFO) + chat(sys->sprint("fat(0x%x)=0x%x...", n, k)); + + # + # check for out of range + # + if(k >= (1<<fb) - 8) + return -1; + return k; +} + +putfat(xf: ref Xfs, n, val: int) +{ + bp := xf.ptr; + if(n < 2 || n >= bp.fatclusters) + panic(sys->sprint("putfat n=%d", n)); + k := (bp.fatbits*n) >> 3; + if(k >= bp.fatsize*bp.sectsize) + panic("putfat"); + sect := k/bp.sectsize + bp.fataddr; + for(; sect<bp.rootaddr; sect+=bp.fatsize) { + o := k%bp.sectsize; + p := getsect(xf, sect); + if(p == nil) + continue; + case bp.fatbits { + 12 => + if(n&1) { + p.iobuf[o] &= byte 16r0f; + p.iobuf[o++] |= byte (val<<4); + if(o >= bp.sectsize) { + p.flags |= BMOD; + putsect(p); + p = getsect(xf, sect+1); + if(p == nil) + continue; + o = 0; + } + p.iobuf[o] = byte (val>>4); + } + else { + p.iobuf[o++] = byte val; + if(o >= bp.sectsize) { + p.flags |= BMOD; + putsect(p); + p = getsect(xf, sect+1); + if(p == nil) + continue; + o = 0; + } + p.iobuf[o] &= byte 16rf0; + p.iobuf[o] |= byte ((val>>8)&16r0f); + } + 16 => + p.iobuf[o++] = byte val; + p.iobuf[o] = byte (val>>8); + 32 => # fat32 is really fat28 + p.iobuf[o++] = byte val; + p.iobuf[o++] = byte (val>>8); + p.iobuf[o++] = byte (val>>16); + p.iobuf[o] = byte ((int p.iobuf[o] & 16rF0) | ((val>>24) & 16r0F)); + * => + panic("putfat fatbits"); + } + + p.flags |= BMOD; + putsect(p); + } +} + +falloc(xf: ref Xfs): int +{ + bp := xf.ptr; + n := bp.freeptr; + for(;;) { + if(getfat(xf, n) == 0) + break; + if(++n >= bp.fatclusters) + n = FATRESRV; + if(n == bp.freeptr) + return 0; + } + bp.freeptr = n+1; + if(bp.freeptr >= bp.fatclusters) + bp.freeptr = FATRESRV; + putfat(xf, n, int 16rffffffff); + k := clust2sect(bp, n); + for(i:=0; i<bp.clustsize; i++) { + p := getosect(xf, k+i); + if(p == nil) + return -1; + for(j:=0; j<len p.iobuf; j++) + p.iobuf[j] = byte 0; + p.flags = BMOD; + putsect(p); + } + return n; +} + +clust2sect(bp: ref Dosbpb, clust: int): int +{ + return bp.dataaddr + (clust - FATRESRV)*bp.clustsize; +} + +sect2clust(bp: ref Dosbpb, sect: int): int +{ + c := (sect - bp.dataaddr) / bp.clustsize + FATRESRV; + # assert(sect == clust2sect(bp, c)); + return c; +} + +bootdump(b: ref Dosboot) +{ + chat(sys->sprint("magic: 0x%2.2x 0x%2.2x 0x%2.2x\n", + int b.magic[0], int b.magic[1], int b.magic[2])); + chat(sys->sprint("version: \"%8.8s\"\n", string b.version)); + chat(sys->sprint("sectsize: %d\n", bytes2short(b.sectsize))); + chat(sys->sprint("allocsize: %d\n", int b.clustsize)); + chat(sys->sprint("nresrv: %d\n", bytes2short(b.nresrv))); + chat(sys->sprint("nfats: %d\n", int b.nfats)); + chat(sys->sprint("rootsize: %d\n", bytes2short(b.rootsize))); + chat(sys->sprint("volsize: %d\n", bytes2short(b.volsize))); + chat(sys->sprint("mediadesc: 0x%2.2x\n", int b.mediadesc)); + chat(sys->sprint("fatsize: %d\n", bytes2short(b.fatsize))); + chat(sys->sprint("trksize: %d\n", bytes2short(b.trksize))); + chat(sys->sprint("nheads: %d\n", bytes2short(b.nheads))); + chat(sys->sprint("nhidden: %d\n", bytes2int(b.nhidden))); + chat(sys->sprint("bigvolsize: %d\n", bytes2int(b.bigvolsize))); + chat(sys->sprint("driveno: %d\n", int b.driveno)); + chat(sys->sprint("bootsig: 0x%2.2x\n", int b.bootsig)); + chat(sys->sprint("volid: 0x%8.8x\n", bytes2int(b.volid))); + chat(sys->sprint("label: \"%11.11s\"\n", string b.label)); +} + +xputtime(d: ref Dosdir, s: int) +{ + if(s == 0) + t := daytime->local((sys->millisec() - nowt1)/1000 + nowt); + else + t = daytime->local(s); + x := (t.hour<<11) | (t.min<<5) | (t.sec>>1); + d.time[0] = byte x; + d.time[1] = byte (x>>8); + x = ((t.year-80)<<9) | ((t.mon+1)<<5) | t.mday; + d.date[0] = byte x; + d.date[1] = byte (x>>8); +} + +puttime(d: ref Dosdir) +{ + xputtime(d, 0); +} + +gtime(a: array of byte): int +{ + tm := ref Daytime->Tm; + i := bytes2short(a[22:24]); # dos time + tm.hour = i >> 11; + tm.min = (i>>5) & 63; + tm.sec = (i & 31) << 1; + i = bytes2short(a[24:26]); # dos date + tm.year = 80 + (i>>9); + tm.mon = ((i>>5) & 15) - 1; + tm.mday = i & 31; + tm.tzoff = tzoff; # DOS time is local time + return daytime->tm2epoch(tm); +} + +dirdump(arr: array of byte, addr, offset: int) +{ + if(!debug) + return; + attrchar:= "rhsvda67"; + d := Dosdir.arr2Dd(arr); + buf := sys->sprint("\"%.8s.%.3s\" ", d.name, d.ext); + p_i:=7; + + for(i := 16r80; i != 0; i >>= 1) { + if((d.attr & byte i) == byte i) + ch := attrchar[p_i]; + else + ch = '-'; + buf += sys->sprint("%c", ch); + p_i--; + } + + i = bytes2short(d.time); + buf += sys->sprint(" %2.2d:%2.2d:%2.2d", i>>11, (i>>5)&63, (i&31)<<1); + i = bytes2short(d.date); + buf += sys->sprint(" %2.2d.%2.2d.%2.2d", 80+(i>>9), (i>>5)&15, i&31); + buf += sys->sprint(" %d %d", bytes2short(d.start), bytes2short(d.length)); + buf += sys->sprint(" %d %d\n",addr,offset); + chat(buf); +} + +putnamesect(longname: string, curslot: int, first: int, sum: int, a: array of byte) +{ + for(i := 0; i < DOSDIRSIZE; i++) + a[i] = byte 16rFF; + if(first) + a[0] = byte (16r40 | curslot); + else + a[0] = byte curslot; + a[11] = byte DLONG; + a[12] = byte 0; + a[13] = byte sum; + a[26] = byte 0; + a[27] = byte 0; + # a[1:1+10] = characters 1 to 5 + n := len longname; + j := (curslot-1)*DOSRUNES; + for(i = 1; i < 1+10; i += 2){ + c := 0; + if(j < n) + c = longname[j++]; + a[i] = byte c; + a[i+1] = byte (c >> 8); + if(c == 0) + return; + } + # a[14:14+12] = characters 6 to 11 + for(i = 14; i < 14+12; i += 2){ + c := 0; + if(j < n) + c = longname[j++]; + a[i] = byte c; + a[i+1] = byte (c >> 8); + if(c == 0) + return; + } + # a[28:28+4] characters 12 to 13 + for(i = 28; i < 28+4; i += 2){ + c := 0; + if(j < n) + c = longname[j++]; + a[i] = byte c; + a[i+1] = byte (c>>8); + if(c == 0) + return; + } +} + +putlongname(xf: ref Xfs, ndp: ref Dosptr, name: string, sname: string): int +{ + bp := xf.ptr; + first := 1; + sum := aliassum(sname); + for(nds := (len name+DOSRUNES-1)/DOSRUNES; nds > 0; nds--) { + putnamesect(name, nds, first, sum, ndp.p.iobuf[ndp.offset:]); + first = 0; + ndp.offset += DOSDIRSIZE; + if(ndp.offset == bp.sectsize) { + if(debug) + chat(sys->sprint("long name %s entry %d/%d crossing sector, addr=%d, naddr=%d", name, nds, (len name+DOSRUNES-1)/DOSRUNES, ndp.addr, ndp.naddr)); + ndp.p.flags |= BMOD; + putsect(ndp.p); + ndp.p = nil; + ndp.d = nil; + + # switch to the next cluster for the next long entry or the subsequent normal dir. entry + # naddr must be set up correctly by searchdir because we'll need one or the other + + ndp.prevaddr = ndp.addr; + ndp.addr = ndp.naddr; + ndp.naddr = -1; + if(ndp.addr < 0) + return -1; + ndp.p = getsect(xf, ndp.addr); + if(ndp.p == nil) + return -1; + ndp.offset = 0; + } + } + return 0; +} + +bytes2int(a: array of byte): int +{ + return (((((int a[3] << 8) | int a[2]) << 8) | int a[1]) << 8) | int a[0]; +} + +bytes2short(a: array of byte): int +{ + return (int a[1] << 8) | int a[0]; +} + +chat(s: string) +{ + if(debug) + sys->fprint(sys->fildes(2), "%s", s); +} + +panic(s: string) +{ + sys->fprint(sys->fildes(2), "dosfs: panic: %s\n", s); + if(pflag) + <-chan of int; # hang here + raise "fail:panic"; +} + +Dosboot.arr2Db(arr: array of byte): ref Dosboot +{ + db := ref Dosboot; + db.magic = arr[0:3]; + db.version = arr[3:11]; + db.sectsize = arr[11:13]; + db.clustsize = arr[13]; + db.nresrv = arr[14:16]; + db.nfats = arr[16]; + db.rootsize = arr[17:19]; + db.volsize = arr[19:21]; + db.mediadesc = arr[21]; + db.fatsize = arr[22:24]; + db.trksize = arr[24:26]; + db.nheads = arr[26:28]; + db.nhidden = arr[28:32]; + db.bigvolsize = arr[32:36]; + db.driveno = arr[36]; + db.bootsig = arr[38]; + db.volid = arr[39:43]; + db.label = arr[43:54]; + return db; +} + +Dosdir.arr2Dd(arr: array of byte): ref Dosdir +{ + dir := ref Dosdir; + for(i := 0; i < 8; i++) + dir.name[len dir.name] = int arr[i]; + for(; i < 11; i++) + dir.ext[len dir.ext] = int arr[i]; + dir.attr = arr[11]; + dir.reserved = arr[12:22]; + dir.time = arr[22:24]; + dir.date = arr[24:26]; + dir.start = arr[26:28]; + dir.length = arr[28:32]; + return dir; +} + +Dosdir.Dd2arr(d: ref Dosdir): array of byte +{ + a := array[32] of byte; + i:=0; + for(j := 0; j < len d.name; j++) + a[i++] = byte d.name[j]; + for(; j<8; j++) + a[i++]= byte 0; + for(j=0; j<len d.ext; j++) + a[i++] = byte d.ext[j]; + for(; j<3; j++) + a[i++]= byte 0; + a[i++] = d.attr; + for(j=0; j<10; j++) + a[i++] = d.reserved[j]; + for(j=0; j<2; j++) + a[i++] = d.time[j]; + for(j=0; j<2; j++) + a[i++] = d.date[j]; + for(j=0; j<2; j++) + a[i++] = d.start[j]; + for(j=0; j<4; j++) + a[i++] = d.length[j]; + return a; +} + +# +# checksum of short name for use in long name directory entries +# assumes sname is already padded correctly to 8+3 +# +aliassum(sname: string): int +{ + i := 0; + for(sum:=0; i<11; i++) + sum = (((sum&1)<<7)|((sum&16rfe)>>1))+sname[i]; + return sum; +} + +# +# track i/o +# + +# An Xfs represents the root of an external file system, anchored +# to the server and the client +Xfs: adt { + next:cyclic ref Xfs; + name: string; # of file containing external f.s. + qid: Sys->Qid; # of file containing external f.s. + refn: int; # attach count + rootqid: Sys->Qid; # of inferno constructed root directory + dev: ref Sys->FD; # FD of the file containing external f.s. + fmt: int; # successfully read format + offset: int; # offset in sectors to file system + ptr: ref Dosbpb; +}; + +# An Xfile represents the mapping of fid's & qid's to the server. +Xfile: adt { + next: cyclic ref Xfile; # in hash bucket + client: int; + fid: int; + flags: int; + qid: Sys->Qid; + xf: ref Xfs; + ptr: ref Dosptr; +}; + +Iosect: adt +{ + next: cyclic ref Iosect; + flags: int; + t: cyclic ref Iotrack; + iobuf: array of byte; +}; + +Iotrack: adt +{ + flags: int; + xf: ref Xfs; + addr: int; + next: cyclic ref Iotrack; # in lru list + prev: cyclic ref Iotrack; + hnext: cyclic ref Iotrack; # in hash list + hprev: cyclic ref Iotrack; + refn: int; + tp: cyclic ref Track; +}; + +Track: adt +{ + create: fn(): ref Track; + p: cyclic array of ref Iosect; + buf: array of byte; +}; + +BMOD: con 1<<0; +BIMM: con 1<<1; +BSTALE: con 1<<2; + +HIOB: con 31; # a prime +NIOBUF: con 20; + +Sectorsize: con 512; +Sect2trk: con 9; # default + +hiob := array[HIOB+1] of ref Iotrack; # hash buckets + lru list +iobuf := array[NIOBUF] of ref Iotrack; # the real ones +freelist: ref Iosect; +sect2trk := Sect2trk; +trksize := Sect2trk*Sectorsize; + +FIDMOD: con 127; # prime +xhead: ref Xfs; +client: int; + +xfiles := array[FIDMOD] of ref Xfile; +iodebug := 0; + +iotrackinit(sectors: int) +{ + if(sectors <= 0) + sectors = 9; + sect2trk = sectors; + trksize = sect2trk*Sectorsize; + + freelist = nil; + + for(i := 0;i < FIDMOD; i++) + xfiles[i] = ref Xfile(nil,0,0,0,Sys->Qid(big 0,0,0),nil,nil); + + for(i = 0; i <= HIOB; i++) + hiob[i] = ref Iotrack; + + for(i = 0; i < HIOB; i++) { + hiob[i].hprev = hiob[i]; + hiob[i].hnext = hiob[i]; + hiob[i].refn = 0; + hiob[i].addr = 0; + } + hiob[i].prev = hiob[i]; + hiob[i].next = hiob[i]; + hiob[i].refn = 0; + hiob[i].addr = 0; + + for(i=0;i<NIOBUF;i++) + iobuf[i] = ref Iotrack; + + for(i=0; i<NIOBUF; i++) { + iobuf[i].hprev = iobuf[i].hnext = iobuf[i]; + iobuf[i].prev = iobuf[i].next = iobuf[i]; + iobuf[i].refn=iobuf[i].addr=0; + iobuf[i].flags = 0; + if(hiob[HIOB].next != iobuf[i]) { + iobuf[i].prev.next = iobuf[i].next; + iobuf[i].next.prev = iobuf[i].prev; + iobuf[i].next = hiob[HIOB].next; + iobuf[i].prev = hiob[HIOB]; + hiob[HIOB].next.prev = iobuf[i]; + hiob[HIOB].next = iobuf[i]; + } + iobuf[i].tp = Track.create(); + } +} + +Track.create(): ref Track +{ + t := ref Track; + t.p = array[sect2trk] of ref Iosect; + t.buf = array[trksize] of byte; + return t; +} + +getsect(xf: ref Xfs, addr: int): ref Iosect +{ + return getiosect(xf, addr, 1); +} + +getosect(xf: ref Xfs, addr: int): ref Iosect +{ + return getiosect(xf, addr, 0); +} + +# get the sector corresponding to the address addr. +getiosect(xf: ref Xfs, addr , rflag: int): ref Iosect +{ + # offset from beginning of track. + toff := addr % sect2trk; + + # address of beginning of track. + taddr := addr - toff; + t := getiotrack(xf, taddr); + + if(rflag && t.flags&BSTALE) { + if(tread(t) < 0) + return nil; + + t.flags &= ~BSTALE; + } + + t.refn++; + if(t.tp.p[toff] == nil) { + p := newsect(); + t.tp.p[toff] = p; + p.flags = t.flags&BSTALE; + p.t = t; + p.iobuf = t.tp.buf[toff*Sectorsize:(toff+1)*Sectorsize]; + } + return t.tp.p[toff]; +} + +putsect(p: ref Iosect) +{ + t: ref Iotrack; + + t = p.t; + t.flags |= p.flags; + p.flags = 0; + t.refn--; + if(t.refn < 0) + panic("putsect: refcount"); + + if(t.flags & BIMM) { + if(t.flags & BMOD) + twrite(t); + t.flags &= ~(BMOD|BIMM); + } +} + +# get the track corresponding to addr +# (which is the address of the beginning of a track +getiotrack(xf: ref Xfs, addr: int): ref Iotrack +{ + p: ref Iotrack; + mp := hiob[HIOB]; + + if(iodebug) + chat(sys->sprint("iotrack %d,%d...", xf.dev.fd, addr)); + + # find bucket in hash table. + h := (xf.dev.fd<<24) ^ addr; + if(h < 0) + h = ~h; + h %= HIOB; + hp := hiob[h]; + + out: for(;;){ + loop: for(;;) { + # look for it in the active list + for(p = hp.hnext; p != hp; p=p.hnext) { + if(p.addr != addr || p.xf != xf) + continue; + if(p.addr == addr && p.xf == xf) { + break out; + } + continue loop; + } + + # not found + # take oldest unref'd entry + for(p = mp.prev; p != mp; p=p.prev) + if(p.refn == 0 ) + break; + if(p == mp) { + if(iodebug) + chat("iotrack all ref'd\n"); + continue loop; + } + + if((p.flags & BMOD)!= 0) { + twrite(p); + p.flags &= ~(BMOD|BIMM); + continue loop; + } + purgetrack(p); + p.addr = addr; + p.xf = xf; + p.flags = BSTALE; + break out; + } + } + + if(hp.hnext != p) { + p.hprev.hnext = p.hnext; + p.hnext.hprev = p.hprev; + p.hnext = hp.hnext; + p.hprev = hp; + hp.hnext.hprev = p; + hp.hnext = p; + } + if(mp.next != p) { + p.prev.next = p.next; + p.next.prev = p.prev; + p.next = mp.next; + p.prev = mp; + mp.next.prev = p; + mp.next = p; + } + return p; +} + +purgetrack(t: ref Iotrack) +{ + refn := sect2trk; + for(i := 0; i < sect2trk; i++) { + if(t.tp.p[i] == nil) { + --refn; + continue; + } + freesect(t.tp.p[i]); + --refn; + t.tp.p[i]=nil; + } + if(t.refn != refn) + panic("purgetrack"); + if(refn!=0) + panic("refn not 0"); +} + +twrite(t: ref Iotrack): int +{ + if(iodebug) + chat(sys->sprint("[twrite %d...", t.addr)); + + if((t.flags & BSTALE)!= 0) { + refn:=0; + for(i:=0; i<sect2trk; i++) + if(t.tp.p[i]!=nil) + ++refn; + + if(refn < sect2trk) { + if(tread(t) < 0) { + if (iodebug) + chat("error]"); + return -1; + } + } + else + t.flags &= ~BSTALE; + } + + if(devwrite(t.xf, t.addr, t.tp.buf) < 0) { + if(iodebug) + chat("error]"); + return -1; + } + + if(iodebug) + chat(" done]"); + + return 0; +} + +tread(t: ref Iotrack): int +{ + refn := 0; + rval: int; + + for(i := 0; i < sect2trk; i++) + if(t.tp.p[i] != nil) + ++refn; + + if(iodebug) + chat(sys->sprint("[tread %d...", t.addr)); + + tbuf := t.tp.buf; + if(refn != 0) + tbuf = array[trksize] of byte; + + rval = devread(t.xf, t.addr, tbuf); + if(rval < 0) { + if(iodebug) + chat("error]"); + return -1; + } + + if(refn != 0) { + for(i=0; i < sect2trk; i++) { + if(t.tp.p[i] == nil) { + t.tp.buf[i*Sectorsize:]=tbuf[i*Sectorsize:(i+1)*Sectorsize]; + if(iodebug) + chat(sys->sprint("%d ", i)); + } + } + } + + if(iodebug) + chat("done]"); + + t.flags &= ~BSTALE; + return 0; +} + +purgebuf(xf: ref Xfs) +{ + for(p := 0; p < NIOBUF; p++) { + if(iobuf[p].xf != xf) + continue; + if(iobuf[p].xf == xf) { + if((iobuf[p].flags & BMOD) != 0) + twrite(iobuf[p]); + + iobuf[p].flags = BSTALE; + purgetrack(iobuf[p]); + } + } +} + +sync() +{ + for(p := 0; p < NIOBUF; p++) { + if(!(iobuf[p].flags & BMOD)) + continue; + + if(iobuf[p].flags & BMOD){ + twrite(iobuf[p]); + iobuf[p].flags &= ~(BMOD|BIMM); + } + } +} + + +newsect(): ref Iosect +{ + if((p := freelist)!=nil) { + freelist = p.next; + p.next = nil; + } else + p = ref Iosect(nil, 0, nil,nil); + + return p; +} + +freesect(p: ref Iosect) +{ + p.next = freelist; + freelist = p; +} + + +# devio from here +deverror(name: string, xf: ref Xfs, addr,n,nret: int): int +{ + if(nret < 0) { + if(iodebug) + chat(sys->sprint("%s errstr=\"%r\"...", name)); + xf.dev = nil; + return -1; + } + if(iodebug) + chat(sys->sprint("dev %d sector %d, %s: %d, should be %d\n", + xf.dev.fd, addr, name, nret, n)); + + panic(name); + return -1; +} + +devread(xf: ref Xfs, addr: int, buf: array of byte): int +{ + if(xf.dev==nil) + return -1; + + sys->seek(xf.dev, big (xf.offset+addr*Sectorsize), sys->SEEKSTART); + nread := sys->read(xf.dev, buf, trksize); + if(nread != trksize) + return deverror("read", xf, addr, trksize, nread); + + return 0; +} + +devwrite(xf: ref Xfs, addr: int, buf: array of byte): int +{ + if(xf.dev == nil) + return -1; + + sys->seek(xf.dev, big (xf.offset+addr*Sectorsize), 0); + nwrite := sys->write(xf.dev, buf, trksize); + if(nwrite != trksize) + return deverror("write", xf, addr, trksize , nwrite); + + return 0; +} + +devcheck(xf: ref Xfs): int +{ + buf := array[Sectorsize] of byte; + + if(xf.dev == nil) + return -1; + + sys->seek(xf.dev, big 0, sys->SEEKSTART); + if(sys->read(xf.dev, buf, Sectorsize) != Sectorsize){ + xf.dev = nil; + return -1; + } + + return 0; +} + +# setup and return the Xfs associated with "name" + +getxfs(name: string): (ref Xfs, string) +{ + if(name == nil) + return (nil, "no file system device specified"); + + + # If the name passed is of the form 'name:offset' then + # offset is used to prime xf->offset. This allows accessing + # a FAT-based filesystem anywhere within a partition. + # Typical use would be to mount a filesystem in the presence + # of a boot manager programm at the beginning of the disc. + + offset := 0; + for(i := 0;i < len name; i++) + if(name[i]==':') + break; + + if(i < len name) { + offset = int name[i+1:]; + if(offset < 0) + return (nil, "invalid device offset to file system"); + offset *= Sectorsize; + name = name[0:i]; + } + + fd := sys->open(name, Sys->ORDWR); + if(fd == nil) { + if(iodebug) + chat(sys->sprint("getxfs: open(%s) failed: %r\n", name)); + return (nil, sys->sprint("can't open %s: %r", name)); + } + + (rval,dir) := sys->fstat(fd); + if(rval < 0) + return (nil, sys->sprint("can't stat %s: %r", name)); + + # lock down the list of xf's. + fxf: ref Xfs; + for(xf := xhead; xf != nil; xf = xf.next) { + if(xf.refn == 0) { + if(fxf == nil) + fxf = xf; + continue; + } + if(xf.qid.path != dir.qid.path || xf.qid.vers != dir.qid.vers) + continue; + + if(xf.name!= name || xf.dev == nil) + continue; + + if(devcheck(xf) < 0) # look for media change + continue; + + if(offset && xf.offset != offset) + continue; + + if(iodebug) + chat(sys->sprint("incref \"%s\", dev=%d...", + xf.name, xf.dev.fd)); + + ++xf.refn; + return (xf, nil); + } + + # this xf doesn't exist, make a new one and stick it on the list. + if(fxf == nil){ + fxf = ref Xfs; + fxf.next = xhead; + xhead = fxf; + } + + if(iodebug) + chat(sys->sprint("alloc \"%s\", dev=%d...", name, fd.fd)); + + fxf.name = name; + fxf.refn = 1; + fxf.qid = dir.qid; + fxf.dev = fd; + fxf.fmt = 0; + fxf.offset = offset; + return (fxf, nil); +} + +refxfs(xf: ref Xfs, delta: int) +{ + xf.refn += delta; + if(xf.refn == 0) { + if (iodebug) + chat(sys->sprint("free \"%s\", dev=%d...", + xf.name, xf.dev.fd)); + + purgebuf(xf); + if(xf.dev !=nil) + xf.dev = nil; + } +} + +xfile(fid, flag: int): ref Xfile +{ + pf: ref Xfile; + + # find hashed file list in LRU? table. + k := (fid^client)%FIDMOD; + + # find if this fid is in the hashed file list. + f:=xfiles[k]; + for(pf = nil; f != nil; f = f.next) { + if(f.fid == fid && f.client == client) + break; + pf=f; + } + + # move this fid to the front of the list if it was further down. + if(f != nil && pf != nil){ + pf.next = f.next; + f.next = xfiles[k]; + xfiles[k] = f; + } + + case flag { + * => + panic("xfile"); + Asis => + if(f != nil && f.xf != nil && f.xf.dev == nil) + return nil; + return f; + Clean => + break; + Clunk => + if(f != nil) { + xfiles[k] = f.next; + clean(f); + } + return nil; + } + + # clean it up .. + if(f != nil) + return clean(f); + + # f wasn't found in the hashtable, make a new one and add it + f = ref Xfile; + f.next = xfiles[k]; + xfiles[k] = f; + # sort out the fid, etc. + f.fid = fid; + f.client = client; + f.flags = 0; + f.qid = Sys->Qid(big 0, 0, Styx->QTFILE); + f.xf = nil; + f.ptr = ref Dosptr(0,0,0,0,0,0,-1,-1,nil,nil); + return f; +} + +clean(f: ref Xfile): ref Xfile +{ + f.ptr = nil; + if(f.xf != nil) { + refxfs(f.xf, -1); + f.xf = nil; + } + f.flags = 0; + f.qid = Sys->Qid(big 0, 0, 0); + return f; +} + +# +# the file at <addr, offset> has moved +# relocate the dos entries of all fids in the same file +# +dosptrreloc(f: ref Xfile, dp: ref Dosptr, addr: int, offset: int) +{ + i: int; + p: ref Xfile; + xdp: ref Dosptr; + + for(i=0; i < FIDMOD; i++){ + for(p = xfiles[i]; p != nil; p = p.next){ + xdp = p.ptr; + if(p != f && p.xf == f.xf + && xdp != nil && xdp.addr == addr && xdp.offset == offset){ + *xdp = *dp; + xdp.p = nil; + # xdp.d = nil; + p.qid.path = big QIDPATH(xdp); + } + } + } +} diff --git a/appl/cmd/du.b b/appl/cmd/du.b new file mode 100644 index 00000000..45b8ee1b --- /dev/null +++ b/appl/cmd/du.b @@ -0,0 +1,163 @@ +implement Du; + +include "sys.m"; + sys: Sys; + sprint: import sys; +include "draw.m"; +include "string.m"; + strmod: String; +include "readdir.m"; + readdir: Readdir; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "arg.m"; + +aflag := 0; # all files, not just directories +nflag := 0; # names only (but see -t); implies -a +sflag := 0; # summary of top level names +tflag := 0; # use modification time, not size; netlib format if -n also given +uflag := 0; # use last use (access) time, not size +blocksize := big 1024; # quantise length to this block size (still displayed in kb) +bout: ref Iobuf; + +Du: module +{ + init: fn(nil: ref Draw->Context, arg: list of string); +}; + +kb(b: big): big +{ + return (((b + blocksize - big 1)/blocksize)*blocksize)/big 1024; +} + +report(name: string, mtime: int, atime: int, l: big, chksum: int) +{ + t := mtime; + if(uflag) + t = atime; + if(nflag){ + if(tflag) + bout.puts(sprint("%q %ud %bd %d\n", name, t, l, chksum)); + else + bout.puts(sprint("%q\n", name)); + }else{ + if(tflag) + bout.puts(sprint("%ud %q\n", t, name)); + else + bout.puts(sprint("%-4bd %q\n", kb(l), name)); + } +} + +# Avoid loops in tangled namespaces. +NCACHE: con 1024; # must be power of two +cache := array[NCACHE] of list of ref sys->Dir; + +seen(dir: ref sys->Dir): int +{ + h := int dir.qid.path & (NCACHE-1); + for(c := cache[h]; c!=nil; c = tl c){ + t := hd c; + if(dir.qid.path==t.qid.path && dir.dtype==t.dtype && dir.dev==t.dev) + return 1; + } + cache[h] = dir :: cache[h]; + return 0; +} + +dir(dirname: string): big +{ + prefix := dirname+"/"; + if(dirname==".") + prefix = nil; + sum := big 0; + (de, nde) := readdir->init(dirname, readdir->NAME); + if(nde < 0) + warn("can't read", dirname); + for(i := 0; i < nde; i++) { + s := prefix+de[i].name; + if(de[i].mode & Sys->DMDIR){ + if(!seen(de[i])){ # arguably should apply to files as well + size := dir(s); + sum += size; + if(!sflag && !nflag) + report(s, de[i].mtime, de[i].atime, size, 0); + } + }else{ + l := de[i].length; + sum += l; + if(aflag) + report(s, de[i].mtime, de[i].atime, l, 0); + } + } + return sum; +} + +du(name: string) +{ + (rc, d) := sys->stat(name); + if(rc < 0){ + warn("can't stat", name); + return; + } + if(d.mode & Sys->DMDIR){ + d.length = dir(name); + if(nflag && !sflag) + return; + } + report(name, d.mtime, d.atime, d.length, 0); +} + +warn(why: string, f: string) +{ + sys->fprint(sys->fildes(2), "du: %s %q: %r\n", why, f); +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + strmod = load String String->PATH; + readdir = load Readdir Readdir->PATH; + arg := load Arg Arg->PATH; + if(arg == nil || bufio==nil || arg==nil || readdir==nil || readdir==nil){ + sys->fprint(sys->fildes(2), "du: load Error: %r\n"); + raise "fail:can't load"; + } + sys->pctl(Sys->FORKFD, nil); + bout = bufio->fopen(sys->fildes(1), bufio->OWRITE); + arg->init(args); + arg->setusage("du [-anstu] [-b bsize] [file ...]"); + while((o := arg->opt()) != 0) + case o { + 'a' => + aflag = 1; + 'b' => + s := arg->earg(); + blocksize = big s; + if(len s > 0 && s[len s-1] == 'k') + blocksize *= big 1024; + if(blocksize <= big 0) + blocksize = big 1; + 'n' => + nflag = 1; + aflag = 1; + 's' => + sflag = 1; + 't' => + tflag = 1; + 'u' => + uflag = 1; + tflag = 1; + * => + arg->usage(); + } + args = arg->argv(); + arg = nil; + + if(args==nil) + args = "." :: nil; + for(; args!=nil; args = tl args) + du(hd args); + bout.close(); +} diff --git a/appl/cmd/echo.b b/appl/cmd/echo.b new file mode 100644 index 00000000..e47b7ed0 --- /dev/null +++ b/appl/cmd/echo.b @@ -0,0 +1,36 @@ +implement Echo; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +Echo: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + if(args != nil) + args = tl args; + addnl := 1; + if(args != nil && (hd args == "-n" || hd args == "--")) { + if(hd args == "-n") + addnl = 0; + args = tl args; + } + s := ""; + if(args != nil) { + s = hd args; + while((args = tl args) != nil) + s += " " + hd args; + } + if(addnl) + s[len s] = '\n'; + a := array of byte s; + if(sys->write(sys->fildes(1), a, len a) < 0){ + sys->fprint(sys->fildes(2), "echo: write error: %r\n"); + raise "fail:write error"; + } +} diff --git a/appl/cmd/ed.b b/appl/cmd/ed.b new file mode 100644 index 00000000..e374ce4e --- /dev/null +++ b/appl/cmd/ed.b @@ -0,0 +1,1588 @@ +# +# Editor +# + +implement Editor; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "regex.m"; + regex: Regex; + Re: import regex; +include "sh.m"; + sh: Sh; + +Editor: module { + init: fn(nil: ref Draw->Context, args: list of string); +}; + +FNSIZE: con 128; # file name +LBSIZE: con 4096; # max line size +BLKSIZE: con 4096; # block size in temp file +NBLK: con 8191; # max size of temp file +ESIZE: con 256; # max size of reg exp +GBSIZE: con 256; # max size of global command +MAXSUB: con 9; # max number of sub reg exp +ESCFLG: con 16rFFFF; # escape Rune - user defined code +EOF: con -1; +BytesPerRune: con 2; +RunesPerBlock: con BLKSIZE / BytesPerRune; + +APPEND_GETTTY, APPEND_GETSUB, APPEND_GETCOPY, APPEND_GETFILE: con iota; + +Subexp: adt { + rsp, rep: int; +}; + +Globp: adt { + s: string; + isnil: int; +}; + +addr1: int; +addr2: int; +anymarks: int; +col: int; +count: int; +dol: int; +dot: int; +fchange: int; +file: string; +genbuf := array[LBSIZE] of int; +given: int; +globp: Globp; +iblock: int; +ichanged: int; +io: ref Sys->FD; +iobuf: ref Iobuf; +lastc: int; +line := array [70] of byte; +linebp := -1; +linebuf := array [LBSIZE] of int; +listf: int; +listn: int; +loc1: int; +loc2: int; +names := array [26] of int; +oblock: int; +oflag: int; +pattern: Re; +peekc: int; +pflag: int; +rescuing: int; +rhsbuf := array [LBSIZE/2] of int; +savedfile: string; +subnewa: int; +subolda: int; +subexp: array of Subexp; +tfname: string; +tline: int; +waiting: int; +wrapp: int; +zero: array of int; +drawctxt: ref Draw->Context; + +Q: con ""; +T: con "TMP"; +WRERR: con "WRITE ERROR"; +bpagesize := 20; +hex: con "0123456789abcdef"; +linp: int; +nlall := 128; +tfile: ref Sys->FD; +vflag := 1; + +debug(s: string) +{ + sys->print("%s", s); +} + +init(ctxt: ref Draw->Context, args: list of string) +{ + drawctxt = ctxt; + + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if (bufio == nil) { + sys->fprint(sys->fildes(2), "can't load %s\n", Bufio->PATH); + return; + } + regex = load Regex Regex->PATH; + if (regex == nil) { + sys->fprint(sys->fildes(2), "can't load %s\n", Regex->PATH); + return; + } + +# notify(notifyf); + + if (args != nil) + args = tl args; + + if (args != nil && hd args == "-o") { + oflag = 1; + vflag = 0; + args = tl args; + } + + if (args != nil && hd args == "-") { + vflag = 0; + args = tl args; + } + + if (oflag) { + savedfile = "/fd/1"; + globp = ("a", 0); + } else if (args != nil) { + savedfile = hd args; + globp = ("r", 0); + } + else + globp = (nil, 1); + zero = array [nlall + 5] of int; + tfname = mktemp("/tmp/eXXXXX"); +# debug(sys->sprint("tfname %s\n", tfname)); + _init(); + for(;;){ + { + commands(); + quit(); + }exception{ + "savej" => + ; + } + } +} + +casee(c: int) +{ + setnoaddr(); + if(vflag && fchange) { + fchange = 0; + error(Q); + } + filename(c); + _init(); + addr2 = 0; + caseread(); +} + +casep() +{ + newline(); + printcom(); +} + +caseq() +{ + setnoaddr(); + newline(); + quit(); +} + +caseread() +{ +#debug("caseread " + file); + if((io=sys->open(file, Sys->OREAD)) == nil) { + lastc = '\n'; + error(file); + } + iobuf = bufio->fopen(io, Sys->OREAD); + setwide(); + squeeze(0); + c := 0 != dol; + append(APPEND_GETFILE, addr2); + exfile(Sys->OREAD); + + fchange = c; +} + +commands() +{ + a1: int; + c, temp: int; + lastsep: int; + + for(;;) { + if(pflag) { + pflag = 0; + addr1 = addr2 = dot; + printcom(); + } + c = '\n'; + for(addr1 = -1;;) { + lastsep = c; + a1 = address(); + c = getchr(); + if(c != ',' && c != ';') + break; + if(lastsep == ',') + error(Q); + if(a1 < 0) { + a1 = 1; + if(a1 > dol) + a1--; + } + addr1 = a1; + if(c == ';') + dot = a1; + } + if(lastsep != '\n' && a1 < 0) + a1 = dol; + if((addr2=a1) < 0) { + given = 0; + addr2 = dot; + } else + given = 1; + if(addr1 < 0) + addr1 = addr2; +#debug(sys->sprint("%d,%d %c\n", addr1, addr2, c)); + case c { + 'a' => + add(0); + continue; + + 'b' => + nonzero(); + browse(); + continue; + + 'c' => + nonzero(); + newline(); + rdelete(addr1, addr2); + append(APPEND_GETTTY, addr1-1); + continue; + + 'd' => + nonzero(); + newline(); + rdelete(addr1, addr2); + continue; + + 'E' => + fchange = 0; + c = 'e'; + casee(c); + continue; + + 'e' => + casee(c); + continue; + + 'f' => + setnoaddr(); + filename(c); + putst(savedfile); + continue; + + 'g' => + global(1); + continue; + + 'i' => + add(-1); + continue; + + 'j' => + if(!given) + addr2++; + newline(); + join(); + continue; + + 'k' => + nonzero(); + c = getchr(); + if(c < 'a' || c > 'z') + error(Q); + newline(); + names[c-'a'] = zero[addr2] & ~16r1; + anymarks |= 16r1; + continue; + + 'm' => + move(0); + continue; + + 'n' => + listn++; + newline(); + printcom(); + continue; + + '\n' => + if(a1 < 0) { + a1 = dot+1; + addr2 = a1; + addr1 = a1; + } + if(lastsep==';') + addr1 = a1; + printcom(); + continue; + + 'l' => + listf++; + casep(); + continue; + + 'p' or 'P' => + casep(); + continue; + + 'Q' => + fchange = 0; + caseq(); + continue; + + 'q' => + caseq(); + continue; + + 'r' => + filename(c); + caseread(); + continue; + + 's' => + nonzero(); + substitute(!globp.isnil); + continue; + + 't' => + move(1); + continue; + + 'u' => + nonzero(); + newline(); + if((zero[addr2]&~8r01) != subnewa) + error(Q); + zero[addr2] = subolda; + dot = addr2; + continue; + + 'v' => + global(0); + continue; + + 'W' or 'w' => + if (c == 'W') + wrapp++; + setwide(); + squeeze(dol>0); + temp = getchr(); + if(temp != 'q' && temp != 'Q') { + peekc = temp; + temp = 0; + } + filename(c); + if(!wrapp || + ((io = sys->open(file, Sys->OWRITE)) == nil) || + ((sys->seek(io, big 0, Sys->SEEKEND)) < big 0)) + if((io = sys->create(file, Sys->OWRITE, 8r0666)) == nil) + error(file); + iobuf = bufio->fopen(io, Sys->OWRITE); + wrapp = 0; + if(dol > 0) + putfile(); + exfile(Sys->OWRITE); + if(addr1<=1 && addr2==dol) + fchange = 0; + if(temp == 'Q') + fchange = 0; + if(temp) + quit(); + continue; + + '=' => + setwide(); + squeeze(0); + newline(); + count = addr2 - 0; + putd(); + putchr('\n'); + continue; + + '!' => + callunix(); + continue; + + EOF => + return; + + } + error(Q); + } +} + +printcom() +{ + a1: int; + + nonzero(); + a1 = addr1; + do { + if(listn) { + count = a1-0; + putd(); + putchr('\t'); + } + putshst(getline(zero[a1++])); + } while(a1 <= addr2); + dot = addr2; + listf = 0; + listn = 0; + pflag = 0; +} + + +address(): int +{ + sign, a, opcnt, nextopand, b, c: int; + + nextopand = -1; + sign = 1; + opcnt = 0; + a = dot; + do { + do { + c = getchr(); + } while(c == ' ' || c == '\t'); + if(c >= '0' && c <= '9') { + peekc = c; + if(!opcnt) + a = 0; + a += sign*getnum(); + } else + case c { + '$' or '.' => + if (c == '$') + a = dol; + if(opcnt) + error(Q); + + '\'' => + c = getchr(); + if(opcnt || c < 'a' || c > 'z') + error(Q); + a = 0; + do { + a++; + } while(a <= dol && names[c-'a'] != (zero[a] & ~8r01)); + + '?' or '/' => + if (c == '?') + sign = -sign; + compile(c); + b = a; + for(;;) { + a += sign; + if(a <= 0) + a = dol; + if(a > dol) + a = 0; + if(match(a)) + break; + if(a == b) + error(Q); + } + break; + + * => + if(nextopand == opcnt) { + a += sign; + if(a < 0 || dol < a) + continue; # error(Q); + } + if(c != '+' && c != '-' && c != '^') { + peekc = c; + if(opcnt == 0) + a = -1; + return a; + } + sign = 1; + if(c != '+') + sign = -sign; + nextopand = ++opcnt; + continue; + } + sign = 1; + opcnt++; + } while(0 <= a && a <= dol); + error(Q); + return -1; +} + +getnum(): int +{ + r, c: int; + + r = 0; + for(;;) { + c = getchr(); + if(c < '0' || c > '9') + break; + r = r*10 + (c-'0'); + } + peekc = c; + return r; +} + +setwide() +{ + if(!given) { + addr1 = 0 + (dol>0); + addr2 = dol; + } +} + +setnoaddr() +{ + if(given) + error(Q); +} + +nonzero() +{ + squeeze(1); +} + +squeeze(i: int) +{ + if(addr1 < 0+i || addr2 > dol || addr1 > addr2) + error(Q); +} + +newline() +{ + c: int; + + c = getchr(); + if(c == '\n' || c == EOF) + return; + if(c == 'p' || c == 'l' || c == 'n') { + pflag++; + if(c == 'l') + listf++; + else + if(c == 'n') + listn++; + c = getchr(); + if(c == '\n') + return; + } + error(Q); +} + +filename(comm: int) +{ + rune: int; + c: int; + + count = 0; + c = getchr(); + if(c == '\n' || c == EOF) { + if(savedfile == nil && comm != 'f') + error(Q); + file = savedfile; + return; + } + if(c != ' ') + error(Q); + while((c=getchr()) == ' ') + ; + if(c == '\n') + error(Q); + file = nil; + do { + if(c == ' ' || c == EOF) + error(Q); + rune = c; + file[len file] = c; + } while((c=getchr()) != '\n'); + if(savedfile == nil || comm == 'e' || comm == 'f') + savedfile = file; +} + +exfile(om: int) +{ + + if(om == Sys->OWRITE) + if(iobuf.flush() < 0) + error(Q); + iobuf.close(); + iobuf = nil; + io = nil; + if(vflag) { + putd(); + putchr('\n'); + } +} + +error1(s: string) +{ + c: int; + + wrapp = 0; + listf = 0; + listn = 0; + count = 0; + sys->seek(sys->fildes(0), big 0, Sys->SEEKEND); # what does this do? + pflag = 0; + if(!globp.isnil) + lastc = '\n'; + globp = (nil, 1); + peekc = lastc; + if(lastc) + for(;;) { + c = getchr(); + if(c == '\n' || c == EOF) + break; + } + if(io != nil) + io = nil; + putchr('?'); + putst(s); +} + +error(s: string) +{ + error1(s); + raise "savej"; +} + +rescue() +{ + rescuing = 1; + if(dol > 0) { + addr1 = 0+1; + addr2 = dol; + io = sys->create("ed.hup", Sys->OWRITE, 8r0666); + if(io != nil){ + iobuf = bufio->fopen(io, Sys->OWRITE); + putfile(); + } + } + fchange = 0; + quit(); +} + +# void +# notifyf(void *a, char *s) +# { +# if(strcmp(s, "interrupt") == 0){ +# if(rescuing || waiting) +# noted(NCONT); +# putchr(L'\n'); +# lastc = '\n'; +# error1(Q); +# notejmp(a, savej, 0); +# } +# if(strcmp(s, "hangup") == 0){ +# if(rescuing) +# noted(NDFLT); +# rescue(); +# } +# fprint(2, "ed: note: %s\n", s); +# abort(); +# } + +getchr(): int +{ + s := array [Sys->UTFmax] of byte; + i: int; + r: int; + status: int; + if(lastc = peekc) { + peekc = 0; +#debug(sys->sprint("getchr: peekc %c\n", lastc)); + return lastc; + } + if(!globp.isnil) { + if (globp.s != nil) { + lastc = globp.s[0]; + globp.s = globp.s[1:]; +#debug(sys->sprint("getchr: globp %c remaining %d\n", lastc, len globp.s)); + return lastc; + } + globp = (nil, 1); +#debug(sys->sprint("getchr: globp end\n")); + return EOF; + } +#debug("globp nil\n"); + for(i=0;;) { + if(sys->read(sys->fildes(0), s[i:], 1) <= 0) + return lastc = EOF; + i++; + (r, nil, status) = sys->byte2char(s, 0); + if (status > 0) + break; + + } + lastc = r; + return lastc; +} + +gety(): int +{ + c: int; + gf: int; + p: int; + + p = 0; + gf = !globp.isnil; + for(;;) { + c = getchr(); + if(c == '\n') { + linebuf[p] = 0; + return 0; + } + if(c == EOF) { + if(gf) + peekc = c; + return c; + } + if(c == 0) + continue; + linebuf[p++] = c; + if(p >= len linebuf) + error(Q); + } + return 0; +} + +gettty(): int +{ + rc: int; + + rc = gety(); + if(rc) + return rc; + if(linebuf[0] == '.' && linebuf[1] == 0) + return EOF; + return 0; +} + +getfile(): int +{ + c: int; + lp: int; + + lp = 0; + do { + c = iobuf.getc(); + if(c < 0) { + if(lp > 0) { + putst("'\\n' appended"); + c = '\n'; + } else + return EOF; + } + if(lp >= len linebuf) { + lastc = '\n'; + error(Q); + } + linebuf[lp++] = c; + count++; + } while(c != '\n'); + linebuf[lp - 1] = 0; +#debug(sys->sprint("getline read %d\n", lp)); + return 0; +} + +putfile() +{ + a1: int; + lp: int; + c: int; + + a1 = addr1; + do { + lp = getline(zero[a1++]); + for(;;) { + count++; + c = linebuf[lp++]; + if(c == 0) { + if (iobuf.putc('\n') < 0) + error(Q); + break; + } + if (iobuf.putc(c) < 0) + error(Q); + } + } while(a1 <= addr2); + if(iobuf.flush() < 0) + error(Q); +} + +append(f: int, a: int): int +{ + a1, a2, rdot, nline, _tl: int; + rv: int; + + nline = 0; + dot = a; + for (;;) { + case f { + APPEND_GETTTY => rv = gettty(); + APPEND_GETSUB => rv = getsub(); + APPEND_GETCOPY => rv = getcopy(); + APPEND_GETFILE => rv = getfile(); + } + if (rv != 0) + break; + if(dol >= nlall) { + nlall += 512; + newzero := array [nlall + 5] of int; + if(newzero == nil) { + error("MEM?"); + rescue(); + } + newzero[0:] = zero; + zero = newzero; + } + _tl = putline(); + nline++; + a1 = ++dol; + a2 = a1+1; + rdot = ++dot; + zero[rdot:] = zero[rdot - 1: a1]; + zero[rdot] = _tl; + } +#debug(sys->sprint("end of append - dot %d\n", dot)); + return nline; +} + +add(i: int) +{ + if(i && (given || dol > 0)) { + addr1--; + addr2--; + } + squeeze(0); + newline(); + append(APPEND_GETTTY, addr2); +} + +bformat, bnum: int; + +browse() +{ + forward, n: int; + + forward = 1; + peekc = getchr(); + if(peekc != '\n'){ + if(peekc == '-' || peekc == '+') { + if(peekc == '-') + forward = 0; + getchr(); + } + n = getnum(); + if(n > 0) + bpagesize = n; + } + newline(); + if(pflag) { + bformat = listf; + bnum = listn; + } else { + listf = bformat; + listn = bnum; + } + if(forward) { + addr1 = addr2; + addr2 += bpagesize; + if(addr2 > dol) + addr2 = dol; + } else { + addr1 = addr2-bpagesize; + if(addr1 <= 0) + addr1 = 0+1; + } + printcom(); +} + +callunix() +{ + buf: string; + c: int; + + if (sh == nil) + sh = load Sh Sh->PATH; + if (sh == nil) { + putst("can't load shell"); + return; + } + setnoaddr(); + while((c=getchr()) != EOF && c != '\n') + buf[len buf] = c; + sh->system(drawctxt, buf); + if(vflag) + putst("!"); +} + +quit() +{ + if(vflag && fchange && dol!=0) { + fchange = 0; + error(Q); + } + sys->remove(tfname); + exit; +} + +onquit(nil: int) +{ + quit(); +} + +rdelete(ad1, ad2: int) +{ + a1, a2, a3: int; + + a1 = ad1; + a2 = ad2+1; + a3 = dol; + dol -= a2 - a1; + do { + zero[a1++] = zero[a2++]; + } while (a2 <= a3); + a1 = ad1; + if(a1 > dol) + a1 = dol; + dot = a1; + fchange = 1; +} + +gdelete() +{ + a1, a2, a3: int; + + a3 = dol; + for(a1=0; (zero[a1]&8r01)==0; a1++) + if(a1>=a3) + return; + for(a2=a1+1; a2<=a3;) { + if(zero[a2] & 8r01) { + a2++; + dot = a1; + } else + zero[a1++] = zero[a2++]; + } + dol = a1-1; + if(dot > dol) + dot = dol; + fchange = 1; +} + +getline(_tl: int): int +{ + lp, bp: int; + nl: int; + block: array of int; +#debug(sys->sprint("getline %d\n", _tl)); + lp = 0; + (block, bp) = getblock(_tl, Sys->OREAD); + nl = len block - bp; + _tl &= ~(RunesPerBlock - 1); + while(linebuf[lp++] = block[bp++]) { + nl--; + if(nl == 0) { + (block, bp) = getblock(_tl += RunesPerBlock, Sys->OREAD); + nl = len block; + } + } + return 0; +} + +putline(): int +{ + lp, bp: int; + nl, _tl: int; + block: array of int; + fchange = 1; + lp = 0; + _tl = tline; + (block, bp) = getblock(_tl, Sys->OWRITE); + nl = len block - bp; + _tl &= ~(RunesPerBlock-1); # _tl is now at the beginning of the block + while(block[bp] = linebuf[lp++]) { + if(block[bp++] == '\n') { + block[bp-1] = 0; + linebp = lp; + break; + } + nl--; + if(nl == 0) { + _tl += RunesPerBlock; + (block, bp) = getblock(_tl, Sys->OWRITE); + nl = len block; + } + } + nl = tline; + tline += ((lp) + 8r03) & 8r077776; + return nl; +} + +tbuf := array [BLKSIZE] of byte; + +getrune(buf: array of byte): int +{ + return int buf[0] + (int buf[1] << 8); +} + +putrune(buf: array of byte, v: int) +{ + buf[0] = byte (v); + buf[1] = byte (v >> 8); +} + +blkio(b: int, buf: array of int, writefunc: int) +{ + sys->seek(tfile, big b * big BLKSIZE, Sys->SEEKSTART); + if (writefunc) { + # flatten buf into tbuf + for (x := 0; x < RunesPerBlock; x++) + putrune(tbuf[x * BytesPerRune:], buf[x]); + if (sys->write(tfile, tbuf, BLKSIZE) != len tbuf) { + error(T); + } + } + else { + if (sys->read(tfile, tbuf, len tbuf) != len tbuf) { + error(T); + } + for (x := 0; x < RunesPerBlock; x++) + buf[x] = getrune(tbuf[x * BytesPerRune:]); + } +} + +ibuff := array [RunesPerBlock] of int; +obuff := array [RunesPerBlock] of int; + +getblock(atl, iof: int): (array of int, int) +{ + bno, off: int; + + bno = atl / RunesPerBlock; + off = (atl * BytesPerRune) & (BLKSIZE-1) & ~8r03; + if(bno >= NBLK) { + lastc = '\n'; + error(T); + } + off /= BytesPerRune; + if(bno == iblock) { + ichanged |= iof; +#debug(sys->sprint("getblock(%d, %d): returns ibuff offset %d\n", atl, iof, off)); + return (ibuff, off); + } + if(bno == oblock) { +#debug(sys->sprint("getblock(%d, %d): returns obuff offset %d\n", atl, iof, off)); + return (obuff, off); + } + if(iof == Sys->OREAD) { + if(ichanged) + blkio(iblock, ibuff, 1); + ichanged = 0; + iblock = bno; + blkio(bno, ibuff, 0); +#debug(sys->sprint("getblock(%d, %d): returns ibuff offset %d\n", atl, iof, off)); + return (ibuff, off); + } + if(oblock >= 0) + blkio(oblock, obuff, 1); + oblock = bno; +#debug(sys->sprint("getblock(%d, %d): returns offset %d\n", atl, iof, off)); + return (obuff, off); +} + +_init() +{ + markp: int; + + tfile = nil; + tline = RunesPerBlock; + for(markp = 0; markp < len names; markp++) + names[markp] = 0; + subnewa = 0; + anymarks = 0; + iblock = -1; + oblock = -1; + ichanged = 0; + if((tfile = sys->create(tfname, Sys->ORDWR, 8r0600)) == nil){ + error1(T); + exit; + } + dot = dol = 0; +} + +global(k: int) +{ + globuf: string; + c, a1: int; + + if(!globp.isnil) + error(Q); + setwide(); + squeeze(dol > 0); + c = getchr(); + if(c == '\n') + error(Q); + compile(c); + globuf = nil; + while((c=getchr()) != '\n') { + if(c == EOF) + error(Q); + if(c == '\\') { + c = getchr(); + if(c != '\n') + globuf[len globuf] = '\\'; + } + globuf[len globuf] = c; + } + if(globuf == nil) + globuf = "p"; + globuf[len globuf] = '\n'; + for(a1=0; a1<=dol; a1++) { + zero[a1] &= ~8r01; + if(a1 >= addr1 && a1 <= addr2 && match(a1) == k) + zero[a1] |= 8r01; + } + + # + # Special case: g/.../d (avoid n^2 algorithm) + + if(globuf[0] == 'd' && globuf[1] == '\n' && globuf[2] == 0) { + gdelete(); + return; + } + for(a1=0; a1<=dol; a1++) { + if(zero[a1] & 8r01) { + zero[a1] &= ~8r01; + dot = a1; + globp = (globuf, 0); + commands(); + a1 = 0; + } + } +} + +join() +{ + gp, lp: int; + a1: int; + + nonzero(); + gp = 0; + for(a1=addr1; a1<=addr2; a1++) { + lp = getline(zero[a1]); + while(genbuf[gp] = linebuf[lp++]) + if(gp++ >= LBSIZE-2) + error(Q); + } + lp = 0; + gp = 0; + while(linebuf[lp++] = genbuf[gp++]) + ; + zero[addr1] = putline(); + if(addr1 < addr2) + rdelete(addr1+1, addr2); + dot = addr1; +} + +substitute(inglob: int) +{ + mp, a1, nl, gsubf, n: int; + + n = getnum(); # OK even if n==0 + gsubf = compsub(); + for(a1 = addr1; a1 <= addr2; a1++) { + if(match(a1)){ + m := n; + + do { + span := loc2-loc1; + + if(--m <= 0) { + dosub(); + if(!gsubf) + break; + if(span == 0) { # null RE match + if(zero[loc2] == 0) + break; + loc2++; + } + } + } while(match(-1)); + if(m <= 0) { + inglob |= 8r01; + subnewa = putline(); + zero[a1] &= ~8r01; + if(anymarks) { + for(mp=0; mp<len names; mp++) + if(names[mp] == zero[a1]) + names[mp] = subnewa; + } + subolda = zero[a1]; + zero[a1] = subnewa; +#debug(sys->sprint("append-getsub linebp = %d\n", linebp)); + nl = append(APPEND_GETSUB, a1); + addr2 += nl; + } + } + } + if(inglob == 0) + error(Q); +} + +compsub(): int +{ + seof, c: int; + p: int; + + seof = getchr(); + if(seof == '\n' || seof == ' ') + error(Q); + compile(seof); + p = 0; + for(;;) { + c = getchr(); + if(c == '\\') { + c = getchr(); + rhsbuf[p++] = ESCFLG; + if(p >= LBSIZE / 2) + error(Q); + } else + if(c == '\n' && (globp.isnil || globp.s == nil)) { + peekc = c; + pflag++; + break; + } else + if(c == seof) + break; + rhsbuf[p++] = c; + if(p >= LBSIZE / 2) + error(Q); + } + rhsbuf[p] = 0; + peekc = getchr(); + if(peekc == 'g') { + peekc = 0; + newline(); + return 1; + } + newline(); + return 0; +} + +getsub(): int +{ + p1, p2: int; + + p1 = 0; + if((p2 = linebp) == -1) + return EOF; + while(linebuf[p1++] = linebuf[p2++]) + ; + linebp = -1; + return 0; +} + +dosub() +{ + lp, sp, rp: int; + c, n: int; + +# lp = linebuf; +# sp = genbuf; +# rp = rhsbuf; + lp = 0; + sp = 0; + rp = 0; + while(lp < loc1) + genbuf[sp++] = linebuf[lp++]; + while(c = rhsbuf[rp++]) { + if(c == '&'){ + sp = place(sp, loc1, loc2); + continue; + } + if(c == ESCFLG && (c = rhsbuf[rp++]) >= '1' && c < MAXSUB+'0') { + n = c-'0'; + if(subexp != nil && subexp[n].rsp >= 0 && subexp[n].rep >= 0) { + sp = place(sp, subexp[n].rsp, subexp[n].rep); + continue; + } + error(Q); + } + genbuf[sp++] = c; + if(sp >= LBSIZE) + error(Q); + } + lp = loc2; + loc2 = sp; + while(genbuf[sp++] = linebuf[lp++]) + if(sp >= LBSIZE) + error(Q); + linebuf[0:] = genbuf[0: sp]; +} + +place(sp: int, l1: int, l2: int): int +{ + + while(l1 < l2) { + genbuf[sp++] = linebuf[l1++]; + if(sp >= LBSIZE) + error(Q); + } + return sp; +} + +move(cflag: int) +{ + _adt, ad1, ad2: int; + + nonzero(); + if((_adt = address()) < 0) # address() guarantees addr is in range + error(Q); + newline(); + if(cflag) { + ad1 = dol; + append(APPEND_GETCOPY, ad1++); + ad2 = dol; + } else { + ad2 = addr2; + for(ad1 = addr1; ad1 <= ad2;) + zero[ad1++] &= ~8r01; + ad1 = addr1; + } + ad2++; + if(_adt<ad1) { + dot = _adt + (ad2-ad1); + if((++_adt)==ad1) + return; + reverse(_adt, ad1); + reverse(ad1, ad2); + reverse(_adt, ad2); + } else + if(_adt >= ad2) { + dot = _adt++; + reverse(ad1, ad2); + reverse(ad2, _adt); + reverse(ad1, _adt); + } else + error(Q); + fchange = 1; +} + +reverse(a1, a2: int) +{ + t: int; + + for(;;) { + t = zero[--a2]; + if(a2 <= a1) + return; + zero[a2] = zero[a1]; + zero[a1++] = t; + } +} + +getcopy(): int +{ + if(addr1 > addr2) + return EOF; + getline(zero[addr1++]); + return 0; +} + +compile(eof: int) +{ + c: int; + + if((c = getchr()) == '\n') { + peekc = c; + c = eof; + } + if(c == eof) { + if(pattern == nil) + error(Q); + return; + } + pattern = nil; + program := ""; + do { + + if(c == '\\') { + program[len program] = '\\'; + if((c = getchr()) == '\n') { + error(Q); + return; + } + } + program[len program] = c; + } while((c = getchr()) != eof && c != '\n'); + if(c == '\n') + peekc = c; + diag: string; +#debug("program " + program + "\n"); + (pattern, diag) = regex->compile(program, 1); +#if (diag != nil) +# debug("diag " + diag + "\n"); + if (diag != nil) + pattern = nil; +} + +mkstring(a: array of int): string +{ + s: string; + for (x := 0; x < len a; x++) { + if (a[x] == 0) + break; + s[x] = a[x]; + } + return s; +} + +match(addr: int): int +{ + rsp: int; + if(pattern == nil) + return 0; + if(addr >= 0){ + if(addr == 0) + return 0; + rsp = getline(zero[addr]); + } else + rsp = loc2; + s := mkstring(linebuf); + subexp = regex->executese(pattern, s, (rsp, len s), rsp == 0, 1); + if(subexp != nil) { + (loc1, loc2) = subexp[0]; + return 1; + } + loc1 = loc2 = -1; + return 0; +} + +putd() +{ + r: int; + + r = count%10; + count /= 10; + if(count) + putd(); + putchr(r + '0'); +} + +putst(s: string) +{ + col = 0; + for(x := 0; x < len s; x++) + putchr(s[x]); + putchr('\n'); +} + +putshst(sp: int) +{ + col = 0; + while(linebuf[sp]) { + putchr(linebuf[sp++]); + } + putchr('\n'); +} + +putchr(ac: int) +{ + lp: int; + c: int; + rune: int; + lp = linp; + c = ac; + if(listf) { + if(c == '\n') { + if(linp != 0 && line[linp - 1] == byte ' ') { + line[lp++] = byte '\\'; + line[lp++] = byte 'n'; + } + } else { + if(col > (72-6-2)) { + col = 8; + line[lp++] = byte '\\'; + line[lp++] = byte '\n'; + line[lp++] = byte '\t'; + } + col++; + if(c=='\b' || c=='\t' || c=='\\') { + line[lp++] = byte '\\'; + if(c == '\b') + c = 'b'; + else + if(c == '\t') + c = 't'; + col++; + } else + if(c<' ' || c>=8r0177) { + line[lp++] = byte '\\'; + line[lp++] = byte 'x'; + line[lp++] = byte hex[c>>12]; + line[lp++] = byte hex[c>>8&16rF]; + line[lp++] = byte hex[c>>4&16rF]; + c = hex[c&16rF]; + col += 5; + } + } + } + + rune = c; + lp += sys->char2byte(rune, line, lp); + + if(c == '\n' || lp >= len line - 5) { + linp = 0; + if (oflag) + sys->write(sys->fildes(2), line, lp); + else + sys->write(sys->fildes(1), line, lp); + return; + } + linp = lp; +} + +stringfromint(i: int): string +{ + s: string; + s[0] = i; + return s; +} + +mktemp(as: string): string +{ + pid: int; + s: string; + + s = nil; + pid = sys->pctl(0, nil); + for (x := len as - 1; x >= 0; x--) + if (as[x] == 'X') { + s = stringfromint('0' + pid % 10) + s; + pid /= 10; + } + else + s = stringfromint(as[x]) + s; + s[len s] = 'a'; + for (;;) { + (rv, nil) := sys->stat(s); + if (rv < 0) + break; + if (s[len s - 1] == 'z') + return "/"; + s[len s - 1]++; + } + return s; +} diff --git a/appl/cmd/emuinit.b b/appl/cmd/emuinit.b new file mode 100644 index 00000000..56b11521 --- /dev/null +++ b/appl/cmd/emuinit.b @@ -0,0 +1,110 @@ +implement Emuinit; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "arg.m"; + arg: Arg; + +Emuinit: module +{ + init: fn(); +}; + +init() +{ + sys = load Sys Sys->PATH; + sys->bind("#e", "/env", sys->MREPL|sys->MCREATE); # if #e not configured, that's fine + args := getenv("emuargs"); + arg = load Arg Arg->PATH; + if (arg == nil) + sys->fprint(sys->fildes(2), "emuinit: cannot load %s: %r\n", Arg->PATH); + else{ + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'g' or 'c' or 'C' or 'm' or 'p' or 'f' or 'r' or 'd' => + arg->arg(); + } + args = arg->argv(); + } + mod: Command; + (mod, args) = loadmod(args); + mod->init(nil, args); +} + +loadmod(args: list of string): (Command, list of string) +{ + path := Command->PATH; + if(args != nil) + path = hd args; + else + args = "-l" :: nil; # add startup option + + # try loading the module directly. + mod: Command; + if (path != nil && path[0] == '/') + mod = load Command path; + else { + mod = load Command "/dis/"+path; + if (mod == nil) + mod = load Command "/"+path; + } + if(mod != nil) + return (mod, args); + + # if we can't load the module directly, try getting the shell to run it. + err := sys->sprint("%r"); + mod = load Command Command->PATH; + if(mod == nil){ + sys->fprint(sys->fildes(2), "emuinit: unable to load %s: %s\n", path, err); + raise "fail:error"; + } + return (mod, "sh" :: "-c" :: "$*" :: args); +} + +getenv(v: string): list of string +{ + fd := sys->open("#e/"+v, Sys->OREAD); + if (fd == nil) + return nil; + (ok, d) := sys->fstat(fd); + if(ok == -1) + return nil; + buf := array[int d.length] of byte; + n := sys->read(fd, buf, len buf); + if (n <= 0) + return nil; + return unquoted(string buf[0:n]); +} + +unquoted(s: string): list of string +{ + args: list of string; + word: string; + inquote := 0; + for(j := len s; j > 0;){ + c := s[j-1]; + if(c == ' ' || c == '\t' || c == '\n'){ + j--; + continue; + } + for(i := j-1; i >= 0 && ((c = s[i]) != ' ' && c != '\t' && c != '\n' || inquote); i--){ # collect word + if(c == '\''){ + word = s[i+1:j] + word; + j = i; + if(!inquote || i == 0 || s[i-1] != '\'') + inquote = !inquote; + else + i--; + } + } + args = (s[i+1:j]+word) :: args; + word = nil; + j = i; + } + # if quotes were unbalanced, balance them and try again. + if(inquote) + return unquoted(s + "'"); + return args; +} diff --git a/appl/cmd/env.b b/appl/cmd/env.b new file mode 100644 index 00000000..e6fd8889 --- /dev/null +++ b/appl/cmd/env.b @@ -0,0 +1,53 @@ +implement Envcmd; + +# +# Copyright © 2000 Vita Nuova Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "env.m"; + +include "readdir.m"; + +Envcmd: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stdout := sys->fildes(1); + if (tl argv != nil) { + sys->fprint(stderr(), "Usage: env\n"); + raise "fail:usage"; + } + env := load Env Env->PATH; + if(env == nil) + error(sys->sprint("can't load %s: %r", Env->PATH)); + readdir := load Readdir Readdir->PATH; + if(readdir == nil) + error(sys->sprint("can't load %s: %r", Readdir->PATH)); + (a, n) := readdir->init("/env", + Readdir->NONE | Readdir->COMPACT | Readdir->DESCENDING); + for(i := 0; i < len a; i++){ + s := a[i].name+"="+env->getenv(a[i].name)+"\n"; + b := array of byte s; + sys->write(stdout, b, len b); + } +} + +error(s: string) +{ + sys->fprint(stderr(), "env: %s\n", s); + raise "fail:error"; +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} diff --git a/appl/cmd/export.b b/appl/cmd/export.b new file mode 100644 index 00000000..f6688fd3 --- /dev/null +++ b/appl/cmd/export.b @@ -0,0 +1,57 @@ +# +# export current name space on a connection +# + +implement Export; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +Export: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(stderr(), "Usage: export [-a] dir [connection]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + # usage: export dir [connection] + sys = load Sys Sys->PATH; + if(argv != nil) + argv = tl argv; + flag := Sys->EXPWAIT; + for(; argv != nil && len hd argv && (hd argv)[0] == '-'; argv = tl argv) + for(i := 1; i < len hd argv; i++) + case (hd argv)[i] { + 'a' => + flag = Sys->EXPASYNC; + * => + usage(); + } + n := len argv; + if (n < 1 || n > 2) + usage(); + fd: ref Sys->FD; + if (n == 2) { + if ((fd = sys->open(hd tl argv, Sys->ORDWR)) == nil) { + sys->fprint(stderr(), "export: can't open %s: %r\n", hd tl argv); + raise "fail:open"; + } + } else + fd = sys->fildes(0); + if (sys->export(fd, hd argv, flag) < 0) { + sys->fprint(stderr(), "export: can't export: %r\n"); + raise "fail:export"; + } +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} diff --git a/appl/cmd/fc.b b/appl/cmd/fc.b new file mode 100644 index 00000000..50393b14 --- /dev/null +++ b/appl/cmd/fc.b @@ -0,0 +1,612 @@ +implement Fc; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "math.m"; + math: Math; +include "string.m"; + str: String; +include "regex.m"; + regex: Regex; + +Fc: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + + +UNARY, BINARY, SPECIAL: con iota; + +oSWAP, oDUP, oREP, oSUM, oPRNUM, oMULT, +oPLUS, oMINUS, oDIV, oDIVIDE, oMOD, oSHIFTL, oSHIFTR, +oAND, oOR, oXOR, oNOT, oUMINUS, oFACTORIAL, +oPOW, oHYPOT, oATAN2, oJN, oYN, oSCALBN, oCOPYSIGN, +oFDIM, oFMIN, oFMAX, oNEXTAFTER, oREMAINDER, oFMOD, +oPOW10, oSQRT, oEXP, oEXPM1, oLOG, oLOG10, oLOG1P, +oCOS, oCOSH, oSIN, oSINH, oTAN, oTANH, oACOS, oASIN, oACOSH, +oASINH, oATAN, oATANH, oERF, oERFC, +oJ0, oJ1, oY0, oY1, oILOGB, oFABS, oCEIL, +oFLOOR, oFINITE, oISNAN, oRINT, oLGAMMA, oMODF, +oDEG, oRAD: con iota; +Op: adt { + name: string; + kind: int; + op: int; +}; + +ops := array[] of { +Op +("swap", SPECIAL, oSWAP), +("dup", SPECIAL, oDUP), +("rep", SPECIAL, oREP), +("sum", SPECIAL, oSUM), +("p", SPECIAL, oPRNUM), +("x", BINARY, oMULT), +("×", BINARY, oMULT), +("pow", BINARY, oPOW), +("xx", BINARY, oPOW), +("+", BINARY, oPLUS), +("-", BINARY, oMINUS), +("/", BINARY, oDIVIDE), +("div", BINARY, oDIV), +("%", BINARY, oMOD), +("shl", BINARY, oSHIFTL), +("shr", BINARY, oSHIFTR), +("and", BINARY, oAND), +("or", BINARY, oOR), +("⋀", BINARY, oAND), +("⋁", BINARY, oOR), +("xor", BINARY, oXOR), +("not", UNARY, oNOT), +("_", UNARY, oUMINUS), +("factorial", UNARY, oFACTORIAL), +("!", UNARY, oFACTORIAL), +("pow", BINARY, oPOW), +("hypot", BINARY, oHYPOT), +("atan2", BINARY, oATAN2), +("jn", BINARY, oJN), +("yn", BINARY, oYN), +("scalbn", BINARY, oSCALBN), +("copysign", BINARY, oCOPYSIGN), +("fdim", BINARY, oFDIM), +("fmin", BINARY, oFMIN), +("fmax", BINARY, oFMAX), +("nextafter", BINARY, oNEXTAFTER), +("remainder", BINARY, oREMAINDER), +("fmod", BINARY, oFMOD), +("pow10", UNARY, oPOW10), +("sqrt", UNARY, oSQRT), +("exp", UNARY, oEXP), +("expm1", UNARY, oEXPM1), +("log", UNARY, oLOG), +("log10", UNARY, oLOG10), +("log1p", UNARY, oLOG1P), +("cos", UNARY, oCOS), +("cosh", UNARY, oCOSH), +("sin", UNARY, oSIN), +("sinh", UNARY, oSINH), +("tan", UNARY, oTAN), +("tanh", UNARY, oTANH), +("acos", UNARY, oACOS), +("asin", UNARY, oASIN), +("acosh", UNARY, oACOSH), +("asinh", UNARY, oASINH), +("atan", UNARY, oATAN), +("atanh", UNARY, oATANH), +("erf", UNARY, oERF), +("erfc", UNARY, oERFC), +("j0", UNARY, oJ0), +("j1", UNARY, oJ1), +("y0", UNARY, oY0), +("y1", UNARY, oY1), +("ilogb", UNARY, oILOGB), +("fabs", UNARY, oFABS), +("ceil", UNARY, oCEIL), +("floor", UNARY, oFLOOR), +("finite", UNARY, oFINITE), +("isnan", UNARY, oISNAN), +("rint", UNARY, oRINT), +("rad", UNARY, oRAD), +("deg", UNARY, oDEG), +("lgamma", SPECIAL, oLGAMMA), +("modf", SPECIAL, oMODF), +}; + +nHEX, nBINARY, nOCTAL, nRADIX1, nRADIX2, nREAL, nCHAR: con iota; +pats0 := array[] of { +nHEX => "-?0[xX][0-9a-fA-F]+", +nBINARY => "-?0[bB][01]+", +nOCTAL => "-?0[0-7]+", +nRADIX1 => "-?[0-9][rR][0-8]+", +nRADIX2 => "-?[0-3][0-9][rR][0-9a-zA-Z]+", +nREAL => "-?(([0-9]+(\\.[0-9]+)?)|([0-9]*(\\.[0-9]+)))([eE]-?[0-9]+)?", +nCHAR => "@.", +}; +RADIX, ANNOTATE, CHAR: con 1 << (iota + 10); + +outbase := 10; +pats: array of Regex->Re; +stack: list of real; +last_op: Op; +stderr: ref Sys->FD; + +usage() +{ + sys->fprint(stderr, + "usage: fc [-xdbB] [-r radix] <postfix expression>\n" + + "option specifies output format:\n" + + "\t-d decimal (default)\n" + + "\t-x hex\n" + + "\t-o octal\n" + + "\t-b binary\n" + + "\t-B annotated binary\n" + + "\t-c character\n" + + "\t-r <radix> specified base in Limbo 99r9999 format\n" + + "operands are decimal(default), hex(0x), octal(0), binary(0b), radix(99r)\n"); + sys->fprint(stderr, "operators are:\n"); + for (i := 0; i < len ops; i++) + sys->fprint(stderr, "%s ", ops[i].name); + sys->fprint(stderr, "\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + math = load Math Math->PATH; + regex = load Regex Regex->PATH; + if (regex == nil) { + sys->fprint(stderr, "fc: cannot load %s: %r\n", Regex->PATH); + raise "fail:error"; + } + + initpats(); + + if (argv == nil || tl argv == nil) + return; + argv = tl argv; + a := hd argv; + if (len a > 1 && a[0] == '-' && number(a).t0 == 0) { + case a[1] { + 'd' => + outbase = 10; + 'x' => + outbase = 16; + 'o' => + outbase = 8; + 'b' => + outbase = 2; + 'c' => + outbase = CHAR; + 'r' => + r := 0; + if (len a > 2) + r = int a[2:]; + else if (tl argv == nil) + usage(); + else { + argv = tl argv; + r = int hd argv; + } + if (r < 2 || r > 36) + usage(); + outbase = r | RADIX; + 'B' => + outbase = 2 | ANNOTATE; + * => + sys->fprint(stderr, "fc: unknown option -%c\n", a[1]); + usage(); + } + argv = tl argv; + } + + math->FPcontrol(0, Math->INVAL|Math->ZDIV|Math->OVFL|Math->UNFL|Math->INEX); + + for (; argv != nil; argv = tl argv) { + (ok, x) := number(hd argv); + if (ok) + stack = x :: stack; + else { + op := find(hd argv); + exec(op); + last_op = op; + } + } + + sp: list of real; + for (; stack != nil; stack = tl stack) + sp = hd stack :: sp; + + # print stack bottom first + for (; sp != nil; sp = tl sp) + printnum(hd sp); +} + +printnum(n: real) +{ + case outbase { + CHAR => + sys->print("@%c\n", int n); + 2 => + sys->print("%s\n", binary(big n)); + 2 | ANNOTATE => + sys->print("%s\n", annotatebinary(big n)); + 8 => + sys->print("%#bo\n", big n); + 10 => + sys->print("%g\n", n); + 16 => + sys->print("%#bx\n", big n); + * => + if ((outbase & RADIX) == 0) + error("unknown output base " + string outbase); + sys->print("%s\n", big2string(big n, outbase & ~RADIX)); + } +} + +# convert to binary string, keeping multiples of 8 digits. +binary(n: big): string +{ + s := "0b"; + for (j := 7; j > 0; j--) + if ((n & (big 16rff << (j * 8))) != big 0) + break; + for (i := 63; i >= 0; i--) + if (i / 8 <= j) + s[len s] = (int (n >> i) & 1) + '0'; + return s; +} + +annotatebinary(n: big): string +{ + s := binary(n); + a := s + "\n "; + ndig := len s - 2; + for (i := ndig - 1; i >= 0; i--) + a[len a] = (i % 10) + '0'; + if (ndig < 10) + return a; + a += "\n "; + for (i = ndig - 1; i >= 10; i--) { + if (i % 10 == 0) + a[len a] = (i / 10) + '0'; + else + a[len a] = ' '; + } + return a; +} + +find(name: string): Op +{ + # XXX could do binary search here if we weren't a lousy performer anyway + for (i := 0; i < len ops; i++) + if (name == ops[i].name) + break; + if (i == len ops) + error("invalid operator '" + name + "'"); + return ops[i]; +} + +exec(op: Op) +{ + case op.kind { + UNARY => + unaryop(op.name, op.op); + BINARY => + binaryop(op.name, op.op); + SPECIAL => + specialop(op.name, op.op); + } +} + +unaryop(name: string, op: int) +{ + assure(1, name); + v := hd stack; + case op { + oNOT => + v = real !(int v); + oUMINUS => + v = -v; + oFACTORIAL => + n := int v; + v = 1.0; + while (n > 0) + v *= real n--; + oPOW10 => + v = math->pow10(int v); + oSQRT => + v = math->sqrt(v); + oEXP => + v = math->exp(v); + oEXPM1 => + v = math->expm1(v); + oLOG => + v = math->log(v); + oLOG10 => + v = math->log10(v); + oLOG1P => + v = math->log1p(v); + oCOS => + v = math->cos(v); + oCOSH => + v = math->cosh(v); + oSIN => + v = math->sin(v); + oSINH => + v = math->sinh(v); + oTAN => + v = math->tan(v); + oTANH => + v = math->tanh(v); + oACOS => + v = math->acos(v); + oASIN => + v = math->asin(v); + oACOSH => + v = math->acosh(v); + oASINH => + v = math->asinh(v); + oATAN => + v = math->atan(v); + oATANH => + v = math->atanh(v); + oERF => + v = math->erf(v); + oERFC => + v = math->erfc(v); + oJ0 => + v = math->j0(v); + oJ1 => + v = math->j1(v); + oY0 => + v = math->y0(v); + oY1 => + v = math->y1(v); + oILOGB => + v = real math->ilogb(v); + oFABS => + v = math->fabs(v); + oCEIL => + v = math->ceil(v); + oFLOOR => + v = math->floor(v); + oFINITE => + v = real math->finite(v); + oISNAN => + v = real math->isnan(v); + oRINT => + v = math->rint(v); + oRAD => + v = (v / 360.0) * 2.0 * Math->Pi; + oDEG => + v = v / (2.0 * Math->Pi) * 360.0; + * => + error("unknown unary operator '" + name + "'"); + } + stack = v :: tl stack; +} + +binaryop(name: string, op: int) +{ + assure(2, name); + v1 := hd stack; + v0 := hd tl stack; + case op { + oMULT => + v0 = v0 * v1; + oPLUS => + v0 = v0 + v1; + oMINUS => + v0 = v0 - v1; + oDIVIDE => + v0 = v0 / v1; + oDIV => + v0 = real (big v0 / big v1); + oMOD => + v0 = real (big v0 % big v1); + oSHIFTL => + v0 = real (big v0 << int v1); + oSHIFTR => + v0 = real (big v0 >> int v1); + oAND => + v0 = real (big v0 & big v1); + oOR => + v0 = real (big v0 | big v1); + oXOR => + v0 = real (big v0 ^ big v1); + oPOW => + v0 = math->pow(v0, v1); + oHYPOT => + v0 = math->hypot(v0, v1); + oATAN2 => + v0 = math->atan2(v0, v1); + oJN => + v0 = math->jn(int v0, v1); + oYN => + v0 = math->yn(int v0, v1); + oSCALBN => + v0 = math->scalbn(v0, int v1); + oCOPYSIGN => + v0 = math->copysign(v0, v1); + oFDIM => + v0 = math->fdim(v0, v1); + oFMIN => + v0 = math->fmin(v0, v1); + oFMAX => + v0 = math->fmax(v0, v1); + oNEXTAFTER => + v0 = math->nextafter(v0, v1); + oREMAINDER => + v0 = math->remainder(v0, v1); + oFMOD => + v0 = math->fmod(v0, v1); + * => + error("unknown binary operator '" + name + "'"); + } + stack = v0 :: tl tl stack; +} + +specialop(name: string, op: int) +{ + case op { + oSWAP => + assure(2, name); + stack = hd tl stack :: hd stack :: tl tl stack; + oDUP => + assure(1, name); + stack = hd stack :: stack; + oREP => + if (last_op.kind != BINARY) + error("invalid operator '" + last_op.name + "' for rep"); + while (stack != nil && tl stack != nil) + exec(last_op); + oSUM => + for (sum := 0.0; stack != nil; stack = tl stack) + sum += hd stack; + stack = sum :: nil; + oPRNUM => + assure(1, name); + printnum(hd stack); + stack = tl stack; + oLGAMMA => + assure(1, name); + (s, lg) := math->lgamma(hd stack); + stack = lg :: real s :: tl stack; + oMODF => + assure(1, name); + (i, r) := math->modf(hd stack); + stack = r :: real i :: tl stack; + * => + error("unknown operator '" + name + "'"); + } +} + +initpats() +{ + pats = array[len pats0] of Regex->Re; + for (i := 0; i < len pats0; i++) { + (re, e) := regex->compile("^" + pats0[i] + "$", 0); + if (re == nil) { + sys->fprint(stderr, "fc: bad number pattern '^%s$': %s\n", pats0[i], e); + raise "fail:error"; + } + pats[i] = re; + } +} + +number(s: string): (int, real) +{ + case s { + "pi" or + "π" => + return (1, Math->Pi); + "e" => + return (1, 2.71828182845904509); + "nan" or + "NaN" => + return (1, Math->NaN); + "-nan" or + "-NaN" => + return (1, -Math->NaN); + "infinity" or + "Infinity" or + "∞" => + return (1, Math->Infinity); + "-infinity" or + "-Infinity" or + "-∞" => + return (1, -Math->Infinity); + "eps" or + "macheps" => + return (1, Math->MachEps); + } + for (i := 0; i < len pats; i++) { + if (regex->execute(pats[i], s) != nil) + break; + } + case i { + nHEX => + return base(s, 2, 16); + nBINARY => + return base(s, 2, 2); + nOCTAL => + return base(s, 1, 8); + nRADIX1 => + return base(s, 2, int s); + nRADIX2 => + return base(s, 3, int s); + nREAL => + return (1, real s); + nCHAR => + return (1, real s[1]); + } + return (0, Math->NaN); +} + +base(s: string, i: int, radix: int): (int, real) +{ + neg := s[0] == '-'; + if (neg) + i++; + n := big 0; + if (radix == 10) + n = big s[i:]; + else if (radix == 0 || radix > 36) + return (0, Math->NaN); + else { + for (; i < len s; i++) { + c := s[i]; + if ('0' <= c && c <= '9') + n = (n * big radix) + big(c - '0'); + else if ('a' <= c && c < 'a' + radix - 10) + n = (n * big radix) + big(c - 'a' + 10); + else if ('A' <= c && c < 'A' + radix - 10) + n = (n * big radix) + big(c - 'A' + 10); + else + return (0, Math->NaN); + } + } + if (neg) + n = -n; + return (1, real n); +} + +# stolen from /appl/cmd/sh/expr.b +big2string(n: big, radix: int): string +{ + if (neg := n < big 0) { + n = -n; + } + s := ""; + do { + c: int; + d := int (n % big radix); + if (d < 10) + c = '0' + d; + else + c = 'a' + d - 10; + s[len s] = c; + n /= big radix; + } while (n > big 0); + t := s; + for (i := len s - 1; i >= 0; i--) + t[len s - 1 - i] = s[i]; + if (radix != 10) + t = string radix + "r" + t; + if (neg) + return "-" + t; + return t; +} + +error(e: string) +{ + sys->fprint(stderr, "fc: %s\n", e); + raise "fail:error"; +} + +assure(n: int, opname: string) +{ + if (len stack < n) + error("stack too small for op '" + opname + "'"); +} diff --git a/appl/cmd/fcp.b b/appl/cmd/fcp.b new file mode 100644 index 00000000..0bc520e4 --- /dev/null +++ b/appl/cmd/fcp.b @@ -0,0 +1,312 @@ +implement Fcp; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; +include "readdir.m"; + readdir: Readdir; + +Fcp: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; +errors := 0; + +fdc: chan of (ref Sys->FD, ref Sys->FD); + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + arg := load Arg Arg->PATH; + if (arg == nil) { + sys->fprint(stderr, "fcp: cannot load %s: %r\n", Arg->PATH); + raise "fail:bad module"; + } + recursive := 0; + nreaders := nwriters := 8; + arg->init(argv); + arg->setusage("\tfcp [-r] [-R nproc] [-W nproc] src target\n\tfcp [-r] [-R nproc] [-W nproc] src ... directory"); + while ((opt := arg->opt()) != 0) { + case opt { + 'R' => + nreaders = int arg->earg(); + 'W' => + nwriters = int arg->earg(); + 'r' => + recursive = 1; + * => + arg->usage(); + } + } + if(nreaders < 1 || nwriters < 1) + arg->usage(); + if(nreaders > 1 || nwriters > 1){ + fdc = chan of (ref Sys->FD, ref Sys->FD); + spawn mstream(fdc, Sys->ATOMICIO, nreaders, nwriters); + } + argv = arg->argv(); + argc := len argv; + if (argc < 2) + arg->usage(); + arg = nil; + + dst: string; + for (t := argv; t != nil; t = tl t) + dst = hd t; + + (ok, dir) := sys->stat(dst); + todir := (ok != -1 && (dir.mode & Sys->DMDIR)); + if (argc > 2 && !todir) { + sys->fprint(stderr, "fcp: %s not a directory\n", dst); + raise "fail:error"; + } + if (recursive) + cpdir(argv, dst); + else { + for (; tl argv != nil; argv = tl argv) { + if (todir) + cp(hd argv, dst, basename(hd argv)); + else + cp(hd argv, dst, nil); + } + } + if(fdc != nil) + fdc <-= (nil, nil); + if (errors) + raise "fail:error"; +} + +basename(s: string): string +{ + for ((nil, ls) := sys->tokenize(s, "/"); ls != nil; ls = tl ls) + s = hd ls; + return s; +} + +cp(src, dst: string, newname: string) +{ + ok: int; + ds, dd: Sys->Dir; + + if (newname != nil) + dst += "/" + newname; + (ok, ds) = sys->stat(src); + if (ok < 0) { + warning(sys->sprint("%s: %r", src)); + return; + } + if (ds.mode & Sys->DMDIR) { + warning(src + " is a directory"); + return; + } + (ok, dd) = sys->stat(dst); + if (ok != -1 && + ds.qid.path == dd.qid.path && + ds.dev == dd.dev && + ds.dtype == dd.dtype) { + warning(src + " and " + dst + " are the same file"); + return; + } + sfd := sys->open(src, sys->OREAD); + if (sfd == nil) { + warning(sys->sprint("cannot open %s: %r", src)); + return; + } + dfd := sys->create(dst, sys->OWRITE, ds.mode); + if (dfd == nil) { + warning(sys->sprint("cannot create %s: %r", dst)); + return; + } + copy(sfd, dfd, src, dst); +} + +mkdir(d: string, mode: int): int +{ + dfd := sys->create(d, sys->OREAD, sys->DMDIR | mode); + if (dfd == nil) { + warning(sys->sprint("cannot make directory %s: %r", d)); + return -1; + } + return 0; +} + +copy(sfd, dfd: ref Sys->FD, src, dst: string): int +{ + if(fdc != nil){ + fdc <-= (sfd, dfd); + return 0; + } + buf := array[Sys->ATOMICIO] of byte; + for (;;) { + r := sys->read(sfd, buf, Sys->ATOMICIO); + if (r < 0) { + warning(sys->sprint("error reading %s: %r", src)); + return -1; + } + if (r == 0) + return 0; + if (sys->write(dfd, buf, r) != r) { + warning(sys->sprint("error writing %s: %r", dst)); + return -1; + } + } +} + +cpdir(argv: list of string, dst: string) +{ + readdir = load Readdir Readdir->PATH; + if (readdir == nil) { + sys->fprint(stderr, "fcp: cannot load %s: %r\n", Readdir->PATH); + raise "fail:bad module"; + } + cache = array[NCACHE] of list of ref Sys->Dir; + dexists := 0; + (ok, dd) := sys->stat(dst); + # destination file exists + if (ok != -1) { + if ((dd.mode & Sys->DMDIR) == 0) { + warning(dst + ": destination not a directory"); + return; + } + dexists = 1; + } + for (; tl argv != nil; argv = tl argv) { + ds: Sys->Dir; + src := hd argv; + (ok, ds) = sys->stat(src); + if (ok < 0) { + warning(sys->sprint("can't stat %s: %r", src)); + continue; + } + if ((ds.mode & Sys->DMDIR) == 0) { + cp(hd argv, dst, basename(hd argv)); + } else if (dexists) { + if (ds.qid.path==dd.qid.path && + ds.dev==dd.dev && + ds.dtype==dd.dtype) { + warning("cannot copy " + src + " into itself"); + continue; + } + copydir(src, dst + "/" + basename(src), ds.mode); + } else { + copydir(src, dst, ds.mode); + } + } +} + +copydir(src, dst: string, srcmode: int) +{ + (ok, nil) := sys->stat(dst); + if (ok != -1) { + warning("cannot copy " + src + " onto another directory"); + return; + } + tmode := srcmode | 8r777; # Fix for Nt + if (mkdir(dst, tmode) == -1) + return; + (entries, n) := readdir->init(src, Readdir->COMPACT); + for (i := 0; i < n; i++) { + e := entries[i]; + path := src + "/" + e.name; + if ((e.mode & Sys->DMDIR) == 0) + cp(path, dst, e.name); + else if (seen(e)) + warning(path + ": directory loop found"); + else + copydir(path, dst + "/" + e.name, e.mode); + } + chmod(dst, srcmode); +} + +# Avoid loops in tangled namespaces. (from du.b) +NCACHE: con 64; # must be power of two +cache: array of list of ref sys->Dir; + +seen(dir: ref sys->Dir): int +{ + savlist := cache[int dir.qid.path&(NCACHE-1)]; + for(c := savlist; c!=nil; c = tl c){ + sav := hd c; + if(dir.qid.path==sav.qid.path && + dir.dtype==sav.dtype && dir.dev==sav.dev) + return 1; + } + cache[int dir.qid.path&(NCACHE-1)] = dir :: savlist; + return 0; +} + +warning(e: string) +{ + sys->fprint(stderr, "fcp: %s\n", e); + errors++; +} + +chmod(s: string, mode: int): int +{ + (ok, d) := sys->stat(s); + if (ok < 0) + return -1; + + if(d.mode == mode) + return 0; + d = sys->nulldir; + d.mode = mode; + if (sys->wstat(s, d) < 0) { + warning(sys->sprint("cannot wstat %s: %r", s)); + return -1; + } + return 0; +} + +mstream(fdc: chan of (ref Sys->FD, ref Sys->FD), bufsize: int, nin, nout: int) +{ + inc := chan of (ref Sys->FD, big, int, ref Sys->FD); + outc := chan of (ref Sys->FD, big, array of byte); + for(i := 0; i < nin; i++) + spawn readproc(inc, outc); + for(i = 0; i < nout; i++) + spawn writeproc(outc); + while(((src, dst) := <-fdc).t0 != nil){ + (ok, stat) := sys->fstat(src); + if(ok == -1) + continue; + tot := stat.length; + o := big 0; + while((n := tot - o) > big 0){ + if(n < big bufsize) + inc <-= (src, o, int n, dst); + else + inc <-= (src, o, bufsize, dst); + o += big bufsize; + } + } + for(i = 0; i < nin; i++) + inc <-= (nil, big 0, 0, nil); + for(i = 0; i < nout; i++) + outc <-= (nil, big 0, nil); +} + +readproc(inc: chan of (ref Sys->FD, big, int, ref Sys->FD), outc: chan of (ref Sys->FD, big, array of byte)) +{ + buf: array of byte; + while(((src, o, nb, dst) := <-inc).t0 != nil){ + if(len buf < nb) + buf = array[nb*2] of byte; + n := sys->pread(src, buf, nb, o); + if(n > 0){ + outc <-= (dst, o, buf[0:n]); + buf = buf[n:]; + } + } +} + +writeproc(outc: chan of (ref Sys->FD, big, array of byte)) +{ + while(((dst, o, buf) := <-outc).t0 != nil) + sys->pwrite(dst, buf, len buf, o); +} diff --git a/appl/cmd/fmt.b b/appl/cmd/fmt.b new file mode 100755 index 00000000..337f9fd2 --- /dev/null +++ b/appl/cmd/fmt.b @@ -0,0 +1,204 @@ +implement Fmt; + +# +# Copyright © 2002 Lucent Technologies Inc. +# based on the Plan 9 command; subject to the Lucent Public License 1.02 +# this Vita Nuova variant uses Limbo channels and processes to avoid accumulating words +# + +# +# block up paragraphs, possibly with indentation +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Fmt: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +extraindent := 0; # how many spaces to indent all lines +indent := 0; # current value of indent, before extra indent +length := 70; # how many columns per output line +join := 1; # can lines be joined? +maxtab := 8; +bout: ref Iobuf; + +Word: adt { + text: string; + indent: int; + bol: int; +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + arg := load Arg Arg->PATH; + + arg->init(args); + arg->setusage("fmt [-j] [-i indent] [-l length] [file...]"); + while((c := arg->opt()) != 0) + case(c){ + 'i' => + extraindent = int arg->earg(); + 'j' => + join = 0; + 'w' or 'l' => + length = int arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + if(length <= extraindent){ + sys->fprint(sys->fildes(2), "fmt: line length<=indentation\n"); + raise "fail:length"; + } + arg = nil; + + err := ""; + bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + if(args == nil){ + bin := bufio->fopen(sys->fildes(0), Bufio->OREAD); + fmt(bin); + }else + for(; args != nil; args = tl args){ + bin := bufio->open(hd args, Bufio->OREAD); + if(bin == nil){ + sys->fprint(sys->fildes(2), "fmt: can't open %s: %r\n", hd args); + err = "open"; + }else{ + fmt(bin); + if(tl args != nil) + bout.putc('\n'); + } + } + bout.flush(); + if(err != nil) + raise "fail:"+err; +} + +fmt(f: ref Iobuf) +{ + words := chan of ref Word; + spawn parser(f, words); + printwords(words); +} + +parser(f: ref Iobuf, words: chan of ref Word) +{ + while((s := f.gets('\n')) != nil){ + if(s[len s-1] == '\n') + s = s[0:len s-1]; + parseline(s, words); + } + words <-= nil; +} + +parseline(line: string, words: chan of ref Word) +{ + ind: int; + (line, ind) = indentof(line); + indent = ind; + bol := 1; + for(i:=0; i < len line;){ + # find next word + if(line[i] == ' ' || line[i] == '\t'){ + i++; + continue; + } + # where does this word end? + for(l:=i; l < len line; l++) + if(line[l]==' ' || line[l]=='\t') + break; + words <-= ref Word(line[i:l], indent, bol); + bol = 0; + i = l; + } + if(bol) + words <-= ref Word("", -1, bol); +} + +indentof(line: string): (string, int) +{ + ind := 0; + for(i:=0; i < len line; i++) + case line[i] { + ' ' => + ind++; + '\t' => + ind += maxtab; + ind -= ind%maxtab; + * => + return (line, ind); + } + # plain white space doesn't change the indent + return (line, indent); +} + +printwords(words: chan of ref Word) +{ + # one output line per loop + nw := <-words; + while((w := nw) != nil){ + # if it's a blank line, print it + if(w.indent == -1){ + bout.putc('\n'); + nw = <-words; + continue; + } + # emit leading indent + col := extraindent+w.indent; + printindent(col); + # emit words until overflow; always emit at least one word + for(n:=0;; n++){ + bout.puts(w.text); + col += len w.text; + if((nw = <-words) == nil) + break; # out of words + if(nw.indent != w.indent) + break; # indent change + nsp := nspaceafter(w.text); + if(col+nsp+len nw.text > extraindent+length) + break; # fold line + if(!join && nw.bol) + break; + for(j:=0; j<nsp; j++) + bout.putc(' '); # emit space; another word will follow + col += nsp; + w = nw; + } + bout.putc('\n'); + } +} + +printindent(w: int) +{ + while(w >= maxtab){ + bout.putc('\t'); + w -= maxtab; + } + while(--w >= 0) + bout.putc(' '); +} + +# give extra space if word ends with punctuation +nspaceafter(s: string): int +{ + if(len s < 2) + return 1; + if(len s < 4 && s[0] >= 'A' && s[0] <= 'Z') + return 1; # assume it's a title, not full stop + if((c := s[len s-1]) == '.' || c == '!' || c == '?') + return 2; + return 1; +} diff --git a/appl/cmd/fone.b b/appl/cmd/fone.b new file mode 100644 index 00000000..51bbede6 --- /dev/null +++ b/appl/cmd/fone.b @@ -0,0 +1,560 @@ +implement fone; + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; + stdout: ref Sys->FD; + logfd: ref Sys->FD; + +include "draw.m"; + draw: Draw; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "sh.m"; + smtp: Command; + +#include "keyring.m"; + +include "daytime.m"; + daytime: Daytime; + +TIMEGRAN: con 60000; +debug := 0; +logflag := 0; +logfile := ""; # name of log file +Nphones := 0; # number of telephone sets configured +voicefile := ""; # name of serial port to DECTalk +voice: ref sys->FD; +mailhost := ""; + +person: adt { + mailaddr: string; + name: string; # name pronounced by the voice + lineno: string; # 4 digit extension + time: string; + orignum: string; # originating number + origname: string; # originating name + state: int; + flags: int; +}; + +# states +ONHOOK: con 0; +RING: con 1; +DISPLAY: con 2; +OFFHOOK: con 3; + +# flags +LOG: con 1; +MAIL: con 2; +ANNOUNCE: con 4; + +telset: adt { + devfile: string; # file name of interface to phone set + apprfile: string; + apprtime: int; # time appearance file is read + phonefd: ref sys->FD; # open FD for this set + numappr: int; # number of appearances on this set + people: array of person; # appearance data for this set + version: string; # telephone set version +}; + +phone:= array[4] of telset; + +months:= array[13] of { 0 => "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug","Sep", "Oct", "Nov", "Dec"}; + +fone: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) { + + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + str = load String String->PATH; + daytime = load Daytime Daytime->PATH; + smtp = load Command "smtp.dis"; +# keyring := load Keyring Keyring->PATH; + + stdout = sys->fildes(1); + logfd = stdout; + stderr = sys->fildes(2); + voicechan := chan of string; + timechan := chan of string; + +# +# set up name space. According to tradition this is done +# outside of the program. Needs to be here so debugging +# is not so tedious. +# + if (sys->pctl(sys->FORKNS, nil) < 0) { + sys->fprint(stderr, "pctl(FORKNS) failed: %r\n"); + exit; + } + if (sys->bind("#t", "/dev", sys->MAFTER) < 0) { + sys->fprint(stderr, "bind #t failed: %r\n"); + exit; + } + if (sys->bind("#p", "/prog", sys->MAFTER) < 0) { + sys->fprint(stderr, "bind #p failed: %r\n"); + exit; + } + + if (sys->bind("#C", "/", sys->MAFTER) < 0) { + sys->fprint(stderr, "bind #C failed: %r\n"); + exit; + } + + argv = tl argv; + while(argv != nil && len hd argv && (arg := hd argv)[0] == '-' && len arg > 1){ + case arg[1] { + 'd' => + debug = 1; + logflag = 1; + } + argv = tl argv; + } + configfile("fone.cfg"); +# +# Sound Blaster using sbtalker and read +# +# voice = SBsetup(); +# +# DECtalk using second serial port + voice = DTsetup(voicefile); + + sys->fprint(voice, "hello.\r"); + + + spawn timekeeper(timechan); + for (phoneid := 0; phoneid < Nphones; phoneid++) + spawn watchphone(phoneid, voicechan); + for (;;) alt { + mesg := <- voicechan => + sys->fprint(voice, "%s", mesg); + tmesg := <- timechan => + case tmesg { + "filecheck" => + for (i:=0; i<Nphones; i++) { + (r, f) := sys->stat(phone[i].apprfile); + if (r < 0) { + sys->fprint(stderr, "cannot stat %s: %r\n", phone[i].apprfile); + continue; + } + if (f.mtime > phone[i].apprtime) + getcallapprinfo(i); + } + } + } +} + +# +# read in the configuration file which tells the program which +# files and devices to use. +# +configfile(cfgname: string): int { + line, errstr: string; + + cfgfd := sys->open(cfgname, sys->OREAD); + if (cfgfd == nil) { + sys->fprint(stderr, "open %s failed, %r\n", cfgname); + bye(); + } + do { + (line, errstr) = getline(cfgfd); + if (errstr != nil) { + sys->fprint(stderr, "error reading config file: %r\n"); + return -1; + } + if (line != nil) { + (i, t) := sys->tokenize(line, ": \t\r\n"); + if ((hd t)[0] == '#') continue; + case hd t { + "logfile" => + if (i < 2) { + sys->fprint(stderr, "no log file name found. %d\n", i); + sys->fprint(stderr, "logfile: log_file_name\n"); + return -1; + } + t = tl t; + logfile = hd t; + if (logfile != nil) { + if ((logfd = sys->open(logfile, sys->OWRITE)) == nil) { + sys->fprint(stderr, "open log file %s failed\n", logfile); + continue; + } + logflag = 1; + } + "mailhost" => + if (i < 2) { + sys->fprint(stderr, "no mailhost found."); + sys->fprint(stderr, "mailhost: host_name\n"); + return -1; + } + t = tl t; + mailhost = hd t; + "voice" => + if (i < 2) { + sys->fprint(stderr, "no log file name found."); + sys->fprint(stderr, "voice: serial_port\n"); + return -1; + } + t = tl t; + voicefile = hd t; + "phone" => + if (i < 3) { + sys->fprint(stderr, "not enough fields for phone attendance line\n"); + sys->fprint(stderr, "attend: serial_port phone_appearance_file_name\n"); + return -1; + } + t = tl t; + phonefile := hd t; + t = tl t; + apprfile := hd t; + phone[Nphones].devfile = phonefile; + phone[Nphones].apprfile = apprfile; + phone[Nphones].phonefd = sys->open(phonefile, sys->ORDWR); + if (phone[Nphones].phonefd == nil) { + sys->fprint(stderr, "open %s failed, %r\n", phonefile); + return -1; + } + (numappr, version) := phoneinit(Nphones); + if (numappr == 0) continue; + phone[Nphones].numappr = numappr; + phone[Nphones].people = array[numappr + 1] of person; + phone[Nphones].version = version; + if (debug) sys->fprint(stderr, "phone %d initialized\n", Nphones); + getcallapprinfo(Nphones); + ++Nphones; + * => + sys->fprint(stderr, "bad keyword <%s> in configuration file\n", hd t); + return -1; + } + } + } while (line != nil); + return 0; +} + +# +# +# +timekeeper(tchan: chan of string) { + for(;;) { + sys->sleep(TIMEGRAN); + tchan <- = sys->sprint("filecheck"); + } +} + +# +# monitor the status messages of the phone(s). +# look for ring indications and subsequent display data to send +# to users if they do not answer their phones. +# If display data is received and the phone is not answered, +# a mail message is sent. +# +watchphone(pindex: int, voicechan: chan of string) { + buf, errbuf: string; + + do { + (buf, errbuf) = getline(phone[pindex].phonefd); + if (errbuf != nil) { + sys->fprint(stderr, "%s\n", errbuf); + return; + } + if (debug) sys->fprint(stderr, "phone %d: %s\n", pindex, buf); + (resultcode, info) := str->splitl(buf, ":"); + if (resultcode == nil) continue; + + # get rid of colon + info = info[1:]; + + (i, t) := sys->tokenize(info, ","); + appr := int hd t; + t = tl t; + --i; + case resultcode { + "RING" or "02" => + if ((phone[pindex].people[appr].flags & ANNOUNCE)) + voicechan <- = sys->sprint("phone call for, %s.\r", phone[pindex].people[appr].name); + phone[pindex].people[appr].state = RING; + phone[pindex].people[appr].time = ""; + phone[pindex].people[appr].orignum = ""; + phone[pindex].people[appr].origname = ""; + "DISPLAY" or "06" => + if (i <= 0) { + sys->fprint(stderr, "not enough args for DISPLAY result code\n"); + continue; + } + displaydata := hd t; + (displaytype, s) := str->toint(displaydata[0:2], 16); + case displaytype { + 16r03 => + # originating number + phone[pindex].people[appr].orignum = displaydata[2:]; + 16r05 => + # originating name + phone[pindex].people[appr].origname = displaydata[2:]; + 16r0a => + correct24hr: int; + + # date and time + if (displaydata[13:15] == "pm") + correct24hr = 12; + else + correct24hr = 0; +# hour := int displaydata[8:10] + correct24hr; + phone[pindex].people[appr].time = sys->sprint("%s %2d %2d:%.2d", months[int displaydata[2:4]], int displaydata[5:7], int displaydata[8:10] % 12 + correct24hr, int displaydata[11:13]); + phone[pindex].people[appr].state = DISPLAY; + if (logflag && (phone[pindex].people[appr].flags & LOG)) + sys->fprint(logfd, "%s: x%s %s (%s)\n", phone[pindex].people[appr].time, phone[pindex].people[appr].lineno, phone[pindex].people[appr].orignum, phone[pindex].people[appr].origname); + } + "SIGNAL" or "13" => + signalcode := hd t; + t = tl t; + --i; + case signalcode { + "4F" => + if (i <= 0) { + if (phone[pindex].people[appr].state == DISPLAY) { + phone[pindex].people[appr].state = OFFHOOK; + } + continue; + } + causecode := hd t; + case causecode { + "10" => + case phone[pindex].people[appr].state { + DISPLAY => + if ((phone[pindex].people[appr].flags & MAIL) && phone[pindex].people[appr].mailaddr != "-") { + mailmesg := sys->sprint("From: phoneca\nTo: %s\nSubject: Phone call from %s\n\n from: %s\n phone: %s\n time: %s\n", phone[pindex].people[appr].mailaddr, phone[pindex].people[appr].orignum, phone[pindex].people[appr].origname, phone[pindex].people[appr].orignum, phone[pindex].people[appr].time); + + spawn smtp->init(nil, "smtp" :: mailhost :: "phoneca" :: phone[pindex].people[appr].mailaddr :: mailmesg :: nil); + } + } + phone[pindex].people[appr].state = ONHOOK; + } + } + } + } while(errbuf == nil); +} + +usage() { + sys->fprint(stderr, "usage: fone -d phone_dev\n"); + bye(); +} + +# +# wait for an OK from a particular phone, part of Hayes protocol +OK(phonefd: ref sys->FD): int { + buf, err: string; + + do { + (buf, err) = getline(phonefd); + if (err != nil) { + sys->fprint(stderr, "%s\n", err); + return(0); + } + if (debug) sys->fprint(stderr, "%s\n", buf); + } while (buf != "OK" && buf != "0"); + return(1); +} + +bye() { + exit; +} + +phoneinit(pindex: int): (int, string) { + buf, err: string; + i: int; + t: list of string; + + phonefd := phone[pindex].phonefd; +# E0=echo OFF, V0=verbal return codes ON/OFF, &D0=ignore DTR transition + if (debug) sys->fprint(stderr, "initialize phone %d serial port...", pindex); + sys->fprint(phonefd, "ATE0V1&D0\r"); + if (!OK(phonefd)) return (0, "cannot initialize phone"); + +# &&I=init phone, I3=report phone type + if (debug) sys->fprint(stderr, "get phone version..."); + sys->fprint(phonefd, "AT&&II3\r"); + do { + (buf, err) = getline(phonefd); + if (err != nil) { + sys->fprint(stderr, "%s\n", err); + return (0, "cannot get phone version"); + } + (i, t) = sys->tokenize(buf, " \n\r"); + } while (i != 4 || hd t != "03-"); + t = tl t; + if (!OK(phonefd)) return (0, "cannot get phone version"); + version := hd t; + if (debug) sys->fprint(stderr, "version <%s>\n", version); + numappr := int version[2:4]; + +# %A0=3 channel assigned to control voice + if (debug) sys->fprint(stderr, "control phone's voice channel..."); + sys->fprint(phonefd, "AT%%A0=3\r"); + if (!OK(phonefd)) return (0, "cannot control voice channel"); + return (numappr, version); +} + +# +# get a line of text (up to a newline or carriage return) +# throw away initial newlines or carriage returns +# +getline(fd: ref sys->FD): (string, string) { + c := array[1] of byte; + s := ""; + i := 0; + + loop: while(i < 4096) { + r := sys->read(fd, c, 1); + if(r < 0) + return (s, sys->sprint("%r")); + if(r == 0) + return (nil, nil); + case int c[0] { + '\r' or + '\n' => + if(i != 0) + break loop; + * => + s[i++] = int c[0]; + } + + } + return (s, nil); +} +# +# read in names and mail addresses for appearances on each phone +# +getcallapprinfo(pindex: int) { + name : string; + filename := phone[pindex].apprfile; + + if (debug) sys->fprint(stderr, "getting call appearance data from %s\n", filename); + who := bufio->open(filename, sys->OREAD); + if (who == nil) { + sys->fprint(stderr, "open %s failed, %r\n", filename); + bye(); + } + phone[pindex].apprtime = daytime->now(); + while ((s := who.gets('\n')) != nil) { + if ((array of byte(s))[0] == byte '#') continue; + (i, t) := sys->tokenize(s, " \t\n\r"); + if(i < 5) { + sys->fprint(stderr, "Error in %s. The line was:\n%s\n", filename, s); + continue; + } + appr := int hd t; + t = tl t; + phone[pindex].people[appr].lineno = hd t; + t = tl t; + flags := hd t; + phone[pindex].people[appr].flags = 0; + for (n:=0; n<len flags; n++) { + case int (array of byte flags)[n] { + 'l' => + phone[pindex].people[appr].flags |= LOG; + 'm' => + phone[pindex].people[appr].flags |= MAIL; + 'a' => + phone[pindex].people[appr].flags |= ANNOUNCE; + * => + sys->fprint(stderr, "unknown flag %c\n", int (array of byte flags)[n]); + } + } + t = tl t; + phone[pindex].people[appr].mailaddr = hd t; + t = tl t; + name = ""; + while(t != nil) { + name += " " + hd t; + t = tl t; + } + phone[pindex].people[appr].name = name; +# if (debug) sys->fprint(stderr, "added user %s at %d\n", phone[pindex].people[appr].name, appr); + } +} + +# +# Setup connection to use READ.EXE command in SounBlaster software +# +SBsetup(): ref sys->FD { + cmd := sys->open("/cmd/clone", sys->ORDWR); + if (cmd == nil) { + sys->fprint(stderr, "open %s failed, %r\n", "/cmd/clone"); + bye(); + } + cmdno := array[32] of byte; + if ((n:=sys->read(cmd, cmdno, 32)) <= 0) { + sys->fprint(stderr, "read error: %r\n"); + bye(); + } + cmddirname := "/cmd/" + string cmdno[0:n]; + + if (debug) sys->fprint(stderr, "exec'ing command\n"); + if ((n=sys->fprint(cmd, "exec command")) < 0) { + sys->fprint(stderr, "fprint of cmd failed:%r\n"); + bye(); + } + + cmddata := sys->open(cmddirname + "/data", sys->ORDWR); + if (cmddata == nil) { + sys->fprint(stderr, "open %s:%r\n", cmddirname + "/data"); + bye(); + } + + buf := array[128] of byte; +# sys->fprint(stderr, "sending sbtalker\n"); + if ((n=sys->fprint(cmddata, "sbtalker /dBLASTER\r")) < 0) { + sys->fprint(stderr, "fprint of cmddata failed:%r\n"); + bye(); + } + n = sys->read(cmddata, buf, 128); + if (n < 0) { + sys->fprint(stderr, "read /cmd/n/data failed:%r\n"); + bye(); + } + sys->fprint(stderr, "%*s\n", n, string buf[0:n]); + +# sys->fprint(stderr, "sending read\n"); + if ((n=sys->fprint(cmddata, "read\r")) < 0) { + sys->fprint(stderr, "fprint of cmddata failed:%r\n"); + bye(); + } + n = sys->read(cmddata, buf, 128); + if (n < 0) { + sys->fprint(stderr, "read /cmd/n/data failed:%r\n"); + bye(); + } + sys->fprint(stderr, "%*s\n", n, string buf[0:n]); + return cmddata; +} + +# +# setup connection to DECTalk +# +DTsetup(voicedev: string): ref sys->FD { + voicel := sys->open(voicedev, sys->ORDWR); + if (voicel == nil) { + sys->fprint(stderr, "open %s failed, %r\n", voicedev); + bye(); + } + voicectl := sys->open(voicedev+"ctl", sys->OWRITE); + if (voicectl == nil) { + sys->fprint(stderr, "open %s failed, %r\n", voicedev+"ctl"); + bye(); + } + if (sys->fprint(voicectl, "B1200") != 5) { + sys->fprint(stderr, "write %s failed, %r\n", voicedev+"ctl"); + bye(); + } + return voicel; +} diff --git a/appl/cmd/fortune.b b/appl/cmd/fortune.b new file mode 100755 index 00000000..7368e992 --- /dev/null +++ b/appl/cmd/fortune.b @@ -0,0 +1,100 @@ +# +# initially generated by c2l +# + +implement Fortune; + +Fortune: module +{ + init: fn(nil: ref Draw->Context, argl: list of string); +}; + +include "sys.m"; + sys: Sys; + Dir: import sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "rand.m"; + rand: Rand; + +include "keyring.m"; +include "security.m"; + +choice: string; +findex := "/lib/games/fortunes.index"; +fortunes := "/lib/games/fortunes"; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + rand = load Rand Rand->PATH; + + if(args != nil) + args = tl args; + if(args != nil) + filename := hd args; + else + filename = fortunes; + if((f := bufio->open(filename, Bufio->OREAD)) == nil){ + sys->fprint(sys->fildes(2), "fortune: can't open %s: %r\n", filename); + raise "fail:open"; + } + ix, nix: ref Sys->FD; + length := big 0; + if(args == nil){ + ix = sys->open(findex, Sys->OREAD); + if(ix != nil){ + (nil, ixbuf) := sys->fstat(ix); + (nil, fbuf) := sys->fstat(f.fd); + if(fbuf.mtime > ixbuf.mtime){ + ix = nil; + nix = sys->create(findex, Sys->OWRITE, 8r666); + }else + length = ixbuf.length; + }else + nix = sys->create(findex, Sys->OWRITE, 8r666); + } + off := array[4] of byte; + if(ix != nil && length != big 0){ + sys->seek(ix, ((big truerand() & ((big 1<<32)-big 1))%length) & ~big 3, 0); + sys->read(ix, off, 4); + f.seek(big (int off[0]|int off[1]<<8|int off[2]<<16|int off[3]<<24), 0); + choice = f.gets('\n'); + if(choice == nil) + choice = "Misfortune!\n"; + }else{ + rand->init(truerand()); + offs := 0; + g := bufio->fopen(ix, Bufio->ORDWR); + for(i := 1;; i++){ + if(nix != nil) + offs = int f.offset(); + p := f.gets('\n'); + if(p == nil) + break; + if(nix != nil){ + off[0] = byte offs; + off[1] = byte (offs>>8); + off[2] = byte (offs>>16); + off[3] = byte (offs>>24); + g.write(off, 4); + } + if(rand->rand(i) == 0) + choice = p; + } + g.flush(); + } + sys->print("%s", choice); +} + +truerand(): int +{ + random := load Random Random->PATH; + return random->randomint(Random->ReallyRandom); +} diff --git a/appl/cmd/freq.b b/appl/cmd/freq.b new file mode 100755 index 00000000..4629da30 --- /dev/null +++ b/appl/cmd/freq.b @@ -0,0 +1,112 @@ +implement Freq; + +# +# Copyright © 2002 Lucent Technologies Inc. +# transliteration of the Plan 9 command; subject to the Lucent Public License 1.02 +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Freq: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +count := array[1<<16] of big; +flag := 0; + +Fdec, Fhex, Foct, Fchar, Frune: con 1<<iota; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + arg := load Arg Arg->PATH; + + arg->init(args); + while((c := arg->opt()) != 0) + case c { + * => + sys->fprint(sys->fildes(2), "freq: unknown option %c\n", c); + raise "fail:usage"; + 'd' => + flag |= Fdec; + 'x' => + flag |= Fhex; + 'o' => + flag |= Foct; + 'c' => + flag |= Fchar; + 'r' => + flag |= Frune; + } + args = arg->argv(); + arg = nil; + + bout := bufio->fopen(sys->fildes(1), Sys->OWRITE); + if((flag&(Fdec|Fhex|Foct|Fchar)) == 0) + flag |= Fdec|Fhex|Foct|Fchar; + if(args == nil){ + freq(sys->fildes(0), "-", bout); + exit; + } + for(; args != nil; args = tl args){ + f := sys->open(hd args, Sys->OREAD); + if(f == nil){ + sys->fprint(sys->fildes(2), "cannot open %s\n", hd args); + continue; + } + freq(f, hd args, bout); + f = nil; + } +} + +freq(f: ref Sys->FD, s: string, bout: ref Iobuf) +{ + c: int; + + bin := bufio->fopen(f, Sys->OREAD); + if(flag&Frune) + for(;;){ + c = bin.getc(); + if(c < 0) + break; + count[c]++; + } + else + for(;;){ + c = bin.getb(); + if(c < 0) + break; + count[c]++; + } + if(c != Bufio->EOF) + sys->fprint(sys->fildes(2), "freq: read error on %s: %r\n", s); + for(i := 0; i < (len count)/4; i++){ + if(count[i] == big 0) + continue; + if(flag&Fdec) + bout.puts(sys->sprint("%3d ", i)); + if(flag&Foct) + bout.puts(sys->sprint("%.3o ", i)); + if(flag&Fhex) + bout.puts(sys->sprint("%.2x ", i)); + if(flag&Fchar) + if(i <= 16r20 || i >= 16r7f && i < 16ra0 || i > 16rff && !(flag&Frune)) + bout.puts("- "); + else + bout.puts(sys->sprint("%c ", i)); + bout.puts(sys->sprint("%8bd\n", count[i])); + } + bout.flush(); +} + diff --git a/appl/cmd/fs.b b/appl/cmd/fs.b new file mode 100644 index 00000000..0314b0bf --- /dev/null +++ b/appl/cmd/fs.b @@ -0,0 +1,109 @@ +implement Fs; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "readdir.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Quit: import Fslib; + +# fs distribution: + +# {filter -d {not {match -r '\.(dis|sbl)$'}} {filter {path /module/fslib.m /module/bundle.m /module/unbundle.m /appl/cmd/fs.b /appl/cmd/fs /appl/lib/fslib.b} /}} + +Fs: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +badmod(path: string) +{ + sys->fprint(stderr(), "fs: cannot load %s: %r\n", path); + raise "fail:bad module"; +} + +init(ctxt: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + fslib->init(); + argv = tl argv; + + if(argv == nil) + usage(); + report := Report.new(); + s := hd argv; + if(tl argv == nil && s != nil && s[0] == '{' && s[len s - 1] == '}') + s = "void " + hd argv; + else { + s = "void {" + hd argv; + for(argv = tl argv; argv != nil; argv = tl argv){ + a := hd argv; + if(a == nil || a[0] != '{') # } + s += sys->sprint(" %q", a); + else + s += " " + hd argv; + } + s += "}"; + } + m := load Fsmodule "/dis/fs/eval.dis"; + if(m == nil) + badmod("/dis/fs/eval.dis"); + if(!fslib->typecompat("as", m->types())){ + sys->fprint(stderr(), "fs: eval module implements incompatible type (usage: %s)\n", + fslib->cmdusage("eval", m->types())); + raise "fail:bad eval module"; + } + m->init(); + v := m->run(ctxt, report, nil, ref Value.S(s) :: nil); + fail: string; + if(v == nil) + fail = "error"; + else{ + sync := v.v().i; + sync <-= 1; + } + report.enable(); + while((e := <-report.reportc) != nil) + sys->fprint(stderr(), "fs: %s\n", e); + if(fail != nil) + raise "fail:" +fail; +} + +usage() +{ + fd := stderr(); + sys->fprint(fd, "usage: fs expression\n"); + sys->fprint(fd, "verbs are:\n"); + if((readdir := load Readdir Readdir->PATH) == nil){ + sys->fprint(fd, "fs: cannot load %s: %r\n", Readdir->PATH); + }else{ + (a, nil) := readdir->init("/dis/fs", Readdir->NAME|Readdir->COMPACT); + for(i := 0; i < len a; i++){ + f := a[i].name; + if(len f < 4 || f[len f - 4:] != ".dis") + continue; + m := load Fsmodule "/dis/fs/" + f; + if(m == nil) + sys->fprint(fd, "\t(%s: cannot load: %r)\n", f[0:len f - 4]); + else + sys->fprint(fd, "\t%s\n", fslib->cmdusage(f[0:len f - 4], m->types())); + } + } + sys->fprint(fd, "automatic conversions:\n"); + sys->fprint(fd, "\tstring -> fs {walk string}\n"); + sys->fprint(fd, "\tfs -> entries {entries fs}\n"); + sys->fprint(fd, "\tstring -> gate {match string}\n"); + sys->fprint(fd, "\tentries -> void {print entries}\n"); + sys->fprint(fd, "\tcommand -> string {run command}\n"); + raise "fail:usage"; +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} diff --git a/appl/cmd/fs/and.b b/appl/cmd/fs/and.b new file mode 100644 index 00000000..ff867409 --- /dev/null +++ b/appl/cmd/fs/and.b @@ -0,0 +1,65 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "pppp*"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + c := chan of Gatequery; + spawn andgate(c, args); + return ref Value.P(c); +} + +andgate(c: Gatechan, args: list of ref Value) +{ + sub: list of Gatechan; + for(; args != nil; args = tl args) + sub = (hd args).p().i :: sub; + sub = rev(sub); + myreply := chan of int; + while(((d, reply) := <-c).t0.t0 != nil){ + for(l := sub; l != nil; l = tl l){ + (hd l) <-= (d, myreply); + if(<-myreply == 0) + break; + } + reply <-= l == nil; + } + for(; sub != nil; sub = tl sub) + hd sub <-= (Nilentry, nil); +} + +rev[T](x: list of T): list of T +{ + l: list of T; + for(; x != nil; x = tl x) + l = hd x :: l; + return l; +} diff --git a/appl/cmd/fs/bundle.b b/appl/cmd/fs/bundle.b new file mode 100644 index 00000000..a4b1cee5 --- /dev/null +++ b/appl/cmd/fs/bundle.b @@ -0,0 +1,195 @@ +implement Bundle; +include "sys.m"; + sys: Sys; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "readdir.m"; + readdir: Readdir; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, report, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; +include "bundle.m"; + +# XXX if we can't open a directory, is it ever worth passing its metadata +# through anyway? + +EOF: con "end of archive\n"; + +types(): string +{ + return "vx"; +} +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: bundle: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + badmod(Readdir->PATH); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + badmod(Readdir->PATH); + bufio->fopen(nil, Sys->OREAD); # XXX no bufio->init! + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Readdir->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + return ref Value.V( + bundle( + report, + bufio->fopen(sys->fildes(1), Sys->OWRITE), + (hd args).x().i + ) + ); +} + +bundle(r: ref Report, iob: ref Iobuf, c: Fschan): chan of int +{ + sync := chan of int; + spawn bundleproc(c, sync, iob, r.start("bundle")); + return sync; +} + +bundleproc(c: Fschan, sync: chan of int, iob: ref Iobuf, errorc: chan of string) +{ + if(sync != nil && <-sync == 0){ + (<-c).t1 <-= Quit; + quit(errorc); + } + (d, reply) := <-c; + if(d.dir == nil){ + report(errorc, "no root directory"); + endarchive(iob, errorc); + } + if(puts(iob, dir2header(d.dir), errorc) == -1){ + reply <-= Quit; + quit(errorc); + } + reply <-= Down; + bundledir(d.dir.name, d, c, iob, errorc); + endarchive(iob, errorc); +} + +endarchive(iob: ref Iobuf, errorc: chan of string) +{ + if(puts(iob, EOF, errorc) != -1) + iob.flush(); + quit(errorc); + exit; +} + +bundledir(path: string, d: Fsdata, + c: Fschan, + iob: ref Iobuf, errorc: chan of string) +{ + if(d.dir.mode & Sys->DMDIR){ + path[len path] = '/'; + for(;;){ + (ent, reply) := <-c; + if(ent.dir == nil){ + reply <-= Skip; + break; + } + if(puts(iob, dir2header(ent.dir), errorc) == -1){ + reply <-= Quit; + quit(errorc); + } + reply <-= Down; + bundledir(path + ent.dir.name, ent, c, iob, errorc); + } + iob.putc('\n'); + }else{ + buf: array of byte; + reply: chan of int; + length := big d.dir.length; + n := big 0; + for(;;){ + ((nil, buf), reply) = <-c; + if(buf == nil){ + reply <-= Skip; + break; + } + if(write(iob, buf, len buf, errorc) != len buf){ + reply <-= Quit; + quit(errorc); + } + n += big len buf; + if(n > length){ # should never happen + report(errorc, sys->sprint("%q is longer than expected (fatal)", path)); + reply <-= Quit; + quit(errorc); + } + if(n == length){ + reply <-= Skip; + break; + } + reply <-= Next; + } + if(n < length){ + report(errorc, sys->sprint("%q is shorter than expected (%bd/%bd); adding null bytes", path, n, length)); + buf = array[Sys->ATOMICIO] of {* => byte 0}; + while(n < length){ + nb := len buf; + if(length - n < big len buf) + nb = int (length - n); + if(write(iob, buf, nb, errorc) != nb){ + (<-c).t1 <-= Quit; + quit(errorc); + } + report(errorc, sys->sprint("added %d null bytes", nb)); + n += big nb; + } + } + } +} + +dir2header(d: ref Sys->Dir): string +{ + return sys->sprint("%q %uo %q %q %ud %bd\n", d.name, d.mode, d.uid, d.gid, d.mtime, d.length); +} + +puts(iob: ref Iobuf, s: string, errorc: chan of string): int +{ + { + if(iob.puts(s) == -1) + report(errorc, sys->sprint("write error: %r")); + return 0; + } exception { + "write on closed pipe" => + return -1; + } +} + +write(iob: ref Iobuf, buf: array of byte, n: int, errorc: chan of string): int +{ + { + nw := iob.write(buf, n); + if(nw < n){ + if(nw >= 0) + report(errorc, "short write"); + else{ + report(errorc, sys->sprint("write error: %r")); + } + } + return nw; + } exception { + "write on closed pipe" => + report(errorc, "write on closed pipe"); + return -1; + } +} diff --git a/appl/cmd/fs/chstat.b b/appl/cmd/fs/chstat.b new file mode 100644 index 00000000..e549527e --- /dev/null +++ b/appl/cmd/fs/chstat.b @@ -0,0 +1,185 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fsfilter: Fsfilter; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +Query: adt { + gate: Gatechan; + stat: Sys->Dir; + mask: int; + cflag: int; + reply: chan of int; + + query: fn(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int; +}; + +types(): string +{ + return "xx-pp-ms-us-gs-ts-as-c"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + fsfilter = load Fsfilter Fsfilter->PATH; + if(fsfilter == nil) + badmod(Fsfilter->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + ws := Sys->nulldir; + mask := 0; + gate: ref Value; + cflag := 0; + for(; opts != nil; opts = tl opts){ + o := (hd opts).args; + case (hd opts).opt { + 'p' => + gate.discard(); + gate = hd o; + 'm' => + ok: int; + m := (hd o).s().i; + (ok, mask, ws.mode) = parsemode(m); + mask &= ~Sys->DMDIR; + if(ok == 0){ + sys->fprint(sys->fildes(2), "fs: chstat: bad mode %#q\n", m); + gate.discard(); + return nil; + } + 'u' => + ws.uid = (hd o).s().i; + 'g' => + ws.gid = (hd o).s().i; + 't' => + ws.mtime = int (hd o).s().i; + 'a' => + ws.atime = int (hd o).s().i; + 'c' => + cflag++; + } + } + + dst := chan of (Fsdata, chan of int); + p: Gatechan; + if(gate != nil) + p = gate.p().i; + spawn chstatproc((hd args).x().i, dst, p, ws, mask, cflag); + return ref Value.X(dst); +} + +chstatproc(src, dst: Fschan, gate: Gatechan, stat: Sys->Dir, mask: int, cflag: int) +{ + fsfilter->filter(ref Query(gate, stat, mask, cflag, chan of int), src, dst); + if(gate != nil) + gate <-= ((nil, nil, 0), nil); +} + +Query.query(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int +{ + c := 1; + if(q.gate != nil){ + q.gate <-= ((d, name, depth), q.reply); + c = <-q.reply; + } + if(c){ + if(q.cflag){ + m := d.mode & 8r700; + d.mode = (d.mode & ~8r77)|(m>>3)|(m>>6); + } + stat := q.stat; + d.mode = (d.mode & ~q.mask) | (stat.mode & q.mask); + if(stat.uid != nil) + d.uid = stat.uid; + if(stat.gid != nil) + d.gid = stat.gid; + if(stat.mtime != ~0) + d.mtime = stat.mtime; + if(stat.atime != ~0) + d.atime = stat.atime; + } + return 1; +} + +# stolen from /appl/cmd/chmod.b +User: con 8r700; +Group: con 8r070; +Other: con 8r007; +All: con User | Group | Other; + +Read: con 8r444; +Write: con 8r222; +Exec: con 8r111; +parsemode(spec: string): (int, int, int) +{ + mask := Sys->DMAPPEND | Sys->DMEXCL | Sys->DMDIR | Sys->DMAUTH; +loop: + for(i := 0; i < len spec; i++){ + case spec[i] { + 'u' => + mask |= User; + 'g' => + mask |= Group; + 'o' => + mask |= Other; + 'a' => + mask |= All; + * => + break loop; + } + } + if(i == len spec) + return (0, 0, 0); + if(i == 0) + mask |= All; + + op := spec[i++]; + if(op != '+' && op != '-' && op != '=') + return (0, 0, 0); + + mode := 0; + for(; i < len spec; i++){ + case spec[i]{ + 'r' => + mode |= Read; + 'w' => + mode |= Write; + 'x' => + mode |= Exec; + 'a' => + mode |= Sys->DMAPPEND; + 'l' => + mode |= Sys->DMEXCL; + 'd' => + mode |= Sys->DMDIR; + 'A' => + mode |= Sys->DMAUTH; + * => + return (0, 0, 0); + } + } + if(op == '+' || op == '-') + mask &= mode; + if(op == '-') + mode = ~mode; + return (1, mask, mode); +} diff --git a/appl/cmd/fs/compose.b b/appl/cmd/fs/compose.b new file mode 100644 index 00000000..69187d6b --- /dev/null +++ b/appl/cmd/fs/compose.b @@ -0,0 +1,100 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Cmpchan, + Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +AinB: con 1<<3; +BinA: con 1<<2; +AoutB: con 1<<1; +BoutA: con 1<<0; + +A: con AinB|AoutB; +AoverB: con AinB|AoutB|BoutA; +AatopB: con AinB|BoutA; +AxorB: con AoutB|BoutA; + +B: con BinA|BoutA; +BoverA: con BinA|BoutA|AoutB; +BatopA: con BinA|AoutB; +BxorA: con BoutA|AoutB; + +ops := array[] of { + AinB => "AinB", + BinA => "BinA", + AoutB => "AoutB", + BoutA => "BoutA", + A => "A", + AoverB => "AoverB", + AatopB => "AatopB", + AxorB => "AxorB", + B => "B", + BoverA => "BoverA", + BatopA => "BatopA", +}; + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +types(): string +{ + return "ms-d"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + c := chan of (ref Sys->Dir, ref Sys->Dir, chan of int); + s := (hd args).s().i; + for(i := 0; i < len ops; i++) + if(ops[i] == s) + break; + if(i == len ops){ + sys->fprint(sys->fildes(2), "fs: join: bad op %q\n", s); + return nil; + } + spawn compose(c, i, opts != nil); + return ref Value.M(c); +} + +compose(c: Cmpchan, op: int, dflag: int) +{ + t := array[4] of {* => 0}; + if(op & AinB) + t[2r11] = 2r01; + if(op & BinA) + t[2r11] = 2r10; + if(op & AoutB) + t[2r01] = 2r01; + if(op & BoutA) + t[2r10] = 2r10; + if(dflag){ + while(((d0, d1, reply) := <-c).t2 != nil){ + x := (d1 != nil) << 1 | d0 != nil; + r := t[d0 != nil | (d1 != nil) << 1]; + if(r == 0 && x == 2r11 && (d0.mode & d1.mode & Sys->DMDIR)) + r = 2r11; + reply <-= r; + } + }else{ + while(((d0, d1, reply) := <-c).t2 != nil) + reply <-= t[(d1 != nil) << 1 | d0 != nil]; + } +} diff --git a/appl/cmd/fs/depth.b b/appl/cmd/fs/depth.b new file mode 100644 index 00000000..19c03b2d --- /dev/null +++ b/appl/cmd/fs/depth.b @@ -0,0 +1,49 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "ps"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + d := int (hd args).s().i; + if(d <= 0){ + sys->fprint(sys->fildes(2), "fs: depth: invalid depth\n"); + return nil; + } + c := chan of Gatequery; + spawn depthgate(c, d); + return ref Value.P(c); +} + +depthgate(c: Gatechan, d: int) +{ + while((((dir, nil, depth), reply) := <-c).t0.t0 != nil) + reply <-= depth <= d; +} diff --git a/appl/cmd/fs/entries.b b/appl/cmd/fs/entries.b new file mode 100644 index 00000000..56aac67f --- /dev/null +++ b/appl/cmd/fs/entries.b @@ -0,0 +1,86 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "tx"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + sc := Entrychan(chan of int, chan of Entry); + spawn entriesproc((hd args).x().i, sc); + return ref Value.T(sc); +} + +entriesproc(c: Fschan, sc: Entrychan) +{ + if(<-sc.sync == 0){ + (<-c).t1 <-= Quit; + exit; + } + indent := 0; + names: list of string; + name: string; +loop: + for(;;){ + (d, reply) := <-c; + if(d.dir != nil){ + p: string; + depth := indent; + if(d.dir.mode & Sys->DMDIR){ + names = name :: names; + if(indent == 0) + name = d.dir.name; + else{ + if(name[len name - 1] != '/') + name[len name] = '/'; + name += d.dir.name; + } + indent++; + reply <-= Down; + p = name; + }else{ + p = name; + if(p[len p - 1] != '/') + p[len p] = '/'; + p += d.dir.name; + reply <-= Next; + } + if(p != nil) + sc.c <-= (d.dir, p, depth); + }else{ + reply <-= Next; + if(d.dir == nil && d.data == nil){ + if(--indent == 0) + break loop; + (name, names) = (hd names, tl names); + } + } + } + sc.c <-= Nilentry; +} diff --git a/appl/cmd/fs/eval.b b/appl/cmd/fs/eval.b new file mode 100644 index 00000000..5eaf9291 --- /dev/null +++ b/appl/cmd/fs/eval.b @@ -0,0 +1,648 @@ +implement Eval; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Context: import sh; +include "readdir.m"; +#include "env.m"; +# env: Env; +#include "string.m"; +# str: String; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Quit: import Fslib; + +# more general: +# eval: fn[V, M](ctxt: ref Context, r: ref Report, expr: string, args:...) with { +# V => +# typec: fn(t: self V): int; +# cvt: fn(t: self V, tc: int): V; +# cvt2s: fn(t: self V): (int, string); +# cvt2v: fn(t: self V): chan of int; +# mkstring: fn(s: string): V; +# mkcmd: fn(c: ref Sh->Cmd): V; +# discard: fn(t: self V); +# type2s: fn(c: int): string; +# loadmod: fn(cmd: string): M; +# M => +# types: fn(): string; +# init: fn(); +# run: fn(ctxt: ref Draw->Context, r: ref Report, cmd: string, +# opts: list of (int, list of V), args: list of V): V; +# } +# how to call eval? +# (eval with [V=>ref Value, M=>Fsmodule])( +# +# sort out error reporting; stderr is not good. + + +# possible things to do: +# pipe [-1pP] [-t command] command fs -> void +# pipe all files in fs through command. +# extract [-r root] gate fs -> fs +# extract the first entry within fs which +# passes through the gate. +# if -r is specified, the entry is placed +# within the given root, and may be a file, +# otherwise files are not allowed. +# apply string fs +# for each file in fs, evaluates string as an fs expression +# (which should yield fs), and replace the file in the +# original hierarchy with the result. +# e.g. +# fs apply '{unbundle $file}' {filter {or {mode +d} *.bundle} .} +# a bit fanciful this... +# merge could take an optional boolean operator +# +# venti? +# +# Cmpgate: chan of Cmpgatequery; +# Cmpgatequery: type (Entry, Entry, chan of int); +# returns 00, 01, 10 or 11 +# used by merge to decide what to do when merging +# used by write to decide what to do when writing +# +# cmpdate [-u] '>' +# cmpquery command + +Eval: module { + types: fn(): string; + init: fn(); + run: fn(ctxt: ref Draw->Context, r: ref Fslib->Report, + opts: list of Fslib->Option, args: list of ref Fslib->Value): ref Fslib->Value; + eval: fn(ctxt: ref Draw->Context, r: ref Fslib->Report, + expr: string, args: list of ref Fslib->Value, ret: int): ref Fslib->Value; +}; + +WORD, SHCMD, VAR: con iota; + +Evalstate: adt { + s: string; + spos: int; + drawctxt: ref Draw->Context; + report: ref Report; + args: array of ref Value; + verbose: int; + + expr: fn(p: self ref Evalstate): ref Value; + getc: fn(p: self ref Evalstate): int; + ungetc: fn(p: self ref Evalstate); + gettok: fn(p: self ref Evalstate): (int, string); +}; + +ops: list of (string, Fsmodule); +lock: chan of int; + +# to do: +# - change value letters to more appropriate (e.g. fs->f, entries->e, gate->g). +# - allow shell $variable expansions + +types(): string +{ + return "as-v"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: eval: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + fslib->init(); +# env = load Env Env->PATH; +# if(env == nil) +# badmod(Env->PATH); +# str = load String String->PATH; +# if(str == nil) +# badmod(String->PATH); + lock = chan[1] of int; +} + +run(ctxt: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + return (ref Evalstate((hd args).s().i, 0, ctxt, report, nil, opts != nil)).expr(); +} + +eval(ctxt: ref Draw->Context, report: ref Report, + expr: string, args: list of ref Value, rtype: int): ref Value +{ + a := array[len args] of ref Value; + for(i := 0; args != nil; args = tl args) + a[i++] = hd args; + e := ref Evalstate(expr, 0, ctxt, report, a, 0); + v := e.expr(); + vl: list of ref Value; + for(i = 0; i < len a; i++) + if(a[i] != nil) + vl = a[i] :: vl; + nv := cvt(e, v, rtype); + if(nv == nil){ + vl = v :: vl; + sys->fprint(stderr(), "fs: eval fn: %s cannot be converted to %s\n", + type2s(v.typec()), type2s(rtype)); + } + if(vl != nil) + spawn discard(nil, vl); + return nv; +} + +tok2s(t: int, s: string): string +{ + case t { + WORD => + return s; + SHCMD => + return "@"; + VAR => + return "$" + s; + } + return sys->sprint("%c", t); +} + +# expr: WORD exprs +# exprs: +# | exprs '{' expr '}' +# | exprs WORD +# | exprs SHCMD +# | exprs VAR +Evalstate.expr(p: self ref Evalstate): ref Value +{ + args: list of ref Value; + t: int; + s: string; + { + (t, s) = p.gettok(); + } exception e { + "parse error" => + return nil; + } + if(t != WORD){ + sys->fprint(stderr(), "fs: eval: syntax error (char %d), expected word, found %#q\n", + p.spos, tok2s(t, s)); + return nil; + } + cmd := s; +loop: + for(;;){ + { + (t, s) = p.gettok(); + } exception e { + "parse error" => + spawn discard(nil, args); + return nil; + } + case t { + '{' => + v := p.expr(); + if(v == nil){ + spawn discard(nil, args); + return nil; + } + args = v :: args; + '}' => + break loop; + WORD => + args = ref Value.S(s) :: args; + VAR => + n := int s; + if(n < 0 || n >= len p.args){ + sys->fprint(stderr(), "fs: eval: invalid arg reference $%s\n", s); + spawn discard(nil, args); + return nil; + } + if(p.args[n] == nil){ + sys->fprint(stderr(), "fs: eval: cannot use $%d twice\n", n); + spawn discard(nil, args); + return nil; + } + args = p.args[n] :: args; + p.args[n] = nil; + SHCMD => + if(sh == nil && (sh = load Sh Sh->PATH) == nil){ + sys->fprint(stderr(), "fs: eval: cannot load %s: %r\n", Sh->PATH); + spawn discard(nil, args); + return nil; + } + (c, err) := sh->parse(s); + if(c == nil){ + sys->fprint(stderr(), "fs: eval: cannot parse shell command @%s: %s\n", s, err); + spawn discard(nil, args); + return nil; + } + args = ref Value.C(c) :: args; + -1 => + break loop; + * => + spawn discard(nil, args); + sys->fprint(stderr(), "fs: eval: syntax error; unexpected token %d before char %d\n", t, p.spos); + return nil; + } + } + return runcmd(p, cmd, rev(args)); +} + +runcmd(p: ref Evalstate, cmd: string, args: list of ref Value): ref Value +{ + m := loadmodule(cmd); + if(m == nil){ + spawn discard(nil, args); + return nil; + } + otype := m->types(); + ok: int; + opts: list of Option; + (ok, opts, args) = cvtargs(p, args, cmd, otype); + if(ok == -1){ + sys->fprint(stderr(), "fs: eval: usage: %s\n", fslib->cmdusage(cmd, otype)); + spawn discard(opts, args); + return nil; + } + r := m->run(p.drawctxt, p.report, opts, args); + if(r == nil) + spawn discard(opts, args); + return r; +} + +cvtargs(e: ref Evalstate, args: list of ref Value, cmd, otype: string): (int, list of Option, list of ref Value) +{ + ok: int; + opts: list of Option; + (nil, at, t) := fslib->splittype(otype); + (ok, opts, args) = cvtopts(e, t, cmd, args); + if(ok == -1) + return (-1, opts, args); + if(len at < 1 || at[0] == '*'){ + sys->fprint(stderr(), "fs: eval: invalid type descriptor %#q for %#q\n", at, cmd); + return (-1, opts, args); + } + n := len args; + if(at[len at - 1] == '*'){ + tc := at[len at - 2]; + at = at[0:len at - 2]; + for(i := len at; i < n; i++) + at[i] = tc; + } + if(n != len at){ + sys->fprint(stderr(), "fs: eval: wrong number of arguments to %#q\n", cmd); + return (-1, opts, args); + } + d: list of ref Value; + (ok, args, d) = cvtvalues(e, at, cmd, args); + if(ok == -1) + args = join(args, d); + return (ok, opts, args); +} + +cvtvalues(e: ref Evalstate, t: string, cmd: string, args: list of ref Value): (int, list of ref Value, list of ref Value) +{ + cargs: list of ref Value; + for(i := 0; i < len t; i++){ + tc := t[i]; + if(args == nil){ + sys->fprint(stderr(), "fs: eval: %q missing argument of type %s\n", cmd, type2s(tc)); + return (-1, cargs, args); + } + v := cvt(e, hd args, tc); + if(v == nil){ + sys->fprint(stderr(), "fs: eval: %q: %s cannot be converted to %s\n", + cmd, type2s((hd args).typec()), type2s(tc)); + return (-1, cargs, args); + } + cargs = v :: cargs; + args = tl args; + } + return (0, rev(cargs), args); +} + +cvtopts(e: ref Evalstate, opttype: string, cmd: string, args: list of ref Value): (int, list of Option, list of ref Value) +{ + if(opttype == nil) + return (0, nil, args); + opts: list of Option; +getopts: + while(args != nil){ + s := ""; + pick v := hd args { + S => + s = v.i; + if(s == nil || s[0] != '-' || len s == 1) + s = nil; + else if(s == "--"){ + args = tl args; + s = nil; + } + } + if(s == nil) + return (0, opts, args); + s = s[1:]; + while(len s > 0){ + opt := s[0]; + if(((ok, t) := fslib->opttypes(opt, opttype)).t0 == -1){ + sys->fprint(stderr(), "fs: eval: %s: unknown option -%c\n", cmd, opt); + return (-1, opts, args); + } + if(t == nil){ + s = s[1:]; + opts = (opt, nil) :: opts; + }else{ + if(len s > 1) + args = ref Value.S(s[1:]) :: tl args; + else + args = tl args; + vl: list of ref Value; + (ok, vl, args) = cvtvalues(e, t, cmd, args); + if(ok == -1) + return (-1, opts, join(vl, args)); + opts = (opt, vl) :: opts; + continue getopts; + } + } + args = tl args; + } + return (0, opts, args); +} + +discard(ol: list of (int, list of ref Value), vl: list of ref Value) +{ + for(; ol != nil; ol = tl ol) + for(ovl := (hd ol).t1; ovl != nil; ovl = tl ovl) + vl = (hd ovl) :: vl; + for(; vl != nil; vl = tl vl) + (hd vl).discard(); +} + +loadmodule(cmd: string): Fsmodule +{ + lock <-= 0; + for(ol := ops; ol != nil; ol = tl ol) + if((hd ol).t0 == cmd) + break; + if(ol != nil){ + <-lock; + return (hd ol).t1; + } + p := cmd + ".dis"; + if(p[0] != '/' && !(p[0] == '.' && p[1] == '/')) + p = "/dis/fs/" + p; + m := load Fsmodule p; + if(m == nil){ + sys->fprint(stderr(), "fs: eval: cannot load %s: %r\n", p); + sys->fprint(stderr(), "fs: eval: unknown verb %#q\n", cmd); + sys->werrstr(sys->sprint("cannot load module %q", cmd)); + <-lock; + return nil; + } + { + m->init(); + } exception e { + "fail:*" => + <-lock; + sys->werrstr(sys->sprint("module init failed: %s", e[5:])); + return nil; + } + ops = (cmd, m) :: ops; + <-lock; + return m; +} + +runexternal(p: ref Evalstate, cmd: string, t: string, opts: list of Option, args: list of ref Value): ref Value +{ + m := loadmodule(cmd); + if(m == nil) + return nil; + if(!fslib->typecompat(t, m->types())){ + sys->fprint(stderr(), "fs: eval: %s has incompatible type\n", cmd); + sys->fprint(stderr(), "fs: eval: expected usage: %s\n", fslib->cmdusage(cmd, t)); + sys->fprint(stderr(), "fs: eval: actually usage: %s\n", fslib->cmdusage(cmd, m->types())); + return nil; + } + return m->run(p.drawctxt, p.report, opts, args); +} + +cvt(e: ref Evalstate, v: ref Value, t: int): ref Value +{ + { + return cvt1(e, v, t); + } exception { + "type conversion" => + return nil; + } +} + +cvt1(e: ref Evalstate, v: ref Value, t: int): ref Value +{ + if(v.typec() == t) + return v; + r: ref Value; + case t { + 't' => + r = runexternal(e, "entries", "tx", nil, cvt1(e, v, 'x') :: nil); + 'x' => + r = runexternal(e, "walk", "xs", nil, cvt1(e, v, 's') :: nil); + 'p' => + r = runexternal(e, "match", "ps", nil, cvt1(e, v, 's') :: nil); + 's' => + r = runexternal(e, "run", "sc", nil, cvt1(e, v, 'c') :: nil); + 'v' => + r = runexternal(e, "print", "vt", nil, cvt1(e, v, 't') :: nil); + } + if(r == nil) + raise "type conversion"; + return r; +} + +Evalstate.getc(p: self ref Evalstate): int +{ + c := -1; + if(p.spos < len p.s) + c = p.s[p.spos]; + p.spos++; + return c; +} + +Evalstate.ungetc(p: self ref Evalstate) +{ + p.spos--; +} + +# XXX backslash escapes newline? +Evalstate.gettok(p: self ref Evalstate): (int, string) +{ + while ((c := p.getc()) == ' ' || c == '\t') + ; + t: int; + s: string; + + case c { + -1 => + t = -1; + '\n' => + t = '\n'; + '{' => + t = '{'; + '}' => + t = '}'; + '@' => # embedded shell command + while((nc := p.getc()) == ' ' || nc == '\t') + ; + if(nc != '{'){ + sys->fprint(stderr(), "fs: eval: expected '{' after '@'\n"); + raise "parse error"; + } + s = "{"; + d := 1; + getcmd: + while((nc = p.getc()) != -1){ + s[len s] = nc; + case nc { + '{' => + d++; + '}' => + if(--d == 0) + break getcmd; + '\'' => + s += getqword(p, 1); + } + } + if(nc == -1){ + sys->fprint(stderr(), "fs: eval: unbalanced '{' in shell command\n"); + raise "parse error"; + } + t = SHCMD; + '$' => + t = VAR; + s = getvar(p); + '\'' => + s = getqword(p, 0); + t = WORD; + * => + do { + s[len s] = c; + c = p.getc(); + if (in(c, " \t{}\n")){ + p.ungetc(); + break; + } + } while (c >= 0); + t = WORD; + } + return (t, s); +} + +getvar(p: ref Evalstate): string +{ + c := p.getc(); + if(c == -1){ + sys->fprint(stderr(), "fs: eval: unexpected eof after '$'\n"); + raise "parse error"; + } + v: string; + while(in(c, " \t\n@{}'") == 0){ + v[len v] = c; + c = p.getc(); + } + p.ungetc(); + for(i := 0; i < len v; i++) + if(v[i] < '0' || v[i] > '9') + break; + if(i < len v || v == nil){ + sys->fprint(stderr(), "fs: eval: invalid $ reference $%q\n", v); + raise "parse error"; + } + return v; +} +# v: string; +# if(c == '\''){ +# v = getqword(p, 0); +# c = p.getc(); +# } else{ +# v[0] = c; +# while((c = p.getc()) != -1){ +# if(in(c, "a-zA-Z0-9*_") == 0) # heuristic stolen from rc +# break; +# v[len v] = c; +# } +# } +# vl := str->unquoted(env->getenv(v)); +# if(vl == nil){ +# sys->fprint(stderr(), "fs: eval: shell variable $%q has %d elements\n", v, len vl); +# raise "parse error"; +# } +# val := hd vl; +# if(c == -1 || in(c, " \t@{}\n")){ +# p.ungetc(); +# return (WORD, val); +# } +# (t, s) = p.gettok(); +# if(t != WORD){ +# sys->fprint(stderr(), "fs: eval: expected word after $%q\n", v); +# raise "parse error"; +# } +# s = val + s; +#} + +in(c: int, s: string): int +{ + for(i := 0; i < len s; i++) + if(s[i] == c) + return 1; + return 0; +} + +# get a quoted word; the starting quote has already been seen +getqword(p: ref Evalstate, keepq: int): string +{ + s := ""; + for(;;) { + while ((nc := p.getc()) != '\'' && nc >= 0) + s[len s] = nc; + if (nc == -1){ + sys->fprint(stderr(), "fs: eval: unterminated quote\n"); + raise "parse error"; + } + if (p.getc() != '\'') { + p.ungetc(); + if(keepq) + s[len s] = '\''; + return s; + } + s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy) + if(keepq) + s[len s] = '\''; + } +} + +rev[T](x: list of T): list of T +{ + l: list of T; + for(; x != nil; x = tl x) + l = hd x :: l; + return l; +} + +# join x to y, leaving result in arbitrary order. +join[T](x, y: list of T): list of T +{ + if(len x > len y) + (x, y) = (y, x); + for(; x != nil; x = tl x) + y = hd x :: y; + return y; +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} diff --git a/appl/cmd/fs/exec.b b/appl/cmd/fs/exec.b new file mode 100644 index 00000000..60beb74e --- /dev/null +++ b/appl/cmd/fs/exec.b @@ -0,0 +1,162 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Context: import sh; +include "fslib.m"; + fslib: Fslib; + Option, Value, Entrychan, Report: import fslib; + +# usage: exec [-n nfiles] [-t endcmd] [-pP] command entries +types(): string +{ + return "vct-ns-tc-p-P"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + sh = load Sh Sh->PATH; + if(sh == nil) + badmod(Sh->PATH); + sh->initialise(); +} + +run(drawctxt: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + n := 1; + pflag := 0; + tcmd: ref Sh->Cmd; + for(; opts != nil; opts = tl opts){ + o := hd opts; + case o.opt { + 'n' => + if((n = int (hd o.args).s().i) <= 0){ + sys->fprint(sys->fildes(2), "fs: exec: invalid argument to -n\n"); + return nil; + } + 't' => + tcmd = (hd o.args).c().i; + 'p' => + pflag = 1; + 'P' => + pflag = 2; + } + } + if(pflag && n > 1){ + sys->fprint(sys->fildes(2), "fs: exec: cannot specify -p with -n %d\n", n); + return nil; + } + cmd := (hd args).c().i; + c := (hd tl args).t().i; + sync := chan of int; + spawn execproc(drawctxt, sync, n, pflag, c, cmd, tcmd, report.start("exec")); + sync <-= 1; + return ref Value.V(sync); +} + +execproc(drawctxt: ref Draw->Context, sync: chan of int, n, pflag: int, + c: Entrychan, cmd, tcmd: ref Sh->Cmd, errorc: chan of string) +{ + sys->pctl(Sys->NEWFD, 0::1::2::nil); + ctxt := Context.new(drawctxt); + <-sync; + if(<-sync == 0){ + c.sync <-= 0; + errorc <-= nil; + exit; + } + c.sync <-= 1; + argv := ref Sh->Listnode(cmd, nil) :: nil; + + fl: list of ref Sh->Listnode; + nf := 0; + while(((d, p, nil) := <-c.c).t0 != nil){ + fl = ref Sh->Listnode(nil, p) :: fl; + if(++nf >= n){ + ctxt.set("file", rev(fl)); + if(pflag) + setstatenv(ctxt, d, pflag); + fl = nil; + nf = 0; + {ctxt.run(argv, 0);} exception {"fail:*" =>;} + } + } + if(nf > 0){ + ctxt.set("file", rev(fl)); + {ctxt.run(argv, 0);} exception {"fail:*" =>;} + } + if(tcmd != nil){ + ctxt.set("file", nil); + {ctxt.run(ref Sh->Listnode(tcmd, nil) :: nil, 0);} exception {"fail:*" =>;} + } + errorc <-= nil; +} + +setenv(ctxt: ref Context, var: string, val: list of string) +{ + ctxt.set(var, sh->stringlist2list(val)); +} + +setstatenv(ctxt: ref Context, dir: ref Sys->Dir, pflag: int) +{ + setenv(ctxt, "mode", modes(dir.mode) :: nil); + setenv(ctxt, "uid", dir.uid :: nil); + setenv(ctxt, "mtime", string dir.mtime :: nil); + setenv(ctxt, "length", string dir.length :: nil); + + if(pflag > 1){ + setenv(ctxt, "name", dir.name :: nil); + setenv(ctxt, "gid", dir.gid :: nil); + setenv(ctxt, "muid", dir.muid :: nil); + setenv(ctxt, "qid", sys->sprint("16r%ubx", dir.qid.path) :: string dir.qid.vers :: nil); + setenv(ctxt, "atime", string dir.atime :: nil); + setenv(ctxt, "dtype", sys->sprint("%c", dir.dtype) :: nil); + setenv(ctxt, "dev", string dir.dev :: nil); + } +} + +mtab := array[] of { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" +}; + +modes(mode: int): string +{ + s: string; + + if(mode & Sys->DMDIR) + s = "d"; + else if(mode & Sys->DMAPPEND) + s = "a"; + else if(mode & Sys->DMAUTH) + s = "A"; + else + s = "-"; + if(mode & Sys->DMEXCL) + s += "l"; + else + s += "-"; + s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; + return s; +} + +rev[T](x: list of T): list of T +{ + l: list of T; + for(; x != nil; x = tl x) + l = hd x :: l; + return l; +} diff --git a/appl/cmd/fs/filter.b b/appl/cmd/fs/filter.b new file mode 100644 index 00000000..9275cc7f --- /dev/null +++ b/appl/cmd/fs/filter.b @@ -0,0 +1,64 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fsfilter: Fsfilter; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + + +Query: adt { + gate: Gatechan; + dflag: int; + reply: chan of int; + query: fn(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int; +}; + +types(): string +{ + return "xpx-d"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + fsfilter = load Fsfilter Fsfilter->PATH; + if(fsfilter == nil) + badmod(Fsfilter->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + dst := chan of (Fsdata, chan of int); + spawn filterproc((hd tl args).x().i, dst, (hd args).p().i, opts != nil); + return ref Value.X(dst); +} + +filterproc(src, dst: Fschan, gate: Gatechan, dflag: int) +{ + fsfilter->filter(ref Query(gate, dflag, chan of int), src, dst); + gate <-= ((nil, nil, 0), nil); +} + +Query.query(q: self ref Query, d: ref Sys->Dir, name: string, depth: int): int +{ + if(depth == 0 || (q.dflag && (d.mode & Sys->DMDIR))) + return 1; + q.gate <-= ((d, name, depth), q.reply); + return <-q.reply; +} diff --git a/appl/cmd/fs/ls.b b/appl/cmd/fs/ls.b new file mode 100644 index 00000000..70beae48 --- /dev/null +++ b/appl/cmd/fs/ls.b @@ -0,0 +1,97 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "daytime.m"; + daytime: Daytime; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Option, Value, Entrychan, Report: import fslib; + +types(): string +{ + return "vt-u-m"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: ls: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + badmod(Daytime->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + sync := chan of int; + spawn lsproc(sync, opts, (hd args).t().i, daytime, report.start("ls")); + return ref Value.V(sync); +} + +lsproc(sync: chan of int, opts: list of Option, c: Entrychan, daytime: Daytime, errorc: chan of string) +{ + now := daytime->now(); + mflag := uflag := 0; + if(<-sync == 0){ + c.sync <-= 0; + errorc <-= nil; + } + c.sync <-= 1; + for(; opts != nil; opts = tl opts){ + case (hd opts).opt { + 'm' => + mflag = 1; + 'u' => + uflag = 1; + } + } + while(((dir, p, nil) := <-c.c).t0 != nil){ + t := dir.mtime; + if(uflag) + t = dir.atime; + s := sys->sprint("%s %c %d %s %s %bud %s %s\n", + modes(dir.mode), dir.dtype, dir.dev, + dir.uid, dir.gid, dir.length, + daytime->filet(now, dir.mtime), p); + if(mflag) + s = "[" + dir.muid + "] " + s; + sys->print("%s", s); + } + errorc <-= nil; +} + +mtab := array[] of { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" +}; + +modes(mode: int): string +{ + s: string; + + if(mode & Sys->DMDIR) + s = "d"; + else if(mode & Sys->DMAPPEND) + s = "a"; + else if(mode & Sys->DMAUTH) + s = "A"; + else + s = "-"; + if(mode & Sys->DMEXCL) + s += "l"; + else + s += "-"; + s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; + return s; +} diff --git a/appl/cmd/fs/match.b b/appl/cmd/fs/match.b new file mode 100644 index 00000000..331867a9 --- /dev/null +++ b/appl/cmd/fs/match.b @@ -0,0 +1,79 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "filepat.m"; + filepat: Filepat; +include "regex.m"; + regex: Regex; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "ps-a-r"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + regex = load Regex Regex->PATH; + if(regex == nil) + badmod(Regex->PATH); + filepat = load Filepat Filepat->PATH; + if(filepat == nil) + badmod(Filepat->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + pat := (hd args).s().i; + aflag := rflag := 0; + for(; opts != nil; opts = tl opts){ + case (hd opts).opt { + 'a' => + aflag = 1; + 'r' => + rflag = 1; + } + } + v := ref Value.P(chan of Gatequery); + re: Regex->Re; + if(rflag){ + err: string; + (re, err) = regex->compile(pat, 0); + if(re == nil){ + sys->fprint(sys->fildes(2), "fs: match: regex error on %#q: %s\n", pat, err); + return nil; + } + } + spawn matchproc(v.i, aflag, pat, re); + return v; +} + +matchproc(c: Gatechan, all: int, pat: string, re: Regex->Re) +{ + while((((d, name, nil), reply) := <-c).t0.t0 != nil){ + if(all == 0) + name = d.name; + if(re != nil) + reply <-= regex->execute(re, name) != nil; # XXX should anchor it? + else + reply <-= filepat->match(pat, name); + } +} diff --git a/appl/cmd/fs/merge.b b/appl/cmd/fs/merge.b new file mode 100644 index 00000000..977102b2 --- /dev/null +++ b/appl/cmd/fs/merge.b @@ -0,0 +1,187 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s: import fslib; + Fschan, Fsdata, Entrychan, Cmpchan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +# e.g.... +# fs select {mode -d} {merge -c {compose -d AoutB} {filter {not {path /chan /dev /usr/rog /n/local /net}} /} {merge {proto FreeBSD} {proto Hp} {proto Irix} {proto Linux} {proto MacOSX} {proto Nt} {proto Nt.ti} {proto Nt.ti925} {proto Plan9} {proto Plan9.ti} {proto Plan9.ti925} {proto Solaris} {proto authsrv} {proto dl} {proto dlsrc} {proto ep7} {proto inferno} {proto inferno.ti} {proto ipaqfs} {proto minitel} {proto os} {proto scheduler.client} {proto scheduler.server} {proto sds} {proto src} {proto src.ti} {proto sword} {proto ti925.ti} {proto ti925bin} {proto tipaq} {proto umec} {proto utils} {proto utils.ti}}} >[2] /dev/null + +types(): string +{ + return "xxxx*-1-cm"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil){ + sys->fprint(sys->fildes(2), "fs: cannot load %s: %r\n", Fslib->PATH); + raise "fail:bad module"; + } +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + recurse := 1; + cmp: Cmpchan; + for(; opts != nil; opts = tl opts){ + case (hd opts).opt { + '1' => + recurse = 0; + 'c' => + cmp = (hd (hd opts).args).m().i; + } + } + dst := chan of (Fsdata, chan of int); + spawn mergeproc((hd args).x().i, (hd tl args).x().i, dst, recurse, cmp, tl tl args == nil); + for(args = tl tl args; args != nil; args = tl args){ + dst1 := chan of (Fsdata, chan of int); + spawn mergeproc(dst, (hd args).x().i, dst1, recurse, cmp, tl args == nil); + dst = dst1; + } + return ref Value.X(dst); +} + +# merge two trees; assume directories are alphabetically sorted. +mergeproc(c0, c1, dst: Fschan, recurse: int, cmp: Cmpchan, killcmp: int) +{ + myreply := chan of int; + ((d0, nil), reply0) := <-c0; + ((d1, nil), reply1) := <-c1; + + if(compare(cmp, d0, d1) == 2r10) + dst <-= ((d1, nil), myreply); + else + dst <-= ((d0, nil), myreply); + r := <-myreply; + reply0 <-= r; + reply1 <-= r; + if(r == Down){ + { + mergedir(c0, c1, dst, recurse, cmp); + } exception {"exit" =>;} + } + if(cmp != nil && killcmp) + cmp <-= (nil, nil, nil); +} + +mergedir(c0, c1, dst: Fschan, recurse: int, cmp: Cmpchan) +{ + myreply := chan of int; + reply0, reply1: chan of int; + d0, d1: ref Sys->Dir; + eof0 := eof1 := 0; + for(;;){ + if(!eof0 && d0 == nil){ + ((d0, nil), reply0) = <-c0; + if(d0 == nil){ + reply0 <-= Next; + eof0 = 1; + } + } + if(!eof1 && d1 == nil){ + ((d1, nil), reply1) = <-c1; + if(d1 == nil){ + reply1 <-= Next; + eof1 = 1; + } + } + if(eof0 && eof1) + break; + + (wd0, wd1) := (d0, d1); + if(d0 != nil && d1 != nil && d0.name != d1.name){ + if(d0.name < d1.name) + wd1 = nil; + else + wd0 = nil; + } + + wc0, wc1: Fschan; + wreply0, wreply1: chan of int; + weof0, weof1: int; + + c := compare(cmp, wd0, wd1); + if(wd0 != nil && wd1 != nil){ + if(c != 0 && recurse && (wd0.mode & wd1.mode & Sys->DMDIR) != 0){ + dst <-= ((wd0, nil), myreply); + r := <-myreply; + reply0 <-= r; + reply1 <-= r; + d0 = d1 = nil; + case r { + Quit => + raise "exit"; + Skip => + return; + Down => + mergedir(c0, c1, dst, 1, cmp); + } + continue; + } + # when we can't merge and there's a clash, choose c0 over c1, unless cmp says otherwise + if(c == 2r10){ + reply0 <-= Next; + d0 = nil; + }else{ + reply1 <-= Next; + d1 = nil; + } + } + if(c & 2r01){ + (wd0, wc0, wreply0, weof0) = (d0, c0, reply0, eof0); + (wd1, wc1, wreply1, weof1) = (d1, c1, reply1, eof1); + d0 = nil; + }else if(c & 2r10){ + (wd0, wc0, wreply0, weof0) = (d1, c1, reply1, eof1); + (wd1, wc1, wreply1, weof1) = (d0, c0, reply0, eof0); + d1 = nil; + }else{ + if(wd0 == nil){ + reply1 <-= Next; + d1 = nil; + }else{ + reply0 <-= Next; + d0 = nil; + } + continue; + } + dst <-= ((wd0, nil), myreply); + r := <-myreply; + wreply0 <-= r; + if(r == Down) + r = fslib->copy(wc0, dst); # XXX hmm, maybe this should be a mergedir() + case r { + Quit or + Skip => + if(wd1 == nil && !weof1) + (nil, wreply1) = <-wc1; + wreply1 <-= r; + if(r == Quit) + raise "exit"; + return; + } + } + dst <-= ((nil, nil), myreply); + if(<-myreply == Quit) + raise "exit"; +} + +compare(cmp: Cmpchan, d0, d1: ref Sys->Dir): int +{ + mask := (d0 != nil) | (d1 != nil) << 1; + if(cmp == nil) + return mask; + reply := chan of int; + cmp <-= (d0, d1, reply); + return <-reply & mask; +} diff --git a/appl/cmd/fs/mergewrite.b b/appl/cmd/fs/mergewrite.b new file mode 100644 index 00000000..3ff1b1f1 --- /dev/null +++ b/appl/cmd/fs/mergewrite.b @@ -0,0 +1,186 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "readdir.m"; + readdir: Readdir; +include "fslib.m"; + fslib: Fslib; + Report, Value, quit, report: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Cmpchan, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "vmsx"; # XXX bad argument ordering... +} + +init() +{ + sys = load Sys Sys->PATH; + readdir = load Readdir Readdir->PATH; + if(readdir == nil){ + sys->fprint(sys->fildes(2), "fs: mergewrite: cannot load %s: %r\n", Readdir->PATH); + raise "fail:bad module"; + } + readdir->init(nil, 0); + + fslib = load Fslib Fslib->PATH; + if(fslib == nil){ + sys->fprint(sys->fildes(2), "fs: mergewrite: cannot load %s: %r\n", Fslib->PATH); + raise "fail:bad module"; + } +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + sync := chan of int; + spawn fswriteproc(sync, (hd args).m().i, (hd tl args).s().i, (hd tl tl args).x().i, report.start("mergewrite")); + <-sync; + return ref Value.V(sync); +} + +fswriteproc(sync: chan of int, cmp: Cmpchan, root: string, c: Fschan, errorc: chan of string) +{ + sys->pctl(Sys->FORKNS, nil); + sync <-= 1; + if(<-sync == 0){ + (<-c).t1 <-= Quit; + quit(errorc); + } + + ((d, nil), reply) := <-c; + if(root != nil){ + d = ref *d; + d.name = root; + } + fswritedir(d.name, cmp, d, reply, c, errorc); + quit(errorc); +} + +fswritedir(path: string, cmp: Cmpchan, dir: ref Sys->Dir, dreply: chan of int, c: Fschan, errorc: chan of string) +{ + fd: ref Sys->FD; + if(dir.mode & Sys->DMDIR){ + fd = sys->create(dir.name, Sys->OREAD, dir.mode|8r300); + made := fd != nil; + if(fd == nil && (fd = sys->open(dir.name, Sys->OREAD)) == nil){ + dreply <-= Next; + report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, dir.mode|8r300)); + return; + } + # XXX if we haven't just made it, we should chmod the old entry u+w to enable writing. + if(sys->chdir(dir.name) == -1){ # XXX beware of names starting with '#' + dreply <-= Next; + report(errorc, sys->sprint("cannot cd to %q: %r", path)); + fd = nil; + sys->remove(dir.name); + return; + } + dreply <-= Down; + entries: array of ref Sys->Dir; + if(made == 0) + entries = readdir->readall(fd, Readdir->NAME|Readdir->COMPACT).t0; + i := 0; + eod := 0; + d0, d1: ref Sys->Dir; + reply: chan of int; + path[len path] = '/'; + for(;;){ + if(!eod && d0 == nil){ + ((d0, nil), reply) = <-c; + if(d0 == nil){ + reply <-= Next; + eod = 1; + } + } + if(d1 == nil && i < len entries) + d1 = entries[i++]; + if(d0 == nil && d1 == nil) + break; + + (wd0, wd1) := (d0, d1); + if(d0 != nil && d1 != nil && d0.name != d1.name){ + if(d0.name < d1.name) + wd1 = nil; + else + wd0 = nil; + } + r := compare(cmp, wd0, wd1); + if(wd1 != nil && (r & 2r10) == 0){ + if(wd1.mode & Sys->DMDIR) + rmdir(wd1.name); + else + remove(wd1.name); + d1 = nil; + } + if(wd0 != nil){ + if((r & 2r01) == 0) + reply <-= Next; + else + fswritedir(path + wd0.name, cmp, d0, reply, c, errorc); + d0 = nil; + } + } + sys->chdir(".."); + if((dir.mode & 8r300) != 8r300){ + ws := Sys->nulldir; + ws.mode = dir.mode; + if(sys->fwstat(fd, ws) == -1) + report(errorc, sys->sprint("cannot wstat %q: %r", path)); + } + }else{ + fd = sys->create(dir.name, Sys->OWRITE, dir.mode); + if(fd == nil){ + dreply <-= Next; + report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, dir.mode|8r300)); + return; + } + dreply <-= Down; + while((((nil, buf), reply) := <-c).t0.data != nil){ + nw := sys->write(fd, buf, len buf); + if(nw < len buf){ + if(nw == -1) + errorc <-= sys->sprint("error writing %q: %r", path); + else + errorc <-= sys->sprint("short write"); + reply <-= Skip; + break; + } + reply <-= Next; + } + reply <-= Next; + } +} + +rmdir(name: string) +{ + (d, n) := readdir->init(name, Readdir->NONE|Readdir->COMPACT); + for(i := 0; i < n; i++){ + path := name+"/"+d[i].name; + if(d[i].mode & Sys->DMDIR) + rmdir(path); + else + remove(path); + } + remove(name); +} + +remove(name: string) +{ + if(sys->remove(name) < 0) + sys->fprint(sys->fildes(2), "mergewrite: cannot remove %q: %r\n", name); +} + +compare(cmp: Cmpchan, d0, d1: ref Sys->Dir): int +{ + mask := (d0 != nil) | (d1 != nil) << 1; + if(cmp == nil) + return mask; + reply := chan of int; + cmp <-= (d0, d1, reply); + return <-reply & mask; +} diff --git a/appl/cmd/fs/mkfile b/appl/cmd/fs/mkfile new file mode 100644 index 00000000..37fb5e12 --- /dev/null +++ b/appl/cmd/fs/mkfile @@ -0,0 +1,60 @@ +<../../../mkconfig +# fs write /n/local/n/fossil/usr/inferno {filter {and {not {or *.dis *.sbl}} {path /appl/cmd/fs /module/fslib.m /appl/lib/fslib.b /appl/cmd/fs.b /man/1/fs}} /} +TARG=\ + and.dis\ + bundle.dis\ + chstat.dis\ + compose.dis\ + depth.dis\ + entries.dis\ + eval.dis\ + exec.dis\ + filter.dis\ + ls.dis\ + match.dis\ + merge.dis\ + mergewrite.dis\ + mode.dis\ + not.dis\ + or.dis\ + path.dis\ + pipe.dis\ + print.dis\ + proto.dis\ + query.dis\ + run.dis\ + select.dis\ + setroot.dis\ + size.dis\ + unbundle.dis\ + walk.dis\ + write.dis\ + void.dis\ + + +INS= ${TARG:%=$ROOT/dis/fs/%} + +SYSMODULES=\ + bufio.m\ + draw.m\ + sh.m\ + sys.m\ + bundle.m\ + fslib.m\ + +DISBIN=$ROOT/dis/fs + +<$ROOT/mkfiles/mkdis + +all:V: $TARG + +install:V: $INS + +nuke:V: clean + rm -f $INS + +clean:V: + rm -f *.dis *.sbl + +uninstall:V: + rm -f $INS diff --git a/appl/cmd/fs/mode.b b/appl/cmd/fs/mode.b new file mode 100644 index 00000000..83d385d7 --- /dev/null +++ b/appl/cmd/fs/mode.b @@ -0,0 +1,120 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +# XXX implement octal modes. + +User: con 8r700; +Group: con 8r070; +Other: con 8r007; +All: con User | Group | Other; + +Read: con 8r444; +Write: con 8r222; +Exec: con 8r111; + +types(): string +{ + return "ps"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + spec := (hd args).s().i; + (ok, mask, mode) := parsemode(spec); + if(ok == 0){ + sys->fprint(sys->fildes(2), "fs: mode: bad mode %#q\n", spec); + return nil; + } + c := chan of Gatequery; + spawn modegate(c, mask, mode); + return ref Value.P(c); +} + +modegate(c: Gatechan, mask, mode: int) +{ + m := mode & mask; + while((((d, nil, nil), reply) := <-c).t0.t0 != nil) + reply <-= ((d.mode & mask) ^ m) == 0; +} + +# stolen from /appl/cmd/chmod.b +parsemode(spec: string): (int, int, int) +{ + mask := Sys->DMAPPEND | Sys->DMEXCL | Sys->DMDIR | Sys->DMAUTH; +loop: + for(i := 0; i < len spec; i++){ + case spec[i] { + 'u' => + mask |= User; + 'g' => + mask |= Group; + 'o' => + mask |= Other; + 'a' => + mask |= All; + * => + break loop; + } + } + if(i == len spec) + return (0, 0, 0); + if(i == 0) + mask |= All; + + op := spec[i++]; + if(op != '+' && op != '-' && op != '=') + return (0, 0, 0); + + mode := 0; + for(; i < len spec; i++){ + case spec[i]{ + 'r' => + mode |= Read; + 'w' => + mode |= Write; + 'x' => + mode |= Exec; + 'a' => + mode |= Sys->DMAPPEND; + 'l' => + mode |= Sys->DMEXCL; + 'd' => + mode |= Sys->DMDIR; + 'A' => + mode |= Sys->DMAUTH; + * => + return (0, 0, 0); + } + } + if(op == '+' || op == '-') + mask &= mode; + if(op == '-') + mode = ~mode; + return (1, mask, mode); +} + + diff --git a/appl/cmd/fs/not.b b/appl/cmd/fs/not.b new file mode 100644 index 00000000..e318f855 --- /dev/null +++ b/appl/cmd/fs/not.b @@ -0,0 +1,48 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "pp"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + c := chan of Gatequery; + spawn notgate(c, (hd args).p().i); + return ref Value.P(c); +} + +notgate(c, sub: Gatechan) +{ + myreply := chan of int; + while(((d, reply) := <-c).t0.t0 != nil){ + sub <-= (d, myreply); + reply <-= !<-myreply; + } + sub <-= (Nilentry, nil); +} diff --git a/appl/cmd/fs/or.b b/appl/cmd/fs/or.b new file mode 100644 index 00000000..ca6668d1 --- /dev/null +++ b/appl/cmd/fs/or.b @@ -0,0 +1,65 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "pppp*"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + c := chan of Gatequery; + spawn orgate(c, args); + return ref Value.P(c); +} + +orgate(c: Gatechan, args: list of ref Value) +{ + sub: list of Gatechan; + for(; args != nil; args = tl args) + sub = (hd args).p().i :: sub; + sub = rev(sub); + myreply := chan of int; + while(((d, reply) := <-c).t0.t0 != nil){ + for(l := sub; l != nil; l = tl l){ + (hd l) <-= (d, myreply); + if(<-myreply) + break; + } + reply <-= l != nil; + } + for(; sub != nil; sub = tl sub) + hd sub <-= (Nilentry, nil); +} + +rev[T](x: list of T): list of T +{ + l: list of T; + for(; x != nil; x = tl x) + l = hd x :: l; + return l; +} diff --git a/appl/cmd/fs/path.b b/appl/cmd/fs/path.b new file mode 100644 index 00000000..2b8c98a0 --- /dev/null +++ b/appl/cmd/fs/path.b @@ -0,0 +1,77 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "pss*-x"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + # XXX cleanname all paths? + c := chan of Gatequery; + p: list of string; + for(; args != nil; args = tl args) + p = (hd args).s().i :: p; + spawn pathgate(c, opts != nil, p); + return ref Value.P(c); +} + +pathgate(c: Gatechan, xflag: int, paths: list of string) +{ + if(xflag){ + while((((d, path, nil), reply) := <-c).t0.t0 != nil){ + for(q := paths; q != nil; q = tl q){ + r := 1; + p := hd q; + if(len path > len p) + r = path[len p] != '/' || path[0:len p] != p; + else if(len path == len p) + r = path != p; + if(r == 0) + break; + } + reply <-= q == nil; + } + }else{ + while((((d, path, nil), reply) := <-c).t0.t0 != nil){ + for(q := paths; q != nil; q = tl q){ + r := 0; + p := hd q; + if(len path > len p) + r = path[len p] == '/' && path[0:len p] == p; + else if(len path == len p) + r = path == p; + else + r = p[len path] == '/' && p[0:len path] == path; + if(r) + break; + } + reply <-= q != nil; + } + } +} diff --git a/appl/cmd/fs/pipe.b b/appl/cmd/fs/pipe.b new file mode 100644 index 00000000..665abdeb --- /dev/null +++ b/appl/cmd/fs/pipe.b @@ -0,0 +1,223 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Context: import sh; +include "fslib.m"; + fslib: Fslib; + Option, Value, Fschan, Report, quit: import fslib; + Skip, Next, Down, Quit: import fslib; + + +# pipe the contents of the files in a filesystem through +# a command. -1 causes one command only to be executed. +# -p and -P (exclusive to -1) cause stat modes to be set in the shell environment. +types(): string +{ + return "vcx-1-p-P"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + sh = load Sh Sh->PATH; + if(sh == nil) + badmod(Sh->PATH); + sh->initialise(); +} + +run(drawctxt: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + n := 1; + oneflag := pflag := 0; + for(; opts != nil; opts = tl opts){ + o := hd opts; + case o.opt { + '1' => + oneflag = 1; + 'p' => + pflag = 1; + 'P' => + pflag = 2; + } + } + if(pflag && oneflag){ + sys->fprint(sys->fildes(2), "fs: exec: cannot specify -p with -1\n"); + return nil; + } + cmd := (hd args).c().i; + c := (hd tl args).x().i; + sync := chan of int; + spawn execproc(drawctxt, sync, oneflag, pflag, c, cmd, report.start("exec")); + sync <-= 1; + return ref Value.V(sync); +} + +execproc(drawctxt: ref Draw->Context, sync: chan of int, oneflag, pflag: int, + c: Fschan, cmd: ref Sh->Cmd, errorc: chan of string) +{ + sys->pctl(Sys->NEWFD, 0::1::2::nil); + ctxt := Context.new(drawctxt); + <-sync; + if(<-sync == 0){ + (<-c).t1 <-= Quit; + quit(errorc); + } + argv := ref Sh->Listnode(cmd, nil) :: nil; + fd: ref Sys->FD; + result := chan of string; + if(oneflag){ + fd = popen(ctxt, argv, result); + if(fd == nil){ + (<-c).t1 <-= Quit; + quit(errorc); + } + } + + names: list of string; + name: string; + indent := 0; + for(;;){ + (d, reply) := <-c; + if(d.dir == nil){ + reply <-= Next; + if(--indent == 0){ + break; + } + (name, names) = (hd names, tl names); + continue; + } + if((d.dir.mode & Sys->DMDIR) != 0){ + reply <-= Down; + names = name :: names; + if(indent > 0 && name != nil && name[len name - 1] != '/') + name[len name] = '/'; + name += d.dir.name; + indent++; + continue; + } + if(!oneflag){ + p := name; + if(p != nil && p[len p - 1] != '/') + p[len p] = '/'; + setenv(ctxt, "file", p + d.dir.name :: nil); + if(pflag) + setstatenv(ctxt, d.dir, pflag); + fd = popen(ctxt, argv, result); + } + if(fd == nil){ + reply <-= Next; + continue; + } + reply <-= Down; + for(;;){ + data: array of byte; + ((nil, data), reply) = <-c; + reply <-= Next; + if(data == nil) + break; + n := -1; + {n = sys->write(fd, data, len data);}exception {"write on closed pipe" => ;} + if(n != len data){ + if(oneflag){ + (<-c).t1 <-= Quit; + quit(errorc); + } + (<-c).t1 <-= Skip; + break; + } + } + if(!oneflag){ + fd = nil; + <-result; + } + } + fd = nil; + if(oneflag) + <-result; + quit(errorc); +} + +popen(ctxt: ref Context, argv: list of ref Sh->Listnode, result: chan of string): ref Sys->FD +{ + sync := chan of int; + fds := array[2] of ref Sys->FD; + sys->pipe(fds); + spawn runcmd(ctxt, argv, fds[0], sync, result); + <-sync; + return fds[1]; +} + +runcmd(ctxt: ref Context, argv: list of ref Sh->Listnode, stdin: ref Sys->FD, sync: chan of int, result: chan of string) +{ + sys->pctl(Sys->FORKFD, nil); + sys->dup(stdin.fd, 0); + stdin = nil; + sys->pctl(Sys->NEWFD, 0::1::2::nil); + ctxt = ctxt.copy(0); + sync <-= 0; + r := ctxt.run(argv, 0); + ctxt = nil; + sys->pctl(Sys->NEWFD, nil); + result <-=r; +} + +setenv(ctxt: ref Context, var: string, val: list of string) +{ + ctxt.set(var, sh->stringlist2list(val)); +} + +setstatenv(ctxt: ref Context, dir: ref Sys->Dir, pflag: int) +{ + setenv(ctxt, "mode", modes(dir.mode) :: nil); + setenv(ctxt, "uid", dir.uid :: nil); + setenv(ctxt, "mtime", string dir.mtime :: nil); + setenv(ctxt, "length", string dir.length :: nil); + + if(pflag > 1){ + setenv(ctxt, "name", dir.name :: nil); + setenv(ctxt, "gid", dir.gid :: nil); + setenv(ctxt, "muid", dir.muid :: nil); + setenv(ctxt, "qid", sys->sprint("16r%ubx", dir.qid.path) :: string dir.qid.vers :: nil); + setenv(ctxt, "atime", string dir.atime :: nil); + setenv(ctxt, "dtype", sys->sprint("%c", dir.dtype) :: nil); + setenv(ctxt, "dev", string dir.dev :: nil); + } +} + +mtab := array[] of { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" +}; + +modes(mode: int): string +{ + s: string; + + if(mode & Sys->DMDIR) + s = "d"; + else if(mode & Sys->DMAPPEND) + s = "a"; + else if(mode & Sys->DMAUTH) + s = "A"; + else + s = "-"; + if(mode & Sys->DMEXCL) + s += "l"; + else + s += "-"; + s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; + return s; +} diff --git a/appl/cmd/fs/print.b b/appl/cmd/fs/print.b new file mode 100644 index 00000000..21761e0e --- /dev/null +++ b/appl/cmd/fs/print.b @@ -0,0 +1,51 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "vt"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + sync := chan of int; + spawn printproc(sync, (hd args).t().i, report.start("print")); + return ref Value.V(sync); +} + +printproc(sync: chan of int, c: Entrychan, errorc: chan of string) +{ + if(<-sync == 0){ + c.sync <-= 0; + quit(errorc); + exit; + } + c.sync <-= 1; + while(((d, p, nil) := <-c.c).t0 != nil) + sys->print("%s\n", p); + quit(errorc); +} diff --git a/appl/cmd/fs/proto.b b/appl/cmd/fs/proto.b new file mode 100644 index 00000000..bc836f44 --- /dev/null +++ b/appl/cmd/fs/proto.b @@ -0,0 +1,388 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "readdir.m"; + readdir: Readdir; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "string.m"; + str: String; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, report, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +File: adt { + name: string; + mode: int; + owner: string; + group: string; + old: string; + flags: int; + sub: cyclic array of ref File; +}; + +Proto: adt { + indent: int; + lastline: string; + iob: ref Iobuf; +}; + +Star, Plus: con 1<<iota; + +types(): string +{ + return "xs-rs"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: proto: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + badmod(Readdir->PATH); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + badmod(Bufio->PATH); + str = load String String->PATH; + if(str == nil) + badmod(String->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + protofile := (hd args).s().i; + rootpath: string; + if(opts != nil) + rootpath = (hd (hd opts).args).s().i; + if(rootpath == nil) + rootpath = "/"; + + proto := ref Proto(0, nil, nil); + if((proto.iob = bufio->open(protofile, Sys->OREAD)) == nil){ + sys->fprint(sys->fildes(2), "fs: proto: cannot open %q: %r\n", protofile); + return nil; + } + root := ref File(rootpath, ~0, nil, nil, nil, 0, nil); + (root.flags, root.sub) = readproto(proto, -1); + c := chan of (Fsdata, chan of int); + spawn protowalk(c, root, report.start("proto")); + return ref Value.X(c); +} + +protowalk(c: Fschan, root: ref File, errorc: chan of string) +{ + protowalk1(c, root.flags, root.name, file2dir(root, nil), root.sub, errorc); + quit(errorc); +} + +protowalk1(c: Fschan, flags: int, path: string, d: ref Sys->Dir, + sub: array of ref File, errorc: chan of string): int +{ + reply := chan of int; + c <-= ((d, nil), reply); + case r := <-reply { + Quit => + quit(errorc); + Next or + Skip => + return r; + } + (a, n) := readdir->init(path, Readdir->NAME|Readdir->COMPACT); + if(len a == 0){ + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; + } + j := 0; + prevsub: string; + for(i := 0; i < n; i++){ + for(; j < len sub; j++){ + s := sub[j].name; + if(s == prevsub){ + report(errorc, sys->sprint("duplicate entry %s", pathconcat(path, s))); + continue; # eliminate duplicates in proto + } + if(s >= a[i].name || sub[j].old != nil) + break; + report(errorc, sys->sprint("%s not found", pathconcat(path, s))); + } + foundsub := j < len sub && (sub[j].name == a[i].name || sub[j].old != nil); + if(foundsub || flags&Plus || + (flags&Star && (a[i].mode & Sys->DMDIR)==0)){ + f: ref File; + if(foundsub){ + f = sub[j++]; + prevsub = f.name; + } + p: string; + d: ref Sys->Dir; + if(foundsub && f.old != nil){ + p = f.old; + (ok, xd) := sys->stat(p); + if(ok == -1){ + report(errorc, sys->sprint("cannot stat %q: %r", p)); + continue; + } + d = ref xd; + }else{ + p = pathconcat(path, a[i].name); + d = a[i]; + } + + d = file2dir(f, d); + r: int; + if((d.mode & Sys->DMDIR) == 0) + r = walkfile(c, p, d, errorc); + else if(flags & Plus) + r = protowalk1(c, Plus, p, d, nil, errorc); + else + r = protowalk1(c, f.flags, p, d, f.sub, errorc); + if(r == Skip) + return Next; + } + } + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; +} + +pathconcat(p, name: string): string +{ + if(p != nil && p[len p - 1] != '/') + p[len p] = '/'; + p += name; + return p; +} + +# from(ish) walk.b +walkfile(c: Fschan, path: string, d: ref Sys->Dir, errorc: chan of string): int +{ + reply := chan of int; + fd := sys->open(path, Sys->OREAD); + if(fd == nil){ + report(errorc, sys->sprint("cannot open %q: %r", path)); + return Next; + } + c <-= ((d, nil), reply); + case r := <-reply { + Quit => + quit(errorc); + Next or + Skip => + return r; + } + length := d.length; + for(n := big 0; n < length; ){ + nr := Sys->ATOMICIO; + if(n + big Sys->ATOMICIO > length) + nr = int (length - n); + buf := array[nr] of byte; + nr = sys->read(fd, buf, nr); + if(nr <= 0){ + if(nr < 0) + report(errorc, sys->sprint("error reading %q: %r", path)); + else + report(errorc, sys->sprint("%q is shorter than expected (%bd/%bd)", + path, n, length)); + break; + }else if(nr < len buf) + buf = buf[0:nr]; + c <-= ((nil, buf), reply); + case <-reply { + Quit => + quit(errorc); + Skip => + return Next; + } + n += big nr; + } + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; +} + +readproto(proto: ref Proto, indent: int): (int, array of ref File) +{ + a := array[10] of ref File; + n := 0; + flags := 0; + while((f := readline(proto, indent)) != nil){ + if(f.name == "*") + flags |= Star; + else if(f.name == "+") + flags |= Plus; + else{ + (f.flags, f.sub) = readproto(proto, proto.indent); + if(n == len a) + a = (array[n * 2] of ref File)[0:] = a; + a[n++] = f; + } + } + if(n < len a) + a = (array[n] of ref File)[0:] = a[0:n]; + mergesort(a, array[n] of ref File); + return (flags, a); +} + +readline(proto: ref Proto, indent: int): ref File +{ + s: string; + if(proto.lastline != nil){ + s = proto.lastline; + proto.lastline = nil; + }else if(proto.indent == -1) + return nil; + else if((s = proto.iob.gets('\n')) == nil){ + proto.indent = -1; + return nil; + } + spc := 0; + for(i := 0; i < len s; i++){ + c := s[i]; + if(c == ' ') + spc++; + else if(c == '\t') + spc += 8; + else + break; + } + if(i == len s || s[i] == '#' || s[i] == '\n') + return readline(proto, indent); # XXX sort out tail recursion! + if(spc <= indent){ + proto.lastline = s; + return nil; + } + proto.indent = spc; + (n, toks) := sys->tokenize(s, " \t\n"); + f := ref File(nil, ~0, nil, nil, nil, 0, nil); + (f.name, toks) = (getname(hd toks, 0), tl toks); + if(toks == nil) + return f; + (f.mode, toks) = (getmode(hd toks), tl toks); + if(toks == nil) + return f; + (f.owner, toks) = (getname(hd toks, 1), tl toks); + if(toks == nil) + return f; + (f.group, toks) = (getname(hd toks, 1), tl toks); + if(toks == nil) + return f; + (f.old, toks) = (hd toks, tl toks); + return f; +} + +mergesort(a, b: array of ref File) +{ + r := len a; + if (r > 1) { + m := (r-1)/2 + 1; + mergesort(a[0:m], b[0:m]); + mergesort(a[m:], b[m:]); + b[0:] = a; + for ((i, j, k) := (0, m, 0); i < m && j < r; k++) { + if(b[i].name > b[j].name) + a[k] = b[j++]; + else + a[k] = b[i++]; + } + if (i < m) + a[k:] = b[i:m]; + else if (j < r) + a[k:] = b[j:r]; + } +} + +getname(s: string, allowminus: int): string +{ + if(s == nil) + return nil; + if(allowminus && s == "-") + return nil; + if(s[0] == '$') + return getenv(s[1:]); + return s; +} + +getenv(s: string): string +{ + # XXX implement env variables + return nil; +} + +getmode(s: string): int +{ + s = getname(s, 1); + if(s == nil) + return ~0; + m := 0; + i := 0; + if(s[i] == 'd'){ + m |= Sys->DMDIR; + i++; + } + if(i < len s && s[i] == 'a'){ + m |= Sys->DMAPPEND; + i++; + } + if(i < len s && s[i] == 'l'){ + m |= Sys->DMEXCL; + i++; + } + (xmode, t) := str->toint(s, 8); + if(t != nil){ + # report(aux.errorc, "bad mode specification %q", s); + return ~0; + } + return xmode | m; +} + +file2dir(f: ref File, old: ref Sys->Dir): ref Sys->Dir +{ + d := ref Sys->nulldir; + if(old != nil){ + if(old.dtype != 'M'){ + d.uid = "sys"; + d.gid = "sys"; + xmode := (old.mode >> 6) & 7; + d.mode = old.mode | xmode | (xmode << 3); + }else{ + d.uid = old.uid; + d.gid = old.gid; + d.mode = old.mode; + } + d.length = old.length; + d.mtime = old.mtime; + d.atime = old.atime; + d.muid = old.muid; + d.name = old.name; + } + if(f != nil){ + d.name = f.name; + if(f.owner != nil) + d.uid = f.owner; + if(f.group != nil) + d.gid = f.group; + if(f.mode != ~0) + d.mode = f.mode; + } + return d; +} diff --git a/appl/cmd/fs/query.b b/appl/cmd/fs/query.b new file mode 100644 index 00000000..421be1d9 --- /dev/null +++ b/appl/cmd/fs/query.b @@ -0,0 +1,130 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Context: import sh; +include "fslib.m"; + fslib: Fslib; + Option, Value, Gatechan, Gatequery, Report, Nilentry: import fslib; + +types(): string +{ + return "pc-p-P"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: query: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + sh = load Sh Sh->PATH; + if(sh == nil) + badmod(Sh->PATH); +} + +run(drawctxt: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + pflag := 0; + for(; opts != nil; opts = tl opts){ + o := hd opts; + case o.opt { + 'p' => + pflag = 1; + 'P' => + pflag = 2; + } + } + + v := ref Value.P(chan of Gatequery); + spawn querygate(drawctxt, v.i, (hd args).c().i, pflag); + v.i <-= (Nilentry, nil); + return v; +} + +querygate(drawctxt: ref Draw->Context, c: Gatechan, cmd: ref Sh->Cmd, pflag: int) +{ + sys->pctl(Sys->NEWFD, 0::1::2::nil); + ctxt := Context.new(drawctxt); + <-c; + argv := ref Sh->Listnode(cmd, nil) :: nil; + while((((d, p, nil), reply) := <-c).t0.t0 != nil){ + ctxt.set("file", ref Sh->Listnode(nil, p) :: nil); + if(pflag) + setstatenv(ctxt, d, pflag); + err := ""; + { + err = ctxt.run(argv, 0); + } exception e { + "fail:*" => + err = e; + } + reply <-= (err == nil); + } +} + +# XXX shouldn't duplicate this... + +setenv(ctxt: ref Context, var: string, val: list of string) +{ + ctxt.set(var, sh->stringlist2list(val)); +} + +setstatenv(ctxt: ref Context, dir: ref Sys->Dir, pflag: int) +{ + setenv(ctxt, "mode", modes(dir.mode) :: nil); + setenv(ctxt, "uid", dir.uid :: nil); + setenv(ctxt, "mtime", string dir.mtime :: nil); + setenv(ctxt, "length", string dir.length :: nil); + + if(pflag > 1){ + setenv(ctxt, "name", dir.name :: nil); + setenv(ctxt, "gid", dir.gid :: nil); + setenv(ctxt, "muid", dir.muid :: nil); + setenv(ctxt, "qid", sys->sprint("16r%ubx", dir.qid.path) :: string dir.qid.vers :: nil); + setenv(ctxt, "atime", string dir.atime :: nil); + setenv(ctxt, "dtype", sys->sprint("%c", dir.dtype) :: nil); + setenv(ctxt, "dev", string dir.dev :: nil); + } +} + +start(startc: chan of (string, chan of string), name: string): chan of string +{ + c := chan of string; + startc <-= (name, c); + return c; +} + +mtab := array[] of { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" +}; + +modes(mode: int): string +{ + s: string; + + if(mode & Sys->DMDIR) + s = "d"; + else if(mode & Sys->DMAPPEND) + s = "a"; + else if(mode & Sys->DMAUTH) + s = "A"; + else + s = "-"; + if(mode & Sys->DMEXCL) + s += "l"; + else + s += "-"; + s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; + return s; +} diff --git a/appl/cmd/fs/readfile.b b/appl/cmd/fs/readfile.b new file mode 100644 index 00000000..4a52ae08 --- /dev/null +++ b/appl/cmd/fs/readfile.b @@ -0,0 +1,144 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, report, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +this is a bad idea, i think +i think walk + filter + setroot is good enough. + +types(): string +{ + # usage: readfile [-f file] name + return "xs-fs"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: readfile: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + path: string; + f := (hd args).s().i; + fd: ref Sys->FD; + seekable: int; + if(f == "-"){ + if(opts == nil){ + sys->fprint(sys->fildes(2), "fs: readfile: must specify a path when reading stdin\n"); + return nil; + } + fd = sys->fildes(0); + seekable = 0; + }else{ + fd = sys->open(f, Sys->OREAD); + seekable = isseekable(fd); + } + if(fd == nil){ + sys->fprint(sys->fildes(2), "fs: readfile: cannot open %s: %r\n", f); + return nil; + } + if(opts != nil) + path = (hd (hd opts).args).s().i; + else + path = f; + + (root, file) := pathsplit(path); + if(file == nil || file == "." || file == ".."){ + sys->fprint(sys->fildes(2), "fs: readfile: invalid filename %q\n", fname); + return nil; + } + d.name = file; + v := ref Value.X(chan of (Fsdata, chan of int)); + spawn readproc(v.i, fd, root, ref d, seekable, report.start("read")); + return v; +} + +readproc(c: Fschan, fd: ref Sys->FD, root: string, d: ref Sys->Dir, seekable: int, errorc: chan of string) +{ + reply := chan of int; + rd := ref Sys->nulldir; + rd.name = root; + c <-= ((rd, nil), reply); + if(<-reply != Down) + quit(errorc); + + c <-= ((d, nil), reply); + case <-reply { + Down => + sendfile(c, fd, errorc); + Skip or + Quit => + quit(errorc); + } + c <-= ((nil, nil), reply); + <-reply; + quit(errorc); +} + +sendfile(c: Fschan, data: list of array of byte, length: big, errorc: chan of string) +{ + reply := chan of int; + for(;;){ + buf: array of byte; + if(fd != nil){ + buf := array[Sys->ATOMICIO] of byte; + if((n := sys->read(fd, buf, len buf)) <= 0){ + if(n < 0) + report(errorc, sys->sprint("read error: %r")); + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return; + } + c <-= ((nil, buf), reply); + case <-reply { + Quit => + quit(errorc); + Skip => + return; + } + } +} + +pathsplit(p: string): (string, string) +{ + for (i := len p - 1; i >= 0; i--) + if (p[i] != '/') + break; + if (i < 0) + return (p, nil); + p = p[0:i+1]; + for (i = len p - 1; i >=0; i--) + if (p[i] == '/') + break; + if (i < 0) + return (".", p); + return (p[0:i+1], p[i+1:]); +} + +# dodgy heuristic... avoid, or using the stat-length of pipes and net connections +isseekable(fd: ref Sys->FD): int +{ + (ok, stat) := sys->stat(iob.fd); + if(ok != -1 && stat.dtype == '|' || stat.dtype == 'I') + return 0; + return 1; +} diff --git a/appl/cmd/fs/run.b b/appl/cmd/fs/run.b new file mode 100644 index 00000000..a5734d7c --- /dev/null +++ b/appl/cmd/fs/run.b @@ -0,0 +1,60 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Context: import sh; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "sc"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + sh = load Sh Sh->PATH; + if(sh == nil) + badmod(Sh->PATH); + sh->initialise(); +} + +run(drawctxt: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + c := (hd args).c().i; + ctxt := Context.new(drawctxt); + ctxt.setlocal("s", nil); + { + ctxt.run(ref Sh->Listnode(c, nil)::nil, 0); + } exception e { + "fail:*" => + sys->fprint(sys->fildes(2), "fs: run: exception %q raised in %s\n", e[5:], sh->cmd2string(c)); + return nil; + } + sl := ctxt.get("s"); + if(sl == nil || tl sl != nil){ + sys->fprint(sys->fildes(2), "fs: run: $s has %d members; exactly one is required\n", len sl); + return nil; + } + s := (hd sl).word; + if(s == nil && (hd sl).cmd != nil) + s = sh->cmd2string((hd sl).cmd); + return ref Value.S(s); +} diff --git a/appl/cmd/fs/select.b b/appl/cmd/fs/select.b new file mode 100644 index 00000000..9fe5e4b0 --- /dev/null +++ b/appl/cmd/fs/select.b @@ -0,0 +1,56 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "tpt"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + dst := Entrychan(chan of int, chan of Entry); + spawn selectproc((hd tl args).t().i, dst, (hd args).p().i); + return ref Value.T(dst); +} + +selectproc(src, dst: Entrychan, query: Gatechan) +{ + if(<-dst.sync == 0){ + query <-= (Nilentry, nil); + src.sync <-= 0; + exit; + } + src.sync <-= 1; + reply := chan of int; + while((d := <-src.c).t0 != nil){ + query <-= (d, reply); + if(<-reply) + dst.c <-= d; + } + dst.c <-= Nilentry; + query <-= (Nilentry, nil); +} diff --git a/appl/cmd/fs/setroot.b b/appl/cmd/fs/setroot.b new file mode 100644 index 00000000..d39a4cf4 --- /dev/null +++ b/appl/cmd/fs/setroot.b @@ -0,0 +1,104 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +# set the root +types(): string +{ + return "xsx-c"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + root := (hd args).s().i; + if(root == nil && opts == nil){ + sys->fprint(sys->fildes(2), "fs: setroot: empty path\n"); + return nil; + } + v := ref Value.X(chan of (Fsdata, chan of int)); + spawn setroot((hd tl args).x().i, v.i, root, opts != nil); + return v; +} + +setroot(src, dst: Fschan, root: string, cflag: int) +{ + ((d, nil), reply) := <-src; + if(cflag){ + createroot(src, dst, root, d, reply); + }else{ + myreply := chan of int; + rd := ref *d; + rd.name = root; + dst <-= ((rd, nil), myreply); + if(<-myreply == Down){ + reply <-= Down; + fslib->copy(src, dst); + } + } +} + +createroot(src, dst: Fschan, root: string, d: ref Sys->Dir, reply: chan of int) +{ + if(root == nil) + root = d.name; + (n, elems) := sys->tokenize(root, "/"); # XXX should really do a cleanname first + if(root[0] == '/'){ + elems = "/" :: elems; + n++; + } + myreply := chan of int; + lev := 0; + r := -1; + for(; elems != nil; elems = tl elems){ + rd := ref *d; + rd.name = hd elems; + dst <-= ((rd, nil), myreply); + case r = <-myreply { + Quit => + (<-src).t1 <-= Quit; + exit; + Skip => + break; + Next => + lev++; + break; + } + lev++; + } + if(r == Down){ + reply <-= Down; + if(fslib->copy(src, dst) == Quit) + exit; + }else + reply <-= Quit; + while(lev-- > 1){ + dst <-= ((nil, nil), myreply); + if(<-myreply == Quit){ + (<-src).t1 <-= Quit; + exit; + } + } +} diff --git a/appl/cmd/fs/size.b b/appl/cmd/fs/size.b new file mode 100644 index 00000000..d72edd99 --- /dev/null +++ b/appl/cmd/fs/size.b @@ -0,0 +1,54 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "vt"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + sync := chan of int; + spawn sizeproc(sync, (hd args).t().i, report.start("size")); + return ref Value.V(sync); +} + +sizeproc(sync: chan of int, c: Entrychan, errorc: chan of string) +{ + if(<-sync == 0){ + c.sync <-= 0; + quit(errorc); + exit; + } + c.sync <-= 1; + + size := big 0; + while(((d, nil, nil) := <-c.c).t0 != nil) + size += d.length; + sys->print("%bd\n", size); + quit(errorc); +} diff --git a/appl/cmd/fs/template.b b/appl/cmd/fs/template.b new file mode 100644 index 00000000..9b08f589 --- /dev/null +++ b/appl/cmd/fs/template.b @@ -0,0 +1,35 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "nil"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: size: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ +} diff --git a/appl/cmd/fs/unbundle.b b/appl/cmd/fs/unbundle.b new file mode 100644 index 00000000..ad500e8e --- /dev/null +++ b/appl/cmd/fs/unbundle.b @@ -0,0 +1,259 @@ +implement Unbundle; +include "sys.m"; + sys: Sys; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "string.m"; + str: String; +include "bundle.m"; + bundle: Bundle; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value,quit, report: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Quit, Next, Skip, Down, + Option: import Fslib; +include "unbundle.m"; + +types(): string +{ + return "xs"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: exec: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + badmod(Bufio->PATH); + str = load String String->PATH; + if(str == nil) + badmod(String->PATH); + bundle = load Bundle Bundle->PATH; + if(bundle == nil) + badmod(Bundle->PATH); + bundle->init(); +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + p := (hd args).s().i; + iob: ref Bufio->Iobuf; + if(p == "-") + iob = bufio->fopen(sys->fildes(0), Sys->OREAD); + else + iob = bufio->open(p, Sys->OREAD); + if(iob == nil){ + sys->fprint(sys->fildes(2), "fs: unbundle: cannot open %q: %r\n", p); + return nil; + } + seekable := p != "-"; + if(seekable) + seekable = isseekable(iob.fd); + return ref Value.X(unbundle(report, iob, seekable, Sys->ATOMICIO)); +} + +# dodgy heuristic... avoid, or using the stat-length of pipes and net connections +isseekable(fd: ref Sys->FD): int +{ + (ok, stat) := sys->fstat(fd); + if(ok != -1 && stat.dtype == '|' || stat.dtype == 'I') + return 0; + return 1; +} + +unbundle(r: ref Report, iob: ref Iobuf, seekable, blocksize: int): Fschan +{ + c := chan of (Fsdata, chan of int); + spawn unbundleproc(iob, c, seekable, blocksize, r.start("bundle")); + return c; +} + +EOF: con "end of archive\n"; + +unbundleproc(iob: ref Iobuf, c: Fschan, seekable, blocksize: int, errorc: chan of string) +{ + reply := chan of int; + p := iob.gets('\n'); + # XXX overall header? + if(p == nil || p == EOF){ + fslib->sendnulldir(c); + quit(errorc); + } + d := header2dir(p); + if(d == nil){ + fslib->sendnulldir(c); + report(errorc, "invalid first header"); + quit(errorc); + } + if((d.mode & Sys->DMDIR) == 0){ + fslib->sendnulldir(c); + report(errorc, "first entry is not a directory"); + quit(errorc); + } + c <-= ((d, nil), reply); + case r := <-reply { + Down => + unbundledir(iob, c, 0, seekable, blocksize, errorc); + c <-= ((nil, nil), reply); + <-reply; + Skip or + Next => + unbundledir(iob, c, 1, seekable, blocksize, errorc); + Quit => + break; + } + quit(errorc); +} + +unbundledir(iob: ref Iobuf, c: Fschan, + skipping, seekable, blocksize: int, errorc: chan of string): int +{ + reply := chan of int; + while((p := iob.gets('\n')) != nil){ + if(p == EOF) + break; + if(p[0] == '\n') + break; + d := header2dir(p); + if(d == nil){ + report(errorc, sys->sprint("invalid bundle header %q", p[0:len p - 1])); + return -1; + } + if(d.mode & Sys->DMDIR){ + if(skipping) + continue; + c <-= ((d, nil), reply); + case <-reply { + Quit => + quit(errorc); + Down => + r := unbundledir(iob, c, 0, seekable, blocksize, errorc); + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + if(r == -1) + return -1; + Skip => + if(unbundledir(iob, c, 1, seekable, blocksize, errorc) == -1) + return -1; + skipping = 1; + Next => + if(unbundledir(iob, c, 1, seekable, blocksize, errorc) == -1) + return -1; + } + }else{ + if(skipping){ + if(skipdata(iob, d.length, seekable) == -1) + return -1; + }else{ + case unbundlefile(iob, d, c, errorc, seekable, blocksize) { + -1 => + return -1; + Skip => + skipping = 1; + } + } + } + } + if(p == nil) + report(errorc, "unexpected eof"); + return 0; +} + +skipdata(iob: ref Iobuf, length: big, seekable: int): int +{ + if(seekable){ + iob.seek(big length, Sys->SEEKRELA); + return 0; + } + buf := array[Sys->ATOMICIO] of byte; + for(n := big 0; n < length; ){ + nb := Sys->ATOMICIO; + if(length - n < big Sys->ATOMICIO) + nb = int (length - n); + nb = iob.read(buf, nb); + if(nb <= 0) + return -1; + n += big nb; + } + return 0; +} + +unbundlefile(iob: ref Iobuf, d: ref Sys->Dir, + c: Fschan, errorc: chan of string, seekable, blocksize: int): int +{ + reply := chan of int; + c <-= ((d, nil), reply); + case <-reply { + Quit => + quit(errorc); + Skip => + if(skipdata(iob, d.length, seekable) == -1) + return -1; + return Skip; + Next => + if(skipdata(iob, d.length, seekable) == -1) + return -1; + return Next; + } + length := d.length; + for(n := big 0; n < length; ){ + nr := blocksize; + if(n + big blocksize > length) + nr = int (length - n); + buf := array[nr] of byte; + nr = iob.read(buf, nr); + if(nr <= 0){ + if(nr < 0) + report(errorc, sys->sprint("read error: %r")); + else + report(errorc, sys->sprint("premature eof")); + return -1; + }else if(nr < len buf) + buf = buf[0:nr]; + c <-= ((nil, buf), reply); + n += big nr; + case <-reply { + Quit => + quit(errorc); + Skip => + if(skipdata(iob, length - n, seekable) == -1) + return -1; + return Next; + } + } + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; +} + +header2dir(s: string): ref Sys->Dir +{ + toks := str->unquoted(s); + nf := len toks; + if(nf != 6) + return nil; + d := ref Sys->nulldir; + (d.name, toks) = (hd toks, tl toks); + (d.mode, toks) = (str->toint(hd toks, 8).t0, tl toks); + (d.uid, toks) = (hd toks, tl toks); + (d.gid, toks) = (hd toks, tl toks); + (d.mtime, toks) = (int hd toks, tl toks); + (d.length, toks) = (big hd toks, tl toks); + return d; +} diff --git a/appl/cmd/fs/void.b b/appl/cmd/fs/void.b new file mode 100644 index 00000000..0f8c72fa --- /dev/null +++ b/appl/cmd/fs/void.b @@ -0,0 +1,33 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, Option: import fslib; + +types(): string +{ + return "vv"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: void: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); +} + +run(nil: ref Draw->Context, nil: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + return (hd args).v(); +} diff --git a/appl/cmd/fs/walk.b b/appl/cmd/fs/walk.b new file mode 100644 index 00000000..1c5016bd --- /dev/null +++ b/appl/cmd/fs/walk.b @@ -0,0 +1,233 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "readdir.m"; + readdir: Readdir; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, report, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +Loopcheck: adt { + a: array of list of ref Sys->Dir; + + new: fn(): ref Loopcheck; + enter: fn(l: self ref Loopcheck, d: ref Sys->Dir): int; + leave: fn(l: self ref Loopcheck, d: ref Sys->Dir); +}; + +types(): string +{ + return "xs-bs"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: walk: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + badmod(Readdir->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + path := (hd args).s().i; + (ok, d) := sys->stat(path); + if(ok== -1){ + sys->fprint(sys->fildes(2), "fs: walk: cannot stat %q: %r\n", path); + return nil; + } + if((d.mode & Sys->DMDIR) == 0){ + # XXX could produce an fs containing just the single file. + # would have to split the path though. + sys->fprint(sys->fildes(2), "fs: walk: %q is not a directory\n", path); + return nil; + } + sync := chan of int; + c := chan of (Fsdata, chan of int); + spawn fswalkproc(sync, path, c, Sys->ATOMICIO, report.start("walk")); + <-sync; + return ref Value.X(c); +} + +# XXX need to avoid loops in the filesystem... +fswalkproc(sync: chan of int, path: string, c: Fschan, blocksize: int, errorc: chan of string) +{ + sys->pctl(Sys->FORKNS, nil); + sync <-= 1; + # XXX could allow a single root file? + if(sys->chdir(path) == -1){ + report(errorc, sys->sprint("cannot cd to %q: %r", path)); + fslib->sendnulldir(c); + quit(errorc); + } + (ok, d) := sys->stat("."); + if(ok == -1){ + report(errorc, sys->sprint("cannot stat %q: %r", path)); + fslib->sendnulldir(c); + quit(errorc); + } + d.name = path; + reply := chan of int; + c <-= ((ref d, nil), reply); + if(<-reply == Down){ + loopcheck := Loopcheck.new(); + loopcheck.enter(ref d); + if(path[len path - 1] != '/') + path[len path] = '/'; + fswalkdir(path, c, blocksize, loopcheck, errorc); + c <-= ((nil, nil), reply); + <-reply; + } + quit(errorc); +} + +fswalkdir(path: string, c: Fschan, blocksize: int, loopcheck: ref Loopcheck, errorc: chan of string) +{ + reply := chan of int; + (a, n) := readdir->init(".", Readdir->NAME|Readdir->COMPACT); + if(n == -1){ + report(errorc, sys->sprint("cannot readdir %q: %r", path)); + return; + } + for(i := 0; i < n; i++) + if(a[i].mode & Sys->DMDIR) + if(loopcheck.enter(a[i]) == 0) + a[i].dtype = ~0; +directory: + for(i = 0; i < n; i++){ + if(a[i].mode & Sys->DMDIR){ + d := a[i]; + if(d.dtype == ~0){ + report(errorc, sys->sprint("filesystem loop at %#q", path + d.name)); + continue; + } + if(sys->chdir("./" + d.name) == -1){ + report(errorc, sys->sprint("cannot cd to %#q: %r", path + a[i].name)); + continue; + } + c <-= ((d, nil), reply); + case <-reply { + Quit => + quit(errorc); + Down => + fswalkdir(path + a[i].name + "/", c, blocksize, loopcheck, errorc); + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + Skip => + sys->chdir(".."); + i++; + break directory; + Next => + break; + } + if(sys->chdir("..") == -1) # XXX what should we do if this fails? + report(errorc, sys->sprint("failed to cd .. from %#q: %r\n", path + a[i].name)); + + } else { + if(fswalkfile(path, a[i], c, blocksize, errorc) == Skip) + break directory; + } + } + for(i = n - 1; i >= 0; i--) + if(a[i].mode & Sys->DMDIR && a[i].dtype != ~0) + loopcheck.leave(a[i]); +} + +fswalkfile(path: string, d: ref Sys->Dir, c: Fschan, blocksize: int, errorc: chan of string): int +{ + reply := chan of int; + fd := sys->open(d.name, Sys->OREAD); + if(fd == nil){ + report(errorc, sys->sprint("cannot open %q: %r", path+d.name)); + return Next; + } + c <-= ((d, nil), reply); + case <-reply { + Quit => + quit(errorc); + Skip => + return Skip; + Next => + return Next; + Down => + break; + } + length := d.length; + for(n := big 0; n < length; ){ + nr := blocksize; + if(n + big blocksize > length) + nr = int (length - n); + buf := array[nr] of byte; + nr = sys->read(fd, buf, nr); + if(nr <= 0){ + if(nr < 0) + report(errorc, sys->sprint("error reading %q: %r", path + d.name)); + else + report(errorc, sys->sprint("%q is shorter than expected (%bd/%bd)", + path + d.name, n, length)); + break; + }else if(nr < len buf) + buf = buf[0:nr]; + c <-= ((nil, buf), reply); + case <-reply { + Quit => + quit(errorc); + Skip => + return Next; + } + n += big nr; + } + c <-= ((nil, nil), reply); + if(<-reply == Quit) + quit(errorc); + return Next; +} + +HASHSIZE: con 32; + +issamedir(d0, d1: ref Sys->Dir): int +{ + (q0, q1) := (d0.qid, d1.qid); + return q0.path == q1.path && + q0.qtype == q1.qtype && + d0.dtype == d1.dtype && + d0.dev == d1.dev; +} + +Loopcheck.new(): ref Loopcheck +{ + return ref Loopcheck(array[HASHSIZE] of list of ref Sys->Dir); +} + +# XXX we're assuming no-one modifies the values in d behind our back... +Loopcheck.enter(l: self ref Loopcheck, d: ref Sys->Dir): int +{ + slot := int d.qid.path & (HASHSIZE-1); + for(ll := l.a[slot]; ll != nil; ll = tl ll) + if(issamedir(d, hd ll)) + return 0; + l.a[slot] = d :: l.a[slot]; + return 1; +} + +Loopcheck.leave(l: self ref Loopcheck, d: ref Sys->Dir) +{ + slot := int d.qid.path & (HASHSIZE-1); + l.a[slot] = tl l.a[slot]; +} diff --git a/appl/cmd/fs/write.b b/appl/cmd/fs/write.b new file mode 100644 index 00000000..934d4d67 --- /dev/null +++ b/appl/cmd/fs/write.b @@ -0,0 +1,111 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, quit, report: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "vsx"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil){ + sys->fprint(sys->fildes(2), "fs: write: cannot load %s: %r\n", Fslib->PATH); + raise "fail:bad module"; + } +} + +run(nil: ref Draw->Context, report: ref Report, + nil: list of Option, args: list of ref Value): ref Value +{ + sync := chan of int; + spawn fswriteproc(sync, (hd args).s().i, (hd tl args).x().i, report.start("fswrite")); + <-sync; + return ref Value.V(sync); +} + +fswriteproc(sync: chan of int, root: string, c: Fschan, errorc: chan of string) +{ + sys->pctl(Sys->FORKNS, nil); + sync <-= 1; + if(<-sync == 0){ + (<-c).t1 <-= Quit; + quit(errorc); + } + + (d, reply) := <-c; + if(root != nil){ + d.dir = ref *d.dir; + d.dir.name = root; + } + fswritedir(d.dir.name, d, reply, c, errorc); + quit(errorc); +} + +fswritedir(path: string, d: Fsdata, dreply: chan of int, c: Fschan, errorc: chan of string) +{ + fd: ref Sys->FD; + if(d.dir.mode & Sys->DMDIR){ + fd = sys->create(d.dir.name, Sys->OREAD, d.dir.mode|8r300); + if(fd == nil && (fd = sys->open(d.dir.name, Sys->OREAD)) == nil){ + dreply <-= Next; + report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, d.dir.mode|8r300)); + return; + } + if(sys->chdir(d.dir.name) == -1){ # XXX beware of names starting with '#' + dreply <-= Next; + report(errorc, sys->sprint("cannot cd to %q: %r", path)); + fd = nil; + sys->remove(d.dir.name); + return; + } + dreply <-= Down; + path[len path] = '/'; + for(;;){ + (ent, reply) := <-c; + if(ent.dir == nil){ + reply <-= Next; + break; + } + fswritedir(path + ent.dir.name, ent, reply, c, errorc); + } + sys->chdir(".."); + if((d.dir.mode & 8r300) != 8r300){ + ws := Sys->nulldir; + ws.mode = d.dir.mode; + if(sys->fwstat(fd, ws) == -1) + report(errorc, sys->sprint("cannot wstat %q: %r", path)); + } + }else{ + fd = sys->create(d.dir.name, Sys->OWRITE, d.dir.mode); + if(fd == nil){ + dreply <-= Next; + report(errorc, sys->sprint("cannot create %q, mode %uo: %r", path, d.dir.mode|8r300)); + return; + } + dreply <-= Down; + while((((nil, buf), reply) := <-c).t0.data != nil){ + nw := sys->write(fd, buf, len buf); + if(nw < len buf){ + if(nw == -1) + errorc <-= sys->sprint("error writing %q: %r", path); + else + errorc <-= sys->sprint("short write"); + reply <-= Skip; + break; + } + reply <-= Next; + } + reply <-= Next; + } +} diff --git a/appl/cmd/ftest.b b/appl/cmd/ftest.b new file mode 100644 index 00000000..46fc6c54 --- /dev/null +++ b/appl/cmd/ftest.b @@ -0,0 +1,153 @@ +implement Ftest; +# +# test file permissions or attributes +# + +include "sys.m"; + sys: Sys; +include "draw.m"; + +stderr: ref Sys->FD; + +Ftest: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +Topr, Topw, Topx, Tope, Topf, Topd, Tops: con iota; + +init(nil: ref Draw->Context, argl: list of string) +{ + if(argl == nil) + return; + + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + if (tl argl == nil) + usage(); + + a := hd tl argl; + argl = tl tl argl; + ok := 0; + case a { + "-f" => + ok = filck(nxtarg(argl), Topf); + "-d" => + ok = filck(nxtarg(argl), Topd); + "-r" => + ok = filck(nxtarg(argl), Topr); + "-w" => + ok = filck(nxtarg(argl), Topw); + "-x" => + ok = filck(nxtarg(argl), Topx); + "-e" => + ok = filck(nxtarg(argl), Tope); + "-s" => + ok = filck(nxtarg(argl), Tops); + "-t" => + fd := 1; + if (argl != nil) { + if (!isint(hd argl)) { + sys->fprint(stderr, "ftest: bad argument to -t\n"); + usage(); + } + fd = int hd argl; + } + ok = isatty(fd); + * => + sys->fprint(stderr, "test: unknown option %s\n", a); + usage(); + } + if (!ok) + raise "fail:false"; +} + +nxtarg(argl: list of string): string +{ + if(argl == nil) { + sys->fprint(stderr, "test: argument expected\n"); + usage(); + } + return hd argl; +} + +usage() +{ + sys->fprint(stderr, "usage: (ftest -fdrwxes file)|(ftest -t fdno)\n"); + raise "fail:usage"; +} + +isint(s: string): int +{ + if(s == nil) + return 0; + for(i := 0; i < len s; i++) + if(s[i] < '0' || s[i] > '9') + return 0; + return 1; +} + + +filck(fname: string, Top: int): int +{ + (ok, dir) := sys->stat(fname); + + if(ok >= 0) { + ok = 0; + case Top { + Topr => # readable + ok = permck(dir, 8r004); + Topw => # writable + ok = permck(dir, 8r002); + Topx => # executable + ok = permck(dir, 8r001); + Tope => # exists + ok = 1; + Topf => # is a regular file + ok = (dir.mode & Sys->DMDIR) == 0; + Topd => # is a directory + ok = (dir.mode & Sys->DMDIR) != 0; + Tops => # has length > 0 + ok = dir.length > big 0; + } + } + + return ok > 0; +} + +permck(dir: Sys->Dir, mask: int): int +{ + uid, gid: string; + fd := sys->open("/dev/user", Sys->OREAD); + if(fd != nil) { + buf := array [28] of byte; + n := sys->read(fd, buf, len buf); + if(n > 0) + uid = string buf[0:n]; + } + # how do I find out what my group is? + + ok := dir.mode & mask<<0; + if(!ok && dir.gid == gid) + ok = dir.mode & mask<<3; + if(!ok && dir.uid == uid) + ok = dir.mode & mask<<6; + + return ok > 0; +} + +isatty(fd: int): int +{ + d1, d2: Sys->Dir; + + ok: int; + (ok, d1) = sys->fstat(sys->fildes(fd)); + if(ok < 0) + return 0; + (ok, d2) = sys->stat("/dev/cons"); + if(ok < 0) + return 0; + + return d1.dtype==d2.dtype && d1.dev==d2.dev && d1.qid.path==d2.qid.path; +} diff --git a/appl/cmd/ftpfs.b b/appl/cmd/ftpfs.b new file mode 100644 index 00000000..010fe9ee --- /dev/null +++ b/appl/cmd/ftpfs.b @@ -0,0 +1,1959 @@ +implement Ftpfs; + +include "sys.m"; + sys: Sys; + FD, Connection, Dir: import Sys; + +include "draw.m"; + +include "arg.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "daytime.m"; + time: Daytime; + Tm: import time; + +include "string.m"; + str: String; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + +include "factotum.m"; + +Ftpfs: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +# +# File system node. Refers to parent and file structure. +# Siblings are linked. The head is parent.children. +# + +Node: adt +{ + dir: Dir; + uniq: int; + parent: cyclic ref Node; + sibs: cyclic ref Node; + children: cyclic ref Node; + file: cyclic ref File; + depth: int; + remname: string; + cached: int; + valid: int; + + extendpath: fn(parent: self ref Node, elem: string): ref Node; + fixsymbolic: fn(n: self ref Node); + invalidate: fn(n: self ref Node); + markcached: fn(n: self ref Node); + uncache: fn(n: self ref Node); + uncachedir: fn(parent: self ref Node, child: ref Node); + + stat: fn(n: self ref Node): array of byte; + qid: fn(n: self ref Node): Sys->Qid; + + fileget: fn(n: self ref Node): ref File; + filefree: fn(n: self ref Node); + fileclean: fn(n: self ref Node); + fileisdirty: fn(n: self ref Node): int; + filedirty: fn(n: self ref Node); + fileread: fn(n: self ref Node, b: array of byte, off, c: int): int; + filewrite: fn(n: self ref Node, b: array of byte, off, c: int): int; + + action: fn(n: self ref Node, cmd: string): int; + createdir: fn(n: self ref Node): int; + createfile: fn(n: self ref Node): int; + changedir: fn(n: self ref Node): int; + docreate: fn(n: self ref Node): int; + pathname: fn(n: self ref Node): string; + readdir: fn(n: self ref Node): int; + readfile: fn(n: self ref Node): int; + removedir: fn(n: self ref Node): int; + removefile: fn(n: self ref Node): int; +}; + +# +# Styx protocol file identifier. +# + +Fid: adt +{ + fid: int; + node: ref Node; + busy: int; +}; + +# +# Foreign file with cache. +# + +File: adt +{ + cache: array of byte; + length: int; + offset: int; + fd: ref FD; + inuse, dirty: int; + atime: int; + node: cyclic ref Node; + tempname: string; + + createtmp: fn(f: self ref File): ref FD; +}; + +ftp: Connection; +dfid: ref FD; +dfidiob: ref Iobuf; +buffresidue: int = 0; +tbuff: array of byte; +rbuff: array of byte; +ccfd: ref FD; +stdin, stderr: ref FD; + +fids: list of ref Fid; + +BSZ: con 8192; +Chunk: con 1024; +Nfiles: con 128; + +CHSYML: con 16r40000000; + +mountpoint: string = "/n/ftp"; +user: string = nil; +password: string; +hostname: string = "kremvax"; +anon: string = "anon"; + +firewall: string = "tcp!$proxy!402"; +myname: string = "anon"; +myhost: string = "lucent.com"; +proxyid: string; +proxyhost: string; + +errstr: string; +net: string; +port: int; + +Enosuchfile: con "file does not exist"; +Eftpproto: con "ftp protocol error"; +Eshutdown: con "remote shutdown"; +Eioerror: con "io error"; +Enotadirectory: con "not a directory"; +Eisadirectory: con "is a directory"; +Epermission: con "permission denied"; +Ebadoffset: con "bad offset"; +Ebadlength: con "bad length"; +Enowstat: con "wstat not implemented"; +Emesgmismatch: con "message size mismatch"; + +remdir: ref Node; +remroot: ref Node; +remrootpath: string; + +heartbeatpid: int; + +# +# FTP protocol codes are 3 digits >= 100. +# The code type is obtained by dividing by 100. +# + +Syserr: con -2; +Syntax: con -1; +Shutdown: con 0; +Extra: con 1; +Success: con 2; +Incomplete: con 3; +TempFail: con 4; +PermFail: con 5; +Impossible: con 6; +Err: con 7; + +debug: int = 0; +quiet: int = 0; +active: int = 0; +cdtoroot: int = 0; + +proxy: int = 0; + +mountfd: ref FD; +styxfd: ref FD; + +# +# Set up FDs for service. +# + +connect(): string +{ + pip := array[2] of ref Sys->FD; + if(sys->pipe(pip) < 0) + return sys->sprint("can't create pipe: %r"); + mountfd = pip[0]; + styxfd = pip[1]; + return nil; +} + +#shut(s: string) +#{ +# sys->print("ftpfs: %s shutdown\n", s); +#} + +# +# Mount server. Must be spawned because it does +# an attach transaction. +# + +mount(mountpoint: string) +{ + if (sys->mount(mountfd, nil, mountpoint, sys->MREPL | sys->MCREATE, nil) < 0) { + sys->print("mount %s failed: %r\n", mountpoint); + shutdown(); + } + mountfd = nil; +} + +# +# Keep the link alive. +# + +beatquanta: con 10; +beatlimit: con 10; +beatcount: int; +activity: int; +transfer: int; + +heartbeat(pidc: chan of int) +{ + pid := sys->pctl(0, nil); + pidc <-= pid; + for (;;) { + sys->sleep(beatquanta * 1000); + if (activity || transfer) { + beatcount = 0; + activity = 0; + continue; + } + beatcount++; + if (beatcount == beatlimit) { + acquire(); + if (sendrequest("NOOP", 0) == Success) + getreply(0); + release(); + beatcount = 0; + activity = 0; + } + } +} + +# +# Control lock. +# + +ctllock: chan of int; + +acquire() +{ + ctllock <-= 1; +} + +release() +{ + <-ctllock; +} + +# +# Data formatting routines. +# + +sendreply(r: ref Rmsg) +{ + if (debug) + sys->print("> %s\n", r.text()); + a := r.pack(); + if(styx->write(styxfd, a, len a) != len a) + sys->print("ftpfs: error replying: %r\n"); +} + +rerror(tag: int, s: string) +{ + if (debug) + sys->print("error: %s\n", s); + sendreply(ref Rmsg.Error(tag, s)); +} + +seterr(e: int, s: string): int +{ + case e { + Syserr => + errstr = Eioerror; + Syntax => + errstr = Eftpproto; + Shutdown => + errstr = Eshutdown; + * => + errstr = s; + } + return -1; +} + +# +# Node routines. +# + +anode: Node; +npath: int = 1; + +newnode(parent: ref Node, name: string): ref Node +{ + n := ref anode; + n.dir.name = name; + n.dir.atime = time->now(); + n.children = nil; + n.remname = name; + if (parent != nil) { + n.parent = parent; + n.sibs = parent.children; + parent.children = n; + n.depth = parent.depth + 1; + n.valid = 0; + } else { + n.parent = n; + n.sibs = nil; + n.depth = 0; + n.valid = 1; + n.dir.uid = anon; + n.dir.gid = anon; + n.dir.mtime = n.dir.atime; + } + n.file = nil; + n.uniq = npath++; + n.cached = 0; + return n; +} + +Node.extendpath(parent: self ref Node, elem: string): ref Node +{ + n: ref Node; + + for (n = parent.children; n != nil; n = n.sibs) + if (n.dir.name == elem) + return n; + return newnode(parent, elem); +} + +Node.markcached(n: self ref Node) +{ + n.cached = 1; + n.dir.atime = time->now(); +} + +Node.uncache(n: self ref Node) +{ + if (n.fileisdirty()) + n.createfile(); + n.filefree(); + n.cached = 0; +} + +Node.uncachedir(parent: self ref Node, child: ref Node) +{ + sp: ref Node; + + if (parent == nil || parent == child) + return; + for (sp = parent.children; sp != nil; sp = sp.sibs) + if (sp != child && sp.file != nil && !sp.file.dirty && sp.file.fd != nil) { + sp.filefree(); + sp.cached = 0; + } +} + +Node.invalidate(node: self ref Node) +{ + n: ref Node; + + node.uncachedir(nil); + for (n = node.children; n != nil; n = n.sibs) { + n.cached = 0; + n.invalidate(); + n.valid = 0; + } +} + +Node.fixsymbolic(n: self ref Node) +{ + if (n.changedir() == 0) { + n.dir.mode |= Sys->DMDIR; + n.dir.qid.qtype = Sys->QTDIR; + } else + n.dir.qid.qtype = Sys->QTFILE; + n.dir.mode &= ~CHSYML; +} + +Node.stat(n: self ref Node): array of byte +{ + return styx->packdir(n.dir); +} + +Node.qid(n: self ref Node): Sys->Qid +{ + if(n.dir.mode & Sys->DMDIR) + return Sys->Qid(big n.uniq, 0, Sys->QTDIR); + return Sys->Qid(big n.uniq, 0, Sys->QTFILE); +} + +# +# File routines. +# + +ntmp: int; +files: list of ref File; +nfiles: int; +afile: File; +atime: int; + +# +# Allocate a file structure for a node. If too many +# are already allocated discard the oldest. +# + +Node.fileget(n: self ref Node): ref File +{ + f, o: ref File; + l: list of ref File; + + if (n.file != nil) + return n.file; + o = nil; + for (l = files; l != nil; l = tl l) { + f = hd l; + if (f.inuse == 0) + break; + if (!f.dirty && (o == nil || o.atime > f.atime)) + o = f; + } + if (l == nil) { + if (nfiles == Nfiles && o != nil) { + o.node.uncache(); + f = o; + } + else { + f = ref afile; + files = f :: files; + nfiles++; + } + } + n.file = f; + f.node = n; + f.atime = atime++; + f.inuse = 1; + f.dirty = 0; + f.length = 0; + f.fd = nil; + return f; +} + +# +# Create a temporary file for a local copy of a file. +# If too many are open uncache parent. +# + +File.createtmp(f: self ref File): ref FD +{ + t := "/tmp/ftp." + string time->now() + "." + string ntmp; + if (ntmp >= 16) + f.node.parent.uncachedir(f.node); + f.fd = sys->create(t, Sys->ORDWR | Sys->ORCLOSE, 8r600); + f.tempname = t; + f.offset = 0; + ntmp++; + return f.fd; +} + +# +# Read 'c' bytes at offset 'off' from a file into buffer 'b'. +# + +Node.fileread(n: self ref Node, b: array of byte, off, c: int): int +{ + f: ref File; + t, i: int; + + f = n.file; + if (off + c > f.length) + c = f.length - off; + for (t = 0; t < c; t += i) { + if (off >= f.length) + return t; + if (off < Chunk) { + i = c; + if (off + i > Chunk) + i = Chunk - off; + b[t:] = f.cache[off: off + i]; + } + else { + if (f.offset != off) { + if (sys->seek(f.fd, big off, Sys->SEEKSTART) < big 0) { + f.offset = -1; + return seterr(Err, sys->sprint("seek temp failed: %r")); + } + } + if (t == 0) + i = sys->read(f.fd, b, c - t); + else + i = sys->read(f.fd, rbuff, c - t); + if (i < 0) { + f.offset = -1; + return seterr(Err, sys->sprint("read temp failed: %r")); + } + if (i == 0) + break; + if (t > 0) + b[t:] = rbuff[0: i]; + f.offset = off + i; + } + off += i; + } + return t; +} + +# +# Write 'c' bytes at offset 'off' to a file from buffer 'b'. +# + +Node.filewrite(n: self ref Node, b: array of byte, off, c: int): int +{ + f: ref File; + t, i: int; + + f = n.fileget(); + if (f.cache == nil) + f.cache = array[Chunk] of byte; + for (t = 0; t < c; t += i) { + if (off < Chunk) { + i = c; + if (off + i > Chunk) + i = Chunk - off; + f.cache[off:] = b[t: t + i]; + } + else { + if (f.fd == nil) { + if (f.createtmp() == nil) + return seterr(Err, sys->sprint("temp file: %r")); + if (sys->write(f.fd, f.cache, Chunk) != Chunk) { + f.offset = -1; + return seterr(Err, sys->sprint("write temp failed: %r")); + } + f.offset = Chunk; + f.length = Chunk; + } + if (f.offset != off) { + if (off > f.length) { + # extend the file with zeroes + # sparse files may not be supported + } + if (sys->seek(f.fd, big off, Sys->SEEKSTART) < big 0) { + f.offset = -1; + return seterr(Err, sys->sprint("seek temp failed: %r")); + } + } + i = sys->write(f.fd, b[t:len b], c - t); + if (i != c - t) { + f.offset = -1; + return seterr(Err, sys->sprint("write temp failed: %r")); + } + } + off += i; + f.offset = off; + } + if (off > f.length) + f.length = off; + return t; +} + +Node.filefree(n: self ref Node) +{ + f: ref File; + + f = n.file; + if (f == nil) + return; + if (f.fd != nil) { + ntmp--; + f.fd = nil; + f.tempname = nil; + } + f.cache = nil; + f.length = 0; + f.inuse = 0; + f.dirty = 0; + n.file = nil; +} + +Node.fileclean(n: self ref Node) +{ + if (n.file != nil) + n.file.dirty = 0; +} + +Node.fileisdirty(n: self ref Node): int +{ + return n.file != nil && n.file.dirty; +} + +Node.filedirty(n: self ref Node) +{ + f: ref File; + + f = n.fileget(); + f.dirty = 1; +} + +# +# Fid management. +# + +afid: Fid; + +getfid(fid: int): ref Fid +{ + l: list of ref Fid; + f, ff: ref Fid; + + ff = nil; + for (l = fids; l != nil; l = tl l) { + f = hd l; + if (f.fid == fid) { + if (f.busy) + return f; + else { + ff = f; + break; + } + } else if (ff == nil && !f.busy) + ff = f; + } + if (ff == nil) { + ff = ref afid; + fids = ff :: fids; + } + ff.node = nil; + ff.fid = fid; + return ff; +} + +# +# FTP protocol. +# + +fail(s: int, l: string) +{ + case s { + Syserr => + sys->print("read fail: %r\n"); + Syntax => + sys->print("%s\n", Eftpproto); + Shutdown => + sys->print("%s\n", Eshutdown); + * => + sys->print("unexpected response: %s\n", l); + } + exit; +} + +getfullreply(echo: int): (int, int, string) +{ + reply := ""; + s: string; + code := -1; + do{ + s = dfidiob.gets('\n'); + if(s == nil) + return (Shutdown, 0, nil); + if(len s >= 2 && s[len s-1] == '\n'){ + if (s[len s - 2] == '\r') + s = s[0: len s - 2]; + else + s = s[0: len s - 1]; + } + if (debug || echo) + sys->print("%s\n", s); + reply = reply+s; + if(code < 0){ + if(len s < 3) + return (Syntax, 0, nil); + code = int s[0:3]; + if(s[3] != '-') + break; + } + }while(len s < 4 || int s[0:3] != code || s[3] != ' '); + + if(code < 100) + return (Syntax, 0, nil); + return (code / 100, code, reply); +} + +getreply(echo: int): (int, string) +{ + (c, code, s) := getfullreply(echo); + return (c, s); +} + +sendrequest2(req: string, echo: int, figleaf: string): int +{ + activity = 1; + if (debug || echo) { + if (figleaf == nil) + figleaf = req; + sys->print("%s\n", figleaf); + } + b := array of byte (req + "\r\n"); + n := sys->write(dfid, b, len b); + if (n < 0) + return Syserr; + if (n != len b) + return Shutdown; + return Success; +} + +sendrequest(req: string, echo: int): int +{ + return sendrequest2(req, echo, req); +} + +sendfail(s: int) +{ + case s { + Syserr => + sys->print("write fail: %r\n"); + Shutdown => + sys->print("%s\n", Eshutdown); + * => + sys->print("internal error\n"); + } + exit; +} + +dataport(l: list of string): string +{ + s := "tcp!" + hd l; + l = tl l; + s = s + "." + hd l; + l = tl l; + s = s + "." + hd l; + l = tl l; + s = s + "." + hd l; + l = tl l; + return s + "!" + string ((int hd l * 256) + (int hd tl l)); +} + +commas(l: list of string): string +{ + s := hd l; + l = tl l; + while (l != nil) { + s = s + "," + hd l; + l = tl l; + } + return s; +} + +third(cmd: string): ref FD +{ + acquire(); + for (;;) { + (n, data) := sys->dial(firewall, nil); + if (n < 0) { + if (debug) + sys->print("dial %s failed: %r\n", firewall); + break; + } + t := sys->sprint("\n%s!*\n\n%s\n%s\n1\n-1\n-1\n", proxyhost, myhost, myname); + b := array of byte t; + n = sys->write(data.dfd, b, len b); + if (n < 0) { + if (debug) + sys->print("firewall write failed: %r\n"); + break; + } + b = array[256] of byte; + n = sys->read(data.dfd, b, len b); + if (n < 0) { + if (debug) + sys->print("firewall read failed: %r\n"); + break; + } + (c, k) := sys->tokenize(string b[:n], "\n"); + if (c < 2) { + if (debug) + sys->print("bad response from firewall\n"); + break; + } + if (hd k != "0") { + if (debug) + sys->print("firewall connect: %s\n", hd tl k); + break; + } + p := hd tl k; + if (debug) + sys->print("portid %s\n", p); + (c, k) = sys->tokenize(p, "!"); + if (c < 3) { + if (debug) + sys->print("bad portid from firewall\n"); + break; + } + n = int hd tl tl k; + (c, k) = sys->tokenize(hd tl k, "."); + if (c != 4) { + if (debug) + sys->print("bad portid ip address\n"); + break; + } + t = sys->sprint("PORT %s,%d,%d", commas(k), n / 256, n & 255); + r := sendrequest(t, 0); + if (r != Success) + break; + (r, nil) = getreply(0); + if (r != Success) + break; + r = sendrequest(cmd, 0); + if (r != Success) + break; + (r, nil) = getreply(0); + if (r != Extra) + break; + n = sys->read(data.dfd, b, len b); + if (n < 0) { + if (debug) + sys->print("firewall read failed: %r\n"); + break; + } + b = array of byte "0\n?\n"; + n = sys->write(data.dfd, b, len b); + if (n < 0) { + if (debug) + sys->print("firewall write failed: %r\n"); + break; + } + release(); + return data.dfd; + } + release(); + return nil; +} + +passive(cmd: string): ref FD +{ + acquire(); + if (sendrequest("PASV", 0) != Success) { + release(); + return nil; + } + (r, m) := getreply(0); + release(); + if (r != Success) + return nil; + (nil, p) := str->splitl(m, "("); + if (p == nil) + str->splitl(m, "0-9"); + else + p = p[1:len p]; + (c, l) := sys->tokenize(p, ","); + if (c < 6) { + sys->print("data: %s\n", m); + return nil; + } + a := dataport(l); + if (debug) + sys->print("data dial %s\n", a); + (s, d) := sys->dial(a, nil); + if (s < 0) + return nil; + acquire(); + r = sendrequest(cmd, 0); + if (r != Success) { + release(); + return nil; + } + (r, m) = getreply(0); + release(); + if (r != Extra) + return nil; + return d.dfd; +} + +getnet(dir: string): (string, int) +{ + buf := array[50] of byte; + n := dir + "/local"; + lfd := sys->open(n, Sys->OREAD); + if (lfd == nil) { + if (debug) + sys->fprint(stderr, "open %s: %r\n", n); + return (nil, 0); + } + length := sys->read(lfd, buf, len buf); + if (length < 0) { + if (debug) + sys->fprint(stderr, "read%s: %r\n", n); + return (nil, 0); + } + (r, l) := sys->tokenize(string buf[0:length], "!"); + if (r != 2) { + if (debug) + sys->fprint(stderr, "tokenize(%s) returned (%d)\n", string buf[0:length], r); + return (nil, 0); + } + if (debug) + sys->print("net is %s!%d\n", hd l, int hd tl l); + return (hd l, int hd tl l); +} + +activate(cmd: string): ref FD +{ + r: int; + + listenport, dataport: Connection; + m: string; + + (r, listenport) = sys->announce("tcp!" + net + "!0"); + if (r < 0) + return nil; + (x1, x2) := getnet(listenport.dir); + (x3, x4) := sys->tokenize(x1, "."); + t := sys->sprint("PORT %s,%d,%d", commas(x4), int x2 / 256, int x2&255); + acquire(); + r = sendrequest(t, 0); + if (r != Success) { + release(); + return nil; + } + (r, m) = getreply(0); + if (r != Success) { + release(); + return nil; + } + r = sendrequest(cmd, 0); + if (r != Success) { + release(); + return nil; + } + (r, m) = getreply(0); + release(); + if (r != Extra) + return nil; + (r, dataport) = sys->listen(listenport); + if (r < 0) { + sys->fprint(stderr, "activate: listen failed: %r\n"); + return nil; + } + fd := sys->open(dataport.dir + "/data", sys->ORDWR); + if (debug) + sys->print("activate: data connection on %s\n", dataport.dir); + if (fd == nil) { + sys->fprint(stderr, "activate: open of %s failed: %r\n", dataport.dir); + return nil; + } + return fd; +} + +data(cmd: string): ref FD +{ + if (proxy) + return third(cmd); + else if (active) + return activate(cmd); + else + return passive(cmd); +} + +# +# File list cracking routines. +# + +fields(l: list of string, n: int): array of string +{ + a := array[n] of string; + for (i := 0; i < n; i++) { + a[i] = hd l; + l = tl l; + } + return a; +} + +now: ref Tm; +months: con "janfebmaraprmayjunjulaugsepoctnovdec"; + +cracktime(month, day, year, hms: string): int +{ + tm: Tm; + + if (now == nil) + now = time->local(time->now()); + tm = *now; + if (month[0] >= '0' && month[0] <= '9') { + tm.mon = int month - 1; + if (tm.mon < 0 || tm.mon > 11) + tm.mon = 5; + } + else if (len month >= 3) { + month = str->tolower(month[0:3]); + for (i := 0; i < 36; i += 3) + if (month == months[i:i+3]) { + tm.mon = i / 3; + break; + } + } + tm.mday = int day; + if (hms != nil) { + (h, z) := str->splitl(hms, "apAP"); + (a, b) := str->splitl(h, ":"); + tm.hour = int a; + if (b != nil) { + (c, d) := str->splitl(b[1:len b], ":"); + tm.min = int c; + if (d != nil) + tm.sec = int d[1:len d]; + } + if (z != nil && str->tolower(z)[0] == 'p') + tm.hour += 12; + } + if (year != nil) { + tm.year = int year; + if (tm.year >= 1900) + tm.year -= 1900; + } + else { + if (tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1)) + tm.year--; + } + return time->tm2epoch(ref tm); +} + +crackmode(p: string): int +{ + flags := 0; + case len p { + 10 => # unix and new style plan 9 + case p[0] { + 'l' => + return CHSYML | 0777; + 'd' => + flags = Sys->DMDIR; + } + p = p[1:10]; + 11 => # old style plan 9 + if (p[0] == 'l') + flags = Sys->DMDIR; + p = p[2:11]; + * => + return Sys->DMDIR | 0777; + } + mode := 0; + n := 0; + for (i := 0; i < 3; i++) { + mode <<= 3; + if (p[n] == 'r') + mode |= 4; + if (p[n+1] == 'w') + mode |= 2; + case p[n+2] { + 'x' or 's' or 'S' => + mode |= 1; + } + n += 3; + } + return mode | flags; +} + +crackdir(p: string): (string, Dir) +{ + d: Dir; + ln, a: string; + + (n, l) := sys->tokenize(p, " \t\r\n"); + f := fields(l, n); + if (n > 2 && f[n - 2] == "->") + n -= 2; + case n { + 8 => # ls -l + ln = f[7]; + d.uid = f[2]; + d.gid = f[2]; + d.mode = crackmode(f[0]); + d.length = big f[3]; + (a, nil) = str->splitl(f[6], ":"); + if (len a != len f[6]) + d.atime = cracktime(f[4], f[5], nil, f[6]); + else + d.atime = cracktime(f[4], f[5], f[6], nil); + 9 => # ls -lg + ln = f[8]; + d.uid = f[2]; + d.gid = f[3]; + d.mode = crackmode(f[0]); + d.length = big f[4]; + (a, nil) = str->splitl(f[7], ":"); + if (len a != len f[7]) + d.atime = cracktime(f[5], f[6], nil, f[7]); + else + d.atime = cracktime(f[5], f[6], f[7], nil); + 10 => # plan 9 + ln = f[9]; + d.uid = f[3]; + d.gid = f[4]; + d.mode = crackmode(f[0]); + d.length = big f[5]; + (a, nil) = str->splitl(f[8], ":"); + if (len a != len f[8]) + d.atime = cracktime(f[6], f[7], nil, f[8]); + else + d.atime = cracktime(f[6], f[7], f[8], nil); + 4 => # NT + ln = f[3]; + d.uid = anon; + d.gid = anon; + if (f[2] == "<DIR>") { + d.length = big 0; + d.mode = Sys->DMDIR | 8r777; + } + else { + d.mode = 8r666; + d.length = big f[2]; + } + (n, l) = sys->tokenize(f[0], "/-"); + if (n == 3) + d.atime = cracktime(hd l, hd tl l, f[2], f[1]); + 1 => # ls + ln = f[0]; + d.uid = anon; + d.gid = anon; + d.mode = 0777; + d.atime = 0; + * => + return (nil, d); + } + if (ln == "." || ln == "..") + return (nil, d); + d.mtime = d.atime; + d.name = ln; + return (ln, d); +} + +longls := 1; + +Node.readdir(n: self ref Node): int +{ + f: ref FD; + p: ref Node; + + if (n.changedir() < 0) + return -1; + transfer = 1; + for (;;) { + if (longls) { + f = data("LIST -la"); + if (f == nil) { + longls = 0; + continue; + } + } + else { + f = data("LIST"); + if (f == nil) { + transfer = 0; + return seterr(Err, Enosuchfile); + } + } + break; + } + b := bufio->fopen(f, sys->OREAD); + if (b == nil) { + transfer = 0; + return seterr(Err, Eioerror); + } + while ((s := b.gets('\n')) != nil) { + if (debug) + sys->print("%s", s); + (l, d) := crackdir(s); + if (l == nil) + continue; + p = n.extendpath(l); + p.dir = d; + p.valid = 1; + } + b = nil; + f = nil; + (r, nil) := getreply(0); + transfer = 0; + if (r != Success) + return seterr(Err, Enosuchfile); + return 0; +} + +Node.readfile(n: self ref Node): int +{ + c: int; + + if (n.parent.changedir() < 0) + return -1; + transfer = 1; + f := data("RETR " + n.remname); + if (f == nil) { + transfer = 0; + return seterr(Err, Enosuchfile); + } + off := 0; + while ((c = sys->read(f, tbuff, BSZ)) > 0) { + if (n.filewrite(tbuff, off, c) != c) { + off = -1; + break; + } + off += c; + } + if (c < 0) { + transfer = 0; + return seterr(Err, Eioerror); + } + f = nil; + if(off == 0) + n.filewrite(tbuff, off, 0); + (s, nil) := getreply(0); + transfer = 0; + if (s != Success) + return seterr(s, Enosuchfile); + return off; +} + +path(a, b: string): string +{ + if (a == nil) + return b; + if (b == nil) + return a; + if (a[len a - 1] == '/') + return a + b; + else + return a + "/" + b; +} + +Node.pathname(n: self ref Node): string +{ + s: string; + + while (n != n.parent) { + s = path(n.remname, s); + n = n.parent; + } + return path(remrootpath, s); +} + +Node.changedir(n: self ref Node): int +{ + t: ref Node; + d: string; + + t = n; + if (t == remdir) + return 0; + if (n.depth == 0) + d = remrootpath; + else + d = n.pathname(); + remdir.uncachedir(nil); + acquire(); + r := sendrequest("CWD " + d, 0); + if (r == Success) + (r, nil) = getreply(0); + release(); + case r { + Success +# or Incomplete + => + remdir = n; + return 0; + * => + return seterr(r, Enosuchfile); + } +} + +Node.docreate(n: self ref Node): int +{ + f: ref FD; + + transfer = 1; + f = data("STOR " + n.remname); + if (f == nil) { + transfer = 0; + return -1; + } + off := 0; + for (;;) { + r := n.fileread(tbuff, off, BSZ); + if (r <= 0) + break; + if (sys->write(f, tbuff, r) < 0) { + off = -1; + break; + } + off += r; + } + transfer = 0; + return off; +} + +Node.createfile(n: self ref Node): int +{ + if (n.parent.changedir() < 0) + return -1; + off := n.docreate(); + if (off < 0) + return -1; + (r, nil) := getreply(0); + if (r != Success) + return -1; + return off; +} + +Node.action(n: self ref Node, cmd: string): int +{ + if (n.parent.changedir() < 0) + return -1; + acquire(); + r := sendrequest(cmd + " " + n.dir.name, 0); + if (r == Success) + (r, nil) = getreply(0); + release(); + if (r != Success) + return -1; + return 0; +} + +Node.createdir(n: self ref Node): int +{ + return n.action("MKD"); +} + +Node.removefile(n: self ref Node): int +{ + return n.action("DELE"); +} + +Node.removedir(n: self ref Node): int +{ + return n.action("RMD"); +} + +pwd(s: string): string +{ + (nil, s) = str->splitl(s, "\""); + if (s == nil || len s < 2) + return "/"; + (s, nil) = str->splitl(s[1:len s], "\""); + return s; +} + +# +# User info for firewall. +# +getuser() +{ + b := array[Sys->NAMEMAX] of byte; + f := sys->open("/dev/user", Sys->OREAD); + if (f != nil) { + n := sys->read(f, b, len b); + if (n > 0) + myname = string b[:n]; + else if (n == 0) + sys->print("warning: empty /dev/user\n"); + else + sys->print("warning: could not read /dev/user: %r\n"); + } else + sys->print("warning: could not open /dev/user: %r\n"); + f = sys->open("/dev/sysname", Sys->OREAD); + if (f != nil) { + n := sys->read(f, b, len b); + if (n > 0) + myhost = string b[:n]; + else if (n == 0) + sys->print("warning: empty /dev/sysname\n"); + else + sys->print("warning: could not read /dev/sysname: %r\n"); + } else + sys->print("warning: could not open /dev/sysname: %r\n"); + if (debug) + sys->print("proxy %s for %s@%s\n", firewall, myname, myhost); +} + +server() +{ + while((t := Tmsg.read(styxfd, 0)) != nil){ + if (debug) + sys->print("< %s\n", t.text()); + pick x := t { + Readerror => + sys->print("ftpfs: read error on mount point: %s\n", x.error); + kill(heartbeatpid); + exit; + Version => + versionT(x); + Auth => + authT(x); + Attach => + attachT(x); + Clunk => + clunkT(x); + Create => + createT(x); + Flush => + flushT(x); + Open => + openT(x); + Read => + readT(x); + Remove => + removeT(x); + Stat => + statT(x); + Walk => + walkT(x); + Write => + writeT(x); + Wstat => + wstatT(x); + * => + rerror(t.tag, "unimp"); + } + } + if (debug) + sys->print("ftpfs: server: exiting\n"); + kill(heartbeatpid); +} + +raw(on: int) +{ + if(ccfd == nil) { + ccfd = sys->open("/dev/consctl", Sys->OWRITE); + if(ccfd == nil) { + sys->fprint(stderr, "ftpfs: cannot open /dev/consctl: %r\n"); + return; + } + } + if(on) + sys->fprint(ccfd, "rawon"); + else + sys->fprint(ccfd, "rawoff"); +} + +prompt(p: string, def: string, echo: int): string +{ + if (def == nil) + sys->print("%s: ", p); + else + sys->print("%s[%s]: ", p, def); + if (!echo) + raw(1); + b := bufio->fopen(stdin, Sys->OREAD); + s := b.gets(int '\n'); + if (!echo) { + raw(0); + sys->print("\n"); + } + if(s != nil) + s = s[0:len s - 1]; + if (s == "") + return def; + return s; +} + +# +# Entry point. Load modules and initiate protocol. +# + +nomod(s: string) +{ + sys->fprint(sys->fildes(2), "ftpfs: can't load %s: %r\n", s); + raise "fail:load"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + l: string; + rv: int; + code: int; + + if (sys == nil) + sys = load Sys Sys->PATH; + stdin = sys->fildes(0); + stderr = sys->fildes(2); + + time = load Daytime Daytime->PATH; + if (time == nil) + nomod(Daytime->PATH); + str = load String String->PATH; + if (str == nil) + nomod(String->PATH); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + nomod(Bufio->PATH); + styx = load Styx Styx->PATH; + if (styx == nil) + nomod(Styx->PATH); + styx->init(); + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + + # parse arguments + # [-/dpq] [-m mountpoint] [-a password] host + arg->init(args); + arg->setusage("ftpfs [-/dpq] [-m mountpoint] [-a password] ftphost"); + keyspec := ""; + while((op := arg->opt()) != 0) + case op { + 'd' => + debug++; + '/' => + cdtoroot = 1; + 'p' => + active = 1; + 'q' => + quiet = 1; + 'm' => + mountpoint = arg->earg(); + 'a' => + password = arg->earg(); + user = "anonymous"; + 'k' => + keyspec = arg->earg(); + * => + arg->usage(); + } + argv := arg->argv(); + if (len argv != 1) + arg->usage(); + arg = nil; + hostname = hd argv; + + if (len hostname > 6 && hostname[:6] == "proxy!") { + hostname = hostname[6:]; + proxy = 1; + } + + if (proxy) { + if (!quiet) + sys->print("dial firewall service %s\n", firewall); + (rv, ftp) = sys->dial(firewall, nil); + if (rv < 0) { + sys->print("dial %s failed: %r\n", firewall); + exit; + } + dfid = ftp.dfd; + getuser(); + t := sys->sprint("\ntcp!%s!tcp.21\n\n%s\n%s\n0\n-1\n-1\n", hostname, myhost, myname); + if (debug) + sys->print("request%s\n", t); + b := array of byte t; + rv = sys->write(dfid, b, len b); + if (rv < 0) { + sys->print("firewall write failed: %r\n"); + exit; + } + b = array[256] of byte; + rv = sys->read(dfid, b, len b); + if (rv < 0) { + sys->print("firewall read failed: %r\n"); + return; + } + (c, k) := sys->tokenize(string b[:rv], "\n"); + if (c < 2) { + sys->print("bad response from firewall\n"); + exit; + } + if (hd k != "0") { + sys->print("firewall connect: %s\n", hd tl k); + exit; + } + proxyid = hd tl k; + if (debug) + sys->print("proxyid %s\n", proxyid); + (c, k) = sys->tokenize(proxyid, "!"); + if (c < 3) { + sys->print("bad proxyid from firewall\n"); + exit; + } + proxyhost = (hd k) + "!" + (hd tl k); + if (debug) + sys->print("proxyhost %s\n", proxyhost); + } else { + d := "tcp!" + hostname + "!ftp"; + (rv, ftp) = sys->dial(d, nil); + if (debug) + sys->print("localdir %s\n", ftp.dir); + if (rv < 0) { + sys->print("dial %s failed: %r\n", d); + exit; + } + dfid = ftp.dfd; + } + dfidiob = bufio->fopen(dfid, sys->OREAD); + (net, port) = getnet(ftp.dir); + tbuff = array[BSZ] of byte; + rbuff = array[BSZ] of byte; + (rv, l) = getreply(!quiet); + if (rv != Success) + fail(rv, l); + if (user == nil) { + getuser(); + user = myname; + user = prompt("User", user, 1); + } + rv = sendrequest("USER " + user, 0); + if (rv != Success) + sendfail(rv); + (rv, code, l) = getfullreply(!quiet); + if (rv != Success) { + if (rv != Incomplete) + fail(rv, l); + if (code == 331) { + if(password == nil){ + factotum := load Factotum Factotum->PATH; + if(factotum != nil){ + factotum->init(); + if(user != nil && keyspec == nil) + keyspec = sys->sprint("user=%q", user); + (nil, password) = factotum->getuserpasswd(sys->sprint("proto=pass server=%s service=ftp %s", hostname, keyspec)); + } + if(password == nil) + password = prompt("Password", nil, 0); + } + rv = sendrequest2("PASS " + password, 0, "PASS XXXX"); + if (rv != Success) + sendfail(rv); + (rv, l) = getreply(0); + if (rv != Success) + fail(rv, l); + } + } + if (cdtoroot) { + rv = sendrequest("CWD /", 0); + if (rv != Success) + sendfail(rv); + (rv, l) = getreply(0); + if (rv != Success) + fail(rv, l); + } + rv = sendrequest("TYPE I", 0); + if (rv != Success) + sendfail(rv); + (rv, l) = getreply(0); + if (rv != Success) + fail(rv, l); + rv = sendrequest("PWD", 0); + if (rv != Success) + sendfail(rv); + (rv, l) = getreply(0); + if (rv != Success) + fail(rv, l); + remrootpath = pwd(l); + remroot = newnode(nil, "/"); + remroot.dir.mode = Sys->DMDIR | 8r777; + remroot.dir.qid.qtype = Sys->QTDIR; + remdir = remroot; + l = connect(); + if (l != nil) { + sys->print("%s\n", l); + exit; + } + ctllock = chan[1] of int; + spawn mount(mountpoint); + pidc := chan of int; + spawn heartbeat(pidc); + heartbeatpid = <-pidc; + if (debug) + sys->print("heartbeatpid %d\n", heartbeatpid); + spawn server(); # dies when receive on chan fails +} + +kill(pid: int): int +{ + if (debug) + sys->print("killing %d\n", pid); + fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if (fd == nil) { + sys->print("kill: open failed\n"); + return -1; + } + if (sys->write(fd, array of byte "kill", 4) != 4) { + sys->print("kill: write failed\n"); + return -1; + } + return 0; +} + +shutdown() +{ + mountfd = nil; +} + +# +# Styx transactions. +# + +versionT(t: ref Tmsg.Version) +{ + (msize, version) := styx->compatible(t, Styx->MAXRPC, Styx->VERSION); + sendreply(ref Rmsg.Version(t.tag, msize, version)); +} + +authT(t: ref Tmsg.Auth) +{ + sendreply(ref Rmsg.Error(t.tag, "authentication not required")); +} + +flushT(t: ref Tmsg.Flush) +{ + sendreply(ref Rmsg.Flush(t.tag)); +} + +attachT(t: ref Tmsg.Attach) +{ + f := getfid(t.fid); + f.busy = 1; + f.node = remroot; + sendreply(ref Rmsg.Attach(t.tag, remroot.qid())); +} + +walkT(t: ref Tmsg.Walk) +{ + f := getfid(t.fid); + qids: array of Sys->Qid; + node := f.node; + if(len t.names > 0){ + qids = array[len t.names] of Sys->Qid; + for(i := 0; i < len t.names; i++) { + if ((node.dir.mode & Sys->DMDIR) == 0){ + if(i == 0) + return rerror(t.tag, Enotadirectory); + break; + } + if (t.names[i] == "..") + node = node.parent; + else if (t.names[i] != ".") { + if (t.names[i] == ".flush.ftpfs") { + node.invalidate(); + node.readdir(); + qids[i] = node.qid(); + continue; + } + node = node.extendpath(t.names[i]); + if (node.parent.cached) { + if (!node.valid) { + if(i == 0) + return rerror(t.tag, Enosuchfile); + break; + } + if ((node.dir.mode & CHSYML) != 0) + node.fixsymbolic(); + } else if (!node.valid) { + if (node.changedir() == 0){ + node.dir.qid.qtype = Sys->QTDIR; + node.dir.mode |= Sys->DMDIR; + }else{ + node.dir.qid.qtype = Sys->QTFILE; + node.dir.mode &= ~Sys->DMDIR; + } + } + qids[i] = node.qid(); + } + } + if(i < len t.names){ + sendreply(ref Rmsg.Walk(t.tag, qids[0:i])); + return; + } + } + if(t.newfid != t.fid){ + n := getfid(t.newfid); + if(n.busy) + return rerror(t.tag, "fid in use"); + n.busy = 1; + n.node = node; + }else + f.node = node; + sendreply(ref Rmsg.Walk(t.tag, qids)); +} + +openT(t: ref Tmsg.Open) +{ + f := getfid(t.fid); + if ((f.node.dir.mode & Sys->DMDIR) != 0 && t.mode != Sys->OREAD) { + rerror(t.tag, Epermission); + return; + } + if ((t.mode & Sys->OTRUNC) != 0) { + f.node.uncache(); + f.node.parent.uncache(); + f.node.filedirty(); + } else if (!f.node.cached) { + f.node.filefree(); + if ((f.node.dir.mode & Sys->DMDIR) != 0) { + f.node.invalidate(); + if (f.node.readdir() < 0) { + rerror(t.tag, Enosuchfile); + return; + } + } + else { + if (f.node.readfile() < 0) { + rerror(t.tag, errstr); + return; + } + } + f.node.markcached(); + } + sendreply(ref Rmsg.Open(t.tag, f.node.qid(), Styx->MAXFDATA)); +} + +createT(t: ref Tmsg.Create) +{ + f := getfid(t.fid); + if ((f.node.dir.mode & Sys->DMDIR) == 0) { + rerror(t.tag, Enotadirectory); + return; + } + f.node = f.node.extendpath(t.name); + f.node.uncache(); + if ((t.perm & Sys->DMDIR) != 0) { + if (f.node.createdir() < 0) { + rerror(t.tag, Epermission); + return; + } + } + else + f.node.filedirty(); + f.node.parent.invalidate(); + f.node.parent.uncache(); + sendreply(ref Rmsg.Create(t.tag, f.node.qid(), Styx->MAXFDATA)); +} + +readT(t: ref Tmsg.Read) +{ + f := getfid(t.fid); + count := t.count; + + if (count < 0) + return rerror(t.tag, Ebadlength); + if (count > Styx->MAXFDATA) + count = Styx->MAXFDATA; + if (t.offset < big 0) + return rerror(t.tag, Ebadoffset); + rv := 0; + if ((f.node.dir.mode & Sys->DMDIR) != 0) { + offset := int t.offset; + for (p := f.node.children; offset > 0 && p != nil; p = p.sibs) + if (p.valid) + offset -= len p.stat(); + for (; rv < count && p != nil; p = p.sibs) { + if (p.valid) { + if ((p.dir.mode & CHSYML) != 0) + p.fixsymbolic(); + a := p.stat(); + size := len a; + if(rv+size > count) + break; + tbuff[rv:] = a; + rv += size; + } + } + } else { + if (!f.node.cached && f.node.readfile() < 0) { + rerror(t.tag, errstr); + return; + } + f.node.markcached(); + rv = f.node.fileread(tbuff, int t.offset, count); + if (rv < 0) { + rerror(t.tag, errstr); + return; + } + } + sendreply(ref Rmsg.Read(t.tag, tbuff[0:rv])); +} + +writeT(t: ref Tmsg.Write) +{ + f := getfid(t.fid); + if ((f.node.dir.mode & Sys->DMDIR) != 0) { + rerror(t.tag, Eisadirectory); + return; + } + count := f.node.filewrite(t.data, int t.offset, len t.data); + if (count < 0) { + rerror(t.tag, errstr); + return; + } + f.node.filedirty(); + sendreply(ref Rmsg.Write(t.tag, count)); +} + +clunkT(t: ref Tmsg.Clunk) +{ + f := getfid(t.fid); + if (f.node.fileisdirty()) { + if (f.node.createfile() < 0) + sys->print("ftpfs: could not create %s\n", f.node.pathname()); + f.node.fileclean(); + f.node.uncache(); + } + f.busy = 0; + sendreply(ref Rmsg.Clunk(t.tag)); +} + +removeT(t: ref Tmsg.Remove) +{ + f := getfid(t.fid); + if ((f.node.dir.mode & Sys->DMDIR) != 0) { + if (f.node.removedir() < 0) { + rerror(t.tag, errstr); + return; + } + } + else { + if (f.node.removefile() < 0) { + rerror(t.tag, errstr); + return; + } + } + f.node.parent.uncache(); + f.node.uncache(); + f.node.valid = 0; + f.busy = 0; + sendreply(ref Rmsg.Remove(t.tag)); +} + +statT(t: ref Tmsg.Stat) +{ + f := getfid(t.fid); + n := f.node.parent; + if (!n.cached) { + n.invalidate(); + n.readdir(); + n.markcached(); + } + if (!f.node.valid) { + rerror(t.tag, Enosuchfile); + return; + } + sendreply(ref Rmsg.Stat(t.tag, f.node.dir)); +} + +wstatT(t: ref Tmsg.Wstat) +{ + rerror(t.tag, Enowstat); +} diff --git a/appl/cmd/getauthinfo.b b/appl/cmd/getauthinfo.b new file mode 100644 index 00000000..84c0f1d4 --- /dev/null +++ b/appl/cmd/getauthinfo.b @@ -0,0 +1,185 @@ +implement Getauthinfo; + +# +# get and save a certificate from a signer in exchange for a valid secret +# + +include "sys.m"; + sys: Sys; + stdin, stdout, stderr: ref Sys->FD; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +include "security.m"; + login: Login; + +include "string.m"; + str: String; + +include "promptstring.b"; + +Getauthinfo: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(stderr, "usage: getauthinfo {net!hostname | default | /file}\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + # Disable echoing in RAWON mode + RAWON_STR = nil; + + argv = tl argv; + if(argv == nil) + usage(); + keyname := hd argv; + if(keyname == nil) + usage(); + + kr = load Keyring Keyring->PATH; + if(kr == nil) + nomod(Keyring->PATH); + + str = load String String->PATH; + if(str == nil) + nomod(String->PATH); + + login = load Login Login->PATH; + if(login == nil) + nomod(Login->PATH); + + user := user(); + path := keyname; + if(path[0] != '/' || len path < 2 || path[0:2] != "./") + path = "/usr/" + user + "/keyring/" + keyname; + + signer := defaultsigner(); + if(signer == nil){ + sys->fprint(stderr, "getauthinfo: warning: can't get default signer server name\n"); + signer = "$SIGNER"; + } + + passwd := ""; + save := "yes"; + redo := "yes"; + for(;;) { + signer = promptstring("use signer", signer, RAWOFF); + user = promptstring("remote user name", user, RAWOFF); + passwd = promptstring("password", passwd, RAWON); + + info := logon(user, passwd, signer, path, save); + if(info != nil) + break; + } +} + +logon(user, passwd, server, path, save: string): ref Keyring->Authinfo +{ + (err, info) := login->login(user, passwd, "net!"+server+"!inflogin"); + if(err != nil){ + sys->fprint(stderr, "getauthinfo: failed to authenticate: %s\n", err); + return nil; + } + + # save the info somewhere for later access + save = promptstring("save in file", save, RAWOFF); + if(save[0] != 'y'){ + (dir, file) := str->splitr(path, "/"); + if(sys->bind("#s", dir, Sys->MBEFORE) < 0){ + sys->fprint(stderr, "getauthinfo: can't bind file channel on %s: %r\n", dir); + return nil; + } + filio := sys->file2chan(dir, file); + if(filio == nil) { + sys->fprint(stderr, "getauthinfo: can't make file2chan %s: %r\n", path); + return nil; + } + sync := chan of int; + spawn infofile(filio, sync); + <-sync; + } + + if(kr->writeauthinfo(path, info) < 0) { + sys->fprint(stderr, "getauthinfo: can't write certificate to %s: %r\n", path); + return nil; + } + + return info; +} + +user(): string +{ + sys = load Sys Sys->PATH; + + 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]; +} + +infofile(fileio: ref Sys->FileIO, sync: chan of int) +{ + infodata := array[0] of byte; + + sys->pctl(Sys->NEWPGRP|Sys->NEWFD, nil); + sync <-= 1; + + for(;;) alt { + (off, nbytes, fid, rc) := <-fileio.read => + if(rc == nil) + break; + if(off > len infodata){ + rc <-= (nil, nil); + } else { + if(off + nbytes > len infodata) + nbytes = len infodata - off; + rc <-= (infodata[off:off+nbytes], nil); + } + + (off, data, fid, wc) := <-fileio.write => + if(wc == nil) + break; + + if(off != len infodata){ + wc <-= (0, "cannot be rewritten"); + } else { + nid := array[len infodata+len data] of byte; + nid[0:] = infodata; + nid[len infodata:] = data; + infodata = nid; + wc <-= (len data, nil); + } + data = nil; + } +} + +# get default signer server name +defaultsigner(): string +{ + return "$SIGNER"; +} + +nomod(s: string) +{ + sys->fprint(stderr, "getauthinfo: can't load %s: %r\n", s); + raise "fail:load"; +} diff --git a/appl/cmd/getfile.b b/appl/cmd/getfile.b new file mode 100644 index 00000000..ec0ff34c --- /dev/null +++ b/appl/cmd/getfile.b @@ -0,0 +1,74 @@ +implement Getfile; + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; +include "draw.m"; + draw: Draw; + Rect: import draw; +include "tk.m"; + tk: Tk; +include "wmlib.m"; + wmlib: Wmlib; +include "arg.m"; + +Getfile: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(stderr, "usage: getfile [-g geom] [-d startdir] [pattern...]\n"); + raise "fail:usage"; +} + +init(ctxt: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + draw = load Draw Draw->PATH; + tk = load Tk Tk->PATH; + wmlib = load Wmlib Wmlib->PATH; + if (wmlib == nil) { + sys->fprint(stderr, "getfile: cannot load %s: %r\n", Wmlib->PATH); + raise "fail:bad module"; + } + arg := load Arg Arg->PATH; + if (arg == nil) { + sys->fprint(stderr, "getfile: cannot load %s: %r\n", Arg->PATH); + raise "fail:bad module"; + } + + if (ctxt == nil) { + sys->fprint(stderr, "getfile: no window context\n"); + raise "fail:bad context"; + } + + wmlib->init(); + + startdir := "."; + geom := "-x " + string (ctxt.screen.image.r.dx() / 5) + + " -y " + string (ctxt.screen.image.r.dy() / 5); + title := "Select a file"; + arg->init(argv); + while (opt := arg->opt()) { + case opt { + 'g' => + geom = arg->arg(); + 'd' => + startdir = arg->arg(); + 't' => + title = arg->arg(); + * => + sys->fprint(stderr, "getfile: unknown option -%c\n", opt); + usage(); + } + } + if (geom == nil || startdir == nil || title == nil) + usage(); + top := tk->toplevel(ctxt.screen, geom); + argv = arg->argv(); + arg = nil; + sys->print("%s\n", wmlib->filename(ctxt.screen, top, title, argv, startdir)); +} diff --git a/appl/cmd/gettar.b b/appl/cmd/gettar.b new file mode 100644 index 00000000..4429ab24 --- /dev/null +++ b/appl/cmd/gettar.b @@ -0,0 +1,248 @@ +implement Gettar; + +include "sys.m"; + sys: Sys; + print, sprint, fprint: import sys; + stdin, stderr: ref sys->FD; + +include "draw.m"; + +include "arg.m"; + +TBLOCK: con 512; # tar logical blocksize + +Header: adt{ + name: string; + size: int; + mode: int; + mtime: int; + skip: int; +}; + +Gettar: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +error(mess: string) +{ + fprint(stderr,"gettar: %s\n",mess); + raise "fail:error"; +} + +verbose := 0; +NBLOCK: con 20; # traditional blocking factor for efficient read +tarbuf := array[NBLOCK*TBLOCK] of byte; # static buffer +nblock := NBLOCK; # how many blocks of data are in tarbuf +recno := NBLOCK; # how many blocks in tarbuf have been consumed + +getblock(): array of byte +{ + if(recno>=nblock){ + i := sys->read(stdin,tarbuf,TBLOCK*NBLOCK); + if(i==0) + return nil; + if(i<0) + error(sys->sprint("read error: %r")); + if(i%TBLOCK!=0) + error("blocksize error"); + nblock = i/TBLOCK; + recno = 0; + } + recno++; + return tarbuf[(recno-1)*TBLOCK:recno*TBLOCK]; +} + + +octal(b:array of byte): int +{ + sum := 0; + for(i:=0; i<len b; i++){ + bi := int b[i]; + if(bi==' ') continue; + if(bi==0) break; + sum = 8*sum + bi-'0'; + } + return sum; +} + +nullterm(b:array of byte): string +{ + for(i:=0; i<len b; i++) + if(b[i]==byte 0) break; + return string b[0:i]; +} + +getdir(): ref Header +{ + dblock := getblock(); + if(len dblock==0) + return nil; + if(dblock[0]==byte 0) + return nil; + + name := nullterm(dblock[0:100]); + if(int dblock[345]!=0) + name = nullterm(dblock[345:500])+"/"+name; + if(!absolute){ + if(name[0] == '#') + name = "./"+name; + else if(name[0] == '/') + name = "."+name; + } + + magic := string(dblock[257:262]); + if(magic[0]!=0 && magic!="ustar") + error("bad magic "+name); + chksum := octal(dblock[148:156]); + for(ci:=148; ci<156; ci++) + dblock[ci] = byte ' '; + for(i:=0; i<TBLOCK; i++) + chksum -= int dblock[i]; + if(chksum!=0) + error("directory checksum error "+name); + + skip := 1; + size := 0; + mode := 0; + mtime := 0; + case int dblock[156]{ + '0' or '7' or 0 => + skip = 0; + size = octal(dblock[124:136]); + mode = 8r777 & octal(dblock[100: 108]); + mtime = octal(dblock[136:148]); + '1' => + fprint(stderr,"gettar: skipping link %s -> %s\n",name,string(dblock[157:257])); + '2' or 's' => + fprint(stderr,"gettar: skipping symlink %s\n",name); + '3' or '4' or '6' => + fprint(stderr,"gettar: skipping special file %s\n",name); + '5' => + if(name[(len name)-1]=='/') + checkdir(name+"."); + else + checkdir(name+"/."); + * => + error(sprint("unrecognized typeflag %d for %s",int dblock[156],name)); + } + return ref Header(name, size, mode, mtime, skip); +} + +keep := 0; +absolute := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stdin = sys->fildes(0); + stderr = sys->fildes(2); + ofile: ref sys->FD; + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("gettar [-kTRv] [file ...]"); + while((o := arg->opt()) != 0) + case o { + 'k' => keep = 1; + 'v' => verbose = 1; + 'R' => absolute = 1; + * => arg->usage(); + } + args = arg->argv(); + arg = nil; + + while((file := getdir())!=nil){ + if(!file.skip){ + if((args == nil || matched(file.name, args)) && !(keep && exists(file.name))){ + if(verbose) + sys->fprint(stderr, "%s\n", file.name); + checkdir(file.name); + ofile = sys->create(file.name, Sys->OWRITE, 8r666); + if(ofile==nil){ + fprint(stderr, "gettar: cannot create %s: %r\n",file.name); + file.skip = 1; + } + }else + file.skip = 1; + } + bytes := file.size; + blocks := (bytes+TBLOCK-1)/TBLOCK; + if(file.skip){ + for(; blocks>0; blocks--) + getblock(); + continue; + } + + for(; blocks>0; blocks--){ + buf := getblock(); + nwrite := bytes; + if(nwrite>TBLOCK) + nwrite = TBLOCK; + if(sys->write(ofile,buf,nwrite)!=nwrite) + error(sprint("write error for %s: %r",file.name)); + bytes -= nwrite; + } + ofile = nil; + stat := sys->nulldir; + stat.mode = file.mode; + stat.mtime = file.mtime; + rc := sys->wstat(file.name,stat); + if(rc<0){ + # try just the mode + stat.mtime = ~0; + rc = sys->wstat(file.name, stat); + if(rc < 0) + fprint(stderr,"gettar: cannot set mode/mtime %s %#o %ud: %r\n",file.name, file.mode, file.mtime); + } + } +} + +checkdir(name: string) +{ + (nc,compl) := sys->tokenize(name,"/"); + path := ""; + while(compl!=nil){ + comp := hd compl; + if(comp=="..") + error(".. pathnames forbidden"); + if(nc>1){ + if(path=="") + path = comp; + else + path += "/"+comp; + (rc,stat) := sys->stat(path); + if(rc<0){ + fd := sys->create(path,Sys->OREAD,Sys->DMDIR+8r777); + if(fd==nil) + error(sprint("cannot mkdir %s: %r",path)); + fd = nil; + }else if(stat.mode&Sys->DMDIR==0) + error(sprint("found non-directory at %s",path)); + } + nc--; compl = tl compl; + } +} + +exists(path: string): int +{ + return sys->stat(path).t0 >= 0; +} + +matched(n: string, names: list of string): int +{ + for(; names != nil; names = tl names){ + p := hd names; + if(prefix(p, n)) + return 1; + } + return 0; +} + +prefix(p: string, s: string): int +{ + l := len p; + if(l > len s) + return 0; + return p == s[0:l] && (l == len s || s[l] == '/'); +} diff --git a/appl/cmd/gif2bit.b b/appl/cmd/gif2bit.b new file mode 100644 index 00000000..1bd35521 --- /dev/null +++ b/appl/cmd/gif2bit.b @@ -0,0 +1,101 @@ +# +# gif2bit - +# +# A simple command line utility for converting GIF images to +# inferno bitmaps. +# +# Craig Newell, Jan. 1999 CraigN@cheque.uq.edu.au +# +implement gif2bit; + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Display: import draw; +include "string.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "imagefile.m"; + +mod_name := "gif2bit"; + +gif2bit : module +{ + init: fn(ctx: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->print("usage: %s <GIF file>\n", mod_name); + exit; +} + +init(ctx: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + + # check arguments + if (argv == nil) + usage(); + mod_name = hd argv; + argv = tl argv; + if (argv == nil) + usage(); + s := hd argv; + if (len s && s[0] == '-') + usage(); + + # load the modules + str := load String String->PATH; + draw = load Draw Draw->PATH; + bufio = load Bufio Bufio->PATH; + remap := load Imageremap Imageremap->PATH; + imgfile := load RImagefile RImagefile->READGIFPATH; + imgfile->init(bufio); + + # open the display + display: ref Draw->Display; + if (ctx == nil) { + display = Display.allocate(nil); + } else { + display = ctx.display; + } + + # process all the files + while (argv != nil) { + + # get the filenames + gif_name := hd argv; + argv = tl argv; + (base_name, nil) := str->splitstrl(gif_name, ".gif"); + bit_name := base_name + ".bit"; + + i := bufio->open(gif_name, Bufio->OREAD); + if (i == nil) { + sys->print("%s: unable to open <%s>\n", mod_name, gif_name); + continue; + } + (raw_img, errstr) := imgfile->read(i); + if (errstr != nil) { + sys->print("%s: %s\n", mod_name, errstr); + continue; + } + i.close(); + + (img, errstr1) := remap->remap(raw_img, display, 0); + if (errstr1 != nil) { + sys->print("%s: %s\n", mod_name, errstr1); + continue; + } + + ofd := sys->create(bit_name, Sys->OWRITE, 8r644); + if (ofd == nil) { + sys->print("%s: unable to create <%s>\n", mod_name, bit_name); + continue; + } + display.writeimage(ofd, img); + ofd = nil; + } +} diff --git a/appl/cmd/grep.b b/appl/cmd/grep.b new file mode 100644 index 00000000..d534de3b --- /dev/null +++ b/appl/cmd/grep.b @@ -0,0 +1,155 @@ +implement Grep; + +include "sys.m"; + sys: Sys; + FD: import Sys; + stdin, stderr, stdout: ref FD; + +include "draw.m"; + Context: import Draw; + +include "regex.m"; + regex: Regex; + Re: import regex; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + + +Grep: module +{ + init: fn(ctxt: ref Context, argv: list of string); +}; + +multi: int; +lflag, nflag, vflag, iflag, Lflag, sflag: int = 0; + +badmodule(path: string) +{ + sys->fprint(stderr, "grep: cannot load %s: %r\n", path); + raise "fail:bad module"; +} + +init(nil: ref Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + arg := load Arg Arg->PATH; + if (arg == nil) + badmodule(Arg->PATH); + + regex = load Regex Regex->PATH; + if(regex == nil) + badmodule(Regex->PATH); + + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + badmodule(Bufio->PATH); + + arg->init(argv); + while ((opt := arg->opt()) != 0) { + case opt { + 'l' => + lflag = 1; + 'n' => + nflag = 1; + 'v' => + vflag = 1; + 'i' => + iflag = 1; + 'L' => + Lflag = 1; + 's' => + sflag = 1; + * => + usage(); + } + } + argv = arg->argv(); + arg = nil; + + if(argv == nil) + usage(); + pattern := hd argv; + argv = tl argv; + if (iflag) + pattern = tolower(pattern); + (re, err) := regex->compile(pattern,0); + if(re == nil) { + sys->fprint(stderr, "grep: %s\n", err); + raise "fail:bad regex"; + } + + matched := 0; + if(argv == nil) + matched = grep(re, bufio->fopen(stdin, Bufio->OREAD), "stdin"); + else { + multi = (tl argv != nil); + for (; argv != nil; argv = tl argv) { + f := bufio->open(hd argv, Bufio->OREAD); + if(f == nil) + sys->fprint(stderr, "grep: cannot open %s: %r\n", hd argv); + else + matched += grep(re, f, hd argv); + } + } + if (!matched) + raise "fail:no matches"; +} + +usage() +{ + sys->fprint(stderr, "usage: grep [-lnviLs] pattern [file...]\n"); + raise "fail:usage"; +} + +grep(re: Re, f: ref Iobuf, file: string): int +{ + matched := 0; + for(line := 1; ; line++) { + s := t := f.gets('\n'); + if(s == nil) + break; + if (iflag) + s = tolower(s); + if((regex->executese(re, s, (0, len s-1), 1, 1) != nil) ^ vflag) { + matched = 1; + if(lflag || sflag) { + if (!sflag) + sys->print("%s\n", file); + return matched; + } + if (!Lflag) { + if(nflag) + if(multi) + sys->print("%s:%d: %s", file, line, t); + else + sys->print("%d:%s", line, t); + else + if(multi) + sys->print("%s: %s", file, t); + else + sys->print("%s", t); + } + } + } + if (Lflag && matched == 0 && !sflag) + sys->print("%s\n", file); + return matched; +} + +tolower(s: string): string +{ + for (i := 0; i < len s; i++) { + c := s[i]; + if (c >= 'A' && c <= 'Z') + s[i] = c - 'A' + 'a'; + } + return s; +} diff --git a/appl/cmd/gunzip.b b/appl/cmd/gunzip.b new file mode 100644 index 00000000..6cb9eaf8 --- /dev/null +++ b/appl/cmd/gunzip.b @@ -0,0 +1,139 @@ +implement Gunzip; + +include "sys.m"; + sys: Sys; + fprint, sprint: import sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "filter.m"; + inflate: Filter; + +Gunzip: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +argv0: con "gunzip"; +stderr: ref Sys->FD; + +INFLATEPATH: con "/dis/lib/inflate.dis"; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + fatal(sys->sprint("cannot load %s: %r", Bufio->PATH)); + str = load String String->PATH; + if (bufio == nil) + fatal(sys->sprint("cannot load %s: %r", String->PATH)); + inflate = load Filter INFLATEPATH; + if (inflate == nil) + fatal(sys->sprint("cannot load %s: %r", INFLATEPATH)); + + inflate->init(); + + if(argv != nil) + argv = tl argv; + + ok := 1; + if(len argv == 0){ + bin := bufio->fopen(sys->fildes(0), Bufio->OREAD); + bout := bufio->fopen(sys->fildes(1), Bufio->OWRITE); + ok = gunzip(bin, bout, "stdin", "stdout"); + bout.close(); + } else { + for(; argv != nil; argv = tl argv) + ok &= gunzipf(hd argv); + } + if(ok == 0) + raise "fail:errors"; +} + +gunzipf(file: string): int +{ + bin := bufio->open(file, Bufio->OREAD); + if(bin == nil){ + fprint(stderr, "%s: can't open %s: %r\n", argv0, file); + return 0; + } + + (nil, ofile) := str->splitr(file, "/"); + n := len ofile; + if(n < 4 || ofile[n-3:] != ".gz"){ + fprint(stderr, "%s: .gz extension required: %s\n", argv0, file); + bin.close(); + return 0; + } else + ofile = ofile[:n-3]; + bout := bufio->create(ofile, Bufio->OWRITE, 8r666); + if(bout == nil){ + fprint(stderr, "%s: can't open %s: %r\n", argv0, ofile); + bin.close(); + return 0; + } + + ok := gunzip(bin, bout, file, ofile); + bin.close(); + bout.close(); + if(ok) { + # did possibly rename file and update modification time here. + if (sys->remove(file) == -1) + sys->fprint(stderr, "%s: cannot remove %s: %r\n", argv0, file); + } + + return ok; +} + +gunzip(bin, bout: ref Iobuf, fin, fout: string): int +{ + rq := inflate->start("h"); + for(;;) { + pick m := <-rq { + Fill => + n := bin.read(m.buf, len m.buf); + m.reply <-= n; + if (n == -1) { + sys->fprint(stderr, "%s: %s: read error: %r\n", argv0, fin); + return 0; + } + Result => + if (len m.buf > 0) { + n := bout.write(m.buf, len m.buf); + if (n != len m.buf) { + m.reply <-= -1; + sys->fprint(stderr, "%s: %s: write error: %r\n", argv0, fout); + return 0; + } + m.reply <-= 0; + } + #Info => + # if m.msg begins with "file", it's the original filename of the compressed file. + # if m.msg begins with "mtime", it's the original modification time. + Finished => + if (bout.flush() != 0) { + sys->fprint(stderr, "%s: %s: flush error: %r\n", argv0, fout); + return 0; + } + return 1; + Error => + sys->fprint(stderr, "%s: %s: inflate error: %s\n", argv0, fin, m.e); + return 0; + } + } +} + +fatal(msg: string) +{ + fprint(stderr, "%s: %s\n", argv0, msg); + raise "fail:error"; +} diff --git a/appl/cmd/gzip.b b/appl/cmd/gzip.b new file mode 100644 index 00000000..b87186d0 --- /dev/null +++ b/appl/cmd/gzip.b @@ -0,0 +1,228 @@ +implement Gzip; + +include "sys.m"; + sys: Sys; + print, fprint: import sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "daytime.m"; + daytime: Daytime; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "filter.m"; + deflate: Filter; + +DEFLATEPATH: con "/dis/lib/deflate.dis"; + +Gzip: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +Arg: adt +{ + argv: list of string; + c: int; + opts: string; + + init: fn(argv: list of string): ref Arg; + opt: fn(arg: self ref Arg): int; + arg: fn(arg: self ref Arg): string; +}; + +argv0: con "gzip"; +stderr: ref Sys->FD; +debug := 0; +verbose := 0; +level := 0; + +usage() +{ + fprint(stderr, "usage: %s [-vD1-9] [file ...]\n", argv0); + raise "fail:usage"; +} + +nomod(path: string) +{ + sys->fprint(stderr, "%s: cannot load %s: %r\n", argv0, path); + raise "fail:bad module"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + nomod(Bufio->PATH); + str = load String String->PATH; + if (str == nil) + nomod(String->PATH); + daytime = load Daytime Daytime->PATH; + if (daytime == nil) + nomod(Daytime->PATH); + deflate = load Filter DEFLATEPATH; + if(deflate == nil) + nomod(DEFLATEPATH); + + arg := Arg.init(argv); + level = 6; + while(c := arg.opt()){ + case c{ + 'D' => + debug++; + 'v' => + verbose++; + '1' to '9' => + level = c - '0'; + * => + usage(); + } + } + + deflate->init(); + + argv = arg.argv; + + ok := 1; + if(len argv == 0){ + bin := bufio->fopen(sys->fildes(0), Bufio->OREAD); + bout := bufio->fopen(sys->fildes(1), Bufio->OWRITE); + ok = gzip(nil, daytime->now(), bin, bout, "stdin", "stdout"); + bout.close(); + bin.close(); + }else{ + for(; argv != nil; argv = tl argv) + ok &= gzipf(hd argv); + } + exit; +} + +gzipf(file: string): int +{ + bin := bufio->open(file, Bufio->OREAD); + if(bin == nil){ + fprint(stderr, "%s: can't open %s: %r\n", argv0, file); + return 0; + } + (ok, dir) := sys->fstat(bin.fd); + if(ok >= 0) + mtime := dir.mtime; + else + mtime = daytime->now(); + + (nil, ofile) := str->splitr(file, "/"); + ofile += ".gz"; + bout := bufio->create(ofile, Bufio->OWRITE, 8r666); + if(bout == nil){ + fprint(stderr, "%s: can't open %s: %r\n", argv0, ofile); + bin.close(); + return 0; + } + + ok = gzip(file, mtime, bin, bout, file, ofile); + bout.close(); + bin.close(); + if (ok) + sys->remove(file); + else + sys->remove(ofile); + + return ok; +} + +gzip(nil: string, nil: int, bin, bout: ref Iobuf, fin, fout: string): int +{ + param := "h" + string level; + incount := outcount := 0; + if (debug) + param += "dv"; + rq := deflate->start(param); + crc := 0; + for (;;) { + pick m := <-rq { + Fill => + n := bin.read(m.buf, len m.buf); + m.reply <-= n; + if (n == -1) { + sys->fprint(stderr, "%s: error reading %s: %r\n", argv0, fin); + return 0; + } + incount += n; + Result => + n := len m.buf; + if (bout.write(m.buf, n) != n) { + sys->fprint(stderr, "%s: error writing %s: %r\n", argv0, fout); + m.reply <-= -1; + return 0; + } + m.reply <-= 0; + outcount += n; + Info => + sys->fprint(stderr, "%s\n", m.msg); + Finished => + comp := 0.0; + if (incount > 0) + comp = 1.0 - real outcount / real incount; + if (verbose) + sys->fprint(stderr, "%s: %5.2f%%\n", fin, comp * 100.0); + return 1; + Error => + sys->fprint(stderr, "%s: error compressing %s: %s\n", argv0, fin, m.e); + return 0; + } + } +} + +fatal(msg: string) +{ + fprint(stderr, "%s: %s\n", argv0, msg); + exit; +} + +Arg.init(argv: list of string): ref Arg +{ + if(argv != nil) + argv = tl argv; + return ref Arg(argv, 0, nil); +} + +Arg.opt(arg: self ref Arg): int +{ + if(arg.opts != ""){ + arg.c = arg.opts[0]; + arg.opts = arg.opts[1:]; + return arg.c; + } + if(arg.argv == nil) + return arg.c = 0; + arg.opts = hd arg.argv; + if(len arg.opts < 2 || arg.opts[0] != '-') + return arg.c = 0; + arg.argv = tl arg.argv; + if(arg.opts == "--") + return arg.c = 0; + arg.c = arg.opts[1]; + arg.opts = arg.opts[2:]; + return arg.c; +} + +Arg.arg(arg: self ref Arg): string +{ + s := arg.opts; + arg.opts = ""; + if(s != "") + return s; + if(arg.argv == nil) + return ""; + s = hd arg.argv; + arg.argv = tl arg.argv; + return s; +} diff --git a/appl/cmd/idea.b b/appl/cmd/idea.b new file mode 100644 index 00000000..b597f8da --- /dev/null +++ b/appl/cmd/idea.b @@ -0,0 +1,116 @@ +implement Idea; + +# +# Copyright © 2002 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "keyring.m"; + keyring: Keyring; + +Idea: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +decerr(s: string) +{ + sys->fprint(sys->fildes(2), "decrypt error: %s (wrong password ?)\n", s); + exit; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stdin := sys->fildes(0); + stdout := sys->fildes(1); + + bufio = load Bufio Bufio->PATH; + keyring = load Keyring Keyring->PATH; + + obuf := array[8] of byte; + buf := array[8] of byte; + key := array[16] of byte; + + argc := len argv; + if((argc != 3 && argc != 4) || (hd tl argv != "-e" && hd tl argv != "-d") || len hd tl tl argv != 16){ + sys->fprint(sys->fildes(2), "usage: idea -[e | d] <16 char key> [inputfile]\n"); + exit; + } + dec := hd tl argv == "-d"; + if(argc == 4){ + s := hd tl tl tl argv; + stdin = sys->open(s, Sys->OREAD); + if(stdin == nil){ + sys->fprint(sys->fildes(2), "cannot open %s\n", s); + exit; + } + if(dec){ + l := len s; + if(s[l-3: l] != ".id"){ + sys->fprint(sys->fildes(2), "input file not a .id file\n"); + exit; + } + s = s[0: l-3]; + } + else + s += ".id"; + stdout = sys->create(s, Sys->OWRITE, 8r666); + if(stdout == nil){ + sys->fprint(sys->fildes(2), "cannot create %s\n", s); + exit; + } + } + for(i := 0; i < 16; i++) + key[i] = byte (hd tl tl argv)[i]; + is := keyring->ideasetup(key, nil); + m := om := 0; + bin := bufio->fopen(stdin, Bufio->OREAD); + bout := bufio->fopen(stdout, Bufio->OWRITE); + for(;;){ + n := bin.read(buf[m: ], 8-m); + if(n <= 0) + break; + m += n; + if(m == 8){ + keyring->ideaecb(is, buf, 8, dec); + if(dec){ # leave last block around + if(om > 0) + bout.write(obuf, 8); + obuf[0: ] = buf[0: 8]; + om = 8; + } + else + bout.write(buf, 8); + m = 0; + } + } + if(dec){ + if(om != 8) + decerr("no last block"); + if(m != 0) + decerr("last block not 8 bytes long"); + m = int obuf[7]; + if(m < 0 || m > 7) + decerr("bad modulus"); + for(i = m; i < 8-1; i++) + if(obuf[i] != byte 0) + decerr("byte not 0"); + bout.write(obuf, m); + } + else{ + for(i = m; i < 8; i++) + buf[i] = byte 0; + buf[7] = byte m; + keyring->ideaecb(is, buf, 8, dec); + bout.write(buf, 8); + } + bout.flush(); + bin.close(); + bout.close(); +} diff --git a/appl/cmd/import.b b/appl/cmd/import.b new file mode 100644 index 00000000..657deb38 --- /dev/null +++ b/appl/cmd/import.b @@ -0,0 +1,192 @@ +implement Import; + +include "sys.m"; + sys: Sys; + +include "draw.m"; +include "keyring.m"; +include "security.m"; +include "factotum.m"; +include "encoding.m"; +include "arg.m"; + +Import: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +factotumfile := "/mnt/factotum/rpc"; + +fail(status, msg: string) +{ + sys->fprint(sys->fildes(2), "import: %s\n", msg); + raise "fail:"+status; +} + +nomod(mod: string) +{ + fail("load", sys->sprint("can't load %s: %r", mod)); +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + factotum := load Factotum Factotum->PATH; + if(factotum == nil) + nomod(Factotum->PATH); + factotum->init(); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + + arg->init(args); + arg->setusage("import [-a|-b] [-c] [-e enc digest] host file [localfile]"); + flags := 0; + cryptalg := ""; # will be rc4_256 sha1 + keyspec := ""; + while((o := arg->opt()) != 0) + case o { + 'a' => + flags |= Sys->MAFTER; + 'b' => + flags |= Sys->MBEFORE; + 'c' => + flags |= Sys->MCREATE; + 'e' => + cryptalg = arg->earg(); + if(cryptalg == "clear") + cryptalg = nil; + 'k' => + keyspec = arg->earg(); + '9' => + ; + * => + arg->usage(); + } + args = arg->argv(); + if(len args != 2 && len args != 3) + arg->usage(); + arg = nil; + addr := hd args; + file := hd tl args; + mountpt := file; + if(len args > 2) + mountpt = hd tl tl args; + + sys->pctl(Sys->FORKFD, nil); + + facfd := sys->open(factotumfile, Sys->ORDWR); + if(facfd == nil) + fail("factotum", sys->sprint("can't open %s: %r", factotumfile)); + + dest := netmkaddr(addr, "net", "exportfs"); + (ok, c) := sys->dial(dest, nil); + if(ok < 0) + fail("dial failed", sys->sprint("can't dial %s: %r", dest)); + ai := factotum->proxy(c.dfd, facfd, "proto=p9any role=client "+keyspec); + if(ai == nil) + fail("auth", sys->sprint("can't authenticate import: %r")); + if(sys->fprint(c.dfd, "%s", file) < 0) + fail("import", sys->sprint("can't write to remote: %r")); + buf := array[256] of byte; + if((n := sys->read(c.dfd, buf, len buf)) != 2 || buf[0] != byte 'O' || buf[1] != byte 'K'){ + if(n >= 4) + sys->werrstr("bad remote tree: "+string buf[0:n]); + fail("import", sys->sprint("import %s %s: %r", addr, file)); + } + if(cryptalg != nil){ + if(ai.secret == nil) + fail("import", "factotum didn't establish shared secret"); + random := load Random Random->PATH; + if(random == nil) + nomod(Random->PATH); + kr := load Keyring Keyring->PATH; + if(kr == nil) + nomod(Keyring->PATH); + base64 := load Encoding Encoding->BASE64PATH; + if(base64 == nil) + nomod(Encoding->BASE64PATH); + if(sys->fprint(c.dfd, "impo nofilter ssl\n") < 0) + fail("import", sys->sprint("can't write to remote: %r")); + key := array[16] of byte; # myrand[4] secret[8] hisrand[4] + key[0:] = random->randombuf(Random->ReallyRandom, 4); + ns := len ai.secret; + if(ns > 8) + ns = 8; + key[4:] = ai.secret[0:ns]; + if(sys->write(c.dfd, key, 4) != 4) + fail("import", sys->sprint("can't write key to remote: %r")); + if(readn(c.dfd, key[12:], 4) != 4) + fail("import", sys->sprint("can't read remote key: %r")); + digest := array[Keyring->SHA1dlen] of byte; + kr->sha1(key, len key, digest, nil); + err: string; + (c.dfd, err) = pushssl(c.dfd, base64->dec(S(digest[0:10])), base64->dec(S(digest[10:20])), cryptalg); + if(err != nil) + fail("import", sys->sprint("can't push security layer: %s", err)); + }else + if(sys->fprint(c.dfd, "impo nofilter clear\n") < 0) + fail("import", sys->sprint("can't write to remote: %r")); + afd := sys->fauth(c.dfd, ""); + if(afd != nil) + factotum->proxy(afd, facfd, "proto=p9any role=client"); + if(sys->mount(c.dfd, afd, mountpt, flags, "") < 0) + fail("mount failed", sys->sprint("import %s %s: mount failed: %r", addr, file)); +} + +readn(fd: ref Sys->FD, buf: array of byte, nb: int): int +{ + for(nr := 0; nr < nb;){ + n := sys->read(fd, buf[nr:], nb-nr); + if(n <= 0){ + if(nr == 0) + return n; + break; + } + nr += n; + } + return nr; +} + +S(a: array of byte): string +{ + s := ""; + for(i:=0; i<len a; i++) + s += sys->sprint("%.2ux", int a[i]); + return s; +} + +pushssl(fd: ref Sys->FD, secretin, secretout: array of byte, alg: string): (ref Sys->FD, string) +{ + ssl := load SSL SSL->PATH; + if(ssl == nil) + nomod(SSL->PATH); + + (err, c) := ssl->connect(fd); + if(err != nil) + return (nil, "can't connect ssl: " + err); + + err = ssl->secret(c, secretin, secretout); + if(err != nil) + return (nil, "can't write secret: " + err); + if(sys->fprint(c.cfd, "alg %s", alg) < 0) + return (nil, sys->sprint("can't push algorithm %s: %r", alg)); + + return (c.dfd, nil); +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/install/NOTICE b/appl/cmd/install/NOTICE new file mode 100644 index 00000000..1c591576 --- /dev/null +++ b/appl/cmd/install/NOTICE @@ -0,0 +1,6 @@ +Most of the code in this directory is a limbo version of Russ Cox's wrap, the +software package manager that was written for Plan9 distributions. His original +C code may have been modularized and partly rewritten to use limbo features, +but the credit and thanks must go to Russ for developing the original system. + + diff --git a/appl/cmd/install/applylog.b b/appl/cmd/install/applylog.b new file mode 100644 index 00000000..6a1b2e63 --- /dev/null +++ b/appl/cmd/install/applylog.b @@ -0,0 +1,699 @@ +implement Applylog; + +# +# apply a plan 9-style replica log +# this version applies everything and doesn't use the database +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "keyring.m"; + kr: Keyring; + +include "daytime.m"; + daytime: Daytime; + +include "logs.m"; + logs: Logs; + Db, Entry, Byname, Byseq: import logs; + S: import logs; + +include "arg.m"; + +Applylog: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Apply, Applydb, Install, Asis, Skip: con iota; + +client: ref Db; # client current state from client log +updates: ref Db; # state delta from new section of server log + +nerror := 0; +nconflict := 0; +debug := 0; +verbose := 0; +resolve := 0; +setuid := 0; +setgid := 0; +nflag := 0; +timefile: string; +clientroot: string; +srvroot: string; +logfd: ref Sys->FD; +now := 0; +gen := 0; +noerr := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + bufio = load Bufio Bufio->PATH; + ensure(bufio, Bufio->PATH); + str = load String String->PATH; + ensure(str, String->PATH); + kr = load Keyring Keyring->PATH; + ensure(kr, Keyring->PATH); + daytime = load Daytime Daytime->PATH; + ensure(daytime, Daytime->PATH); + logs = load Logs Logs->PATH; + ensure(logs, Logs->PATH); + logs->init(bufio); + + arg := load Arg Arg->PATH; + ensure(arg, Arg->PATH); + arg->init(args); + arg->setusage("applylog [-vuged] [-sc] [-T timefile] clientlog clientroot serverroot [path ... ] <serverlog"); + dump := 0; + while((o := arg->opt()) != 0) + case o { + 'T' => timefile = arg->earg(); + 'd' => dump = 1; debug = 1; + 'e' => noerr = 1; + 'g' => setgid = 1; + 'n' => nflag = 1; verbose = 1; + 's' or 'c' => resolve = o; + 'u' => setuid = 1; + 'v' => verbose = 1; + * => arg->usage(); + } + args = arg->argv(); + if(len args < 3) + arg->usage(); + arg = nil; + + now = daytime->now(); + client = Db.new("client log"); + updates = Db.new("update log"); + clientlog := hd args; args = tl args; + clientroot = hd args; args = tl args; + srvroot = hd args; args = tl args; + if(args != nil) + error("restriction by path not yet done"); + + checkroot(clientroot, "client root"); + checkroot(srvroot, "server root"); + + # replay the client log to build last installation state of files taken from server + if(nflag) + logfd = sys->open(clientlog, Sys->OREAD); + else + logfd = sys->open(clientlog, Sys->ORDWR); + if(logfd == nil) + error(sys->sprint("can't open %s: %r", clientlog)); + f := bufio->fopen(logfd, Sys->OREAD); + if(f == nil) + error(sys->sprint("can't open %s: %r", clientlog)); + while((log := readlog(f)) != nil) + replaylog(client, log); + f = nil; + sys->seek(logfd, big 0, 2); + if(dump) + dumpstate(); + if(debug){ + sys->print(" CLIENT STATE\n"); + client.sort(Byname); + dumpdb(client, 0); + } + + # read server's log and use the new section to build a sequence of update actions + minseq := big 0; + if(timefile != nil) + minseq = readseq(timefile); + f = bufio->fopen(sys->fildes(0), Sys->OREAD); + while((log = readlog(f)) != nil) + if(log.seq > minseq) + update(updates, updates.look(log.path), log); + updates.sort(Byseq); + if(debug){ + sys->print(" SEQUENCED UPDATES\n"); + dumpdb(updates, 1); + } + + # apply those actions + maxseq := minseq; + skip := 0; + for(i := 0; i < updates.nstate; i++){ + e := updates.state[i]; + ce := client.look(e.path); + if(ce != nil && ce.seq >= e.seq){ # replay + if(debug) + sys->print("replay %c %q\n", e.action, e.path); + if(!nflag && !skip) + maxseq = e.seq; + continue; + } + if(verbose) + sys->print("%s\n", e.sumtext()); + case chooseaction(e) { + Install => + if(debug) + sys->print("resolve %q to install\n", e.path); + c := e; + c.action = 'a'; # force (re)creation/installation + if(!enact(c)){ + skip = 1; + continue; # don't update db + } + Apply => + if(!enact(e)){ + skip = 1; + continue; # don't update db + } + Applydb => + if(debug) + sys->print("resolve %q to update db\n", e.path); + # carry on to update the log + Asis => + if(debug) + sys->print("resolve %q to client\n", e.path); + #continue; # ? + Skip => + if(debug) + sys->print("conflict %q\n", e.path); + skip = 1; + continue; + * => + error("internal error: unexpected result from chooseaction"); + } + # action complete: add to client log + if(ce == nil) + ce = client.entry(e.seq, e.path, e.d); + ce.update(e); + if(!nflag){ + if(!skip) + maxseq = e.seq; + if(logfd != nil){ + # append action, now accepted, to client's own log + if(sys->fprint(logfd, "%s\n", e.logtext()) < 0) + error(sys->sprint("error writing to %q: %r", clientlog)); + } + } + } + sys->fprint(sys->fildes(2), "maxseq: %bud %bud\n", maxseq>>32, maxseq & 16rFFFFFFFF); + if(!nflag && !skip && timefile != nil) + writeseq(timefile, maxseq); + if(nconflict) + raise sys->sprint("fail:%d conflicts", nconflict); + if(nerror) + raise sys->sprint("fail:%d errors", nerror); +} + +checkroot(dir: string, what: string) +{ + (ok, d) := sys->stat(dir); + if(ok < 0) + error(sys->sprint("can't stat %s %q: %r", what, dir)); + if((d.mode & Sys->DMDIR) == 0) + error(sys->sprint("%s %q: not a directory", what, dir)); +} + +readlog(in: ref Iobuf): ref Entry +{ + (e, err) := Entry.read(in); + if(err != nil) + error(err); + return e; +} + +readseq(file: string): big +{ + fd := sys->open(file, Sys->OREAD); + if(fd == nil) + error(sys->sprint("can't open %q: %r", file)); + buf := array[128] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + error(sys->sprint("can't read valid seq from %q", file)); + (nf, flds) := sys->tokenize(string buf[0:n], " \t\n"); + if(nf != 2) + error(sys->sprint("illegal sequence number in %q", file)); + n0 := bigof(hd flds, 10); + n1 := bigof(hd tl flds, 10); + return (n0 << 32) | n1; +} + +writeseq(file: string, n: big) +{ + fd := sys->create(file, Sys->OWRITE, 8r666); + if(fd == nil) + error(sys->sprint("can't create %q: %r", file)); + if(sys->fprint(fd, "%11bud %11bud", n>>32, n&16rFFFFFFFF) < 0) + error(sys->sprint("error writing seq to %q: %r", file)); +} + +# +# replay a log to reach the state wrt files previously taken from the server +# +replaylog(db: ref Db, log: ref Entry) +{ + e := db.look(log.path); + indb := e != nil && !e.removed(); + case log.action { + 'a' => # add new file + if(indb){ + note(sys->sprint("%q duplicate create", log.path)); + return; + } + 'c' => # contents + if(!indb){ + note(sys->sprint("%q contents but no entry", log.path)); + return; + } + 'd' => # delete + if(!indb){ + note(sys->sprint("%q deleted but no entry", log.path)); + return; + } + if(e.d.mtime > log.d.mtime){ + note(sys->sprint("%q deleted but it's newer", log.path)); + return; + } + 'm' => # metadata + if(!indb){ + note(sys->sprint("%q metadata but no entry", log.path)); + return; + } + * => + error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF)); + } + update(db, e, log); +} + +# +# update file state e to reflect the effect of the log, +# creating a new entry if necessary +# +update(db: ref Db, e: ref Entry, log: ref Entry) +{ + if(e == nil) + e = db.entry(log.seq, log.path, log.d); + e.update(log); +} + +chooseaction(e: ref Entry): int +{ + cf := logs->mkpath(clientroot, e.path); + sf := logs->mkpath(srvroot, e.serverpath); + (ishere, cd) := sys->stat(logs->mkpath(clientroot, e.path)); + ishere = ishere >= 0; # in local file system + db := client.look(e.path); + indb := db != nil && !db.removed(); # previously arrived from server + + unchanged := indb && ishere && (samestat(db.d, cd) || samecontents(sf, cf)) || !indb && !ishere; + if(unchanged && (e.action != 'm' || samemeta(db.d, cd))) + return Apply; + if(!ishere && e.action == 'd'){ + if(indb) + return Applydb; + return Asis; + } + case resolve { + 'c' => + return Asis; + 's' => + if(!ishere || e.action == 'm' && !unchanged) + return Install; + return Apply; + * => + # describe source of conflict + if(indb){ + if(ishere){ + if(e.action == 'm' && unchanged && !samemeta(db.d, cd)) + conflict(e.path, "locally modified metadata", action(e.action)); + else + conflict(e.path, "locally modified", action(e.action)); + }else + conflict(e.path, "locally removed", action(e.action)); + }else{ + if(db != nil) + conflict(e.path, "locally retained or recreated", action(e.action)); # server installed it but later removed it + else + conflict(e.path, "locally created", action(e.action)); + } + return Skip; + } +} + +enact(e: ref Entry): int +{ + if(nflag) + return 0; + srcfile := logs->mkpath(srvroot, e.serverpath); + dstfile := logs->mkpath(clientroot, e.path); + case e.action { + 'a' => # create and copy in + if(debug) + sys->print("create %q\n", dstfile); + if(e.d.mode & Sys->DMDIR) + err := mkdir(dstfile, e); + else + err = copyin(srcfile, dstfile, 1, e); + if(err != nil){ + if(noerr) + error(err); + warn(err); + return 0; + } + 'c' => # contents + err := copyin(srcfile, dstfile, 0, e); + if(err != nil){ + if(noerr) + error(err); + warn(err); + return 0; + } + 'd' => # delete + if(debug) + sys->print("remove %q\n", dstfile); + if(remove(dstfile) < 0){ + warn(sys->sprint("can't remove %q: %r", dstfile)); + return 0; + } + 'm' => # metadata + if(debug) + sys->print("wstat %q\n", dstfile); + d := sys->nulldir; + d.mode = e.d.mode; + if(sys->wstat(dstfile, d) < 0) + warn(sys->sprint("%q: can't change mode to %uo", dstfile, d.mode)); + if(setgid){ + d = sys->nulldir; + d.gid = e.d.gid; + if(sys->wstat(dstfile, d) < 0) + warn(sys->sprint("%q: can't change gid to %q", dstfile, d.gid)); + } + if(setuid){ + d = sys->nulldir; + d.uid = e.d.uid; + if(sys->wstat(dstfile, d) < 0) + warn(sys->sprint("%q: can't change uid to %q", dstfile, d.uid)); + } + * => + error(sys->sprint("unexpected log operation: %c %q", e.action, e.path)); + return 0; + } + return 1; +} + +rev[T](l: list of T): list of T +{ + rl: list of T; + for(; l != nil; l = tl l) + rl = hd l :: rl; + return rl; +} + +ensure[T](m: T, path: string) +{ + if(m == nil) + error(sys->sprint("can't load %s: %r", path)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "applylog: %s\n", s); + raise "fail:error"; +} + +note(s: string) +{ + sys->fprint(sys->fildes(2), "applylog: note: %s\n", s); +} + +warn(s: string) +{ + sys->fprint(sys->fildes(2), "applylog: warning: %s\n", s); + nerror++; +} + +conflict(name: string, why: string, wont: string) +{ + sys->fprint(sys->fildes(2), "%q: %s; will not %s\n", name, why, wont); + nconflict++; +} + +action(a: int): string +{ + case a { + 'a' => return "create"; + 'c' => return "update"; + 'd' => return "delete"; + 'm' => return "update metadata"; + * => return sys->sprint("unknown action %c", a); + } +} + +samecontents(path1, path2: string): int +{ + f1 := sys->open(path1, Sys->OREAD); + if(f1 == nil) + return 0; + f2 := sys->open(path2, Sys->OREAD); + if(f2 == nil) + return 0; + b1 := array[Sys->ATOMICIO] of byte; + b2 := array[Sys->ATOMICIO] of byte; + n := 256; # start with something small; dis files and big executables should fail more quickly + n1, n2: int; + do{ + n1 = sys->read(f1, b1, n); + n2 = sys->read(f2, b2, n); + if(n1 != n2) + return 0; + for(i := 0; i < n1; i++) + if(b1[i] != b2[i]) + return 0; + n += len b1 - n; + }while(n1 > 0); + return 1; +} + +samestat(a: Sys->Dir, b: Sys->Dir): int +{ + # doesn't check permission/ownership, does check QTDIR/QTFILE + if(a.mode & Sys->DMDIR) + return (b.mode & Sys->DMDIR) != 0; + return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name? +} + +samemeta(a: Sys->Dir, b: Sys->Dir): int +{ + return a.mode == b.mode && (!setuid || a.uid == b.uid) && (!setgid || a.gid == b.gid) && samestat(a, b); +} + +bigof(s: string, base: int): big +{ + (b, r) := str->tobig(s, base); + if(r != nil) + error("cruft in integer field in log entry: "+s); + return b; +} + +intof(s: string, base: int): int +{ + return int bigof(s, base); +} + +mkdir(dstpath: string, e: ref Entry): string +{ + fd := create(dstpath, Sys->OREAD, e.d.mode); + if(fd == nil) + return sys->sprint("can't mkdir %q: %r", dstpath); + fchmod(fd, e.d.mode); + if(setgid) + fchgrp(fd, e.d.gid); + if(setuid) + fchown(fd, e.d.uid); +# e.d.mtime = now; + return nil; +} + +fchmod(fd: ref Sys->FD, mode: int) +{ + d := sys->nulldir; + d.mode = mode; + if(sys->fwstat(fd, d) < 0) + warn(sys->sprint("%q: can't set mode %o: %r", sys->fd2path(fd), mode)); +} + +fchgrp(fd: ref Sys->FD, gid: string) +{ + d := sys->nulldir; + d.gid = gid; + if(sys->fwstat(fd, d) < 0) + warn(sys->sprint("%q: can't set group id %s: %r", sys->fd2path(fd), gid)); +} + +fchown(fd: ref Sys->FD, uid: string) +{ + d := sys->nulldir; + d.uid = uid; + if(sys->fwstat(fd, d) < 0) + warn(sys->sprint("%q: can't set user id %s: %r", sys->fd2path(fd), uid)); +} + +copyin(srcpath: string, dstpath: string, dowstat: int, e: ref Entry): string +{ + if(debug) + sys->print("copyin %q -> %q\n", srcpath, dstpath); + f := sys->open(srcpath, Sys->OREAD); + if(f == nil) + return sys->sprint("can't open %q: %r", srcpath); + t: ref Sys->FD; + (ok, nil) := sys->stat(dstpath); + if(ok < 0){ + t = create(dstpath, Sys->OWRITE, e.d.mode | 8r222); + if(t == nil) + return sys->sprint("can't create %q: %r", dstpath); + # TO DO: force access to parent directory + dowstat = 1; + }else{ + t = sys->open(dstpath, Sys->OWRITE|Sys->OTRUNC); + if(t == nil){ + err := sys->sprint("%r"); + if(!contains(err, "permission")) + return sys->sprint("can't overwrite %q: %s", dstpath, err); + } + } + (nw, err) := copy(f, t); + if(err != nil) + return err; + if(nw != e.d.length) + warn(sys->sprint("%q: log said %bud bytes, copied %bud bytes", dstpath, e.d.length, nw)); + f = nil; + if(dowstat){ + fchmod(t, e.d.mode); + if(setgid) + fchgrp(t, e.d.gid); + if(setuid) + fchown(t, e.d.uid); + } + nd := sys->nulldir; + nd.mtime = e.d.mtime; + if(sys->fwstat(t, nd) < 0) + warn(sys->sprint("%q: can't set mtime: %r", dstpath)); + return nil; +} + +copy(f: ref Sys->FD, t: ref Sys->FD): (big, string) +{ + buf := array[Sys->ATOMICIO] of byte; + nw := big 0; + while((n := sys->read(f, buf, len buf)) > 0){ + if(sys->write(t, buf, n) != n) + return (nw, sys->sprint("error writing %q: %r", sys->fd2path(t))); + nw += big n; + } + if(n < 0) + return (nw, sys->sprint("error reading %q: %r", sys->fd2path(f))); + return (nw, nil); +} + +contents(e: ref Entry): string +{ + s := ""; + for(cl := e.contents; cl != nil; cl = tl cl) + s += " " + hd cl; + return s; +} + +dumpstate() +{ + for(i := 0; i < client.nstate; i++) + sys->print("%d\t%s\n", i, client.state[i].text()); +} + +dumpdb(db: ref Db, tag: int) +{ + for(i := 0; i < db.nstate; i++){ + if(!tag) + s := db.state[i].dbtext(); + else + s = db.state[i].text(); + if(s != nil) + sys->print("%s\n", s); + } +} + +# +# perhaps these should be in a utility module +# +parent(name: string): string +{ + slash := -1; + for(i := 0; i < len name; i++) + if(name[i] == '/') + slash = i; + if(slash > 0) + return name[0:slash]; + return "/"; +} + +writableparent(name: string): (int, string) +{ + p := parent(name); + (ok, d) := sys->stat(p); + if(ok < 0) + return (-1, nil); + nd := sys->nulldir; + nd.mode |= 8r222; + sys->wstat(p, nd); + return (d.mode, p); +} + +create(name: string, rw: int, mode: int): ref Sys->FD +{ + fd := sys->create(name, rw, mode); + if(fd == nil){ + err := sys->sprint("%r"); + if(!contains(err, "permission")){ + sys->werrstr(err); + return nil; + } + (pm, p) := writableparent(name); + if(pm >= 0){ + fd = sys->create(name, rw, mode); + d := sys->nulldir; + d.mode = pm; + sys->wstat(p, d); + } + sys->werrstr(err); + } + return fd; +} + +remove(name: string): int +{ + if(sys->remove(name) >= 0) + return 0; + err := sys->sprint("%r"); + if(contains(err, "entry not found") || contains(err, "not exist")) + return 0; + (pm, p) := writableparent(name); + rc := sys->remove(name); + d := sys->nulldir; + if(pm >= 0){ + d.mode = pm; + sys->wstat(p, d); + } + sys->werrstr(err); + return rc; +} + +contains(s: string, sub: string): int +{ + return str->splitstrl(s, sub).t1 != nil; +} diff --git a/appl/cmd/install/arch.b b/appl/cmd/install/arch.b new file mode 100644 index 00000000..3f4d660d --- /dev/null +++ b/appl/cmd/install/arch.b @@ -0,0 +1,288 @@ +implement Arch; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "daytime.m"; + daytime : Daytime; +include "string.m"; + str : String; +include "bufio.m"; + bufio : Bufio; + Iobuf : import bufio; +include "sh.m"; +include "arch.m"; + +addp := 1; + +buf := array[Sys->ATOMICIO] of byte; + +init(bio: Bufio) +{ + sys = load Sys Sys->PATH; + if(bio == nil) + bufio = load Bufio Bufio->PATH; + else + bufio = bio; + daytime = load Daytime Daytime->PATH; + str = load String String->PATH; +} + +addperms(p: int) +{ + addp = p; +} + +openarch(file : string) : ref Archive +{ + return openarch0(file, 1); +} + +openarchfs(file : string) : ref Archive +{ + return openarch0(file, 0); +} + +openarch0(file : string, newpgrp : int) : ref Archive +{ + pid := 0; + canseek := 1; + b := bufio->open(file, Bufio->OREAD); + if (b == nil) + return nil; + if (b.getb() == 16r1f && ((c := b.getb()) == 16r8b || c == 16r9d)) { + # spawn gunzip + canseek = 0; + (b, pid) = gunzipstream(file, newpgrp); + if (b == nil) + return nil; + } + else + b.seek(big 0, Bufio->SEEKSTART); + ar := ref Archive; + ar.b = b; + ar.nexthdr = 0; + ar.canseek = canseek; + ar.pid = pid; + ar.hdr = ref Ahdr; + ar.hdr.d = ref Sys->Dir; + return ar; +} + +EOARCH : con "end of archive\n"; +PREMEOARCH : con "premature end of archive"; +NFLDS : con 6; + +openarchgz(file : string) : (string, ref Sys->FD) +{ + ar := openarch(file); + if (ar == nil || ar.canseek) + return (nil, nil); + (newfile, fd) := opentemp("wrap.gz"); + if (fd == nil) + return (nil, nil); + bout := bufio->fopen(fd, Bufio->OWRITE); + if (bout == nil) + return (nil, nil); + while ((a := gethdr(ar)) != nil) { + if (len a.name >= 5 && a.name[0:5] == "/wrap") { + puthdr(bout, a.name, a.d); + getfile(ar, bout, int a.d.length); + } + else + break; + } + closearch(ar); + bout.puts(EOARCH); + bout.flush(); + sys->seek(fd, big 0, Sys->SEEKSTART); + return (newfile, fd); +} + +gunzipstream(file : string, newpgrp : int) : (ref Iobuf, int) +{ + p := array[2] of ref Sys->FD; + if (sys->pipe(p) < 0) + return (nil, 0); + fd := sys->open(file, Sys->OREAD); + if (fd == nil) + return (nil, 0); + b := bufio->fopen(p[0], Bufio->OREAD); + if (b == nil) + return (nil, 0); + c := chan of int; + spawn gunzip(fd, p[1], c, newpgrp); + pid := <- c; + p[0] = p[1] = nil; + if (pid < 0) + return (nil, 0); + return (b, pid); +} + +GUNZIP : con "/dis/gunzip.dis"; + +gunzip(stdin : ref Sys->FD, stdout : ref Sys->FD, c : chan of int, newpgrp : int) +{ + if (newpgrp) + pid := sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + else + pid = sys->pctl(Sys->FORKFD, nil); + sys->dup(stdin.fd, 0); + sys->dup(stdout.fd, 1); + sys->dup(1, 2); + stdin = stdout = nil; + cmd := load Command GUNZIP; + if (cmd == nil) { + c <-= -1; + return; + } + c <-= pid; + cmd->init(nil, GUNZIP :: nil); +} + +closearch(ar : ref Archive) +{ + if (ar.pid != 0) { + fd := sys->open("#p/" + string ar.pid + "/ctl", sys->OWRITE); + if (fd != nil) + sys->fprint(fd, "killgrp"); + } + ar.b.close(); + ar.b = nil; +} + +gethdr(ar : ref Archive) : ref Ahdr +{ + a := ar.hdr; + b := ar.b; + m := int b.offset(); + n := ar.nexthdr; + if (m != n) { + if (ar.canseek) + b.seek(big n, Bufio->SEEKSTART); + else { + if (m > n) + fatal(sys->sprint("bad offset in gethdr: m=%d n=%d", m, n)); + if(drain(ar, n-m) < 0) + return nil; + } + } + if ((s := b.gets('\n')) == nil) { + ar.err = PREMEOARCH; + return nil; + } +# fd := sys->open("./debug", Sys->OWRITE); +# sys->seek(fd, 0, Sys->SEEKEND); +# sys->fprint(fd, "gethdr: %d %d %d %d %s\n", ar.canseek, m, n, b.offset(), s); +# fd = nil; + if (s == EOARCH) + return nil; + (nf, fs) := sys->tokenize(s, " \t\n"); + if(nf != NFLDS) { + ar.err = "too few fields in file header"; + return nil; + } + a.name = hd fs; fs = tl fs; + (a.d.mode, nil) = str->toint(hd fs, 8); fs = tl fs; + a.d.uid = hd fs; fs = tl fs; + a.d.gid = hd fs; fs = tl fs; + (a.d.mtime, nil) = str->toint(hd fs, 10); fs = tl fs; + (tmp, nil) := str->toint(hd fs, 10); fs = tl fs; + a.d.length = big tmp; + ar.nexthdr = int (b.offset()+a.d.length); + return a; +} + +getfile(ar : ref Archive, bout : ref Bufio->Iobuf, n : int) : string +{ + err: string; + bin := ar.b; + while (n > 0) { + m := len buf; + if (n < m) + m = n; + p := bin.read(buf, m); + if (p != m) + return PREMEOARCH; + p = bout.write(buf, m); + if (p != m) + err = sys->sprint("cannot write: %r"); + n -= m; + } + return err; +} + +puthdr(b : ref Iobuf, name : string, d : ref Sys->Dir) +{ + mode := d.mode; + if(addp){ + mode |= 8r664; + if(mode & Sys->DMDIR || mode & 8r111) + mode |= 8r111; + } + b.puts(sys->sprint("%s %uo %s %s %ud %d\n", name, mode, d.uid, d.gid, d.mtime, int d.length)); +} + +putstring(b : ref Iobuf, s : string) +{ + b.puts(s); +} + +putfile(b : ref Iobuf, f : string, n : int) : string +{ + fd := sys->open(f, Sys->OREAD); + if (fd == nil) + return sys->sprint("cannot open %s: %r", f); + i := 0; + for (;;) { + m := sys->read(fd, buf, len buf); + if (m < 0) + return sys->sprint("cannot read %s: %r", f); + if (m == 0) + break; + if (b.write(buf, m) != m) + return sys->sprint("%s: cannot write: %r", f); + i += m; + } + if (i != n) { + b.seek(big (n-i), Sys->SEEKRELA); + return sys->sprint("%s: %d bytes written: should be %d", f, i, n); + } + return nil; +} + +putend(b : ref Iobuf) +{ + b.puts(EOARCH); + b.flush(); +} + +drain(ar : ref Archive, n : int) : int +{ + while (n > 0) { + m := n; + if (m > len buf) + m = len buf; + p := ar.b.read(buf, m); + if (p != m){ + ar.err = "unexpectedly short read"; + return -1; + } + n -= m; + } + return 0; +} + +opentemp(prefix: string): (string, ref Sys->FD) +{ + name := sys->sprint("/tmp/%s.%ud.%d", prefix, daytime->now(), sys->pctl(0, nil)); + # would use ORCLOSE here but it messes up under Nt + fd := sys->create(name, Sys->ORDWR, 8r600); + return (name, fd); +} + +fatal(s : string) +{ + sys->fprint(sys->fildes(2), "%s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/install/arch.m b/appl/cmd/install/arch.m new file mode 100644 index 00000000..03837445 --- /dev/null +++ b/appl/cmd/install/arch.m @@ -0,0 +1,36 @@ +Arch : module +{ + PATH : con "/dis/install/arch.dis"; + + Ahdr : adt { + name : string; + modestr : string; + d : ref Sys->Dir; + }; + + Archive : adt { + b : ref Bufio->Iobuf; + nexthdr : int; + canseek : int; + pid : int; + hdr : ref Ahdr; + err : string; + }; + + init: fn(bio: Bufio); + + openarch: fn(name : string) : ref Archive; + openarchfs: fn(name : string) : ref Archive; + openarchgz: fn(name : string) : (string, ref Sys->FD); + gethdr: fn(ar : ref Archive) : ref Ahdr; + getfile: fn(ar : ref Archive, bout : ref Bufio->Iobuf, n : int) : string; + drain: fn(ar : ref Archive, n : int) : int; + closearch: fn(ar : ref Archive); + + puthdr: fn(b : ref Bufio->Iobuf, name : string, d : ref Sys->Dir); + putstring: fn(b : ref Bufio->Iobuf, s : string); + putfile: fn(b : ref Bufio->Iobuf, f : string, n : int) : string; + putend: fn(b : ref Bufio->Iobuf); + + addperms: fn(p: int); +}; diff --git a/appl/cmd/install/archfs.b b/appl/cmd/install/archfs.b new file mode 100644 index 00000000..3705aee9 --- /dev/null +++ b/appl/cmd/install/archfs.b @@ -0,0 +1,579 @@ +implement Archfs; + +include "sys.m"; + sys : Sys; +include "draw.m"; +include "bufio.m"; + bufio : Bufio; +include "arg.m"; + arg : Arg; +include "string.m"; + str : String; +include "daytime.m"; + daytime : Daytime; +include "styx.m"; + styx: Styx; +include "archfs.m"; +include "arch.m"; + arch : Arch; + +# add write some day + +Iobuf : import bufio; +Tmsg, Rmsg: import styx; + +Einuse : con "fid already in use"; +Ebadfid : con "bad fid"; +Eopen : con "fid already opened"; +Enotfound : con "file does not exist"; +Enotdir : con "not a directory"; +Eperm : con "permission denied"; +Ebadarg : con "bad argument"; +Eexists : con "file already exists"; + +UID : con "inferno"; +GID : con "inferno"; + +DEBUG: con 0; + +Dir : adt { + dir : Sys->Dir; + offset : int; + parent : cyclic ref Dir; + child : cyclic ref Dir; + sibling : cyclic ref Dir; +}; + +Fid : adt { + fid : int; + open: int; + dir : ref Dir; + next : cyclic ref Fid; +}; + +HTSZ : con 32; +fidtab := array[HTSZ] of ref Fid; + +root : ref Dir; +qid : int; +mtpt := "/mnt"; +bio : ref Iobuf; +buf : array of byte; +skip := 0; + +# Archfs : module +# { +# init : fn(ctxt : ref Draw->Context, args : list of string); +# }; + +init(nil : ref Draw->Context, args : list of string) +{ + init0(nil, args, nil); +} + +initc(args : list of string, c : chan of int) +{ + init0(nil, args, c); +} + +chanint : chan of int; + +init0(nil : ref Draw->Context, args : list of string, chi : chan of int) +{ + chanint = chi; + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + arg = load Arg Arg->PATH; + str = load String String->PATH; + daytime = load Daytime Daytime->PATH; + styx = load Styx Styx->PATH; + arch = load Arch Arch->PATH; + if (bufio == nil || arg == nil || styx == nil || arch == nil) + fatal("failed to load modules", 1); + styx->init(); + arch->init(bufio); + arg->init(args); + while ((c := arg->opt()) != 0) { + case c { + 'm' => + mtpt = arg->arg(); + if (mtpt == nil) + fatal("mount point missing", 1); + 's' => + skip = 1; + } + } + args = arg->argv(); + if (args == nil) + fatal("missing archive file", 1); + buf = array[Sys->ATOMICIO] of byte; + # root = newdir("/", UID, GID, 8r755|Sys->DMDIR, daytime->now()); + root = newdir(basename(mtpt), UID, GID, 8r755|Sys->DMDIR, daytime->now()); + root.parent = root; + readarch(hd args, tl args); + p := array[2] of ref Sys->FD; + if(sys->pipe(p) < 0) + fatal("can't create pipe", 1); + ch := chan of ref Tmsg; + sync := chan of int; + spawn reader(p[1], ch, sync); + <- sync; + pidch := chan of int; + spawn serve(p[1], ch, pidch); + pid := <- pidch; + if(sys->mount(p[0], nil, mtpt, Sys->MREPL, nil) < 0) + fatal(sys->sprint("cannot mount archive on %s: %r", mtpt), 1); + p[0] = p[1] = nil; + if (chi != nil) { + chi <-= pid; + chanint = nil; + } +} + +reply(fd: ref Sys->FD, m: ref Rmsg): int +{ + if(DEBUG) + sys->fprint(sys->fildes(2), "R: %s\n", m.text()); + s := m.pack(); + if(s == nil) + return -1; + return sys->write(fd, s, len s); +} + +error(fd: ref Sys->FD, m: ref Tmsg, e : string) +{ + reply(fd, ref Rmsg.Error(m.tag, e)); +} + +reader(fd: ref Sys->FD, ch: chan of ref Tmsg, sync: chan of int) +{ + sys->pctl(Sys->NEWFD|Sys->NEWNS, fd.fd :: nil); + sync <-= 1; + while((m := Tmsg.read(fd, Styx->MAXRPC)) != nil && tagof m != tagof Tmsg.Readerror) + ch <-= m; + ch <-= m; +} + +serve(fd: ref Sys->FD, ch : chan of ref Tmsg, pidch : chan of int) +{ + e : string; + f : ref Fid; + + pidch <-= sys->pctl(0, nil); + for (;;) { + m0 := <- ch; + if (m0 == nil) + return; + if(DEBUG) + sys->fprint(sys->fildes(2), "T: %s\n", m0.text()); + pick m := m0 { + Readerror => + fatal("read error on styx server", 1); + Version => + (s, v) := styx->compatible(m, Styx->MAXRPC, Styx->VERSION); + reply(fd, ref Rmsg.Version(m.tag, s, v)); + Auth => + error(fd, m, "no authentication required"); + Flush => + reply(fd, ref Rmsg.Flush(m.tag)); + Walk => + (f, e) = mapfid(m.fid); + if (e != nil) { + error(fd, m, e); + continue; + } + if (f.open) { + error(fd, m, Eopen); + continue; + } + err := 0; + dir := f.dir; + nq := 0; + nn := len m.names; + qids := array[nn] of Sys->Qid; + if(nn > 0){ + for(k := 0; k < nn; k++){ + if ((dir.dir.mode & Sys->DMDIR) == 0) { + if(k == 0){ + error(fd, m, Enotdir); + err = 1; + } + break; + } + dir = lookup(dir, m.names[k]); + if (dir == nil) { + if(k == 0){ + error(fd, m, Enotfound); + err = 1; + } + break; + } + qids[nq++] = dir.dir.qid; + } + } + if(err) + continue; + if(nq < nn) + qids = qids[0: nq]; + if(nq == nn){ + if(m.newfid != m.fid){ + f = newfid(m.newfid); + if (f == nil) { + error(fd, m, Einuse); + continue; + } + } + f.dir = dir; + } + reply(fd, ref Rmsg.Walk(m.tag, qids)); + Open => + (f, e) = mapfid(m.fid); + if (e != nil) { + error(fd, m, e); + continue; + } + if (m.mode & (Sys->OWRITE|Sys->ORDWR|Sys->OTRUNC|Sys->ORCLOSE)) { + error(fd, m, Eperm); + continue; + } + f.open = 1; + reply(fd, ref Rmsg.Open(m.tag, f.dir.dir.qid, Styx->MAXFDATA)); + Create => + error(fd, m, Eperm); + Read => + (f, e) = mapfid(m.fid); + if (e != nil) { + error(fd, m, e); + continue; + } + data := readdir(f.dir, int m.offset, m.count); + reply(fd, ref Rmsg.Read(m.tag, data)); + Write => + error(fd, m, Eperm); + Clunk => + (f, e) = mapfid(m.fid); + if (e != nil) { + error(fd, m, e); + continue; + } + freefid(f); + reply(fd, ref Rmsg.Clunk(m.tag)); + Stat => + (f, e) = mapfid(m.fid); + if (e != nil) { + error(fd, m, e); + continue; + } + reply(fd, ref Rmsg.Stat(m.tag, f.dir.dir)); + Remove => + error(fd, m, Eperm); + Wstat => + error(fd, m, Eperm); + Attach => + f = newfid(m.fid); + if (f == nil) { + error(fd, m, Einuse); + continue; + } + f.dir = root; + reply(fd, ref Rmsg.Attach(m.tag, f.dir.dir.qid)); + * => + fatal("unknown styx message", 1); + } + } +} + +newfid(fid : int) : ref Fid +{ + (f, nil) := mapfid(fid); + if(f != nil) + return nil; + f = ref Fid; + f.fid = fid; + f.open = 0; + hv := hashval(fid); + f.next = fidtab[hv]; + fidtab[hv] = f; + return f; +} + +freefid(f: ref Fid) +{ + hv := hashval(f.fid); + lf : ref Fid; + for(ff := fidtab[hv]; ff != nil; ff = ff.next){ + if(f == ff){ + if(lf == nil) + fidtab[hv] = ff.next; + else + lf.next = ff.next; + return; + } + lf = ff; + } + fatal("cannot find fid", 1); +} + +mapfid(fid : int) : (ref Fid, string) +{ + hv := hashval(fid); + for (f := fidtab[hv]; f != nil; f = f.next) + if (int f.fid == fid) + break; + if (f == nil) + return (nil, Ebadfid); + if (f.dir == nil) + return (nil, Enotfound); + return (f, nil); +} + +hashval(n : int) : int +{ + return (n & ~Sys->DMDIR)%HTSZ; +} + +readarch(f : string, args : list of string) +{ + ar := arch->openarchfs(f); + if(ar == nil || ar.b == nil) + fatal(sys->sprint("cannot open %s(%r)\n", f), 1); + bio = ar.b; + while ((a := arch->gethdr(ar)) != nil) { + if (args != nil) { + if (!selected(a.name, args)) { + if (skip) + return; + arch->drain(ar, int a.d.length); + continue; + } + mkdirs("/", a.name); + } + d := mkdir(a.name, a.d.mode, a.d.mtime, a.d.uid, a.d.gid, 0); + if((a.d.mode & Sys->DMDIR) == 0) { + d.dir.length = a.d.length; + d.offset = int bio.offset(); + } + arch->drain(ar, int a.d.length); + } + if (ar.err != nil) + fatal(ar.err, 0); +} + +selected(s: string, args: list of string): int +{ + for(; args != nil; args = tl args) + if(fileprefix(hd args, s)) + return 1; + return 0; +} + +fileprefix(prefix, s: string): int +{ + n := len prefix; + m := len s; + if(n > m || !str->prefix(prefix, s)) + return 0; + if(m > n && s[n] != '/') + return 0; + return 1; +} + +basename(f : string) : string +{ + for (i := len f; i > 0; ) + if (f[--i] == '/') + return f[i+1:]; + return f; +} + +split(p : string) : (string, string) +{ + if (p == nil) + fatal("nil string in split", 1); + if (p[0] != '/') + fatal("p0 not / in split", 1); + while (p[0] == '/') + p = p[1:]; + i := 0; + while (i < len p && p[i] != '/') + i++; + if (i == len p) + return (p, nil); + else + return (p[0:i], p[i:]); +} + +mkdirs(basedir, name: string) +{ + (nil, names) := sys->tokenize(name, "/"); + while(names != nil) { + # sys->print("mkdir %s\n", basedir); + mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1); + if(tl names == nil) + break; + basedir = basedir + "/" + hd names; + names = tl names; + } +} + +readdir(d : ref Dir, offset : int, n : int) : array of byte +{ + if (d.dir.mode & Sys->DMDIR) + return readd(d, offset, n); + else + return readf(d, offset, n); +} + +readd(d : ref Dir, o : int, n : int) : array of byte +{ + k := 0; + m := 0; + b := array[n] of byte; + for (s := d.child; s != nil; s = s.sibling) { + l := styx->packdirsize(s.dir); + if(k < o){ + k += l; + continue; + } + if(m+l > n) + break; + b[m: ] = styx->packdir(s.dir); + m += l; + } + return b[0: m]; +} + +readf(d : ref Dir, offset : int, n : int) : array of byte +{ + leng := int d.dir.length; + if (offset+n > leng) + n = leng-offset; + if (n <= 0 || offset < 0) + return nil; + bio.seek(big (d.offset+offset), Bufio->SEEKSTART); + a := array[n] of byte; + p := 0; + m := 0; + for ( ; n != 0; n -= m) { + l := len buf; + if (n < l) + l = n; + m = bio.read(buf, l); + if (m <= 0 || m != l) + fatal("premature eof", 1); + a[p:] = buf[0:m]; + p += m; + } + return a; +} + +mkdir(f : string, mode : int, mtime : int, uid : string, gid : string, existsok : int) : ref Dir +{ + if (f == "/") + return nil; + d := newdir(basename(f), uid, gid, mode, mtime); + addfile(d, f, existsok); + return d; +} + +addfile(d : ref Dir, path : string, existsok : int) +{ + elem : string; + + opath := path; + p := prev := root; + basedir := ""; +# sys->print("addfile %s : %s\n", d.dir.name, path); + while (path != nil) { + (elem, path) = split(path); + basedir += "/" + elem; + op := p; + p = lookup(p, elem); + if (path == nil) { + if (p != nil) { + if (!existsok && (p.dir.mode&Sys->DMDIR) == 0) + sys->fprint(sys->fildes(2), "addfile: %s already there", opath); + # fatal(sys->sprint("addfile: %s already there", opath), 1); + return; + } + if (prev.child == nil) + prev.child = d; + else { + for (s := prev.child; s.sibling != nil; s = s.sibling) + ; + s.sibling = d; + } + d.parent = prev; + } + else { + if (p == nil) { + mkdir(basedir, 8r775|Sys->DMDIR, daytime->now(), UID, GID, 1); + p = lookup(op, elem); + if (p == nil) + fatal("bad file system", 1); + } + } + prev = p; + } +} + +lookup(p : ref Dir, f : string) : ref Dir +{ + if ((p.dir.mode&Sys->DMDIR) == 0) + fatal("not a directory in lookup", 1); + if (f == ".") + return p; + if (f == "..") + return p.parent; + for (d := p.child; d != nil; d = d.sibling) + if (d.dir.name == f) + return d; + return nil; +} + +newdir(name, uid, gid : string, mode, mtime : int) : ref Dir +{ + dir : Sys->Dir; + + dir.name = name; + dir.uid = uid; + dir.gid = gid; + dir.qid.path = big (qid++); + if(mode&Sys->DMDIR) + dir.qid.qtype = Sys->QTDIR; + else + dir.qid.qtype = Sys->QTFILE; + dir.qid.vers = 0; + dir.mode = mode; + dir.atime = dir.mtime = mtime; + dir.length = big 0; + dir.dtype = 'X'; + dir.dev = 0; + + d := ref Dir; + d.dir = dir; + d.offset = 0; + return d; +} + +# pr(d : ref Dir) +# { +# dir := d.dir; +# sys->print("%s %s %s %x %x %x %d %d %d %d %d %d\n", +# dir.name, dir.uid, dir.gid, dir.qid.path, dir.qid.vers, dir.mode, dir.atime, dir.mtime, dir.length, dir.dtype, dir.dev, d.offset); +# } + +fatal(e : string, pr: int) +{ + if(pr){ + sys->fprint(sys->fildes(2), "fatal: %s\n", e); + if (chanint != nil) + chanint <-= -1; + } + else{ + # probably not an archive file + if (chanint != nil) + chanint <-= -2; + } + exit; +} diff --git a/appl/cmd/install/archfs.m b/appl/cmd/install/archfs.m new file mode 100644 index 00000000..57c32542 --- /dev/null +++ b/appl/cmd/install/archfs.m @@ -0,0 +1,7 @@ +Archfs : module +{ + PATH : con "/dis/install/archfs.dis"; + + init : fn(ctxt : ref Draw->Context, args : list of string); + initc : fn(args : list of string, c : chan of int); +}; diff --git a/appl/cmd/install/ckproto.b b/appl/cmd/install/ckproto.b new file mode 100644 index 00000000..1e214f96 --- /dev/null +++ b/appl/cmd/install/ckproto.b @@ -0,0 +1,267 @@ +implement Ckproto; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "arg.m"; + arg: Arg; +include "readdir.m"; + readdir : Readdir; +include "proto.m"; + proto : Proto; +include "protocaller.m"; + protocaller : Protocaller; + +WARN, ERROR, FATAL : import Protocaller; + +Ckproto: module{ + init: fn(nil: ref Draw->Context, nil: list of string); + protofile: fn(new : string, old : string, d : ref Sys->Dir); + protoerr: fn(lev : int, line : int, err : string); +}; + +Dir : adt { + name : string; + proto : string; + parent : cyclic ref Dir; + child : cyclic ref Dir; + sibling : cyclic ref Dir; +}; + +root := "/"; +droot : ref Dir; +protof : string; +stderr : ref Sys->FD; +omitgen := 0; # forget generated files +verbose : int; +ckmode: int; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + arg = load Arg Arg->PATH; + readdir = load Readdir Readdir->PATH; + proto = load Proto Proto->PATH; + protocaller = load Protocaller "$self"; + + stderr = sys->fildes(2); + sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil); + arg->init(args); + while ((c := arg->opt()) != 0) { + case c { + 'r' => + root = arg->arg(); + if (root == nil) + fatal("missing argument to -r"); + 'o' => + omitgen = 1; + 'v' => + verbose = 1; + 'm' => + ckmode = 1; + * => + fatal("usage: install/ckproto [-o] [-v] [-m] [-r root] protofile ...."); + } + } + droot = ref Dir("/", nil, nil, nil, nil); + droot.parent = droot; + args = arg->argv(); + while (args != nil) { + protof = hd args; + proto->rdproto(hd args, root, protocaller); + args = tl args; + } + if (verbose) + prtree(droot, -1); + ckdir(root, droot); +} + +protofile(new : string, old : string, nil : ref Sys->Dir) +{ + if (verbose) { + if (old == new) + sys->print("%s\n", new); + else + sys->print("%s %s\n", new, old); + } + addfile(droot, old); + if (new != old) + addfile(droot, new); +} + +protoerr(lev : int, line : int, err : string) +{ + s := "line " + string line + " : " + err; + case lev { + WARN => warn(s); + ERROR => error(s); + FATAL => fatal(s); + } +} + +ckdir(d : string, dird : ref Dir) +{ + (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT); + for (i := 0; i < n; i++) { + dire := lookup(dird, dir[i].name); + if(omitgen && generated(dir[i].name)) + continue; + if (dire == nil){ + sys->print("%s missing\n", mkpath(d, dir[i].name)); + continue; + } + if(ckmode){ + if(dir[i].mode & Sys->DMDIR){ + if((dir[i].mode & 8r775) != 8r775) + sys->print("directory %s not 775 at least\n", mkpath(d, dir[i].name)); + } + else{ + if((dir[i].mode & 8r664) != 8r664) + sys->print("file %s not 664 at least\n", mkpath(d, dir[i].name)); + } + } + if (dir[i].mode & Sys->DMDIR) + ckdir(mkpath(d, dir[i].name), dire); + } +} + +addfile(root : ref Dir, path : string) +{ + elem : string; + + # ckexists(path); + + curd := root; + opath := path; + while (path != nil) { + (elem, path) = split(path); + d := lookup(curd, elem); + if (d == nil) { + d = ref Dir(elem, protof, curd, nil, nil); + if (curd.child == nil) + curd.child = d; + else { + prev, this : ref Dir; + + for (this = curd.child; this != nil; this = this.sibling) { + if (elem < this.name) { + d.sibling = this; + if (prev == nil) + curd.child = d; + else + prev.sibling = d; + break; + } + prev = this; + } + if (this == nil) + prev.sibling = d; + } + } + else if (path == nil && d.proto == protof) + sys->print("%s repeated in proto %s\n", opath, protof); + curd = d; + } +} + +lookup(p : ref Dir, f : string) : ref Dir +{ + if (f == ".") + return p; + if (f == "..") + return p.parent; + for (d := p.child; d != nil; d = d.sibling) { + if (d.name == f) + return d; + if (d.name > f) + return nil; + } + return nil; +} + +prtree(root : ref Dir, indent : int) +{ + if (indent >= 0) + sys->print("%s%s\n", string array[indent] of { * => byte '\t' }, root.name); + for (s := root.child; s != nil; s = s.sibling) + prtree(s, indent+1); +} + +mkpath(prefix, elem: string): string +{ + slash1 := slash2 := 0; + if (len prefix > 0) + slash1 = prefix[len prefix - 1] == '/'; + if (len elem > 0) + slash2 = elem[0] == '/'; + if (slash1 && slash2) + return prefix+elem[1:]; + if (!slash1 && !slash2) + return prefix+"/"+elem; + return prefix+elem; +} + +split(p : string) : (string, string) +{ + if (p == nil) + fatal("nil string in split"); + if (p[0] != '/') + fatal("p0 notg / in split"); + while (p[0] == '/') + p = p[1:]; + i := 0; + while (i < len p && p[i] != '/') + i++; + if (i == len p) + return (p, nil); + else + return (p[0:i], p[i:]); +} + + +gens := array[] of { + "dis", "sbl", "out", "0", "1", "2", "5", "8", "k", "q", "v", "t" +}; + +generated(f : string) : int +{ + for (i := len f -1; i >= 0; i--) + if (f[i] == '.') + break; + if (i < 0) + return 0; + suff := f[i+1:]; + for (i = 0; i < len gens; i++) + if (suff == gens[i]) + return 1; + return 0; +} + +warn(s: string) +{ + sys->print("%s: %s\n", protof, s); +} + +error(s: string) +{ + sys->fprint(stderr, "%s: %s\n", protof, s); + exit;; +} + +fatal(s: string) +{ + sys->fprint(stderr, "fatal: %s\n", s); + exit; +} + +ckexists(path: string) +{ + s := mkpath(root, path); + (ok, nil) := sys->stat(s); + if(ok < 0) + sys->print("%s does not exist\n", s); +} diff --git a/appl/cmd/install/create.b b/appl/cmd/install/create.b new file mode 100644 index 00000000..848fdc6b --- /dev/null +++ b/appl/cmd/install/create.b @@ -0,0 +1,445 @@ +implement Create; + +include "sys.m"; + sys: Sys; + Dir, sprint, fprint: import sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "string.m"; + str: String; +include "arg.m"; + arg: Arg; +include "daytime.m"; +include "keyring.m"; + keyring : Keyring; +include "sh.m"; +include "wrap.m"; + wrap : Wrap; +include "arch.m"; + arch : Arch; +include "proto.m"; + proto : Proto; +include "protocaller.m"; + protocaller : Protocaller; + +WARN, ERROR, FATAL : import Protocaller; + +Create: module{ + init: fn(nil: ref Draw->Context, nil: list of string); + protofile: fn(new : string, old : string, d : ref Sys->Dir); + protoerr: fn(lev : int, line : int, err : string); +}; + +bout: ref Iobuf; # stdout when writing archive +protof: string; +notesf: string; +oldroot: string; +buf: array of byte; +buflen := 1024-8; +verb: int; +xflag: int; +stderr: ref Sys->FD; +uid, gid : string; +desc : string; +pass : int; +update : int; +md5s : ref Keyring->DigestState; +w : ref Wrap->Wrapped; +root := "/"; +prefix, notprefix: list of string; +onlist: list of (string, string); # NEW +remfile: string; + +n2o(n: string): string +{ + for(onl := onlist; onl != nil; onl = tl onl) + if((hd onl).t1 == n) + return (hd onl).t0; + return n; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + str = load String String->PATH; + arg = load Arg Arg->PATH; + wrap = load Wrap Wrap->PATH; + wrap->init(bufio); + arch = load Arch Arch->PATH; + arch->init(bufio); + daytime := load Daytime Daytime->PATH; + now := daytime->now(); + # { + # for(i := 0; i < 21; i++){ + # n := now+(i-9)*100000000; + # sys->print("%d -> %s\n", n, wrap->now2string(n)); + # if(wrap->string2now(wrap->now2string(n)) != n) + # sys->print("%d wrong\n", n); + # } + # } + daytime = nil; + proto = load Proto Proto->PATH; + protocaller = load Protocaller "$self"; + + sys->pctl(Sys->NEWPGRP|Sys->FORKNS|Sys->FORKFD, nil); + stderr = sys->fildes(2); + if(arg == nil) + error(sys->sprint("can't load %s: %r", Arg->PATH)); + name := ""; + desc = "inferno"; + tostdout := 0; + not := 0; + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'n' => + not = 1; + 'o' => + tostdout = 1; + 'p' => + protof = reqarg("proto file (-p)"); + 'r' => + root = reqarg("root directory (-r)"); + 's' => + oldroot = reqarg("source directory (-d)"); + 'u' => + update = 1; + 'v' => + verb = 1; + 'x' => + xflag = 1; + 'N' => + uid = reqarg("user name (-U)"); + 'G' => + gid = reqarg("group name (-G)"); + 'd' or 'D' => + desc = reqarg("product description (-D)"); + 't' => + rt := reqarg("package time (-t)"); + now = int rt; + 'i' => + notesf = reqarg("file (-i)"); + 'R' => + remfile = reqarg("remove file (-R)"); + 'P' => + arch->addperms(0); + * => + usage(); + } + + args = arg->argv(); + if(args == nil) + usage(); + if (tostdout || xflag) { + bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + if(bout == nil) + error(sys->sprint("can't open standard output for archive: %r")); + } + else { + # ar := sys->sprint("%ud", now); + ar := wrap->now2string(now, 0); + bout = bufio->create(ar, Sys->OWRITE, 8r664); + if(bout == nil) + error(sys->sprint("can't create %s for archive: %r", ar)); + sys->print("archiving package %s to %s\n", hd args, ar); + } + buf = array [buflen] of byte; + name = hd args; + if(update){ + if(not) + notprefix = tl args; + else + prefix = tl args; + } + else if (tl args != nil) + fatal("only one name allowed"); + if (!xflag) + digest := wrapinit(name, now); + fprint(stderr, "processing %s\n", protof); + proto->rdproto(protof, oldroot, protocaller); + if (!xflag) + wrapend(digest); + if (!xflag) + fprint(stderr, "file system made\n"); + arch->putend(bout); + exits(); +} + +protofile(new : string, old : string, d : ref Sys->Dir) +{ + if(xflag && bout != nil){ + bout.puts(sys->sprint("%s\t%d\t%bd\n", new, d.mtime, d.length)); + return; + } + d.uid = uid; + d.gid = gid; + if (!(d.mode & Sys->DMDIR)) { + # if(verb) + # fprint(stderr, "%s\n", new); + f := sys->open(old, Sys->OREAD); + if(f == nil){ + warn(sys->sprint("can't open %s: %r", old)); + return; + } + } + mkarch(new, old, d); +} + +protoerr(lev : int, line : int, err : string) +{ + s := "line " + string line + " : " + err; + case lev { + WARN => warn(s); + ERROR => error(s); + FATAL => fatal(s); + } +} + +quit() +{ + if(bout != nil) + bout.flush(); + exits(); +} + +reqarg(what: string): string +{ + if((o := arg->arg()) == nil){ + sys->fprint(stderr, "missing %s\n", what); + exits(); + } + return o; +} + +puthdr(f : string, d: ref Dir) +{ + if (d.mode & Sys->DMDIR) + d.length = big 0; + arch->puthdr(bout, f, d); +} + +error(s: string) +{ + fprint(stderr, "%s: %s\n", protof, s); + quit(); +} + +fatal(s: string) +{ + fprint(stderr, "fatal: %s\n", s); + exits(); +} + +warn(s: string) +{ + fprint(stderr, "%s: %s\n", protof, s); +} + +usage() +{ + fprint(stderr, "usage: install/create [-ovx] [-N uid] [-G gid] [-r root] [-d desc] [-s src-fs] [-p proto] name\n"); + fprint(stderr, "or install/create -u [-ovx] [-N uid] [-G gid] [-r root] [-d desc] [-s src-fs] [-p proto] old-package [prefix ...]\n"); + exits(); +} + +wrapinit(name : string, t : int) : array of byte +{ + rmfile : string; + rmfd: ref Sys->FD; + + if (uid == nil) + uid = "inferno"; + if (gid == nil) + gid = "inferno"; + if (update) { + w = wrap->openwraphdr(name, root, nil, 0); + if (w == nil) + fatal("no such package found"); + # ignore any updates - NEW commented out + # while (w.nu > 0 && w.u[w.nu-1].typ == wrap->UPD) + # w.nu--; + + # w.nu = 1; NEW commented out + if (protof == nil) + protof = w.u[0].dir + "/proto"; + name = w.name; + } + else { + if (protof == nil) + fatal("proto file missing"); + } + (md5file, md5fd) := opentemp("wrap.md5", t); + if (md5fd == nil) + fatal(sys->sprint("cannot create %s", md5file)); + keyring = load Keyring Keyring->PATH; + md5s = keyring->md5(nil, 0, nil, nil); + md5b := bufio->fopen(md5fd, Bufio->OWRITE); + if (md5b == nil) + fatal(sys->sprint("cannot open %s", md5file)); + fprint(stderr, "wrap pass %s\n", protof); + obout := bout; + bout = md5b; + pass = 0; + proto->rdproto(protof, oldroot, protocaller); + bout.flush(); + bout = md5b = nil; + digest := array[keyring->MD5dlen] of { * => byte 0 }; + keyring->md5(nil, 0, digest, md5s); + md5s = nil; + (md5sort, md5sfd) := opentemp("wrap.md5s", t); + if (md5sfd == nil) + fatal(sys->sprint("cannot create %s", md5sort)); + endc := chan of int; + md5fd = nil; # close md5file + spawn fsort(md5sfd, md5file, endc); + md5sfd = nil; + res := <- endc; + if (res < 0) + fatal("sort failed"); + if (update) { + (rmfile, rmfd) = opentemp("wrap.rm", t); + if (rmfd == nil) + fatal(sys->sprint("cannot create %s", rmfile)); + rmed: list of string; + for(i := w.nu-1; i >= 0; i--){ # NEW does loop + w.u[i].bmd5.seek(big 0, Bufio->SEEKSTART); + while ((p := w.u[i].bmd5.gets('\n')) != nil) { + if(prefix != nil && !wrap->match(p, prefix)) + continue; + if(notprefix != nil && !wrap->notmatch(p, notprefix)) + continue; + (q, nil) := str->splitl(p, " "); + q = pathcat(root, q); + (ok, nil) := sys->stat(q); + if(ok < 0) + (ok, nil) = sys->stat(n2o(q)); + if (len q >= 7 && q[len q - 7:] == "emu.new") # quick hack for now + continue; + if (ok < 0){ + for(r := rmed; r != nil; r = tl r) # NEW to avoid duplication + if(hd r == q) + break; + if(r == nil){ + # sys->fprint(rmfd, "%s\n", q); + rmed = q :: rmed; + } + } + } + } + for(r := rmed; r != nil; r = tl r) + sys->fprint(rmfd, "%s\n", hd r); + if(remfile != nil){ + rfd := sys->open(remfile, Sys->OREAD); + rbuf := array[128] of byte; + for(;;){ + n := sys->read(rfd, rbuf, 128); + if(n <= 0) + break; + sys->write(rmfd, rbuf, n); + } + } + rmfd = nil; + rmed = nil; + } + bout = obout; + if (update) + wrap->putwrap(bout, name, t, desc, w.tfull, prefix == nil && notprefix == nil, uid, gid); + else + wrap->putwrap(bout, name, t, desc, 0, 1, uid, gid); + wrap->putwrapfile(bout, name, t, "proto", protof, uid, gid); + wrap->putwrapfile(bout, name, t, "md5sum", md5sort, uid, gid); + if (update) + wrap->putwrapfile(bout, name, t, "remove", rmfile, uid, gid); + if(notesf != nil) + wrap->putwrapfile(bout, name, t, "notes", notesf, uid, gid); + md5s = keyring->md5(nil, 0, nil, nil); + pass = 1; + return digest; +} + +wrapend(digest : array of byte) +{ + digest0 := array[keyring->MD5dlen] of { * => byte 0 }; + keyring->md5(nil, 0, digest0, md5s); + md5s = nil; + if (wrap->memcmp(digest, digest0, keyring->MD5dlen) != 0) + warn(sys->sprint("files changed underfoot %s %s", wrap->md5conv(digest), wrap->md5conv(digest0))); +} + +mkarch(new : string, old : string, d : ref Dir) +{ + if(pass == 0 && old != new) + onlist = (old, new) :: onlist; + if(prefix != nil && !wrap->match(new, prefix)) + return; + if(notprefix != nil && !wrap->notmatch(new, notprefix)) + return; + digest := array[keyring->MD5dlen] of { * => byte 0 }; + wrap->md5file(old, digest); + (ok, nil) := wrap->getfileinfo(w, new, digest, nil, nil); + if (ok >= 0) + return; + n := array of byte new; + keyring->md5(n, len n, nil, md5s); + if (pass == 0) { + bout.puts(sys->sprint("%s %s\n", new, wrap->md5conv(digest))); + return; + } + if(verb) + fprint(stderr, "%s\n", new); + puthdr(new, d); + if(!(d.mode & Sys->DMDIR)) { + err := arch->putfile(bout, old, int d.length); + if (err != nil) + warn(err); + } +} + +fsort(fd : ref Sys->FD, file : string, c : chan of int) +{ + sys->pctl(Sys->FORKFD, nil); + sys->dup(fd.fd, 1); + cmd := "/dis/sort.dis"; + m := load Command cmd; + if(m == nil) { + c <-= -1; + return; + } + m->init(nil, cmd :: file :: nil); + c <-= 0; +} + +tmpfiles: list of string; + +opentemp(prefix: string, t: int): (string, ref Sys->FD) +{ + name := sys->sprint("/tmp/%s.%ud.%d", prefix, t, sys->pctl(0, nil)); + fd := sys->create(name, Sys->ORDWR, 8r666); + # fd := sys->create(name, Sys->ORDWR | Sys->ORCLOSE, 8r666); not on Nt + tmpfiles = name :: tmpfiles; + return (name, fd); +} + +exits() +{ + wrap->end(); + for( ; tmpfiles != nil; tmpfiles = tl tmpfiles) + sys->remove(hd tmpfiles); + exit; +} + +pathcat(s : string, t : string) : string +{ + if (s == nil) return t; + if (t == nil) return s; + slashs := s[len s - 1] == '/'; + slasht := t[0] == '/'; + if (slashs && slasht) + return s + t[1:]; + if (!slashs && !slasht) + return s + "/" + t; + return s + t; +} diff --git a/appl/cmd/install/eproto.b b/appl/cmd/install/eproto.b new file mode 100644 index 00000000..b87a4390 --- /dev/null +++ b/appl/cmd/install/eproto.b @@ -0,0 +1,357 @@ +implement Fsmodule; +include "sys.m"; + sys: Sys; +include "readdir.m"; + readdir: Readdir; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "string.m"; + str: String; +include "draw.m"; +include "sh.m"; +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, report, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +File: adt { + name: string; + mode: int; + owner: string; + group: string; + old: string; + flags: int; + sub: cyclic array of ref File; +}; + +Proto: adt { + indent: int; + lastline: string; + iob: ref Iobuf; +}; + +Star, Plus: con 1<<iota; + +types(): string +{ + return "ts-rs"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: eproto: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + badmod(Readdir->PATH); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + badmod(Bufio->PATH); + str = load String String->PATH; + if(str == nil) + badmod(String->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + protofile := (hd args).s().i; + rootpath: string; + if(opts != nil) + rootpath = (hd (hd opts).args).s().i; + if(rootpath == nil) + rootpath = "/"; + + proto := ref Proto(0, nil, nil); + if((proto.iob = bufio->open(protofile, Sys->OREAD)) == nil){ + sys->fprint(sys->fildes(2), "fs: eproto: cannot open %q: %r\n", protofile); + return nil; + } + root := ref File(rootpath, ~0, nil, nil, nil, 0, nil); + (root.flags, root.sub) = readproto(proto, -1); + c := Entrychan(chan of int, chan of Entry); + spawn protowalk(c, root, report.start("proto")); + return ref Value.T(c); +} + +protowalk(c: Entrychan, root: ref File, errorc: chan of string) +{ + if(<-c.sync == 0){ + quit(errorc); + exit; + } + protowalk1(c, root.flags, root.name, file2dir(root, nil), root.sub, -1, errorc); + c.c <-= (nil, nil, 0); + quit(errorc); +} + +protowalk1(c: Entrychan, flags: int, path: string, d: ref Sys->Dir, + sub: array of ref File, depth: int, errorc: chan of string): int +{ + if(depth >= 0) + c.c <-= (d, path, depth); + depth++; + (a, n) := readdir->init(path, Readdir->NAME|Readdir->COMPACT); + j := 0; + prevsub: string; + for(i := 0; i < n; i++){ + for(; j < len sub; j++){ + s := sub[j].name; + if(s == prevsub){ + report(errorc, sys->sprint("duplicate entry %s", pathconcat(path, s))); + continue; # eliminate duplicates in proto + } + if(s >= a[i].name || sub[j].old != nil) + break; + report(errorc, sys->sprint("%s not found", pathconcat(path, s))); + } + foundsub := j < len sub && (sub[j].name == a[i].name || sub[j].old != nil); + if(foundsub || flags&Plus || + (flags&Star && (a[i].mode & Sys->DMDIR)==0)){ + f: ref File; + if(foundsub){ + f = sub[j++]; + prevsub = f.name; + } + p: string; + d: ref Sys->Dir; + if(foundsub && f.old != nil){ + p = f.old; + (ok, xd) := sys->stat(p); + if(ok == -1){ + report(errorc, sys->sprint("cannot stat %q: %r", p)); + continue; + } + d = ref xd; + }else{ + p = pathconcat(path, a[i].name); + d = a[i]; + } + + d = file2dir(f, d); + r: int; + if((d.mode & Sys->DMDIR) == 0) + r = walkfile(c, p, d, depth, errorc); + else if(flags & Plus) + r = protowalk1(c, Plus, p, d, nil, depth, errorc); + else + r = protowalk1(c, f.flags, p, d, f.sub, depth, errorc); + if(r == Skip) + return Next; + } + } + return Next; +} + +pathconcat(p, name: string): string +{ + if(p != nil && p[len p - 1] != '/') + p[len p] = '/'; + return p+name; +} + +# from(ish) walk.b +walkfile(c: Entrychan, path: string, d: ref Sys->Dir, depth: int, errorc: chan of string): int +{ + fd := sys->open(path, Sys->OREAD); + if(fd == nil) + report(errorc, sys->sprint("cannot open %q: %r", path)); + else + c.c <-= (d, path, depth); + return Next; +} + +readproto(proto: ref Proto, indent: int): (int, array of ref File) +{ + a := array[10] of ref File; + n := 0; + flags := 0; + while((f := readline(proto, indent)) != nil){ + if(f.name == "*") + flags |= Star; + else if(f.name == "+") + flags |= Plus; + else{ + (f.flags, f.sub) = readproto(proto, proto.indent); + if(n == len a) + a = (array[n * 2] of ref File)[0:] = a; + a[n++] = f; + } + } + if(n < len a) + a = (array[n] of ref File)[0:] = a[0:n]; + mergesort(a, array[n] of ref File); + return (flags, a); +} + +readline(proto: ref Proto, indent: int): ref File +{ + s: string; + if(proto.lastline != nil){ + s = proto.lastline; + proto.lastline = nil; + }else if(proto.indent == -1) + return nil; + else if((s = proto.iob.gets('\n')) == nil){ + proto.indent = -1; + return nil; + } + spc := 0; + for(i := 0; i < len s; i++){ + c := s[i]; + if(c == ' ') + spc++; + else if(c == '\t') + spc += 8; + else + break; + } + if(i == len s || s[i] == '#' || s[i] == '\n') + return readline(proto, indent); # XXX sort out tail recursion! + if(spc <= indent){ + proto.lastline = s; + return nil; + } + proto.indent = spc; + (nil, toks) := sys->tokenize(s, " \t\n"); + f := ref File(nil, ~0, nil, nil, nil, 0, nil); + (f.name, toks) = (getname(hd toks, 0), tl toks); + if(toks == nil) + return f; + (f.mode, toks) = (getmode(hd toks), tl toks); + if(toks == nil) + return f; + (f.owner, toks) = (getname(hd toks, 1), tl toks); + if(toks == nil) + return f; + (f.group, toks) = (getname(hd toks, 1), tl toks); + if(toks == nil) + return f; + (f.old, toks) = (hd toks, tl toks); + return f; +} + +mergesort(a, b: array of ref File) +{ + r := len a; + if (r > 1) { + m := (r-1)/2 + 1; + mergesort(a[0:m], b[0:m]); + mergesort(a[m:], b[m:]); + b[0:] = a; + for ((i, j, k) := (0, m, 0); i < m && j < r; k++) { + if(b[i].name > b[j].name) + a[k] = b[j++]; + else + a[k] = b[i++]; + } + if (i < m) + a[k:] = b[i:m]; + else if (j < r) + a[k:] = b[j:r]; + } +} + +getname(s: string, allowminus: int): string +{ + if(s == nil) + return nil; + if(allowminus && s == "-") + return nil; + if(s[0] == '$'){ + s = getenv(s[1:]); + if(s == nil) + ; # TO DO: w.warn(sys->sprint("can't read environment variable %s", s)); + return s; + } + return s; +} + +getenv(s: string): string +{ + if(s == "user") + return readfile("/dev/user"); # more accurate? + return readfile("/env/"+s); +} + +readfile(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd != nil){ + a := array[256] of byte; + n := sys->read(fd, a, len a); + if(n > 0) + return string a[0:n]; + } + return nil; +} + +getmode(s: string): int +{ + s = getname(s, 1); + if(s == nil) + return ~0; + m := 0; + i := 0; + if(s[i] == 'd'){ + m |= Sys->DMDIR; + i++; + } + if(i < len s && s[i] == 'a'){ + m |= Sys->DMAPPEND; + i++; + } + if(i < len s && s[i] == 'l'){ + m |= Sys->DMEXCL; + i++; + } + (xmode, t) := str->toint(s, 8); + if(t != nil){ + # report(aux.errorc, "bad mode specification %q", s); + return ~0; + } + return xmode | m; +} + +file2dir(f: ref File, old: ref Sys->Dir): ref Sys->Dir +{ + d := ref Sys->nulldir; + if(old != nil){ + if(old.dtype != 'M'){ + d.uid = "sys"; + d.gid = "sys"; + xmode := (old.mode >> 6) & 7; + d.mode = old.mode | xmode | (xmode << 3); + }else{ + d.uid = old.uid; + d.gid = old.gid; + d.mode = old.mode; + } + d.length = old.length; + d.mtime = old.mtime; + d.atime = old.atime; + d.muid = old.muid; + d.name = old.name; + } + if(f != nil){ + d.name = f.name; + if(f.owner != nil) + d.uid = f.owner; + if(f.group != nil) + d.gid = f.group; + if(f.mode != ~0) + d.mode = f.mode; + } + return d; +} diff --git a/appl/cmd/install/info.b b/appl/cmd/install/info.b new file mode 100644 index 00000000..1c95128f --- /dev/null +++ b/appl/cmd/install/info.b @@ -0,0 +1,73 @@ +implement Info; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "daytime.m"; + daytime: Daytime; +include "arg.m"; + arg: Arg; +include "wrap.m"; + wrap : Wrap; + +Info: module{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +root : string; + +TYPLEN : con 4; +typestr := array[TYPLEN] of { "???", "package", "update", "full update" }; + +fatal(err : string) +{ + sys->fprint(sys->fildes(2), "%s\n", err); + raise "fail:error"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + daytime = load Daytime Daytime->PATH; + arg = load Arg Arg->PATH; + wrap = load Wrap Wrap->PATH; + wrap->init(bufio); + + arg->init(args); + while ((c := arg->opt()) != 0) { + case c { + 'r' => + root = arg->arg(); + if (root == nil) + fatal("missing root name"); + * => + fatal(sys->sprint("bad argument -%c", c)); + } + } + args = arg->argv(); + if (args == nil || tl args != nil) + fatal("usage: install/info [-r root] package"); + w := wrap->openwraphdr(hd args, root, nil, 0); + if (w == nil) + fatal("no such package found"); + tm := daytime->text(daytime->local(w.tfull)); + sys->print("%s (complete as of %s)\n", w.name, tm[0:28]); + for (i := w.nu; --i >= 0;) { + typ := w.u[i].typ; + if (typ < 0 || typ >= TYPLEN) + sys->print("%s", typestr[0]); + else + sys->print("%s", typestr[typ]); + sys->print(" %s", wrap->now2string(w.u[i].time, 0)); + if (typ & wrap->UPD) + sys->print(" updating %s", wrap->now2string(w.u[i].utime, 0)); + if (w.u[i].desc != nil) + sys->print(": %s", w.u[i].desc); + sys->print("\n"); + } + wrap->end(); +} diff --git a/appl/cmd/install/inst.b b/appl/cmd/install/inst.b new file mode 100644 index 00000000..dfec4785 --- /dev/null +++ b/appl/cmd/install/inst.b @@ -0,0 +1,500 @@ +implement Inst; + +include "sys.m"; + sys: Sys; + Dir, sprint, fprint: import sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "string.m"; + str: String; +include "arg.m"; + arg: Arg; +include "keyring.m"; + keyring : Keyring; +include "arch.m"; + arch : Arch; +include "wrap.m"; + wrap : Wrap; + +Inst: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +LEN: con Sys->ATOMICIO; + +tflag := 0; +uflag := 0; +hflag := 0; +vflag := 0; +fflag := 1; +stderr: ref Sys->FD; +bout: ref Iobuf; +argv0 := "inst"; +oldw, w : ref Wrap->Wrapped; +root := "/"; +force := 0; +stoponerr := 1; + +# membogus(argv: list of string) +# { +# +# } + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + error(sys->sprint("cannot load %s: %r\n", Bufio->PATH)); + + str = load String String->PATH; + if(str == nil) + error(sys->sprint("cannot load %s: %r\n", String->PATH)); + + arg = load Arg Arg->PATH; + if(arg == nil) + error(sys->sprint("cannot load %s: %r\n", Arg->PATH)); + keyring = load Keyring Keyring->PATH; + if(keyring == nil) + error(sys->sprint("cannot load %s: %r\n", Keyring->PATH)); + arch = load Arch Arch->PATH; + if(arch == nil) + error(sys->sprint("cannot load %s: %r\n", Arch->PATH)); + arch->init(bufio); + wrap = load Wrap Wrap->PATH; + if(wrap == nil) + error(sys->sprint("cannot load %s: %r\n", Wrap->PATH)); + wrap->init(bufio); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'f' => + fflag = 0; + 'h' => + hflag = 1; + bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + if(bout == nil) + error(sys->sprint("can't access standard output: %r")); + 't' => + tflag = 1; + 'u' => + uflag = 1; + 'v' => + vflag = 1; + 'r' => + root = arg->arg(); + if (root == nil) + fatal("root missing"); + 'F' => + force = 1; + 'c' => + stoponerr = 0; + * => + usage(); + } + args = arg->argv(); + if (args == nil) + usage(); + ar := arch->openarch(hd args); + if(ar == nil || ar.b == nil) + error(sys->sprint("can't access %s: %r", hd args)); + w = wrap->openwraphdr(hd args, root, nil, 0); + if (w == nil) + fatal("no such package found"); + if(w.nu != 1) + fatal("strange package: more than one piece"); + if (force == 0) + oldw = wrap->openwrap(w.name, root, 0); + if (force == 0 && w.u[0].utime && (oldw == nil || oldw.tfull < w.u[0].utime)){ + tfull: int; + if(oldw == nil) + tfull = -1; + else + tfull = oldw.tfull; + fatal(sys->sprint("need %s version of %s already installed (pkg %d)", wrap->now2string(w.u[0].utime, 0), w.name, tfull)); + } + args = tl args; + digest := array[Keyring->MD5dlen] of byte; + digest0 := array[Keyring->MD5dlen] of byte; + digest1 := array[Keyring->MD5dlen] of byte; + + while ((a := arch->gethdr(ar)) != nil) { + why := ""; + docopy := 0; + if(force) + docopy = 1; + else if(a.d.mode & Sys->DMDIR) + docopy = 1; + else if(wrap->md5file(root+a.name, digest) < 0) + docopy = 1; + else{ + wrap->md5filea(root+a.name, digest1); + (ok, t) := wrap->getfileinfo(oldw, a.name, digest, nil, digest1); + if (ok >= 0) { + if(t > w.u[0].time){ + docopy = 0; + why = "version from newer package exists"; + } + else + docopy = 1; + } + else { + (ok, t) = wrap->getfileinfo(oldw, a.name, nil, nil, nil); + if(ok >= 0){ + docopy = 0; + why = "locally modified"; + } + else{ + docopy = 0; + why = "locally created"; + } + } + } + if(!docopy){ + wrap->md5sum(ar.b, digest0, int a.d.length); + if(wrap->memcmp(digest, digest0, Keyring->MD5dlen)) + skipfile(a.name, why); + continue; + } + if(args != nil){ + if(!selected(a.name, args)){ + arch->drain(ar, int a.d.length); + continue; + } + if (!hflag) + mkdirs(root, a.name); + } + name := pathcat(root, a.name); + if(hflag){ + bout.puts(sys->sprint("%s %uo %s %s %ud %d\n", + name, a.d.mode, a.d.uid, a.d.gid, a.d.mtime, int a.d.length)); + arch->drain(ar, int a.d.length); + continue; + } + if(a.d.mode & Sys->DMDIR) + mkdir(name, a.d); + else + extract(ar, name, a.d); + } + arch->closearch(ar); + if(ar.err == nil){ + # fprint(stderr, "done\n"); + quit(nil); + } + else { + fprint(stderr, "%s\n", ar.err); + quit("eof"); + } +} + +skipfile(f : string, why : string) +{ + sys->fprint(stderr, "skipping %s: %s\n", f, why); +} + +skiprmfile(f: string, why: string) +{ + sys->fprint(stderr, "not removing %s: %s\n", f, why); +} + +doremove(s : string) +{ + p := pathcat(root, s); + digest := array[Keyring->MD5dlen] of { * => byte 0 }; + digest1 := array[Keyring->MD5dlen] of { * => byte 0 }; + if(wrap->md5file(p, digest) < 0) + ; + else{ + wrap->md5filea(p, digest1); + (ok, nil) := wrap->getfileinfo(oldw, s, digest, nil, digest1); + if(force == 0 && ok < 0) + skiprmfile(p, "locally modified"); + else{ + if (vflag) + sys->print("rm %s\n", p); + remove(p); + } + } +} + +quit(s: string) +{ + if (s == nil) { + p := w.u[0].dir + "/remove"; + if ((b := bufio->open(p, Bufio->OREAD)) != nil) { + while ((t := b.gets('\n')) != nil) { + lt := len t; + if (t[lt-1] == '\n') + t = t[0:lt-1]; + doremove(t); + } + } + } + if(bout != nil) + bout.flush(); + if(wrap != nil) + wrap->end(); + if(s != nil) + raise "fail: "+s; + else + fprint(stderr, "done\n"); + exit; +} + +fileprefix(prefix, s: string): int +{ + n := len prefix; + m := len s; + if(n > m || !str->prefix(prefix, s)) + return 0; + if(m > n && s[n] != '/') + return 0; + return 1; +} + +selected(s: string, args: list of string): int +{ + for(; args != nil; args = tl args) + if(fileprefix(hd args, s)) + return 1; + return 0; +} + +mkdirs(basedir, name: string) +{ + (nil, names) := sys->tokenize(name, "/"); + while(names != nil) { + create(basedir, Sys->OREAD, 8r775|Sys->DMDIR); + if(tl names == nil) + break; + basedir = basedir + "/" + hd names; + names = tl names; + } +} + +mkdir(name: string, dir : ref Sys->Dir) +{ + d: Dir; + i: int; + + if(vflag) { + MTPT : con "/n/remote"; + s := name; + if (len name >= len MTPT && name[0:len MTPT] == MTPT) + s = name[len MTPT:]; + sys->print("installing directory %s\n", s); + } + fd := create(name, Sys->OREAD, dir.mode); + if(fd == nil) { + err := sys->sprint("%r"); + (i, d) = sys->stat(name); + if(i < 0 || !(d.mode & Sys->DMDIR)){ + werr(sys->sprint("can't make directory %s: %s", name, err)); + return; + } + } + else { + (i, d) = sys->fstat(fd); + if(i < 0) + warn(sys->sprint("can't stat %s: %r", name)); + fd = nil; + } + d = sys->nulldir; + (nil, p) := str->splitr(name, "/"); + if(p == nil) + p = name; + d.name = p; + d.mode = dir.mode; + if(tflag || uflag) + d.mtime = dir.mtime; + if(uflag){ + d.uid = dir.uid; + d.gid = dir.gid; + } + fd = nil; + if(sys->wstat(name, d) < 0){ + e := sys->sprint("%r"); + if(wstat(name, d) < 0) + warn(sys->sprint("can't set modes for %s: %s", name, e)); + } + if(uflag){ + (i, d) = sys->stat(name); + if(i < 0) + warn(sys->sprint("can't reread modes for %s: %r", name)); + if(dir.uid != d.uid) + warn(sys->sprint("%s: uid mismatch %s %s", name, dir.uid, d.uid)); + if(dir.gid != d.gid) + warn(sys->sprint("%s: gid mismatch %s %s", name, dir.gid, d.gid)); + } +} + +extract(ar : ref Arch->Archive, name: string, dir : ref Sys->Dir) +{ + sfd := create(name, Sys->OWRITE, dir.mode); + if(sfd == nil) { + if(!fflag || remove(name) == -1 || + (sfd = create(name, Sys->OWRITE, dir.mode)) == nil) { + werr(sys->sprint("can't make file %s: %r", name)); + arch->drain(ar, int dir.length); + return; + } + } + b := bufio->fopen(sfd, Bufio->OWRITE); + if (b == nil) { + warn(sys->sprint("can't open file %s for bufio : %r", name)); + arch->drain(ar, int dir.length); + return; + } + err := arch->getfile(ar, b, int dir.length); + if (err != nil) { + if (len err >= 9 && err[0:9] == "premature") + fatal(err); + else + warn(err); + } + (i, d) := sys->fstat(b.fd); + if(i < 0) + warn(sys->sprint("can't stat %s: %r", name)); + d = sys->nulldir; + (nil, p) := str->splitr(name, "/"); + if(p == nil) + p = name; + d.name = p; + d.mode = dir.mode; + if(tflag || uflag) + d.mtime = dir.mtime; + if(uflag){ + d.uid = dir.uid; + d.gid = dir.gid; + } + if(b.flush() == Bufio->ERROR) + werr(sys->sprint("error writing %s: %r", name)); + b.close(); + sfd = nil; + if(sys->wstat(name, d) < 0){ + e := sys->sprint("%r"); + if(wstat(name, d) < 0) + warn(sys->sprint("can't set modes for %s: %s", name, e)); + } + if(uflag){ + (i, d) = sys->stat(name); + if(i < 0) + warn(sys->sprint("can't reread modes for %s: %r", name)); + if(d.uid != dir.uid) + warn(sys->sprint("%s: uid mismatch %s %s", name, dir.uid, d.uid)); + if(d.gid != dir.gid) + warn(sys->sprint("%s: gid mismatch %s %s", name, dir.gid, d.gid)); + } +} + +error(s: string) +{ + fprint(stderr, "%s: %s\n", argv0, s); + quit("error"); +} + +werr(s: string) +{ + fprint(stderr, "%s: %s\n", argv0, s); + if(stoponerr) + quit("werr"); +} + +warn(s: string) +{ + fprint(stderr, "%s: %s\n", argv0, s); +} + +usage() +{ + fprint(stderr, "Usage: inst [-h] [-u] [-v] [-f] [-c] [-F] [-r dest-root] [file ...]\n"); + raise "fail: usage"; +} + +fatal(s : string) +{ + sys->fprint(stderr, "inst: %s\n", s); + if(wrap != nil) + wrap->end(); + exit; +} + +parent(name : string) : string +{ + slash := -1; + for (i := 0; i < len name; i++) + if (name[i] == '/') + slash = i; + if (slash > 0) + return name[0:slash]; + return "/"; +} + +create(name : string, rw : int, mode : int) : ref Sys->FD +{ + fd := sys->create(name, rw, mode); + if (fd == nil) { + p := parent(name); + (ok, d) := sys->stat(p); + if (ok < 0) + return nil; + omode := d.mode; + d = sys->nulldir; + d.mode = omode | 8r222; # ensure parent is writable + sys->wstat(p, d); + fd = sys->create(name, rw, mode); + d.mode = omode; + sys->wstat(p, d); + } + return fd; +} + +remove(name : string) : int +{ + if (sys->remove(name) < 0) { + (ok, d) := sys->stat(name); + if (ok < 0) + return -1; + omode := d.mode; + d.mode |= 8r222; + sys->wstat(name, d); + if (sys->remove(name) >= 0) + return 0; + d.mode = omode; + sys->wstat(name, d); + return -1; + } + return 0; +} + +wstat(name : string, d : Dir) : int +{ + (ok, dir) := sys->stat(name); + if (ok < 0) + return -1; + omode := dir.mode; + dir.mode |= 8r222; + sys->wstat(name, dir); + if (sys->wstat(name, d) >= 0) + return 0; + dir.mode = omode; + sys->wstat(name, dir); + return -1; +} + +pathcat(s : string, t : string) : string +{ + if (s == nil) return t; + if (t == nil) return s; + slashs := s[len s - 1] == '/'; + slasht := t[0] == '/'; + if (slashs && slasht) + return s + t[1:]; + if (!slashs && !slasht) + return s + "/" + t; + return s + t; +} diff --git a/appl/cmd/install/install.b b/appl/cmd/install/install.b new file mode 100644 index 00000000..858f3a27 --- /dev/null +++ b/appl/cmd/install/install.b @@ -0,0 +1,430 @@ +implement Install; + +# +# Determine which packages need installing and calls install/inst +# to actually install each one +# + +# usage: install/install -d -F -g -s -u -i installdir -p platform -r root -P package + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "string.m"; + str: String; +include "arg.m"; + arg: Arg; +include "readdir.m"; + readdir : Readdir; +include "sh.m"; + +Install: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +# required dirs, usually in the standard inferno root. +# The network download doesn't include them because of +# problems with versions of tar that won't create empty dirs +# so we'll make sure they exist. + +reqdirs := array [] of { + "/mnt", + "/mnt/wrap", + "/n", + "/n/remote", + "/tmp", +}; + +YES, NO, QUIT, ERR : con iota; +INST : con "install/inst"; # actual install program +MTPT : con "/n/remote"; # mount point for user's inferno root + +debug := 0; +force := 0; +exitemu := 0; +uflag := 0; +stderr : ref Sys->FD; +installdir := "/install"; +platform := "Plan9"; +lcplatform : string; +root := "/usr/inferno"; +local: int; +global: int = 1; +waitfd : ref Sys->FD; + +Product : adt { + name : string; + pkgs : ref Package; + nxt : ref Product; +}; + +Package : adt { + name : string; + nxt : ref Package; +}; + +instprods : ref Product; # products/packages already installed + +# platform independent packages +xpkgs := array[] of { "inferno", "utils", "src", "ipaq", "minitel", "sds" }; +ypkgs: list of string; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + # Hack for network download... + # make sure the dirs we need exist + for (dirix := 0; dirix < len reqdirs; dirix++) { + dir := reqdirs[dirix]; + (exists, nil) := sys->stat(dir); + if (exists == -1) { + fd := sys->create(dir, Sys->OREAD, Sys->DMDIR + 8r7775); + if (fd == nil) + fatal(sys->sprint("cannot create directory %s: %r\n", dir)); + fd = nil; + } + } + + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + fatal(sys->sprint("cannot load %s: %r\n", Bufio->PATH)); + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + fatal(sys->sprint("cannot load %s: %r\n", Readdir->PATH)); + str = load String String->PATH; + if(str == nil) + fatal(sys->sprint("cannot load %s: %r\n", String->PATH)); + arg = load Arg Arg->PATH; + if(arg == nil) + fatal(sys->sprint("cannot load %s: %r\n", Arg->PATH)); + arg->init(args); + while((c := arg->opt()) != 0) { + case c { + 'd' => + debug = 1; + 'F' => + force = 1; + 's' => + exitemu = 1; + 'i' => + installdir = arg->arg(); + if (installdir == nil) + fatal("install directory missing"); + 'p' => + platform = arg->arg(); + if (platform == nil) + fatal("platform missing"); + 'P' => + pkg := arg->arg(); + if (pkg == nil) + fatal("package missing"); + ypkgs = pkg :: ypkgs; + 'r' => + root = arg->arg(); + if (root == nil) + fatal("inferno root missing"); + 'u' => + uflag = 1; + 'g' => + global = 0; + '*' => + usage(); + } + } + if (arg->argv() != nil) + usage(); + lcplatform = str->tolower(platform); + (ok, dir) := sys->stat(installdir); + if (ok < 0) + fatal(sys->sprint("cannot open install directory %s", installdir)); + nt := lcplatform == "nt"; + if (nt) { + # root os of the form ?:/......... + if (len root < 3 || root[1] != ':' || root[2] != '/') + fatal(sys->sprint("root %s not of the form ?:/.......", root)); + spec := root[0:2]; + root = root[2:]; + if (sys->bind("#U"+spec, MTPT, Sys->MREPL|Sys->MCREATE) < 0) + fatal(sys->sprint("cannot bind to drive %s", spec)); + } + else { + if (root[0] != '/') + fatal(sys->sprint("root %s must be an absolute path name", root)); + if (sys->bind("#U*", MTPT, Sys->MREPL|Sys->MCREATE) < 0) + fatal("cannot bind to system root"); + } + (ok, dir) = sys->stat(MTPT+root); + if (ok >= 0) { + if ((dir.mode & Sys->DMDIR) == 0) + fatal(sys->sprint("inferno root %s is not a directory", root)); + } + else if (sys->create(MTPT+root, Sys->OREAD, 8r775 | Sys->DMDIR) == nil) + fatal(sys->sprint("cannot create inferno root %s: %r", root)); + # need a writable tmp directory /tmp in case installing from CD + (ok, dir) = sys->stat(MTPT+root+"/tmp"); + if (ok >= 0) { + if ((dir.mode & Sys->DMDIR) == 0) + fatal(sys->sprint("inferno root tmp %s is not a directory", root+"/tmp")); + } + else if (sys->create(MTPT+root+"/tmp", Sys->OREAD, 8r775 | Sys->DMDIR) == nil) + fatal(sys->sprint("cannot create inferno root tmp %s: %r", root+"/tmp")); + if (sys->bind(MTPT+root, MTPT, Sys->MREPL | Sys->MCREATE) < 0) + fatal("cannot bind inferno root"); + if (sys->bind(MTPT+"/tmp", "/tmp", Sys->MREPL | Sys->MCREATE) < 0) + fatal("cannot bind inferno root tmp"); + root = MTPT; + + if (nt || 1) + local = 1; + else { + sys->print("You can either install software specific to %s only or\n", platform); + sys->print(" install software for all platforms that we support.\n"); + sys->print("If you are unsure what to do, answer yes to the question following.\n"); + sys->print(" You can install the remainder of the software at a later date if desired.\n"); + sys->print("\n"); + b := bufio->fopen(sys->fildes(0), Bufio->OREAD); + if (b == nil) + fatal("cannot open stdin"); + for (;;) { + sys->print("Install software specific to %s only ? (yes/no/quit) ", platform); + resp := getresponse(b); + ans := answer(resp); + if (ans == QUIT) + exit; + else if (ans == ERR) + sys->print("bad response %s\n\n", resp); + else { + local = ans == YES; + break; + } + } + } + instprods = dowraps(root+"/wrap"); + doprods(installdir); + if (!nt) + sys->print("installation complete\n"); + if (exitemu) + shutdown(); +} + +getresponse(b : ref Iobuf) : string +{ + s := b.gets('\n'); + while (s != nil && (s[0] == ' ' || s[0] == '\t')) + s = s[1:]; + while (s != nil && ((c := s[len s - 1]) == ' ' || c == '\t' || c == '\n')) + s = s[0: len s - 1]; + return s; +} + +answer(s : string) : int +{ + s = str->tolower(s); + if (s == "y" || s == "yes") + return YES; + if (s == "n" || s == "no") + return NO; + if (s == "q" || s == "quit") + return QUIT; + return ERR; +} + +usage() +{ + fatal("Usage: install [-d] [-F] [-s] [-u] [-i installdir ] [-p platform ] [-r root]"); +} + +fatal(s : string) +{ + sys->fprint(stderr, "install: %s\n", s); + exit; +} + +dowraps(d : string) : ref Product +{ + p : ref Product; + + # make an inventory of what is already apparently installed + (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT); + for (i := 0; i < n; i++) { + if (dir[i].mode & Sys->DMDIR) { + p = ref Product(str->tolower(dir[i].name), nil, p); + p.pkgs = dowrap(d + "/" + dir[i].name); + } + } + return p; +} + +dowrap(d : string) : ref Package +{ + p : ref Package; + + (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT); + for (i := 0; i < n; i++) + p = ref Package(dir[i].name, p); + return p; +} + +doprods(d : string) +{ + (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT); + for (i := 0; i < n; i++) { + if (dir[i].mode & Sys->DMDIR) + doprod(str->tolower(dir[i].name), d + "/" + dir[i].name); + } +} + +doprod(pr : string, d : string) +{ + # base package, updates and update packages have the name + # <timestamp> or <timestamp.gz> + if (!wanted(pr)) + return; + (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT); + for (i := 0; i < n; i++) { + pk := dir[i].name; + l := len pk; + if (l >= 4 && pk[l-3:l] == ".gz") + pk = pk[0:l-3]; + else if (l >= 5 && (pk[l-4:] == ".tgz" || pk[l-4:] == ".9gz")) + pk = pk[0:l-4]; + dopkg(pk, pr, d+"/"+dir[i].name); + + } +} + +dopkg(pk : string, pr : string, d : string) +{ + if (!installed(pk, pr)) + install(d); +} + +installed(pkg : string, prd : string) : int +{ + for (pr := instprods; pr != nil; pr = pr.nxt) { + if (pr.name == prd) { + for (pk := pr.pkgs; pk != nil; pk = pk.nxt) { + if (pk.name == pkg) + return 1; + } + return 0; + } + } + return 0; +} + +lookup(pr : string) : int +{ + for (i := 0; i < len xpkgs; i++) { + if (xpkgs[i] == pr) + return i; + } + return -1; +} + +plookup(pr: string): int +{ + for(ps := ypkgs; ps != nil; ps = tl ps) + if(pr == hd ps) + return 1; + return 0; +} + +wanted(pr : string) : int +{ + if (!local || global) + return 1; + if(ypkgs != nil) # overrides everything else + return plookup(pr); + found := lookup(pr); + if (found >= 0) + return 1; + return pr == lcplatform || prefix(lcplatform, pr); +} + +install(d : string) +{ + if (waitfd == nil) + waitfd = openwait(sys->pctl(0, nil)); + sys->fprint(stderr, "installing package %s\n", d); + if (debug) + return; + c := chan of int; + args := "-t" :: "-v" :: "-r" :: root :: d :: nil; + if (uflag) + args = "-u" :: args; + if (force) + args = "-F" :: args; + spawn exec(INST, INST :: args, c); + execpid := <- c; + wait(waitfd, execpid); +} + +exec(cmd : string, argl : list of string, ci : chan of int) +{ + ci <-= sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: stderr.fd :: nil); + file := cmd; + if(len file<4 || file[len file-4:] !=".dis") + file += ".dis"; + c := load Command file; + if(c == nil) { + err := sys->sprint("%r"); + if(file[0] !='/' && file[0:2] !="./") { + c = load Command "/dis/"+file; + if(c == nil) + err = sys->sprint("%r"); + } + if(c == nil) + fatal(sys->sprint("%s: %s\n", cmd, err)); + } + c->init(nil, argl); +} + +openwait(pid : int) : ref Sys->FD +{ + w := sys->sprint("#p/%d/wait", pid); + fd := sys->open(w, Sys->OREAD); + if (fd == nil) + fatal("fd == nil in wait"); + return fd; +} + +wait(wfd : ref Sys->FD, wpid : int) +{ + n : int; + + buf := array[Sys->WAITLEN] of byte; + status := ""; + for(;;) { + if ((n = sys->read(wfd, buf, len buf)) < 0) + fatal("bad read in wait"); + status = string buf[0:n]; + break; + } + if (int status != wpid) + fatal("bad status in wait"); + if(status[len status - 1] != ':') + fatal(sys->sprint("%s\n", status)); +} + +shutdown() +{ + fd := sys->open("/dev/sysctl", sys->OWRITE); + if(fd == nil) + fatal("cannot shutdown emu"); + if (sys->write(fd, array of byte "halt", 4) < 0) + fatal(sys->sprint("shutdown: write failed: %r\n")); +} + +prefix(s, t : string) : int +{ + if (len s <= len t) + return t[0:len s] == s; + return 0; +} diff --git a/appl/cmd/install/log.b b/appl/cmd/install/log.b new file mode 100644 index 00000000..d624f446 --- /dev/null +++ b/appl/cmd/install/log.b @@ -0,0 +1,76 @@ +implement Fsmodule; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "sh.m"; + +include "daytime.m"; + daytime: Daytime; + +include "fslib.m"; + fslib: Fslib; + Report, Value, type2s, quit: import fslib; + Fschan, Fsdata, Entrychan, Entry, + Gatechan, Gatequery, Nilentry, Option, + Next, Down, Skip, Quit: import Fslib; + +types(): string +{ + return "vt-us-gs"; +} + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "fs: log: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init() +{ + sys = load Sys Sys->PATH; + fslib = load Fslib Fslib->PATH; + if(fslib == nil) + badmod(Fslib->PATH); + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + badmod(Daytime->PATH); +} + +run(nil: ref Draw->Context, report: ref Report, + opts: list of Option, args: list of ref Value): ref Value +{ + uid, gid: string; + for(; opts != nil; opts = tl opts){ + o := hd (hd opts).args; + case (hd opts).opt { + 'u' => uid = o.s().i; + 'g' => gid = o.s().i; + } + } + sync := chan of int; + spawn logproc(sync, (hd args).t().i, report.start("log"), uid, gid); + return ref Value.V(sync); +} + +logproc(sync: chan of int, c: Entrychan, errorc: chan of string, uid: string, gid: string) +{ + if(<-sync == 0){ + c.sync <-= 0; + quit(errorc); + exit; + } + c.sync <-= 1; + + now := daytime->now(); + for(seq := 0; ((d, p, nil) := <-c.c).t0 != nil; seq++){ + if(uid != nil) + d.uid = uid; + if(gid != nil) + d.gid = gid; + sys->print("%ud %ud %c %q - - %uo %q %q %ud %bd%s\n", now, seq, 'a', p, d.mode, d.uid, d.gid, d.mtime, d.length, ""); + } + quit(errorc); +} diff --git a/appl/cmd/install/logs.b b/appl/cmd/install/logs.b new file mode 100644 index 00000000..20135622 --- /dev/null +++ b/appl/cmd/install/logs.b @@ -0,0 +1,287 @@ +implement Logs; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "logs.m"; + +Hashsize: con 1024; +Incr: con 500; + +init(bio: Bufio): string +{ + sys = load Sys Sys->PATH; + bufio = bio; + str = load String String->PATH; + if(str == nil) + return sys->sprint("can't load %s: %r", String->PATH); + return nil; +} + +Entry.read(in: ref Iobuf): (ref Entry, string) +{ + if((s := in.gets('\n')) == nil) + return (nil, nil); + if(s[len s-1] == '\n') + s = s[0:len s-1]; + + e := ref Entry; + e.x = -1; + + l := str->unquoted(s); + fields := array[11] of string; + for(i := 0; l != nil; l = tl l) + fields[i++] = S(hd l); + + # time gen verb path serverpath mode uid gid mtime length + # 1064889121 4 a sys/src/cmd/ip/httpd/webls.denied - 664 sys sys 1064887847 3 + # time[0] gen[1] op[2] path[3] (serverpath|"-")[4] mode[5] uid[6] gid[7] mtime[8] length[9] + + if(i < 10 || len fields[2] != 1) + return (nil, sys->sprint("bad log entry: %q", s)); + e.action = fields[2][0]; + case e.action { + 'a' or 'c' or 'd' or 'm' => + ; + * => + return (nil, sys->sprint("bad log entry: %q", s)); + } + + time := bigof(fields[0], 10); + sgen := bigof(fields[1], 10); + e.seq = (time << 32) | sgen; # for easier comparison + + # time/gen check + # name check + + if(fields[4] == "-") # undocumented + fields[4] = fields[3]; + e.path = fields[3]; + e.serverpath = fields[4]; + e.d = sys->nulldir; + { + e.d.mode = intof(fields[5], 8); + e.d.qid.qtype = e.d.mode>>24; + e.d.uid = fields[6]; + if(e.d.uid == "-") + e.d.uid = ""; + e.d.gid = fields[7]; + if(e.d.gid == "-") + e.d.gid = ""; + e.d.mtime = intof(fields[8], 10); + e.d.length = bigof(fields[9], 10); + }exception ex { + "log format:*" => + return (nil, sys->sprint("%s in log entry %q", ex, s)); + } + e.contents = fields[10] :: nil; # optional + return (e, nil); +} + +rev[T](l: list of T): list of T +{ + rl: list of T; + for(; l != nil; l = tl l) + rl = hd l :: rl; + return rl; +} + +bigof(s: string, base: int): big +{ + (b, r) := str->tobig(s, base); + if(r != nil) + raise "invalid integer field"; + return b; +} + +intof(s: string, base: int): int +{ + return int bigof(s, base); +} + +mkpath(root: string, name: string): string +{ + if(len root > 0 && root[len root-1] != '/' && (len name == 0 || name[0] != '/')) + return root+"/"+name; + return root+name; +} + +contents(e: ref Entry): string +{ + if(e.contents == nil) + return ""; + s := ""; + for(cl := e.contents; cl != nil; cl = tl cl) + s += " " + hd cl; + return s[1:]; +} + +Entry.text(e: self ref Entry): string +{ + a := e.action; + if(a == 0) + a = '?'; + return sys->sprint("%bd %bd %q [%d] %c m=%uo l=%bd t=%ud c=%q", e.seq>>32, e.seq & 16rFFFFFFFF, e.path, e.x, a, e.d.mode, e.d.length, e.d.mtime, contents(e)); +} + +Entry.sumtext(e: self ref Entry): string +{ + case e.action { + 'a' or 'm' => + return sys->sprint("%c %q %uo %q %q %ud", e.action, e.path, e.d.mode, e.d.uid, e.d.gid, e.d.mtime); + 'd' or 'c' => + return sys->sprint("%c %q", e.action, e.path); + * => + return sys->sprint("? %q", e.path); + } +} + +Entry.dbtext(e: self ref Entry): string +{ + # path dpath|"-" mode uid gid mtime length + return sys->sprint("%bd %bd %q - %uo %q %q %ud %bd%s", e.seq>>32, e.seq & 16rFFFFFFFF, e.path, e.d.mode, e.d.uid, e.d.gid, e.d.mtime, e.d.length, contents(e)); +} + +Entry.logtext(e: self ref Entry): string +{ + # gen n act path spath|"-" dpath|"-" mode uid gid mtime length + a := e.action; + if(a == 0) + a = '?'; + sf := e.serverpath; + if(sf == nil || sf == e.path) + sf = "-"; + return sys->sprint("%bd %bd %c %q %q %uo %q %q %ud %bd%s", e.seq>>32, e.seq & 16rFFFFFFFF, a, e.path, sf, e.d.mode, e.d.uid, e.d.gid, e.d.mtime, e.d.length, contents(e)); +} + +Entry.remove(e: self ref Entry) +{ + e.action = 'd'; +} + +Entry.removed(e: self ref Entry): int +{ + return e.action == 'd'; +} + +Entry.update(e: self ref Entry, n: ref Entry) +{ + if(n == nil) + return; + if(n.action == 'd') + e.contents = nil; + else + e.d = n.d; + if(n.action != 'm' || e.action == 'd') + e.action = n.action; + e.serverpath = S(n.serverpath); + for(nl := rev(n.contents); nl != nil; nl = tl nl) + e.contents = hd nl :: e.contents; + if(n.seq > e.seq) + e.seq = n.seq; +} + +Db.new(name: string): ref Db +{ + db := ref Db; + db.name = name; + db.stateht = array[Hashsize] of list of ref Entry; + db.nstate = 0; + db.state = array[50] of ref Entry; + return db; +} + +Db.look(db: self ref Db, name: string): ref Entry +{ + (b, nil) := hash(name, len db.stateht); + for(l := db.stateht[b]; l != nil; l = tl l) + if((hd l).path == name) + return hd l; + return nil; +} + +Db.entry(db: self ref Db, seq: big, name: string, d: Sys->Dir): ref Entry +{ + e := ref Entry; + e.action = 'a'; + e.seq = seq; + e.path = name; + e.d = d; + e.x = db.nstate++; + if(e.x >= len db.state){ + a := array[len db.state + Incr] of ref Entry; + a[0:] = db.state; + db.state = a; + } + db.state[e.x] = e; + (b, nil) := hash(name, len db.stateht); + db.stateht[b] = e :: db.stateht[b]; + return e; +} + +Db.sort(db: self ref Db, key: int) +{ + sortentries(db.state[0:db.nstate], key); +} + +sortentries(a: array of ref Entry, key: int): (array of ref Entry, int) +{ + mergesort(a, array[len a] of ref Entry, key); + return (a, len a); +} + +mergesort(a, b: array of ref Entry, key: int) +{ + r := len a; + if(r > 1) { + m := (r-1)/2 + 1; + mergesort(a[0:m], b[0:m], key); + mergesort(a[m:], b[m:], key); + b[0:] = a; + for((i, j, k) := (0, m, 0); i < m && j < r; k++) { + if(key==Byname && b[i].path > b[j].path || key==Byseq && b[i].seq > b[j].seq) + a[k] = b[j++]; + else + a[k] = b[i++]; + } + if(i < m) + a[k:] = b[i:m]; + else if(j < r) + a[k:] = b[j:r]; + } +} + +strings: array of list of string; + +S(s: string): string +{ + if(strings == nil) + strings = array[257] of list of string; + h := hash(s, len strings).t0; + for(sl := strings[h]; sl != nil; sl = tl sl) + if(hd sl == s) + return hd sl; + strings[h] = s :: strings[h]; + return s; +} + +hash(s: string, n: int): (int, int) +{ + # hashpjw + h := 0; + for(i:=0; i<len s; i++){ + h = (h<<4) + s[i]; + if((g := h & int 16rF0000000) != 0) + h ^= ((g>>24) & 16rFF) | g; + } + return ((h&~(1<<31))%n, h); +} diff --git a/appl/cmd/install/logs.m b/appl/cmd/install/logs.m new file mode 100644 index 00000000..bed3c68d --- /dev/null +++ b/appl/cmd/install/logs.m @@ -0,0 +1,44 @@ +Logs: module +{ + PATH: con "/dis/install/logs.dis"; + + Entry: adt + { + seq: big; # time<<32 | gen + action: int; + path: string; + serverpath: string; + x: int; + d: Sys->Dir; + contents: list of string; # MD5 hash of content, most recent first + + read: fn(in: ref Bufio->Iobuf): (ref Entry, string); + remove: fn(e: self ref Entry); + removed: fn(e: self ref Entry): int; + update: fn(e: self ref Entry, n: ref Entry); + text: fn(e: self ref Entry): string; + dbtext: fn(e: self ref Entry): string; + sumtext: fn(e: self ref Entry): string; + logtext: fn(e: self ref Entry): string; + }; + + Db: adt + { + name: string; + state: array of ref Entry; + nstate: int; + stateht: array of list of ref Entry; + + new: fn(name: string): ref Db; + entry: fn(db: self ref Db, seq: big, name: string, d: Sys->Dir): ref Entry; + look: fn(db: self ref Db, name: string): ref Entry; + sort: fn(db: self ref Db, byname: int); + }; + + Byseq, Byname: con iota; + + init: fn(bio: Bufio): string; + + S: fn(s: string): string; + mkpath: fn(root: string, name: string): string; +}; diff --git a/appl/cmd/install/mergelog.b b/appl/cmd/install/mergelog.b new file mode 100644 index 00000000..8998d8a9 --- /dev/null +++ b/appl/cmd/install/mergelog.b @@ -0,0 +1,239 @@ +implement Mergelog; + +# +# combine old and new log sections into one with the most recent data +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "keyring.m"; + kr: Keyring; + +include "daytime.m"; + daytime: Daytime; + +include "logs.m"; + logs: Logs; + Db, Entry, Byname, Byseq: import logs; + S: import logs; + +include "arg.m"; + +Mergelog: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Apply, Applydb, Install, Asis, Skip: con iota; + +client: ref Db; # client current state from client log +updates: ref Db; # state delta from new section of server log + +nerror := 0; +nconflict := 0; +debug := 0; +verbose := 0; +resolve := 0; +setuid := 0; +setgid := 0; +nflag := 0; +timefile: string; +clientroot: string; +srvroot: string; +logfd: ref Sys->FD; +now := 0; +gen := 0; +noerr := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + bufio = load Bufio Bufio->PATH; + ensure(bufio, Bufio->PATH); + str = load String String->PATH; + ensure(str, String->PATH); + kr = load Keyring Keyring->PATH; + ensure(kr, Keyring->PATH); + daytime = load Daytime Daytime->PATH; + ensure(daytime, Daytime->PATH); + logs = load Logs Logs->PATH; + ensure(logs, Logs->PATH); + logs->init(bufio); + + arg := load Arg Arg->PATH; + ensure(arg, Arg->PATH); + arg->init(args); + arg->setusage("mergelog [-vd] oldlog [path ... ] <newlog"); + dump := 0; + while((o := arg->opt()) != 0) + case o { + 'd' => dump = 1; debug = 1; + 'v' => verbose = 1; + * => arg->usage(); + } + args = arg->argv(); + if(len args < 3) + arg->usage(); + arg = nil; + + now = daytime->now(); + client = Db.new("existing log"); + updates = Db.new("update log"); + clientlog := hd args; args = tl args; + if(args != nil) + error("restriction by path not yet done"); + + # replay the client log to build last installation state of files taken from server + logfd = sys->open(clientlog, Sys->OREAD); + if(logfd == nil) + error(sys->sprint("can't open %s: %r", clientlog)); + f := bufio->fopen(logfd, Sys->OREAD); + if(f == nil) + error(sys->sprint("can't open %s: %r", clientlog)); + while((log := readlog(f)) != nil) + replaylog(client, log); + f = nil; + + # read new log entries and use the new section to build a sequence of update actions + f = bufio->fopen(sys->fildes(0), Sys->OREAD); + while((log = readlog(f)) != nil) + replaylog(client, log); + client.sort(Byseq); + dumpdb(client); + if(nerror) + raise sys->sprint("fail:%d errors", nerror); +} + +readlog(in: ref Iobuf): ref Entry +{ + (e, err) := Entry.read(in); + if(err != nil) + error(err); + return e; +} + +# +# replay a log to reach the state wrt files previously taken from the server +# +replaylog(db: ref Db, log: ref Entry) +{ + e := db.look(log.path); + indb := e != nil && !e.removed(); + case log.action { + 'a' => # add new file + if(indb){ + note(sys->sprint("%q duplicate create", log.path)); + return; + } + 'c' => # contents + if(!indb){ + note(sys->sprint("%q contents but no entry", log.path)); + return; + } + 'd' => # delete + if(!indb){ + note(sys->sprint("%q deleted but no entry", log.path)); + return; + } + if(e.d.mtime > log.d.mtime){ + note(sys->sprint("%q deleted but it's newer", log.path)); + return; + } + 'm' => # metadata + if(!indb){ + note(sys->sprint("%q metadata but no entry", log.path)); + return; + } + * => + error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF)); + } + update(db, e, log); +} + +# +# update file state e to reflect the effect of the log, +# creating a new entry if necessary +# +update(db: ref Db, e: ref Entry, log: ref Entry) +{ + if(e == nil) + e = db.entry(log.seq, log.path, log.d); + e.update(log); +} + +rev[T](l: list of T): list of T +{ + rl: list of T; + for(; l != nil; l = tl l) + rl = hd l :: rl; + return rl; +} + +ensure[T](m: T, path: string) +{ + if(m == nil) + error(sys->sprint("can't load %s: %r", path)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "applylog: %s\n", s); + raise "fail:error"; +} + +note(s: string) +{ + sys->fprint(sys->fildes(2), "applylog: note: %s\n", s); +} + +warn(s: string) +{ + sys->fprint(sys->fildes(2), "applylog: warning: %s\n", s); + nerror++; +} + +samestat(a: Sys->Dir, b: Sys->Dir): int +{ + # doesn't check permission/ownership, does check QTDIR/QTFILE + if(a.mode & Sys->DMDIR) + return (b.mode & Sys->DMDIR) != 0; + return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name? +} + +samemeta(a: Sys->Dir, b: Sys->Dir): int +{ + return a.mode == b.mode && (!setuid || a.uid == b.uid) && (!setgid || a.gid == b.gid) && samestat(a, b); +} + +bigof(s: string, base: int): big +{ + (b, r) := str->tobig(s, base); + if(r != nil) + error("cruft in integer field in log entry: "+s); + return b; +} + +intof(s: string, base: int): int +{ + return int bigof(s, base); +} + +dumpdb(db: ref Db) +{ + for(i := 0; i < db.nstate; i++){ + s := db.state[i].text(); + if(s != nil) + sys->print("%s\n", s); + } +} diff --git a/appl/cmd/install/mkfile b/appl/cmd/install/mkfile new file mode 100644 index 00000000..5da4b55c --- /dev/null +++ b/appl/cmd/install/mkfile @@ -0,0 +1,43 @@ +<../../../mkconfig + +TARG=\ + create.dis\ + info.dis\ + wdiff.dis\ + inst.dis\ + wrap.dis\ + archfs.dis\ + install.dis\ + arch.dis\ + proto.dis\ + ckproto.dis\ + proto2list.dis\ + wrap2list.dis\ + wfind.dis\ + mkproto.dis\ + applylog.dis\ + logs.dis\ + log.dis\ + mergelog.dis\ + updatelog.dis\ + eproto.dis\ + +MODULES=\ + wrap.m\ + arch.m\ + archfs.m\ + logs.m\ + proto.m\ + protocaller.m\ + +SYSMODULES=\ + arg.m\ + bufio.m\ + sys.m\ + draw.m\ + bufio.m\ + string.m\ + +DISBIN=$ROOT/dis/install + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/install/mkproto.b b/appl/cmd/install/mkproto.b new file mode 100644 index 00000000..cee3fd21 --- /dev/null +++ b/appl/cmd/install/mkproto.b @@ -0,0 +1,99 @@ +# +# Copyright © 2000 Vita Nuova (Holdings) Limited. All rights reserved. +# + +implement Mkproto; + +# make a proto description of the directory or file + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "readdir.m"; + readdir: Readdir; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +Mkproto: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: mkproto [ file|directory ... ]\n"); + raise "fail:usage"; +} + +not: list of string; +bout: ref Iobuf; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + readdir = load Readdir Readdir->PATH; + bufio = load Bufio Bufio->PATH; + + bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + argv = tl argv; + while (argv != nil && hd argv != nil && (hd argv)[0] == '-') { + not = (hd argv)[1:] :: not; + argv = tl argv; + } + if (argv == nil) + visit(".", nil, -1); + else if (tl argv == nil) + visit(hd argv, nil, -1); + else { + for ( ; argv != nil; argv = tl argv) + visit(hd argv, hd argv, 0); + } + bout.flush(); +} + +warn(s: string) +{ + sys->fprint(sys->fildes(2), "mkproto: %s\n", s); +} + +visit(fulln: string, reln: string, depth: int) +{ + if (depth == 0) { + for (n := not; n != nil; n = tl n) { + if (hd n == reln) { + # sys->fprint(stderr, "skipping %s\n", reln); + return; + } + } + # sys->fprint(stderr, "doing %s\n", reln); + } + (ok, d) := sys->stat(fulln); + if(ok < 0){ + warn(sys->sprint("cannot stat %s: %r", fulln)); + return; + } + if (depth >= 0) + visitf(fulln, reln, d, depth); + if (d.mode & Sys->DMDIR) + visitd(fulln, reln, d, depth); +} + +visitd(fulln: string, nil: string, nil: Sys->Dir, depth: int) +{ + (dir, n) := readdir->init(fulln, Readdir->NAME|Readdir->COMPACT); + for (i := 0; i < n; i++) { + path := "/"+dir[i].name; + visit(fulln+path, dir[i].name, depth+1); + } +} + +visitf(nil: string, reln: string, nil: Sys->Dir, depth: int) +{ + for (i := 0; i < depth; i++) + bout.putc('\t'); + bout.puts(sys->sprint("%q\n", reln)); +} diff --git a/appl/cmd/install/proto.b b/appl/cmd/install/proto.b new file mode 100644 index 00000000..c25ee220 --- /dev/null +++ b/appl/cmd/install/proto.b @@ -0,0 +1,320 @@ +implement Proto; + +include "sys.m"; + sys: Sys; + Dir : import Sys; +include "draw.m"; +include "bufio.m"; + bufio : Bufio; + Iobuf : import bufio; +include "string.m"; + str: String; +include "readdir.m"; + readdir : Readdir; +include "proto.m"; +include "protocaller.m"; + +NAMELEN: con 8192; + +WARN, ERROR, FATAL : import Protocaller; + +File: adt { + new: string; + elem: string; + old: string; + uid: string; + gid: string; + mode: int; +}; + +indent: int; +lineno := 0; +newfile: string; +oldfile: string; +oldroot : string; +b: ref Iobuf; +cmod : Protocaller; + +rdproto(proto : string, root : string, pcmod : Protocaller) : int +{ + if (sys == nil) { + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + str = load String String->PATH; + readdir = load Readdir Readdir->PATH; + } + cmod = pcmod; + oldroot = root; + b = bufio->open(proto, Sys->OREAD); + if(b == nil){ + cmod->protoerr(FATAL, lineno, sys->sprint("can't open %s: %r: skipping\n", proto)); + b.close(); + return -1; + } + lineno = 0; + indent = 0; + file := ref File; + file.mode = 0; + mkfs(file, -1); + b.close(); + return 0; +} + +mkfs(me: ref File, level: int) +{ + (child, fp) := getfile(me); + if(child == nil) + return; + if(child.elem == "+" || child.elem == "*" || child.elem == "%"){ + rec := child.elem[0] == '+'; + filesonly := child.elem[0] == '%'; + child.new = me.new; + setnames(child); + mktree(child, rec, filesonly); + (child, fp) = getfile(me); + } + while(child != nil && indent > level){ + if(mkfile(child)) + mkfs(child, indent); + (child, fp) = getfile(me); + } + if(child != nil){ + b.seek(big fp, 0); + lineno--; + } +} + +mktree(me: ref File, rec: int, filesonly: int) +{ + fd := sys->open(oldfile, Sys->OREAD); + if(fd == nil){ + cmod->protoerr(WARN, lineno, sys->sprint("can't open %s: %r", oldfile)); + return; + } + child := ref *me; + (d, n) := readdir->init(oldfile, Readdir->NAME|Readdir->COMPACT); + for (i := 0; i < n; i++) { + if (filesonly && (d[i].mode & Sys->DMDIR)) + continue; + child.new = mkpath(me.new, d[i].name); + if(me.old != nil) + child.old = mkpath(me.old, d[i].name); + child.elem = d[i].name; + setnames(child); + if(copyfile(child, d[i]) && rec) + mktree(child, rec, filesonly); + } +} + +mkfile(f: ref File): int +{ + (i, dir) := sys->stat(oldfile); + if(i < 0){ + cmod->protoerr(WARN, lineno, sys->sprint("can't stat file %s: %r", oldfile)); + skipdir(); + return 0; + } + return copyfile(f, ref dir); +} + +copyfile(f: ref File, d: ref Dir): int +{ + d.name = f.elem; + if(f.mode != ~0){ + if((d.mode&Sys->DMDIR) != (f.mode&Sys->DMDIR)) + cmod->protoerr(WARN, lineno, sys->sprint("inconsistent mode for %s", f.new)); + else + d.mode = f.mode; + } + cmod->protofile(newfile, oldfile, d); + return (d.mode & Sys->DMDIR) != 0; +} + +setnames(f: ref File) +{ + newfile = f.new; + if(f.old != nil){ + if(f.old[0] == '/') + oldfile = mkpath(oldroot, f.old); + else + oldfile = f.old; + }else + oldfile = mkpath(oldroot, f.new); +} + +# +# skip all files in the proto that +# could be in the current dir +# +skipdir() +{ + if(indent < 0) + return; + level := indent; + for(;;){ + indent = 0; + fp := b.offset(); + p := b.gets('\n'); + if (p != nil && p[len p - 1] != '\n') + p += "\n"; + lineno++; + if(p == nil){ + indent = -1; + return; + } + for(j := 0; (c := p[j++]) != '\n';) + if(c == ' ') + indent++; + else if(c == '\t') + indent += 8; + else + break; + if(indent <= level){ + b.seek(fp, 0); + lineno--; + return; + } + } +} + +getfile(old: ref File): (ref File, int) +{ + f: ref File; + p, elem: string; + c: int; + + if(indent < 0) + return (nil, 0); + fp := int b.offset(); + do { + indent = 0; + p = b.gets('\n'); + if (p != nil && p[len p - 1] != '\n') + p += "\n"; + lineno++; + if(p == nil){ + indent = -1; + return (nil, 0); + } + for(; (c = p[0]) != '\n'; p = p[1:]) + if(c == ' ') + indent++; + else if(c == '\t') + indent += 8; + else + break; + } while(c == '\n' || c == '#'); + f = ref File; + (elem, p) = getname(p, NAMELEN); + f.new = mkpath(old.new, elem); + (nil, f.elem) = str->splitr(f.new, "/"); + if(f.elem == nil) + cmod->protoerr(ERROR, lineno, sys->sprint("can't find file name component of %s", f.new)); + (f.mode, p) = getmode(p); + (f.uid, p) = getname(p, NAMELEN); + if(f.uid == nil) + f.uid = "-"; + (f.gid, p) = getname(p, NAMELEN); + if(f.gid == nil) + f.gid = "-"; + f.old = getpath(p); + if(f.old == "-") + f.old = nil; + if(f.old == nil && old.old != nil) + f.old = mkpath(old.old, elem); + setnames(f); + return (f, fp); +} + +getpath(p: string): string +{ + for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:]) + ; + for(n := 0; (c = p[n]) != '\n' && c != ' ' && c != '\t'; n++) + ; + return p[0:n]; +} + +getname(p: string, lim: int): (string, string) +{ + for(; (c := p[0]) == ' ' || c == '\t'; p = p[1:]) + ; + i := 0; + s := ""; + for(; (c = p[0]) != '\n' && c != ' ' && c != '\t'; p = p[1:]) + s[i++] = c; + if(len s >= lim){ + cmod->protoerr(WARN, lineno, sys->sprint("name %s too long; truncated", s)); + s = s[0:lim-1]; + } + if(len s > 0 && s[0] == '$'){ + s = getenv(s[1:]); + if(s == nil) + cmod->protoerr(ERROR, lineno, sys->sprint("can't read environment variable %s", s)); + if(len s >= NAMELEN) + s = s[0:NAMELEN-1]; + } + return (s, p); +} + +getenv(s: string): string +{ + if(s == "user") + return getuser(); + return nil; +} + +getuser(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + if(fd != nil){ + u := array [100] of byte; + n := sys->read(fd, u, len u); + if(n > 0) + return string u[0:n]; + } + return nil; +} + +getmode(p: string): (int, string) +{ + s: string; + + (s, p) = getname(p, 7); + if(s == nil || s == "-") + return (~0, p); + m := 0; + if(s[0] == 'd'){ + m |= Sys->DMDIR; + s = s[1:]; + } + if(s[0] == 'a'){ + #m |= CHAPPEND; + s = s[1:]; + } + if(s[0] == 'l'){ + #m |= CHEXCL; + s = s[1:]; + } + for(i:=0; i<len s || i < 3; i++) + if(i >= len s || !(s[i]>='0' && s[i]<='7')){ + cmod->protoerr(WARN, lineno, sys->sprint("bad mode specification %s", s)); + return (~0, p); + } + (v, nil) := str->toint(s, 8); + return (m|v, p); +} + +mkpath(prefix, elem: string): string +{ + slash1 := slash2 := 0; + if (len prefix > 0) + slash1 = prefix[len prefix - 1] == '/'; + if (len elem > 0) + slash2 = elem[0] == '/'; + if (slash1 && slash2) + return prefix+elem[1:]; + if (!slash1 && !slash2) + return prefix+"/"+elem; + return prefix+elem; +} diff --git a/appl/cmd/install/proto.m b/appl/cmd/install/proto.m new file mode 100644 index 00000000..07d3507f --- /dev/null +++ b/appl/cmd/install/proto.m @@ -0,0 +1,6 @@ +Proto : module +{ + PATH : con "/dis/install/proto.dis"; + + rdproto: fn(proto : string, root : string, pcmod : Protocaller) : int; +};
\ No newline at end of file diff --git a/appl/cmd/install/proto2list.b b/appl/cmd/install/proto2list.b new file mode 100644 index 00000000..b5997c15 --- /dev/null +++ b/appl/cmd/install/proto2list.b @@ -0,0 +1,209 @@ +# +# Copyright © 2001 Vita Nuova (Holdings) Limited. All rights reserved. +# + +implement Proto2list; + +# make a version list suitable for SDS from a series of proto files + +include "sys.m"; + sys : Sys; +include "draw.m"; +include "bufio.m"; + bufio : Bufio; + Iobuf : import bufio; +include "crc.m"; + crcm : Crc; +include "proto.m"; + proto : Proto; +include "protocaller.m"; + protocaller : Protocaller; + +WARN, ERROR, FATAL : import Protocaller; + +Proto2list: module +{ + init : fn(ctxt: ref Draw->Context, argv: list of string); + protofile: fn(new : string, old : string, d : ref Sys->Dir); + protoerr: fn(lev : int, line : int, err : string); +}; + +stderr: ref Sys->FD; +protof: string; + +Element: type (string, string); + +List: adt{ + as: array of Element; + n: int; + init: fn(l: self ref List); + add: fn(l: self ref List, e: Element); + end: fn(l: self ref List): array of Element; +}; + +flist: ref List; + +List.init(l: self ref List) +{ + l.as = array[1024] of Element; + l.n = 0; +} + +List.add(l: self ref List, e: Element) +{ + if(l.n == len l.as) + l.as = (array[2*l.n] of Element)[0:] = l.as; + l.as[l.n++] = e; +} + +List.end(l: self ref List): array of Element +{ + return l.as[0: l.n]; +} + +usage() +{ + sys->fprint(stderr, "Usage: proto2list protofile ...\n"); + exit; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + crcm = load Crc Crc->PATH; + proto = load Proto Proto->PATH; + protocaller = load Protocaller "$self"; + stderr = sys->fildes(2); + root := "/"; + flist = ref List; + flist.init(); + for(argv = tl argv; argv != nil; argv = tl argv){ + protof = hd argv; + proto->rdproto(hd argv, root, protocaller); + } + fs := flist.end(); + sort(fs); + fs = uniq(fs); + out(fs); +} + +protofile(new : string, old : string, nil : ref Sys->Dir) +{ + if(new == old) + new = "-"; + flist.add((old, new)); +} + +out(fs: array of Element) +{ + nf := len fs; + for(i := 0; i < nf; i++){ + (f, g) := fs[i]; + (ok, d) := sys->stat(f); + if (ok < 0) { + sys->fprint(stderr, "cannot open %s\n", f); + continue; + } + if (d.mode & Sys->DMDIR) + d.length = big 0; + sys->print("%s %s %d %d %d %d %d\n", f, g, int d.length, d.mode, d.mtime, crc(f, d), 0); + } +} + +protoerr(lev : int, line : int, err : string) +{ + s := "line " + string line + " : " + err; + case lev { + WARN => warn(s); + ERROR => error(s); + FATAL => fatal(s); + } +} + +crc(f : string, d: Sys->Dir) : int +{ + crcs := crcm->init(0, int 16rffffffff); + if (d.mode & Sys->DMDIR) + return 0; + fd := sys->open(f, Sys->OREAD); + if (fd == nil) { + sys->fprint(stderr, "cannot open %s\n", f); + return 0; + } + crc := 0; + buf := array[Sys->ATOMICIO] of byte; + for (;;) { + nr := sys->read(fd, buf, len buf); + if (nr < 0) { + sys->fprint(stderr, "bad read on %s : %r\n", f); + return 0; + } + if (nr <= 0) + break; + crc = crcm->crc(crcs, buf, nr); + } + crcm->reset(crcs); + return crc; +} + +sort(a: array of Element) +{ + mergesort(a, array[len a] of Element); +} + +mergesort(a, b: array of Element) +{ + r := len a; + if (r > 1) { + m := (r-1)/2 + 1; + mergesort(a[0:m], b[0:m]); + mergesort(a[m:], b[m:]); + b[0:] = a; + for ((i, j, k) := (0, m, 0); i < m && j < r; k++) { + if (b[i].t0 > b[j].t0) + a[k] = b[j++]; + else + a[k] = b[i++]; + } + if (i < m) + a[k:] = b[i:m]; + else if (j < r) + a[k:] = b[j:r]; + } +} + + +uniq(a: array of Element): array of Element +{ + m := n := len a; + for(i := 0; i < n-1; ){ + if(a[i].t0 == a[i+1].t0){ + if(a[i].t1 != a[i+1].t1) + warn(sys->sprint("duplicate %s(%s %s)", a[i].t0, a[i].t1, a[i+1].t1)); + a[i+1:] = a[i+2: n--]; + } + else + i++; + } + if(n == m) + return a; + return a[0: n]; +} + +error(s: string) +{ + sys->fprint(stderr, "%s: %s\n", protof, s); + exit; +} + +fatal(s: string) +{ + sys->fprint(stderr, "fatal: %s\n", s); + exit; +} + +warn(s: string) +{ + sys->fprint(stderr, "%s: %s\n", protof, s); +} diff --git a/appl/cmd/install/protocaller.m b/appl/cmd/install/protocaller.m new file mode 100644 index 00000000..1e269d1f --- /dev/null +++ b/appl/cmd/install/protocaller.m @@ -0,0 +1,8 @@ +Protocaller : module{ + init: fn(ctxt : ref Draw->Context, args : list of string); + protofile: fn(new : string, old : string, d : ref Sys->Dir); + + WARN, ERROR, FATAL : con iota; + + protoerr: fn(lev : int, line : int, err : string); +};
\ No newline at end of file diff --git a/appl/cmd/install/updatelog.b b/appl/cmd/install/updatelog.b new file mode 100644 index 00000000..d9c6959e --- /dev/null +++ b/appl/cmd/install/updatelog.b @@ -0,0 +1,386 @@ +implement Updatelog; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "daytime.m"; + daytime: Daytime; + +include "string.m"; + str: String; + +include "keyring.m"; + kr: Keyring; + +include "logs.m"; + logs: Logs; + Db, Entry, Byname, Byseq: import logs; + S, mkpath: import logs; + Log: type Entry; + +include "fsproto.m"; + fsproto: FSproto; + Direntry: import fsproto; + +include "arg.m"; + +Updatelog: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +now: int; +gen := 0; +changesonly := 0; +uid: string; +gid: string; +debug := 0; +state: ref Db; +rootdir := "."; +scanonly: list of string; +exclude: list of string; +sums := 0; +stderr: ref Sys->FD; +Seen: con 1<<31; +bout: ref Iobuf; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + ensure(bufio, Bufio->PATH); + fsproto = load FSproto FSproto->PATH; + ensure(fsproto, FSproto->PATH); + daytime = load Daytime Daytime->PATH; + ensure(daytime, Daytime->PATH); + str = load String String->PATH; + ensure(str, String->PATH); + logs = load Logs Logs->PATH; + ensure(logs, Logs->PATH); + kr = load Keyring Keyring->PATH; + ensure(kr, Keyring->PATH); + + arg := load Arg Arg->PATH; + if(arg == nil) + error(sys->sprint("can't load %s: %r", Arg->PATH)); + + protofile := "/lib/proto/all"; + arg->init(args); + arg->setusage("updatelog [-p proto] [-r root] [-t now gen] [-c] [-x path] x.log [path ...]"); + while((o := arg->opt()) != 0) + case o { + 'D' => + debug = 1; + 'p' => + protofile = arg->earg(); + 'r' => + rootdir = arg->earg(); + 'c' => + changesonly = 1; + 'u' => + uid = arg->earg(); + 'g' => + gid = arg->earg(); + 's' => + sums = 1; + 't' => + now = int arg->earg(); + gen = int arg->earg(); + 'x' => + s := arg->earg(); + exclude = trimpath(s) :: exclude; + * => + arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + stderr = sys->fildes(2); + bout = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + + fsproto->init(); + logs->init(bufio); + + logfile := hd args; + while((args = tl args) != nil) + scanonly = trimpath(hd args) :: scanonly; + checkroot(rootdir, "replica root"); + + state = Db.new("server state"); + + # + # replay log to rebuild server state + # + logfd := sys->open(logfile, Sys->OREAD); + if(logfd == nil) + error(sys->sprint("can't open %s: %r", logfile)); + f := bufio->fopen(logfd, Sys->OREAD); + if(f == nil) + error(sys->sprint("can't open %s: %r", logfile)); + while((log := readlog(f)) != nil) + replaylog(state, log); + + # + # walk the set of names produced by the proto file, comparing against the server state + # + now = daytime->now(); + doproto(rootdir, protofile); + + if(changesonly){ + bout.flush(); + exit; + } + + # + # names in the original state that we didn't see in the walk must have been removed: + # print 'd' log entries for them, in reverse lexicographic order (children before parents) + # + state.sort(Logs->Byname); + for(i := state.nstate; --i >= 0;){ + e := state.state[i]; + if((e.x & Seen) == 0 && considered(e.path)){ + change('d', e, e.seq, e.d, e.path, e.serverpath, e.contents); # TO DO: content + if(debug) + sys->fprint(sys->fildes(2), "remove %q\n", e.path); + } + } + bout.flush(); +} + +ensure[T](m: T, path: string) +{ + if(m == nil) + error(sys->sprint("can't load %s: %r", path)); +} + +checkroot(dir: string, what: string) +{ + (ok, d) := sys->stat(dir); + if(ok < 0) + error(sys->sprint("can't stat %s %q: %r", what, dir)); + if((d.mode & Sys->DMDIR) == 0) + error(sys->sprint("%s %q: not a directory", what, dir)); +} + +considered(s: string): int +{ + if(scanonly != nil && !islisted(s, scanonly)) + return 0; + return exclude == nil || !islisted(s, exclude); +} + +readlog(in: ref Iobuf): ref Log +{ + (e, err) := Entry.read(in); + if(err != nil) + error(err); + return e; +} + +# +# replay a log to reach the state wrt files previously taken from the server +# +replaylog(db: ref Db, log: ref Log) +{ + e := db.look(log.path); + indb := e != nil && !e.removed(); + case log.action { + 'a' => # add new file + if(indb){ + note(sys->sprint("%q duplicate create", log.path)); + return; + } + 'c' => # contents + if(!indb){ + note(sys->sprint("%q contents but no entry", log.path)); + return; + } + 'd' => # delete + if(!indb){ + note(sys->sprint("%q deleted but no entry", log.path)); + return; + } + if(e.d.mtime > log.d.mtime){ + note(sys->sprint("%q deleted but it's newer", log.path)); + return; + } + 'm' => # metadata + if(!indb){ + note(sys->sprint("%q metadata but no entry", log.path)); + return; + } + * => + error(sys->sprint("bad log entry: %bd %bd", log.seq>>32, log.seq & big 16rFFFFFFFF)); + } + update(db, e, log); +} + +# +# update file state e to reflect the effect of the log, +# creating a new entry if necessary +# +update(db: ref Db, e: ref Entry, log: ref Entry) +{ + if(e == nil) + e = db.entry(log.seq, log.path, log.d); + e.update(log); +} + +doproto(tree: string, protofile: string) +{ + entries := chan of Direntry; + warnings := chan of (string, string); + err := fsproto->readprotofile(protofile, tree, entries, warnings); + if(err != nil) + error(sys->sprint("can't read %s: %s", protofile, err)); + for(;;)alt{ + (old, new, d) := <-entries => + if(d == nil) + return; + if(debug) + sys->fprint(stderr, "old=%q new=%q length=%bd\n", old, new, d.length); + while(new != nil && new[0] == '/') + new = new[1:]; + if(!considered(new)) + continue; + if(sums && (d.mode & Sys->DMDIR) == 0) + digests := md5sum(old) :: nil; + if(uid != nil) + d.uid = uid; + if(gid != nil) + d.gid = gid; + old = relative(old, rootdir); + db := state.look(new); + if(db == nil){ + if(!changesonly){ + db = state.entry(nextseq(), new, *d); + change('a', db, db.seq, db.d, db.path, old, digests); + } + }else{ + if(!samestat(db.d, *d)) + change('c', db, nextseq(), *d, new, old, digests); + if(!samemeta(db.d, *d)) + change('m', db, nextseq(), *d, new, old, nil); # need digest? + } + if(db != nil) + db.x |= Seen; + (old, msg) := <-warnings => + #if(contains(msg, "entry not found") || contains(msg, "not exist")) + # break; + sys->fprint(sys->fildes(2), "updatelog: warning[old=%s]: %s\n", old, msg); + } +} + +change(action: int, e: ref Entry, seq: big, d: Sys->Dir, path: string, serverpath: string, digests: list of string) +{ + log := ref Entry; + log.seq = seq; + log.action = action; + log.d = d; + log.path = path; + log.serverpath = serverpath; + log.contents = digests; + e.update(log); + bout.puts(log.logtext()+"\n"); +} + +samestat(a: Sys->Dir, b: Sys->Dir): int +{ + # doesn't check permission/ownership, does check QTDIR/QTFILE + if(a.mode & Sys->DMDIR) + return (b.mode & Sys->DMDIR) != 0; + return a.length == b.length && a.mtime == b.mtime && a.qid.qtype == b.qid.qtype; # TO DO: a.name==b.name? +} + +samemeta(a: Sys->Dir, b: Sys->Dir): int +{ + return a.mode == b.mode && (uid == nil || a.uid == b.uid) && (gid == nil || a.gid == b.gid) && samestat(a, b); +} + +nextseq(): big +{ + return (big now << 32) | big gen++; +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "updatelog: %s\n", s); + raise "fail:error"; +} + +note(s: string) +{ + sys->fprint(sys->fildes(2), "updatelog: note: %s\n", s); +} + +contains(s: string, sub: string): int +{ + return str->splitstrl(s, sub).t1 != nil; +} + +isprefix(a, b: string): int +{ + la := len a; + lb := len b; + if(la > lb) + return 0; + if(la == lb) + return a == b; + return a == b[0:la] && b[la] == '/'; +} + +trimpath(s: string): string +{ + while(len s > 1 && s[len s-1] == '/') + s = s[0:len s-1]; + while(s != nil && s[0] == '/') + s = s[1:]; + return s; +} + +relative(name: string, root: string): string +{ + if(root == nil || name == nil) + return name; + if(isprefix(root, name)){ + name = name[len root:]; + while(name != nil && name[0] == '/') + name = name[1:]; + } + return name; +} + +islisted(s: string, l: list of string): int +{ + for(; l != nil; l = tl l) + if(isprefix(hd l, s)) + return 1; + return 0; +} + +md5sum(file: string): string +{ + fd := sys->open(file, Sys->OREAD); + if(fd == nil) + error(sys->sprint("can't open %s: %r", file)); + ds: ref Keyring->DigestState; + buf := array[Sys->ATOMICIO] of byte; + while((n := sys->read(fd, buf, len buf)) > 0) + ds = kr->md5(buf, n, nil, ds); + if(n < 0) + error(sys->sprint("error reading %s: %r", file)); + digest := array[Keyring->MD5dlen] of byte; + kr->md5(nil, 0, digest, ds); + s: string; + for(i := 0; i < len digest; i++) + s += sys->sprint("%.2ux", int digest[i]); + return s; +} diff --git a/appl/cmd/install/wdiff.b b/appl/cmd/install/wdiff.b new file mode 100644 index 00000000..47088417 --- /dev/null +++ b/appl/cmd/install/wdiff.b @@ -0,0 +1,148 @@ +implement Wdiff; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "arg.m"; + arg: Arg; +include "wrap.m"; + wrap : Wrap; +include "sh.m"; +include "keyring.m"; + keyring : Keyring; + + +Wdiff: module{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +root := "/"; +bflag : int; +listing : int; +package: int; + +diff(w : ref Wrap->Wrapped, name : string, c : chan of int) +{ + sys->pctl(Sys->FORKFD, nil); + wrapped := w.root+"/"+name; + local := root+"/"+name; + (ok, dir) := sys->stat(local); + if (ok < 0) { + sys->print("cannot stat %s\n", local); + c <-= -1; + return; + } + (ok, dir) = sys->stat(wrapped); + if (ok < 0) { + sys->print("cannot stat %s\n", wrapped); + c <-= -1; + return; + } + cmd := "/dis/diff.dis"; + m := load Command cmd; + if(m == nil) { + c <-= -1; + return; + } + if (bflag) + m->init(nil, cmd :: "-b" :: wrapped :: local :: nil); + else + m->init(nil, cmd :: wrapped :: local :: nil); + c <-= 0; +} + +fatal(err : string) +{ + sys->fprint(sys->fildes(2), "%s\n", err); + exit; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + arg = load Arg Arg->PATH; + keyring = load Keyring Keyring->PATH; + wrap = load Wrap Wrap->PATH; + wrap->init(bufio); + + arg->init(args); + while ((c := arg->opt()) != 0) { + case c { + 'b' => + bflag = 1; + 'l' => + listing = 1; + 'p' => + package = 1; + 'r' => + root = arg->arg(); + if (root == nil) + fatal("missing root name"); + * => + fatal(sys->sprint("bad argument -%c", c)); + } + } + args = arg->argv(); + if (args == nil || tl args != nil) + fatal("usage: install/wdiff [-blp] [-r root] package"); + (ok, dir) := sys->stat(hd args); + if (ok < 0) + fatal(sys->sprint("no such file %s", hd args)); + w := wrap->openwraphdr(hd args, root, nil, !listing); + if (w == nil) + fatal("no such package found"); + + if(package){ + while(w.nu > 0 && w.u[w.nu-1].typ == wrap->UPD) + w.nu--; + } + + digest := array[keyring->MD5dlen] of { * => byte 0 }; + digest0 := array[keyring->MD5dlen] of { * => byte 0 }; + + # loop through each md5sum file of each package in increasing time order + for(i := 0; i < w.nu; i++){ + b := bufio->open(w.u[i].dir+"/md5sum", Sys->OREAD); + if (b == nil) + fatal("md5sum file not found"); + while ((p := b.gets('\n')) != nil) { + (n, lst) := sys->tokenize(p, " \t\n"); + if (n != 2) + fatal("error in md5sum file"); + p = hd lst; + q := root+"/"+p; + (ok, dir) = sys->stat(q); + if (ok >= 0 && (dir.mode & Sys->DMDIR)) + continue; + t: int; + (ok, t) = wrap->getfileinfo(w, p, nil, digest0, nil); + if(ok < 0){ + sys->print("cannot happen\n"); + continue; + } + if(t != w.u[i].time) # covered by later update + continue; + if (wrap->md5file(q, digest) < 0) { + sys->print("%s removed\n", p); + continue; + } + str := wrap->md5conv(digest); + str0 := wrap->md5conv(digest0); + # if (str == hd tl lst) + if(str == str0) + continue; + if (listing) + sys->print("%s modified\n", p); + else { + endc := chan of int; + spawn diff(w, p, endc); + <- endc; + } + } + } + wrap->end(); +} diff --git a/appl/cmd/install/wfind.b b/appl/cmd/install/wfind.b new file mode 100644 index 00000000..579fd946 --- /dev/null +++ b/appl/cmd/install/wfind.b @@ -0,0 +1,204 @@ +implement Wfind; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "arg.m"; + arg: Arg; +include "wrap.m"; + wrap : Wrap; +include "sh.m"; +include "keyring.m"; + keyring : Keyring; +include "readdir.m"; + readdir : Readdir; + +Wfind: module{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +fatal(err : string) +{ + sys->fprint(sys->fildes(2), "%s\n", err); + exit; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + arg = load Arg Arg->PATH; + keyring = load Keyring Keyring->PATH; + readdir = load Readdir Readdir->PATH; + wrap = load Wrap Wrap->PATH; + wrap->init(bufio); + + pkgs: list of string; + indir := "/install"; + arg->init(args); + while ((c := arg->opt()) != 0) { + case c { + 'p' => + pkg := arg->arg(); + if (pkg == nil) + fatal("missing package name"); + pkgs = pkg :: pkgs; + * => + fatal(sys->sprint("bad argument -%c", c)); + } + } + args = arg->argv(); + if (args == nil) + fatal("usage: install/wfind [-p package ... ] file ..."); + # (ok, dir) := sys->stat(indir); + # if (ok < 0) + # fatal(sys->sprint("cannot open install directory %s", indir)); + if(pkgs != nil){ + npkgs: list of string; + for(pkg := pkgs; pkg != nil; pkg = tl pkg) + npkgs = hd pkg :: npkgs; + pkgs = npkgs; + for(pkg = pkgs; pkg != nil; pkg = tl pkg) + scanpkg(hd pkg, indir+"/"+hd pkg, args); + } + else + scanpkgs(indir, args); + prfiles(); +} + +scanpkgs(d : string, files: list of string) +{ + (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT); + for (i := 0; i < n; i++) { + if (dir[i].mode & Sys->DMDIR) + scanpkg(dir[i].name, d + "/" + dir[i].name, files); + } +} + +scanpkg(pkg : string, d : string, files: list of string) +{ + # base package, updates and update packages have the name + # <timestamp> or <timestamp.gz> + (dir, n) := readdir->init(d, Readdir->NAME|Readdir->COMPACT); + for (i := 0; i < n; i++) { + f := dir[i].name; + l := len f; + if (l >= 4 && f[l-3:l] == ".gz") + f = f[0:l-3]; + scanfile(f, pkg, d+"/"+dir[i].name, files); + } + w := wrap->openwrap(pkg, "/", 0); + if(w == nil) + return; + for(i = 0; i < w.nu; i++) + scanw(w, i, files, WRAP, pkg); +} + +scanfile(f: string, pkg: string, d: string, files: list of string) +{ + f = nil; + # sys->print("%s %s %s\n", f, pkg, d); + w := wrap->openwraphdr(d, "/", nil, 0); + if(w == nil) + return; + if(w.nu != 1) + fatal("strange package: more than one piece"); + # sys->print(" %s %d %s %d %d %d\n", w.name, w.tfull, w.u[0].desc, w.u[0].time, w.u[0].utime, w.u[0].typ); + scanw(w, 0, files, INSTALL, pkg); +} + +scanw(w: ref Wrap->Wrapped, i: int, files: list of string, where: int, pkg: string) +{ + w.u[i].bmd5.seek(big 0, Bufio->SEEKSTART); + while ((p := w.u[i].bmd5.gets('\n')) != nil){ + # sys->print("%s", p); + (n, l) := sys->tokenize(p, " \n"); + if(n != 2) + fatal(sys->sprint("bad md5 file in %s\n", wtype(where)+"/"+w.name+"/"+wrap->now2string(w.u[i].time, 0))); + file := hd l; + md5 := hd tl l; + for(fs := files; fs != nil; fs = tl fs){ + if(strsuffix(file, hd fs)){ + # sys->print("%s %s %s %d\n", pkg, file, md5, where); + addfile(file, w, i, md5, where, pkg); + } + } + } +} + +Stat: adt{ + name: string; + occs: list of (ref Wrap->Wrapped, int, string, int, string); + md5: string; +}; + +stats: list of ref Stat; + +addfile(file: string, w: ref Wrap->Wrapped, i: int, md5: string, where: int, pkg: string) +{ + for(sts := stats; sts != nil; sts = tl sts){ + st := hd sts; + if(st.name == file){ + st.occs = (w, i, md5, where, pkg) :: st.occs; + return; + } + } + digest := array[keyring->MD5dlen] of { * => byte 0 }; + if (wrap->md5file(file, digest) < 0) + str := "non-existent"+blanks(32-12); + else + str = wrap->md5conv(digest); + st := ref Stat; + st.name = file; + st.occs = (w, i, md5, where, pkg) :: nil; + st.md5 = str; + stats = st :: stats; +} + +prfiles() +{ + for(sts := stats; sts != nil; sts = tl sts){ + st := hd sts; + sys->print("%s\n", st.name); + proccs(st.occs); + sys->print("\t%s %s\n", st.md5, st.name); + } +} + +proccs(ocs: list of (ref Wrap->Wrapped, int, string, int, string)) +{ + if(ocs != nil){ + proccs(tl ocs); + (w, i, md5, where, pkg) := hd ocs; + sys->print("\t%s %s/%s(%s)\t%s\n", md5, w.name, wrap->now2string(w.u[i].time, 0), ptype(w.u[i].typ), wtype(where)+"/"+pkg); + } +} + +ptype(p: int): string +{ + return (array[] of { "???", "package ", "update ", "full upd" })[p]; +} + +INSTALL: con 0; +WRAP: con 1; + +wtype(w: int): string +{ + return (array[] of { "/install", "/wrap" })[w]; +} + +strsuffix(s: string, suf: string): int +{ + return (l1 := len s) >= (l2 := len suf) && s[l1-l2: l1] == suf; +} + +blanks(n: int): string +{ + s := ""; + for(i := 0; i < n; i++) + s += " "; + return s; +} diff --git a/appl/cmd/install/wrap.b b/appl/cmd/install/wrap.b new file mode 100644 index 00000000..1b90c765 --- /dev/null +++ b/appl/cmd/install/wrap.b @@ -0,0 +1,684 @@ +implement Wrap; + +include "sys.m"; + sys : Sys; +include "draw.m"; +include "bufio.m"; + bufio : Bufio; + Iobuf : import bufio; +include "keyring.m"; + keyring : Keyring; +include "sh.m"; +include "arch.m"; + arch : Arch; +include "wrap.m"; +include "archfs.m"; + +archpid := -1; +gzfd: ref Sys->FD; +gzfile: string; + +init(bio: Bufio) +{ + sys = load Sys Sys->PATH; + if(bio == nil) + bufio = load Bufio Bufio->PATH; + else + bufio = bio; + keyring = load Keyring Keyring->PATH; + arch = load Arch Arch->PATH; + arch->init(bufio); +} + +end() +{ + if(gzfile != nil) + sys->remove(gzfile); + if (archpid > 0){ + fd := sys->open("#p/" + string archpid + "/ctl", sys->OWRITE); + if (fd != nil) + sys->fprint(fd, "killgrp"); + } +} + +archfs(f : string, mtpt : string, all : int, c : chan of int) +{ + sys->pctl(Sys->NEWPGRP, nil); + cmd := "/dis/install/archfs.dis"; + m := load Archfs Archfs->PATH; + if(m == nil) { + c <-= -1; + return; + } + ch := chan of int; + if (all) + spawn m->initc(cmd :: "-m" :: mtpt :: f :: nil, ch); + else + spawn m->initc(cmd :: "-s" :: "-m" :: mtpt :: f :: "/wrap" :: nil, ch); + pid := <- ch; + c <-= pid; +} + +mountarch(f : string, mtpt : string, all : int) : int +{ + c := chan of int; + spawn archfs(f, mtpt, all, c); + pid := <- c; + if (pid < 0) { + if(pid == -1) + sys->fprint(sys->fildes(2), "fatal: cannot run archfs\n"); + # else probably not an archive file + return -1; + } + archpid = pid; + return 0; +} + +openmount(f : string, d : string) : ref Wrapped +{ + if (f == nil) { + p := d+"/wrap"; + f = getfirstdir(p); + if (f == nil) + return nil; + } + w := ref Wrapped; + w.name = f; + w.root = d; + # p := d + "/wrap/" + f; + p := pathcat(d, pathcat("wrap", f)); + (w.u, w.nu, w.tfull) = openupdate(p); + if (w.nu < 0) { + closewrap(w); + return nil; + } + return w; +} + +closewrap(w : ref Wrapped) +{ + w = nil; +} + +openwraphdr(f : string, d : string, argl : list of string, all : int) : ref Wrapped +{ + argl = nil; + (ok, dir) := sys->stat(f); + if (ok < 0 || dir.mode & Sys->DMDIR) + return openwrap(f, d, all); + (nf, fd) := arch->openarchgz(f); + if (nf != nil) { + gzfile = nf; + f = nf; + gzfd = fd; + } + return openwrap(f, "/mnt/wrap", all); +} + +openwrap(f : string, d : string, all : int) : ref Wrapped +{ + if (d == nil) + d = "/"; + if((w := openmount(f, d)) != nil) + return w; # don't mess about if /wrap/ structure exists + (ok, dir) := sys->stat(f); + if (ok < 0) + return nil; + # accept root/ or root/wrap/pkgname + if (dir.mode & Sys->DMDIR) { + d = f; + if ((i := strstr(f, "/wrap/")) >= 0) { + f = f[i+6:]; + d = d[0:i+6]; + } + else + f = nil; + return openmount(f, d); + } + (ok, dir) = sys->stat(f); + if (ok < 0 || dir.mode & Sys->DMDIR) + return openmount(f, d); # ? + if (mountarch(f, d, all) < 0) + return nil; + return openmount(nil, d); +} + +getfirstdir(d : string) : string +{ + if ((fd := sys->open(d, Sys->OREAD)) == nil) + return nil; + for(;;){ + (n, dir) := sys->dirread(fd); + if(n <= 0) + break; + for(i:=0; i<n; i++) + if(dir[i].mode & Sys->DMDIR) + return dir[i].name; + } + return nil; +} + +NONE : con 0; + +sniffdir(base : string, elem : string) : (int, int) +{ + # t := int elem; + t := string2now(elem, 0); + if (t == 0) + return (NONE, 0); + # buf := sys->sprint("%ud", t); + # if (buf != elem) + # return (NONE, 0); + rv := NONE; + p := base + "/" + elem + "/package"; + (ok, nil) := sys->stat(p); + if (ok >= 0) + rv |= FULL; + p = base + "/" + elem + "/update"; + (ok, nil) = sys->stat(p); + if (ok >= 0) + rv |= UPD; + return (rv, t); +} + +openupdate(d : string) : (array of Update, int, int) +{ + u : array of Update; + + if ((fd := sys->open(d, Sys->OREAD)) == nil) + return (nil, -1, 0); + # + # We are looking to find the most recent full + # package; anything before that is irrelevant. + # Also figure out the most recent package update. + # Non-package updates before that are irrelevant. + # If there are no packages installed, + # grab all the updates we can find. + # + tbase := -1; + tfull := -1; + nu := 0; + for(;;){ + (n, dir) := sys->dirread(fd); + if(n <= 0) + break; + for(i := 0; i < n; i++){ + (k, t) := sniffdir(d, dir[i].name); + case (k) { + FULL => + nu++; + if (t > tfull) + tfull = t; + if (t > tbase) + tbase = t; + FULL|UPD => + nu++; + if (t > tfull) + tfull = t; + UPD => + nu++; + } + } + } + if (nu == 0) + return (nil, -1, 0); + u = nil; + nu = 0; + if ((fd = sys->open(d, Sys->OREAD)) == nil) + return (nil, -1, 0); + for(;;){ + (n, dir) := sys->dirread(fd); + if(n <= 0) + break; + for(i := 0; i < n; i++){ + (k, t) := sniffdir(d, dir[i].name); + if (k == 0) + continue; + if (t < tbase) + continue; + if (t < tfull && k == UPD) + continue; + if (nu%8 == 0) { + newu := array[nu+8] of Update; + newu[0:] = u[0:nu]; + u = newu; + } + u[nu].typ = k; + if (readupdate(u, nu, d, dir[i].name) != nil) + nu++; + } + } + if (nu == 0) + return (nil, -1, 0); + qsort(u, nu); + return (u, nu, tfull); +} + +readupdate(u : array of Update, ui : int, base : string, elem : string) : array of Update +{ + # u[ui].dir = base + "/" + elem; + u[ui].dir = pathcat(base, elem); + p := u[ui].dir + "/desc"; + u[ui].desc = readfile(p); + # u[ui].time = int elem; + u[ui].time = string2now(elem, 0); + p = u[ui].dir + "/md5sum"; + u[ui].bmd5 = bufio->open(p, Bufio->OREAD); + p = u[ui].dir + "/update"; + q := readfile(p); + if (q != nil) + u[ui].utime = int q; + else + u[ui].utime = 0; + if (u[ui].bmd5 == nil) + return nil; + return u; +} + +readfile(s : string) : string +{ + (ok, d) := sys->stat(s); + if (ok < 0) + return nil; + buf := array[int d.length] of byte; + if ((fd := sys->open(s, Sys->OREAD)) == nil || sys->read(fd, buf, int d.length) != int d.length) + return nil; + s = string buf; + ls := len s; + if (s[ls-1] == '\n') + s = s[0:ls-1]; + return s; +} + +hex(c : int) : int +{ + if (c >= '0' && c <= '9') + return c-'0'; + if (c >= 'a' && c <= 'f') + return c-'a'+10; + if (c >= 'A' && c <= 'F') + return c-'A'+10; + return -1; +} + +getfileinfo(w : ref Wrapped, f : string, rdigest : array of byte, wdigest : array of byte, ardigest: array of byte) : (int, int) +{ + p : string; + + if (w == nil) + return (-1, 0); + digest := array[keyring->MD5dlen] of { * => byte 0 }; + for (i := w.nu-1; i >= 0; i--){ + if ((p = bsearch(w.u[i].bmd5, f)) == nil) + continue; + if (p == nil) + continue; + k := 0; + while (k < len p && p[k] != ' ') + k++; + if (k == len p) + continue; + q := p[k+1:]; + if (q == nil) + continue; + if (len q != 2*Keyring->MD5dlen+1) + continue; + for (j := 0; j < Keyring->MD5dlen; j++) { + a := hex(q[2*j]); + b := hex(q[2*j+1]); + if (a < 0 || b < 0) + break; + digest[j] = byte ((a<<4)|b); + } + if(j != Keyring->MD5dlen) + continue; + if(rdigest == nil || memcmp(rdigest, digest, keyring->MD5dlen) == 0 || (ardigest != nil && memcmp(ardigest, digest, keyring->MD5dlen) == 0)) + break; + else + return (-1, 0); # NEW + } + if(i < 0) + return (-1, 0); + if(wdigest != nil) + wdigest[0:] = rdigest; + return (0, w.u[i].time); + + +} + +bsearch(b : ref Bufio->Iobuf, p : string) : string +{ + if (b == nil) + return nil; + lo := 0; + b.seek(big 0, Bufio->SEEKEND); + hi := int b.offset(); + l := len p; + while (lo < hi) { + m := (lo+hi)/2; + b.seek(big m, Bufio->SEEKSTART); + b.gets('\n'); + if (int b.offset() == hi) { + bgetbackc(b); + m = int b.offset(); + while (m-- > lo) { + if (bgetbackc(b) == '\n') { + b.getc(); + break; + } + } + } + s := b.gets('\n'); + if (len s >= l+1 && s[0:l] == p && (s[l] == ' ' || s[l] == '\n')) + return s; + if (s < p) + lo = int b.offset(); + else + hi = int b.offset()-len s; + } + return nil; +} + +bgetbackc(b : ref Bufio->Iobuf) : int +{ + m := int b.offset(); + b.seek(big (m-1), Bufio->SEEKSTART); + c := b.getc(); + b.ungetc(); + return c; +} + +strstr(s : string, p : string) : int +{ + lp := len p; + ls := len s; + for (i := 0; i < ls-lp; i++) + if (s[i:i+lp] == p) + return i; + return -1; +} + +qsort(a : array of Update, n : int) +{ + i, j : int; + t : Update; + + while(n > 1) { + i = n>>1; + t = a[0]; a[0] = a[i]; a[i] = t; + i = 0; + j = n; + for(;;) { + do + i++; + while(i < n && a[i].time < a[0].time); + do + j--; + while(j > 0 && a[j].time > a[0].time); + if(j < i) + break; + t = a[i]; a[i] = a[j]; a[j] = t; + } + t = a[0]; a[0] = a[j]; a[j] = t; + n = n-j-1; + if(j >= n) { + qsort(a, j); + a = a[j+1:]; + } else { + qsort(a[j+1:], n); + n = j; + } + } +} + +md5file(file : string, digest : array of byte) : int +{ + (ok, d) := sys->stat(file); + if (ok < 0) + return -1; + if (d.mode & Sys->DMDIR) + return 0; + bio := bufio->open(file, Bufio->OREAD); + if (bio == nil) + return -1; + # return md5sum(bio, digest, d.length); + buff := array[Sys->ATOMICIO] of byte; + ds := keyring->md5(nil, 0, nil, nil); + while ((n := bio.read(buff, len buff)) > 0) + keyring->md5(buff, n, nil, ds); + keyring->md5(nil, 0, digest, ds); + bio = nil; + return 0; +} + +md5sum(b : ref Iobuf, digest : array of byte, leng : int) : int +{ + ds := keyring->md5(nil, 0, nil, nil); + buff := array[Sys->ATOMICIO] of byte; + while (leng > 0) { + if (leng > len buff) + n := len buff; + else + n = leng; + if ((n = b.read(buff, n)) <= 0) + return -1; + keyring->md5(buff, n, nil, ds); + leng -= n; + } + keyring->md5(nil, 0, digest, ds); + return 0; +} + +md5conv(d : array of byte) : string +{ + s : string = nil; + + for (i := 0; i < keyring->MD5dlen; i++) + s += sys->sprint("%.2ux", int d[i]); + return s; +} + +zd : Sys->Dir; + +newd(time : int, uid : string, gid : string) : ref Sys->Dir +{ + d := ref Sys->Dir; + *d = zd; + d.uid = uid; + d.gid = gid; + d.mtime = time; + return d; +} + +putwrapfile(b : ref Iobuf, name : string, time : int, elem : string, file : string, uid : string, gid : string) +{ + d := newd(time, uid, gid); + d.mode = 8r444; + (ok, dir) := sys->stat(file); + if (ok < 0) + sys->fprint(sys->fildes(2), "cannot stat %s: %r", file); + d.length = dir.length; + # s := "/wrap/"+name+"/"+sys->sprint("%ud", time)+"/"+elem; + s := "/wrap/"+name+"/"+now2string(time, 0)+"/"+elem; + arch->puthdr(b, s, d); + arch->putfile(b, file, int d.length); +} + +putwrap(b : ref Iobuf, name : string, time : int, desc : string, utime : int, pkg : int, uid : string, gid : string) +{ + if (!(utime || pkg)) + sys->fprint(sys->fildes(2), "bad precondition in putwrap()"); + d := newd(time, uid, gid); + d.mode = Sys->DMDIR|8r775; + s := "/wrap"; + arch->puthdr(b, s, d); + s += "/"+name; + arch->puthdr(b, s, d); + # s += "/"+sys->sprint("%ud", time); + s += "/"+now2string(time, 0); + arch->puthdr(b, s, d); + d.mode = 8r444; + s += "/"; + dir := s; + if (utime) { + s = dir+"update"; + d.length = big 23; + arch->puthdr(b, s, d); + arch->putstring(b, sys->sprint("%22ud\n", utime)); + } + if (pkg) { + s = dir+"package"; + d.length = big 0; + arch->puthdr(b, s, d); + } + if (desc != nil) { + s = dir+"desc"; + d.length = big (len desc+1); + d.mode = 8r444; + arch->puthdr(b, s, d); + arch->putstring(b, desc+"\n"); + } +} + +memcmp(b1, b2 : array of byte, n : int) : int +{ + for (i := 0; i < n; i++) + if (b1[i] < b2[i]) + return -1; + else if (b1[i] > b2[i]) + return 1; + return 0; +} + +strprefix(s: string, pre: string): int +{ + return len s >= (l := len pre) && s[0:l] == pre; +} + +match(s: string, pre: list of string): int +{ + if(pre == nil || s == "/wrap" || strprefix(s, "/wrap/")) + return 1; + for( ; pre != nil; pre = tl pre) + if(strprefix(s, hd pre)) + return 1; + return 0; +} + +notmatch(s: string, pre: list of string): int +{ + if(pre == nil || s == "/wrap" || strprefix(s, "/wrap/")) + return 1; + for( ; pre != nil; pre = tl pre) + if(strprefix(s, hd pre)) + return 0; + return 1; +} + +pathcat(s : string, t : string) : string +{ + if (s == nil) return t; + if (t == nil) return s; + slashs := s[len s - 1] == '/'; + slasht := t[0] == '/'; + if (slashs && slasht) + return s + t[1:]; + if (!slashs && !slasht) + return s + "/" + t; + return s + t; +} + +md5filea(file : string, digest : array of byte) : int +{ + n, n0: int; + + (ok, d) := sys->stat(file); + if (ok < 0) + return -1; + if (d.mode & Sys->DMDIR) + return 0; + bio := bufio->open(file, Bufio->OREAD); + if (bio == nil) + return -1; + buff := array[Sys->ATOMICIO] of byte; + m := len buff; + ds := keyring->md5(nil, 0, nil, nil); + r := 0; + while(1){ + if(r){ + if((n = bio.read(buff[1:], m-1)) <= 0) + break; + n++; + } + else{ + if ((n = bio.read(buff, m)) <= 0) + break; + } + (n0, r) = remcr(buff, n); + if(r){ + keyring->md5(buff, n0-1, nil, ds); + buff[0] = byte '\r'; + } + else + keyring->md5(buff, n0, nil, ds); + } + if(r) + keyring->md5(buff, 1, nil, ds); + keyring->md5(nil, 0, digest, ds); + bio = nil; + return 0; +} + +remcr(b: array of byte, n: int): (int, int) +{ + if(n == 0) + return (0, 0); + for(i := 0; i < n; ){ + if(b[i] == byte '\r' && i+1 < n && b[i+1] == byte '\n') + b[i:] = b[i+1:n--]; + else + i++; + } + return (n, b[n-1] == byte '\r'); +} + +TEN2EIGHT: con 100000000; + +now2string(n: int, flag: int): string +{ + if(flag == 0) + return sys->sprint("%ud", n); + if(n < 0) + return nil; + q := n/TEN2EIGHT; + s := "0" + string (n-TEN2EIGHT*q); + while(len s < 9) + s = "0" + s; + if(q <= 9) + s[0] = '0' + q - 0; + else if(q <= 21) + s[0] = 'A' + q - 10; + else + return nil; + return s; +} + +string2now(s: string, flag: int): int +{ + if(flag == 0 && s[0] != 'A') + return int s; + if(len s != 9) + return 0; + r := int s[1: ]; + c := s[0]; + if(c >= '0' && c <= '9') + q := c - '0' + 0; + else if(c >= 'A' && c <= 'L') + q = c - 'A' + 10; + else + return 0; + n := TEN2EIGHT*q + r; + if(n < 0) + return 0; + return n; +} diff --git a/appl/cmd/install/wrap.m b/appl/cmd/install/wrap.m new file mode 100644 index 00000000..c15624ca --- /dev/null +++ b/appl/cmd/install/wrap.m @@ -0,0 +1,41 @@ +Wrap : module +{ + PATH : con "/dis/install/wrap.dis"; + + FULL, UPD : con iota+1; + + Update : adt { + desc : string; + dir : string; + time : int; + utime : int; + bmd5 : ref Bufio->Iobuf; + typ : int; + }; + + Wrapped : adt { + name : string; + root : string; + tfull : int; + u : array of Update; + nu : int; + }; + + init: fn(bio: Bufio); + openwrap: fn(f : string, d : string, all : int) : ref Wrapped; + openwraphdr: fn(f : string, d : string, argl : list of string, all : int) : ref Wrapped; + getfileinfo: fn(w : ref Wrapped, f : string, rdigest : array of byte, wdigest: array of byte, ardigest: array of byte) : (int, int); + putwrapfile: fn(b : ref Bufio->Iobuf, name : string, time : int, elem : string, file : string, uid : string, gid : string); + putwrap: fn(b : ref Bufio->Iobuf, name : string, time : int, desc : string, utime : int, pkg : int, uid : string, gid : string); + md5file: fn(file : string, digest : array of byte) : int; + md5filea: fn(file : string, digest : array of byte) : int; + md5sum: fn(b : ref Bufio->Iobuf, digest : array of byte, leng : int) : int; + md5conv: fn(d : array of byte) : string; + # utilities + match: fn(s: string, pre: list of string): int; + notmatch: fn(s: string, pre: list of string): int; + memcmp: fn(b1, b2: array of byte, n: int): int; + end: fn(); + now2string: fn(n: int, flag: int): string; + string2now: fn(s: string, flag: int): int; +}; diff --git a/appl/cmd/install/wrap2list.b b/appl/cmd/install/wrap2list.b new file mode 100644 index 00000000..d2656cf5 --- /dev/null +++ b/appl/cmd/install/wrap2list.b @@ -0,0 +1,305 @@ +# +# Copyright © 2001 Vita Nuova (Holdings) Limited. All rights reserved. +# + +implement Wrap2list; + +# make a version list suitable for SDS from /wrap + +include "sys.m"; + sys : Sys; +include "draw.m"; +include "bufio.m"; + bufio : Bufio; + Iobuf : import bufio; +include "crc.m"; + crcm : Crc; +include "wrap.m"; + wrap: Wrap; + +Wrap2list: module +{ + init : fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +HASHSZ: con 64; + +Element: type string; + +Hash: adt{ + elems: array of Element; + nelems: int; +}; + +List: adt{ + tabs: array of ref Hash; + init: fn(l: self ref List); + add: fn(l: self ref List, e: Element); + subtract: fn(l: self ref List, e: Element); + end: fn(l: self ref List): array of Element; +}; + +flist: ref List; + +hash(s: string): int +{ + h := 0; + n := len s; + for(i := 0; i < n; i++) + h += s[i]; + if(h < 0) + h = -h; + return h%HASHSZ; +} + +List.init(l: self ref List) +{ + ts := l.tabs = array[HASHSZ] of ref Hash; + for(i := 0; i < HASHSZ; i++){ + t := ts[i] = ref Hash; + t.elems = array[HASHSZ] of Element; + t.nelems = 0; + } +} + +List.add(l: self ref List, e: Element) +{ + h := hash(e); + t := l.tabs[h]; + n := t.nelems; + es := t.elems; + for(i := 0; i < n; i++){ + if(e == es[i]) + return; + } + if(n == len es) + es = t.elems = (array[2*n] of Element)[0:] = es; + es[t.nelems++] = e; +# sys->print("+ %s\n", e); +} + +List.subtract(l: self ref List, e: Element) +{ + h := hash(e); + t := l.tabs[h]; + n := t.nelems; + es := t.elems; + for(i := 0; i < n; i++){ + if(e == es[i]){ + es[i] = nil; + break; + } + } +# sys->print("- %s\n", e); +} + +List.end(l: self ref List): array of Element +{ + tot := 0; + ts := l.tabs; + for(i := 0; i < HASHSZ; i++) + tot += ts[i].nelems; + a := array[tot] of Element; + m := 0; + for(i = 0; i < HASHSZ; i++){ + t := ts[i]; + n := t.nelems; + es := t.elems; + a[m:] = es[0: n]; + m += n; + } + return a; +} + +usage() +{ + sys->fprint(stderr, "Usage: wrap2list [ file ... ]\n"); + exit; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + crcm = load Crc Crc->PATH; + wrap = load Wrap Wrap->PATH; + wrap->init(bufio); + if(argv != nil) + argv = tl argv; + init := 0; + if(argv != nil && hd argv == "-i"){ + init = 1; + argv = tl argv; + } + stderr = sys->fildes(2); + # root := "/"; + flist = ref List; + flist.init(); + fd := sys->open("/wrap", Sys->OREAD); + for(;;){ + (nd, d) := sys->dirread(fd); + if(nd <= 0) + break; + for(i:=0; i<nd; i++){ + if((d[0].mode & Sys->DMDIR) && (w := wrap->openwrap(d[i].name, "/", 1)) != nil){ + # sys->fprint(stderr, "%s %s %d %d\n", w.name, w.root, w.tfull, w.nu); + for(j := 0; j < w.nu; j++){ + addfiles(w.u[j].bmd5); + if((b := bufio->open(w.u[j].dir+"/remove", Bufio->OREAD)) != nil) + subtractfiles(b); + # sys->fprint(stderr, "%d: %s %s %d %d %d\n", i, w.u[j].desc, w.u[j].dir, w.u[j].time, w.u[j].utime, w.u[j].typ); + } + } + } + } + for( ; argv != nil; argv = tl argv){ + if((b := bufio->open(hd argv, Bufio->OREAD)) != nil) + addfiles(b); + } + out(uniq(rmnil(sort(flist.end()))), init); +} + +addfiles(b: ref Bufio->Iobuf) +{ + b.seek(big 0, Bufio->SEEKSTART); + while((s := b.gets('\n')) != nil){ + (n, l) := sys->tokenize(s, " \n"); + if(n > 0) + flist.add(hd l); + } +} + +subtractfiles(b: ref Bufio->Iobuf) +{ + b.seek(big 0, Bufio->SEEKSTART); + while((s := b.gets('\n')) != nil){ + (n, l) := sys->tokenize(s, " \n"); + if(n > 0) + flist.subtract(hd l); + } +} + +out(fs: array of Element, init: int) +{ + nf := len fs; + for(i := 0; i < nf; i++){ + f := fs[i]; + outl(f, nil, init); + l := len f; + if(l >= 7 && f[l-7:] == "emu.new"){ + g := f; + f[l-3] = 'e'; + f[l-2] = 'x'; + f[l-1] = 'e'; + outl(f, g, init); # try emu.exe + outl(f[0: l-4], g, init); # try emu +# sys->fprint(sys->fildes(2), "%s %s\n", f, g); + } + } +} + +outl(f: string, g: string, init: int) +{ + (ok, d) := sys->stat(f); + if(ok < 0){ + # sys->fprint(stderr, "cannot open %s\n", f); + return; + } + if(g == nil) + g = "-"; + if(d.mode & Sys->DMDIR) + d.length = big 0; + if(init) + mtime := 0; + else + mtime = d.mtime; + sys->print("%s %s %d %d %d %d %d\n", f, g, int d.length, d.mode, mtime, crc(f, d), 0); +} + +crc(f: string, d: Sys->Dir): int +{ + crcs := crcm->init(0, int 16rffffffff); + if(d.mode & Sys->DMDIR) + return 0; + fd := sys->open(f, Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "cannot open %s\n", f); + return 0; + } + crc := 0; + buf := array[Sys->ATOMICIO] of byte; + for(;;){ + nr := sys->read(fd, buf, len buf); + if(nr < 0){ + sys->fprint(stderr, "bad read on %s : %r\n", f); + return 0; + } + if(nr <= 0) + break; + crc = crcm->crc(crcs, buf, nr); + } + crcm->reset(crcs); + return crc; +} + +sort(a: array of Element): array of Element +{ + qsort(a, len a); + return a; +} + +rmnil(a: array of Element): array of Element +{ + n := len a; + for(i := 0; i < n; i++) + if(a[i] != nil) + break; + return a[i: n]; +} + +uniq(a: array of Element): array of Element +{ + n := len a; + for(i := 0; i < n-1; ){ + if(a[i] == a[i+1]) + a[i+1:] = a[i+2: n--]; + else + i++; + } + return a[0: n]; +} + +qsort(a: array of Element, n: int) +{ + i, j: int; + t: Element; + + while(n > 1){ + i = n>>1; + t = a[0]; a[0] = a[i]; a[i] = t; + i = 0; + j = n; + for(;;){ + do + i++; + while(i < n && a[i] < a[0]); + do + j--; + while(j > 0 && a[j] > a[0]); + if(j < i) + break; + t = a[i]; a[i] = a[j]; a[j] = t; + } + t = a[0]; a[0] = a[j]; a[j] = t; + n = n-j-1; + if(j >= n){ + qsort(a, j); + a = a[j+1:]; + }else{ + qsort(a[j+1:], n); + n = j; + } + } +} diff --git a/appl/cmd/iostats.b b/appl/cmd/iostats.b new file mode 100644 index 00000000..c70aadc4 --- /dev/null +++ b/appl/cmd/iostats.b @@ -0,0 +1,635 @@ +implement Iostats; + +# +# iostats - gather file system access statistics +# + +include "sys.m"; + sys: Sys; + Qid: import sys; + +include "draw.m"; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg, NOFID, NOTAG: import styx; + +include "workdir.m"; + workdir: Workdir; + +include "sh.m"; + +include "arg.m"; + +Iostats: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Maxmsg: con 128*1024+Styx->IOHDRSZ; +Ns2ms: con big 1000000; + +Rpc: adt +{ + name: string; + count: big; + time: big; + lo: big; + hi: big; + bin: big; + bout: big; +}; + +Stats: adt +{ + totread: big; + totwrite: big; + nrpc: int; + nproto: int; + rpc: array of ref Rpc; # Maxrpc +}; + +Fid: adt { + nr: int; # fid number + path: ref Path; # path used to open Fid + qid: Qid; + mode: int; + nread: big; + nwrite: big; + bread: big; + bwrite: big; + offset: big; # for directories +}; + +Path: adt { + parent: cyclic ref Path; + name: string; +}; + +Frec: adt +{ + op: ref Path; # first name? + qid: Qid; + nread: big; + nwrite: big; + bread: big; + bwrite: big; + opens: int; +}; + +Tag: adt { + m: ref Tmsg; + fid: ref Fid; + stime: big; + next: cyclic ref Tag; +}; + +NTAGHASH: con 1<<4; # power of 2 +NFIDHASH: con 1<<4; # power of 2 + +tags := array[NTAGHASH] of ref Tag; +fids := array[NFIDHASH] of list of ref Fid; +dbg := 0; + +stats: Stats; +frecs: list of ref Frec; + +replymap := array[tagof Rmsg.Stat+1] of { + tagof Rmsg.Version => tagof Tmsg.Version, + tagof Rmsg.Auth => tagof Tmsg.Auth, + tagof Rmsg.Attach => tagof Tmsg.Attach, + tagof Rmsg.Flush => tagof Tmsg.Flush, + tagof Rmsg.Clunk => tagof Tmsg.Clunk, + tagof Rmsg.Remove => tagof Tmsg.Remove, + tagof Rmsg.Wstat => tagof Tmsg.Wstat, + tagof Rmsg.Walk => tagof Tmsg.Walk, + tagof Rmsg.Create => tagof Tmsg.Create, + tagof Rmsg.Open => tagof Tmsg.Open, + tagof Rmsg.Read => tagof Tmsg.Read, + tagof Rmsg.Write => tagof Tmsg.Write, + tagof Rmsg.Stat => tagof Tmsg.Stat, + * => -1, +}; + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + workdir = load Workdir Workdir->PATH; + sh := load Sh Sh->PATH; + styx = load Styx Styx->PATH; + styx->init(); + + wd := workdir->init(); + + dbfile := "iostats.out"; + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("iostats [-d] [-f debugfile] cmds [args ...]"); + while((o := arg->opt()) != 0) + case o { + 'd' => dbg++; + 'f' => dbfile = arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + sys->pctl(Sys->FORKFD|Sys->FORKNS|Sys->NEWPGRP|Sys->FORKENV, nil); + + if(dbg){ + fd := sys->create(dbfile, Sys->OWRITE, 8r666); + if(fd == nil) + fatal(sys->sprint("can't create %q: %r", dbfile)); + sys->dup(fd.fd, 2); + } + + if(sys->chdir("/") < 0) + fatal(sys->sprint("chdir /: %r")); + + stats.totread = big 0; + stats.totwrite = big 0; + stats.nrpc = 0; + stats.nproto = 0; + stats.rpc = array[tagof Tmsg.Wstat + 1] of ref Rpc; + stats.rpc[tagof Tmsg.Version] = mkrpc("version"); + stats.rpc[tagof Tmsg.Auth] = mkrpc("auth"); + stats.rpc[tagof Tmsg.Flush] = mkrpc("flush"); + stats.rpc[tagof Tmsg.Attach] = mkrpc("attach"); + stats.rpc[tagof Tmsg.Walk] = mkrpc("walk"); + stats.rpc[tagof Tmsg.Open] = mkrpc("open"); + stats.rpc[tagof Tmsg.Create] = mkrpc("create"); + stats.rpc[tagof Tmsg.Clunk] = mkrpc("clunk"); + stats.rpc[tagof Tmsg.Read] = mkrpc("read"); + stats.rpc[tagof Tmsg.Write] = mkrpc("write"); + stats.rpc[tagof Tmsg.Remove] = mkrpc("remove"); + stats.rpc[tagof Tmsg.Stat] = mkrpc("stat"); + stats.rpc[tagof Tmsg.Wstat] = mkrpc("wstat"); + + mpipe := array[2] of ref Sys->FD; + if(sys->pipe(mpipe) < 0) + fatal(sys->sprint("can't create pipe: %r")); + pids := chan of int; + cmddone := chan of int; + spawn cmd(sh, ctxt, args, wd, mpipe[0], pids, cmddone); + <-pids; + mpipe[0] = nil; + epipe := array[2] of ref Sys->FD; + if(sys->pipe(epipe) < 0) + fatal(sys->sprint("can't create pipe: %r")); + spawn export(epipe[1], pids); + <-pids; + epipe[1] = nil; + iodone := chan of int; + spawn iostats(epipe[0], mpipe[1], pids, iodone); + <-pids; + epipe[0] = mpipe[1] = nil; + <-cmddone; + <-iodone; + results(); +} + +cmd(sh: Sh, ctxt: ref Draw->Context, args: list of string, wdir: string, fsfd: ref Sys->FD, pids: chan of int, done: chan of int) +{ + { + pids <-= sys->pctl(Sys->FORKNS|Sys->FORKFD, nil); + if(sys->mount(fsfd, nil, "/", Sys->MREPL, "") < 0) + fatal(sys->sprint("can't mount /: %r")); + fsfd = nil; + sys->bind("#e", "/env", Sys->MREPL | Sys->MCREATE); + sys->bind("#d", "/fd", Sys->MREPL); # better than nothing + if(sys->chdir(wdir) < 0) + fatal(sys->sprint("can't chdir to %s: %r", wdir)); + sh->run(ctxt, args); + }exception{ + "fail:*" => + ; # don't mention it + * => + raise; # cause the fault + } + done <-= 1; +} + +iostats(expfd: ref Sys->FD, mountfd: ref Sys->FD, pids: chan of int, done: chan of int) +{ + pids <-= sys->pctl(Sys->NEWFD|Sys->NEWPGRP, 1 :: 2 :: expfd.fd :: mountfd.fd :: nil); + timefd := sys->open("/dev/time", Sys->OREAD); + if(timefd == nil) + fatal(sys->sprint("can't open /dev/time: %r")); + tmsgs := chan of (int, ref Tmsg); + spawn Treader(mountfd, expfd, tmsgs); + (tpid, nil) := <-tmsgs; + rmsgs := chan of (int, ref Rmsg); + spawn Rreader(expfd, mountfd, rmsgs); + (rpid, nil) := <-rmsgs; + expfd = mountfd = nil; + stderr := sys->fildes(2); +Run: + for(;;)alt{ + (n, t) := <-tmsgs => # n.b.: received on tmsgs before it goes to server + if(t == nil || tagof t == tagof Tmsg.Readerror) + break Run; # TO DO? + if(dbg) + sys->fprint(stderr, "->%s\n", t.text()); + tag := newtag(t, nsec(timefd)); + stats.nrpc++; + stats.nproto += n; + rpc := stats.rpc[tagof t]; + if(rpc == nil){ + sys->fprint(stderr, "iostats: unexpected T-msg %d\n", tagof t); + continue; + } + rpc.count++; + rpc.bin += big n; + pick pt := t { + Auth => + tag.fid = newfid(pt.afid); + Attach => + tag.fid = newfid(pt.fid); + Walk => + tag.fid = findfid(pt.fid); + Open => + tag.fid = findfid(pt.fid); + Create => + tag.fid = findfid(pt.fid); + Read => + tag.fid = findfid(pt.fid); + Write => + tag.fid = findfid(pt.fid); + pt.data = nil; # don't need to keep data + Clunk or + Stat or + Remove => + tag.fid = findfid(pt.fid); + Wstat => + tag.fid = findfid(pt.fid); + } + (n, r) := <-rmsgs => + if(r == nil || tagof r == tagof Rmsg.Readerror){ + break Run; # TO DO + } + if(dbg) + sys->fprint(stderr, "<-%s\n", r.text()); + stats.nproto += n; + tag := findtag(r.tag, 1); + if(tag == nil) + continue; # client or server error TO DO: account for flush + if(tagof r < len replymap && (tt := replymap[tagof r]) >= 0 && (rpc := stats.rpc[tt]) != nil){ + update(rpc, nsec(timefd)-tag.stime); + rpc.bout += big n; + } + fid := tag.fid; + pick pr := r { + Error => + pick m := tag.m { + Auth => + if(fid != nil){ + if(fid.nread != big 0 || fid.nwrite != big 0) + fidreport(fid); + freefid(fid); + } + } + Version => + # could pick up message size + # flush fids/tags + tags = array[len tags] of ref Tag; + fids = array[len fids] of list of ref Fid; + Auth => + # afid from fid.t, qaid from auth + if(fid != nil){ + fid.qid = pr.aqid; + fid.path = ref Path(nil, "#auth"); + } + Attach => + if(fid != nil){ + fid.qid = pr.qid; + fid.path = ref Path(nil, "/"); + } + Walk => + pick m := tag.m { + Walk => + if(len pr.qids != len m.names) + break; # walk failed, no change + if(fid == nil) + break; + if(m.newfid != m.fid){ + nf := newfid(m.newfid); + nf.path = fid.path; + fid = nf; # walk new fid + } + for(i := 0; i < len m.names; i++){ + fid.qid = pr.qids[i]; + if(m.names[i] == ".."){ + if(fid.path.parent != nil) + fid.path = fid.path.parent; + }else + fid.path = ref Path(fid.path, m.names[i]); + } + } + Open or + Create => + if(fid != nil) + fid.qid = pr.qid; + Read => + fid.nread++; + nr := big len pr.data; + fid.bread += nr; + stats.totread += nr; + Write => + # count + fid.nwrite++; + fid.bwrite += big pr.count; + stats.totwrite += big pr.count; + Flush => + pick m := tag.m { + Flush => + findtag(m.oldtag, 1); # discard if there + } + Clunk or + Remove => + if(fid != nil){ + if(fid.nread != big 0 || fid.nwrite != big 0) + fidreport(fid); + freefid(fid); + } + } + } + kill(rpid, "kill"); + kill(tpid, "kill"); + done <-= 1; +} + +results() +{ + stderr := sys->fildes(2); + rpc := stats.rpc[tagof Tmsg.Read]; + brpsec := real stats.totread / ((real rpc.time/1.0e9)+.000001); + + rpc = stats.rpc[tagof Tmsg.Write]; + bwpsec := real stats.totwrite / ((real rpc.time/1.0e9)+.000001); + + ttime := big 0; + for(n := 0; n < len stats.rpc; n++){ + rpc = stats.rpc[n]; + if(rpc == nil || rpc.count == big 0) + continue; + ttime += rpc.time; + } + + bppsec := real stats.nproto / ((real ttime/1.0e9)+.000001); + + sys->fprint(stderr, "\nread %bud bytes, %g Kb/sec\n", stats.totread, brpsec/1024.0); + sys->fprint(stderr, "write %bud bytes, %g Kb/sec\n", stats.totwrite, bwpsec/1024.0); + sys->fprint(stderr, "protocol %ud bytes, %g Kb/sec\n", stats.nproto, bppsec/1024.0); + sys->fprint(stderr, "rpc %ud count\n\n", stats.nrpc); + + sys->fprint(stderr, "%-10s %5s %5s %5s %5s %5s T R\n", + "Message", "Count", "Low", "High", "Time", " Avg"); + + for(n = 0; n < len stats.rpc; n++){ + rpc = stats.rpc[n]; + if(rpc == nil || rpc.count == big 0) + continue; + sys->fprint(stderr, "%-10s %5bud %5bud %5bud %5bud %5bud ms %8bud %8bud bytes\n", + rpc.name, + rpc.count, + rpc.lo/Ns2ms, + rpc.hi/Ns2ms, + rpc.time/Ns2ms, + rpc.time/Ns2ms/rpc.count, + rpc.bin, + rpc.bout); + } + + # unclunked fids + for(n = 0; n < NFIDHASH; n++) + for(fl := fids[n]; fl != nil; fl = tl fl){ + fid := hd fl; + if(fid.nread != big 0 || fid.nwrite != big 0) + fidreport(fid); + } + if(frecs == nil) + exit; + + sys->fprint(stderr, "\nOpens Reads (bytes) Writes (bytes) File\n"); + for(frl := frecs; frl != nil; frl = tl frl){ + fr := hd frl; + case s := makepath(fr.op) { + "/fd/0" => s = "(stdin)"; + "/fd/1" => s = "(stdout)"; + "/fd/2" => s = "(stderr)"; + "" => s = "/."; + } + sys->fprint(stderr, "%5ud %8bud %8bud %8bud %8bud %s\n", fr.opens, fr.nread, fr.bread, + fr.nwrite, fr.bwrite, s); + } +} + +Treader(fd: ref Sys->FD, ofd: ref Sys->FD, out: chan of (int, ref Tmsg)) +{ + out <-= (sys->pctl(0, nil), nil); + fd = sys->fildes(fd.fd); + ofd = sys->fildes(ofd.fd); + for(;;){ + (a, err) := styx->readmsg(fd, Maxmsg); + if(err != nil){ + out <-= (0, ref Tmsg.Readerror(0, err)); + break; + } + if(a == nil){ + out <-= (0, nil); + break; + } + (nil, m) := Tmsg.unpack(a); + if(m == nil){ + out <-= (0, ref Tmsg.Readerror(0, "bad Styx T-message format")); + break; + } + out <-= (len a, m); + sys->write(ofd, a, len a); # TO DO: errors + } +} + +Rreader(fd: ref Sys->FD, ofd: ref Sys->FD, out: chan of (int, ref Rmsg)) +{ + out <-= (sys->pctl(0, nil), nil); + fd = sys->fildes(fd.fd); + ofd = sys->fildes(ofd.fd); + for(;;){ + (a, err) := styx->readmsg(fd, Maxmsg); + if(err != nil){ + out <-= (0, ref Rmsg.Readerror(0, err)); + break; + } + if(a == nil){ + out <-= (0, nil); + break; + } + (nil, m) := Rmsg.unpack(a); + if(m == nil){ + out <-= (0, ref Rmsg.Readerror(0, "bad Styx R-message format")); + break; + } + out <-= (len a, m); + sys->write(ofd, a, len a); # TO DO: errors + } +} + +reply(fd: ref Sys->FD, m: ref Rmsg) +{ + d := m.pack(); + sys->write(fd, d, len d); +} + +mkrpc(s: string): ref Rpc +{ + return ref Rpc(s, big 0, big 0, big 1 << 40, big 0, big 0, big 0); +} + +newfid(nr: int): ref Fid +{ + h := nr%NFIDHASH; + for(fl := fids[h]; fl != nil; fl = tl fl) + if((hd fl).nr == nr) + return hd fl; # shouldn't happen: faulty client + fid := ref Fid; + fid.nr = nr; + fid.nread = big 0; + fid.nwrite = big 0; + fid.bread = big 0; + fid.bwrite = big 0; + fid.qid = Qid(big 0, 0, -1); + fids[h] = fid :: fids[h]; + return fid; +} + +findfid(nr: int): ref Fid +{ + for(fl := fids[nr%NFIDHASH]; fl != nil; fl = tl fl) + if((hd fl).nr == nr) + return hd fl; + return nil; +} + +freefid(fid: ref Fid) +{ + h := fid.nr%NFIDHASH; + nl: list of ref Fid; + for(fl := fids[h]; fl != nil; fl = tl fl) + if((hd fl).nr != fid.nr) + nl = hd fl :: nl; + fids[h] = nl; +} + +makepath(p: ref Path): string +{ + nl: list of string; + for(; p != nil; p = p.parent) + if(p.name != "/") + nl = p.name :: nl; + s := ""; + for(; nl != nil; nl = tl nl) + if(s != nil) + s += "/" + hd nl; + else + s = hd nl; + return "/"+s; +} + +fatal(s: string) +{ + sys->fprint(sys->fildes(2), "iostats: %s: %r\n", s); + raise "fatal:error"; +} + +nsec(fd: ref Sys->FD): big +{ + buf := array[100] of byte; + n := sys->pread(fd, buf, len buf, big 0); + if(n <= 0) + return big 0; + return big string buf[0:n]; +} + +fidreport(f: ref Fid) +{ + for(fl := frecs; fl != nil; fl = tl fl){ + fr := hd fl; + if(eqqid(f.qid, fr.qid)){ + # could put f.path in list of paths if aliases were interesting + fr.nread += f.nread; + fr.nwrite += f.nwrite; + fr.bread += f.bread; + fr.bwrite += f.bwrite; + fr.opens++; + return; + } + } + + fr := ref Frec; + fr.op = f.path; + fr.qid = f.qid; + fr.nread = f.nread; + fr.nwrite = f.nwrite; + fr.bread = f.bread; + fr.bwrite = f.bwrite; + fr.opens = 1; + frecs = fr :: frecs; +} + +update(rpc: ref Rpc, t: big) +{ + if(t < big 0) + t = big 0; + + rpc.time += t; + if(t < rpc.lo) + rpc.lo = t; + if(t > rpc.hi) + rpc.hi = t; +} + +newtag(m: ref Tmsg, t: big): ref Tag +{ + slot := m.tag & (NTAGHASH - 1); + tag := ref Tag(m, nil, t, tags[slot]); + tags[slot] = tag; + return tag; +} + +findtag(tag: int, destroy: int): ref Tag +{ + slot := tag & (NTAGHASH - 1); + prev: ref Tag; + for(t := tags[slot]; t != nil; t = t.next){ + if(t.m.tag == tag) + break; + prev = t; + } + if(t == nil || !destroy) + return t; + if(prev == nil) + tags[slot] = t.next; + else + prev.next = t.next; + return t; +} + +eqqid(a, b: Qid): int +{ + return a.path == b.path && a.qtype == b.qtype; +} + +export(fd: ref Sys->FD, pid: chan of int) +{ + pid <-= sys->pctl(Sys->NEWFD|Sys->FORKNS, fd.fd::0::1::2::nil); + sys->export(fd, "/", Sys->EXPWAIT); +} + +kill(pid: int, what: string) +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "%s", what); +} diff --git a/appl/cmd/ip/bootpd.b b/appl/cmd/ip/bootpd.b new file mode 100644 index 00000000..bf3313b2 --- /dev/null +++ b/appl/cmd/ip/bootpd.b @@ -0,0 +1,662 @@ +implement Bootpd; + +# +# to do: +# DHCP +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "attrdb.m"; + attrdb: Attrdb; + Attr, Db, Dbentry, Tuples: import attrdb; + +include "ip.m"; + ip: IP; + IPaddr, Udphdr: import ip; + +include "ipattr.m"; + ipattr: IPattr; + +include "ether.m"; + ether: Ether; + +include "arg.m"; + +Bootpd: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; +debug: int; +sniff: int; +verbose: int; + +siaddr: IPaddr; +netmask: IPaddr; +myname: string; +progname := "bootpd"; +net := "/net"; +ndb: ref Db; +ndbfile := "/lib/ndb/local"; +mtime := 0; +testing := 0; + +Udphdrsize: con IP->OUdphdrlen; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + loadfail(Bufio->PATH); + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + loadfail(Attrdb->PATH); + attrdb->init(); + ip = load IP IP->PATH; + if(ip == nil) + loadfail(IP->PATH); + ip->init(); + ipattr = load IPattr IPattr->PATH; + if(ipattr == nil) + loadfail(IPattr->PATH); + ipattr->init(attrdb, ip); + ether = load Ether Ether->PATH; + if(ether == nil) + loadfail(Ether->PATH); + ether->init(); + + verbose = 1; + sniff = 0; + debug = 0; + arg := load Arg Arg->PATH; + if(arg == nil) + raise "fail: load Arg"; + arg->init(args); + arg->setusage("bootpd [-dsqv] [-f file] [-x network]"); + progname = arg->progname(); + while((o := arg->opt()) != 0) + case o { + 'd' => debug++; + 's' => sniff = 1; debug = 255; + 'q' => verbose = 0; + 'v' => verbose = 1; + 'x' => net = arg->earg(); + 'f' => ndbfile = arg->earg(); + 't' => testing = 1; debug = 1; verbose = 1; + * => arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + + sys->pctl(Sys->FORKFD|Sys->FORKNS, nil); + + if(!sniff && (err := dbread()) != nil) + error(err); + + myname = sysname(); + if(myname == nil) + error("system name not set"); + (siaddr, err) = csquery(myname); + if(err != nil) + error(sys->sprint("can't find IP address for %s: %s", myname, err)); + if(debug) + sys->fprint(stderr, "bootpd: local IP address is %s\n", siaddr.text()); + + addr := net+"/udp!*!67"; + if(testing) + addr = net+"/udp!*!499"; + if(debug) + sys->fprint(stderr, "bootpd: announcing %s\n", addr); + (ok, c) := sys->announce(addr); + if(ok < 0) + error(sys->sprint("can't announce %s: %r", addr)); + if(sys->fprint(c.cfd, "headers") < 0) + error(sys->sprint("can't set headers mode: %r")); + sys->fprint(c.cfd, "oldheaders"); + + if(debug) + sys->fprint(stderr, "bootpd: opening %s/data\n", c.dir); + c.dfd = sys->open(c.dir+"/data", sys->ORDWR); + if(c.dfd == nil) + error(sys->sprint("can't open %s/data: %r", c.dir)); + + spawn server(c); +} + +loadfail(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(stderr, "bootpd: %s\n", s); + raise "fail:error"; +} + +server(c: Sys->Connection) +{ + buf := array[2048] of byte; + badread := 0; + for(;;) { + n := sys->read(c.dfd, buf, len buf); + if(n <0) { + if (badread++ > 10) + break; + continue; + } + badread = 0; + if(n < Udphdrsize) { + if(debug) + sys->fprint(stderr, "bootpd: short Udphdr: %d bytes\n", n); + continue; + } + hdr := Udphdr.unpack(buf, Udphdrsize); + if(debug) + sys->fprint(stderr, "bootpd: received request from udp!%s!%d\n", hdr.raddr.text(), hdr.rport); + if(n < Udphdrsize+300) { + if(debug) + sys->fprint(stderr, "bootpd: short request of %d bytes\n", n - Udphdrsize); + continue; + } + + (bootp, err) := Bootp.unpack(buf[Udphdrsize:]); + if(err != nil) { + if(debug) + sys->fprint(stderr, "bootpd: can't unpack packet: %s\n", err); + continue; + } + if(debug >= 2) + sys->fprint(stderr, "bootpd: recvd {%s}\n", bootp.text()); + if(sniff) + continue; + if(bootp.htype != 1 || bootp.hlen != 6) { + # if it isn't ether, we don't do it + if(debug) + sys->fprint(stderr, "bootpd: hardware type not ether; ignoring.\n"); + continue; + } + if((err = dbread()) != nil) { + sys->fprint(stderr, "bootpd: getreply: dbread failed: %s\n", err); + continue; + } + rec := lookup(bootp); + if(rec == nil) { + # we can't answer this request + if(debug) + sys->fprint(stderr, "bootpd: cannot answer request.\n"); + continue; + } + if(debug) + sys->fprint(stderr, "bootpd: found a matching entry: {%s}\n", rec.text()); + mkreply(bootp, rec); + if(verbose) + sys->print("bootpd: %s -> %s %s\n", ether->text(rec.ha), rec.hostname, rec.ip.text()); + if(debug) + sys->fprint(stderr, "bootpd: reply {%s}\n", bootp.text()); + repl := bootp.pack(); + if(!testing) + arpenter(rec.ip.text(), ether->text(rec.ha)); + send(hdr, repl); + } + sys->fprint(stderr, "bootpd: %d read errors: %r\n", badread); +} + +arpenter(ip, ha: string) +{ + if(debug) + sys->fprint(stderr, "bootpd: arp: %s -> %s\n", ip, ha); + fd := sys->open(net+"/arp", Sys->OWRITE); + if(fd == nil) { + if(debug) + sys->fprint(stderr, "bootpd: arp open failed: %r\n"); + return; + } + if(sys->fprint(fd, "add %s %s", ip, ha) < 0){ + if(debug) + sys->fprint(stderr, "bootpd: error writing arp: %r\n"); + } +} + +sysname(): string +{ + t := rf("/dev/sysname"); + if(t != nil) + return t; + return rf("#e/sysname"); +} + +rf(name: string): string +{ + fd := sys->open(name, Sys->OREAD); + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return nil; + return string buf[0:n]; +} + +csquery(name: string): (IPaddr, string) +{ + siaddr = ip->noaddr; + # get a local IP address by translating our sysname with cs(8) + csfile := net+"/cs"; + fd := sys->open(net+"/cs", Sys->ORDWR); + if(fd == nil) + return (ip->noaddr, sys->sprint("can't open %s/cs: %r", csfile)); + if(sys->fprint(fd, "net!%s!0", name) < 0) + return (ip->noaddr, sys->sprint("can't translate net!%s!0: %r", name)); + sys->seek(fd, big 0, 0); + a := array[1024] of byte; + n := sys->read(fd, a, len a); + if(n <= 0) + return (ip->noaddr, "no result from "+csfile); + reply := string a[0:n]; + (l, addr):= sys->tokenize(reply, " "); + if(l != 2) + return (ip->noaddr, "bad cs reply format"); + (l, addr) = sys->tokenize(hd tl addr, "!"); + if(l < 2) + return (ip->noaddr, "bad cs reply format"); + (ok, ipa) := IPaddr.parse(hd addr); + if(ok < 0 || !ipok(siaddr)) + return (ip->noaddr, "can't parse address: "+hd addr); + return (ipa, nil); +} + +Hostinfo: adt { + hostname: string; + + ha: array of byte; # hardware addr + ip: IPaddr; # client IP addr + bootf: string; # boot file path + netmask: IPaddr; # subnet mask + ipgw: IPaddr; # gateway IP addr + fs: IPaddr; # file server IP addr + auth: IPaddr; # authentication server IP addr + + text: fn(inf: self ref Hostinfo): string; +}; + +send(hdr: ref Udphdr, msg: array of byte) +{ + replyaddr := net+"/udp!255.255.255.255!68"; # TO DO: gateway + if(testing) + replyaddr = sys->sprint("udp!%s!%d", hdr.raddr.text(), hdr.rport); + lport := "67"; + if(testing) + lport = "499"; + (n, c) := sys->dial(replyaddr, lport); + if(n < 0) { + sys->fprint(stderr, "bootpd: can't dial %s for reply: %r\n", replyaddr); + return; + } + n = sys->write(c.dfd, msg, len msg); + if(n != len msg) + sys->fprint(stderr, "bootpd: udp write error: %r\n"); +} + +mkreply(bootp: ref Bootp, rec: ref Hostinfo) +{ + bootp.op = 2; # boot reply + bootp.yiaddr = rec.ip; + bootp.siaddr = siaddr; + bootp.giaddr = ip->noaddr; + bootp.sname = myname; + bootp.file = string rec.bootf; + bootp.vend = array of byte sys->sprint("p9 %s %s %s %s", rec.netmask.text(), rec.fs.text(), rec.auth.text(), rec.ipgw.text()); +} + +dbread(): string +{ + if(ndb == nil){ + ndb = Db.open(ndbfile); + if(ndb == nil) + return sys->sprint("cannot open %s: %r", ndbfile); + }else if(ndb.changed()) + ndb.reopen(); + return nil; +} + +ipok(a: IPaddr): int +{ + return a.isv4() && !(a.eq(ip->v4noaddr) || a.eq(ip->noaddr) || a.ismulticast()); +} + +lookup(bootp: ref Bootp): ref Hostinfo +{ + if(ndb == nil) + return nil; + inf: ref Hostinfo; + hwaddr := ether->text(bootp.chaddr); + if(ipok(bootp.ciaddr)){ + # client thinks it knows address; check match with MAC address + ipaddr := bootp.ciaddr.text(); + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = ndb.findbyattr(ptr, "ip", ipaddr, "ether"); + if(e == nil) + break; + # TO DO: check result + inf = matchandfill(e, "ip", ipaddr, "ether", hwaddr); + if(inf != nil) + return inf; + } + } + # look up an ip address associated with given MAC address + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = ndb.findbyattr(ptr, "ether", hwaddr, "ip"); + if(e == nil) + break; + # TO DO: check right net etc. + inf = matchandfill(e, "ether", hwaddr, "ip", nil); + if(inf != nil) + return inf; + } + return nil; +} + +matchandfill(e: ref Dbentry, attr: string, val: string, rattr: string, rval: string): ref Hostinfo +{ + matches := e.findbyattr(attr, val, rattr); + for(; matches != nil; matches = tl matches){ + (line, attrs) := hd matches; + for(; attrs != nil; attrs = tl attrs){ + if(rval == nil || (hd attrs).val == rval){ + inf := fillup(line, e); + if(inf != nil) + return inf; + break; + } + } + } + return nil; +} + +fillup(line: ref Tuples, e: ref Dbentry): ref Hostinfo +{ + ok: int; + inf := ref Hostinfo; + inf.netmask = ip->noaddr; + inf.ipgw = ip->noaddr; + inf.fs = ip->v4noaddr; + inf.auth = ip->v4noaddr; + inf.hostname = find(line, e, "sys"); + s := find(line, e, "ether"); + if(s != nil) + inf.ha = ether->parse(s); + s = find(line, e, "ip"); + if(s == nil) + return nil; + (ok, inf.ip) = IPaddr.parse(s); + if(ok < 0) + return nil; + (results, err) := ipattr->findnetattrs(ndb, "ip", s, list of{"ipmask", "ipgw", "fs", "FILESERVER", "SIGNER", "auth", "bootf"}); + if(err != nil) + return nil; + for(; results != nil; results = tl results){ + (a, nattrs) := hd results; + if(!a.eq(inf.ip)) + continue; # different network + for(; nattrs != nil; nattrs = tl nattrs){ + na := hd nattrs; + case na.name { + "ipmask" => + inf.netmask = takeipmask(na.pairs, inf.netmask); + "ipgw" => + inf.ipgw = takeipattr(na.pairs, inf.ipgw); + "fs" or "FILESERVER" => + inf.fs = takeipattr(na.pairs, inf.fs); + "auth" or "SIGNER" => + inf.auth = takeipattr(na.pairs, inf.auth); + "bootf" => + inf.bootf = takeattr(na.pairs, inf.bootf); + } + } + } + return inf; +} + +takeattr(pairs: list of ref Attr, s: string): string +{ + if(s != nil || pairs == nil) + return s; + return (hd pairs).val; +} + +takeipattr(pairs: list of ref Attr, a: IPaddr): IPaddr +{ + if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr))) + return a; + (ok, na) := parseip((hd pairs).val); + if(ok < 0) + return a; + return na; +} + +takeipmask(pairs: list of ref Attr, a: IPaddr): IPaddr +{ + if(pairs == nil || !(a.eq(ip->noaddr) || a.eq(ip->v4noaddr))) + return a; + (ok, na) := IPaddr.parsemask((hd pairs).val); + if(ok < 0) + return a; + return na; +} + +findip(line: ref Tuples, e: ref Dbentry, attr: string): (int, IPaddr) +{ + s := find(line, e, attr); + if(s == nil) + return (-1, ip->noaddr); + return parseip(s); +} + +parseip(s: string): (int, IPaddr) +{ + (ok, a) := IPaddr.parse(s); + if(ok < 0){ + # look it up if it's a system name + s = findbyattr("sys", s, "ip"); + (ok, a) = IPaddr.parse(s); + } + return (ok, a); +} + +find(line: ref Tuples, e: ref Dbentry, attr: string): string +{ + if(line != nil){ + a := line.find(attr); + if(a != nil) + return (hd a).val; + } + if(e != nil){ + for(matches := e.find(attr); matches != nil; matches = tl matches){ + (nil, a) := hd matches; + if(a != nil) + return (hd a).val; + } + } + return nil; +} + +findbyattr(attr: string, val: string, rattr: string): string +{ + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = ndb.findbyattr(ptr, attr, val, rattr); + if(e == nil) + break; + rvl := e.find(rattr); + if(rvl != nil){ + (nil, al) := hd rvl; + return (hd al).val; + } + } + return nil; +} + +missing(rec: ref Hostinfo): string +{ + s := ""; + if(rec.ha == nil) + s += " hardware address"; + if(rec.ip.eq(ip->noaddr)) + s += " IP address"; + if(rec.bootf == nil) + s += " bootfile"; + if(rec.netmask.eq(ip->noaddr)) + s += " subnet mask"; + if(rec.ipgw.eq(ip->noaddr)) + s += " gateway"; + if(rec.fs.eq(ip->noaddr)) + s += " file server"; + if(rec.auth.eq(ip->noaddr)) + s += " authentication server"; + if(s != "") + return s[1:]; + return nil; +} + +dtoa(data: array of byte): string +{ + if(data == nil) + return nil; + result: string; + for(i:=0; i < len data; i++) + result += sys->sprint(".%d", int data[i]); + return result[1:]; +} + +magic(cookie: array of byte): string +{ + if(eqa(cookie, array[] of { byte 'p', byte '9', byte ' ', byte ' ' })) + return "plan9"; + if(eqa(cookie, array[] of { byte 99, byte 130, byte 83, byte 99 })) + return "rfc1048"; + if(eqa(cookie, array[] of { byte 'C', byte 'M', byte 'U', byte 0 })) + return "cmu"; + return dtoa(cookie); +} + +eqa(a1: array of byte, a2: array of byte): int +{ + if(len a1 != len a2) + return 0; + for(i := 0; i < len a1; i++) + if(a1[i] != a2[i]) + return 0; + return 1; +} + +Hostinfo.text(rec: self ref Hostinfo): string +{ + return sys->sprint("ha=%s ip=%s bf=%s sm=%s gw=%s fs=%s au=%s", + ether->text(rec.ha), rec.ip.text(), rec.bootf, rec.netmask.masktext(), rec.ipgw.text(), rec.fs.text(), rec.auth.text()); +} + +Bootp: adt +{ + op: int; # opcode [1] + htype: int; # hardware type[1] + hlen: int; # hardware address length [1] + hops: int; # gateway hops [1] + xid: int; # random number [4] + secs: int; # seconds elapsed since client started booting [2] + flags: int; # flags[2] + ciaddr: IPaddr; # client ip address (client->server)[4] + yiaddr: IPaddr; # your ip address (server->client)[4] + siaddr: IPaddr; # server's ip address [4] + giaddr: IPaddr; # gateway ip address [4] + chaddr: array of byte; # client hardware (mac) address [16] + sname: string; # server host name [64] + file: string; # boot file name [128] + vend: array of byte; # vendor-specific [128] + + unpack: fn(a: array of byte): (ref Bootp, string); + pack: fn(bp: self ref Bootp): array of byte; + text: fn(bp: self ref Bootp): string; +}; + +Bootp.unpack(data: array of byte): (ref Bootp, string) +{ + if(len data < 300) + return (nil, "too short"); + + bp := ref Bootp; + bp.op = int data[0]; + bp.htype = int data[1]; + bp.hlen = int data[2]; + if(bp.hlen > 16) + return (nil, "length error"); + bp.hops = int data[3]; + bp.xid = ip->get4(data, 4); + bp.secs = ip->get2(data, 8); + bp.flags = ip->get2(data, 10); + bp.ciaddr = IPaddr.newv4(data[12:16]); + bp.yiaddr = IPaddr.newv4(data[16:20]); + bp.siaddr = IPaddr.newv4(data[20:24]); + bp.giaddr = IPaddr.newv4(data[24:28]); + bp.chaddr = data[28:28+bp.hlen]; + bp.sname = ctostr(data[44:108]); + bp.file = ctostr(data[108:236]); + bp.vend = data[236:300]; + return (bp, nil); +} + +Bootp.pack(bp: self ref Bootp): array of byte +{ + data := array[364] of { * => byte 0 }; + data[0] = byte bp.op; + data[1] = byte bp.htype; + data[2] = byte bp.hlen; + data[3] = byte bp.hops; + ip->put4(data, 4, bp.xid); + ip->put2(data, 8, bp.secs); + ip->put2(data, 10, bp.flags); + data[12:] = bp.ciaddr.v4(); + data[16:] = bp.yiaddr.v4(); + data[20:] = bp.siaddr.v4(); + data[24:] = bp.giaddr.v4(); + data[28:] = bp.chaddr; + data[44:] = array of byte bp.sname; + data[108:] = array of byte bp.file; + data[236:] = bp.vend; + return data; +} + +ctostr(cstr: array of byte): string +{ + for(i:=0; i<len cstr; i++) + if(cstr[i] == byte 0) + break; + return string cstr[0:i]; +} + +Bootp.text(bp: self ref Bootp): string +{ + s := sys->sprint("op=%d htype=%d hlen=%d hops=%d xid=%ud secs=%ud ciaddr=%s yiaddr=%s", + int bp.op, bp.htype, bp.hlen, bp.hops, bp.xid, bp.secs, bp.ciaddr.text(), bp.yiaddr.text()); + s += sys->sprint(" server=%s gateway=%s hwaddr=%q host=%q file=%q magic=%q", + bp.siaddr.text(), bp.giaddr.text(), ether->text(bp.chaddr), bp.sname, bp.file, magic(bp.vend[0:4])); + if(magic(bp.vend[0:4]) == "plan9") + s += "("+ctostr(bp.vend)+")"; + return s; +} diff --git a/appl/cmd/ip/dhcp.b b/appl/cmd/ip/dhcp.b new file mode 100644 index 00000000..63ac2255 --- /dev/null +++ b/appl/cmd/ip/dhcp.b @@ -0,0 +1,162 @@ +implement Dhcp; + +# +# configure an interface using DHCP +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "ip.m"; + ip: IP; + IPv4off, IPaddrlen, OUdphdrlen: import IP; + IPaddr: import ip; + get2, get4, put2, put4: import ip; + +include "dhcp.m"; + dhcpclient: Dhcpclient; + Bootconf, Lease: import dhcpclient; + +include "arg.m"; + +Dhcp: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +RetryTime: con 10*1000; # msec + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + ip = load IP IP->PATH; + dhcpclient = load Dhcpclient Dhcpclient->PATH; + + sys->pctl(Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: nil); + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("dhcp [-bdmnpr] [-g ipgw] [-h hostname] [-x /net] ifcdir [ip [ipmask]]"); + trace := 0; + pcfg := 0; + bootp := 0; + monitor := 0; + retry := 0; + noctl := 0; + netdir := "/net"; + cfg := Bootconf.new(); + while((o := arg->opt()) != 0) + case o { + 'b' => bootp = 1; + 'd' => trace++; + 'g' => cfg.ipgw = arg->earg(); + 'h' => cfg.puts(Dhcpclient->Ohostname, arg->earg()); + 'm' => monitor = 1; + 'n' => noctl = 1; + 'p' => pcfg = 1; + 'r' => retry = 1; + 'x' => netdir = arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(len args == 0) + arg->usage(); + + ifcdir := hd args; + args = tl args; + if(args != nil){ + cfg.ip = hd args; + args = tl args; + if(args != nil){ + cfg.ipmask = hd args; + args = tl args; + if(args != nil) + arg->usage(); + } + } + arg = nil; + + ifcctl: ref Sys->FD; + if(noctl == 0){ + ifcctl = sys->open(ifcdir+"/ctl", Sys->OWRITE); + if(ifcctl == nil) + err(sys->sprint("cannot open %s/ctl: %r", ifcdir)); + } + etherdir := finddev(ifcdir); + if(etherdir == nil) + err(sys->sprint("cannot find network device in %s/status: %r", ifcdir)); + if(etherdir[0] != '/' && etherdir[0] != '#') + etherdir = netdir+"/"+etherdir; + + ip->init(); + dhcpclient->init(); + dhcpclient->tracing(trace); + e: string; + lease: ref Lease; + for(;;){ + if(bootp){ + (cfg, e) = dhcpclient->bootp(netdir, ifcctl, etherdir+"/addr", cfg); + if(e == nil){ + if(cfg != nil) + dhcpclient->applycfg(netdir, ifcctl, cfg); + if(pcfg) + printcfg(cfg); + break; + } + }else{ + (cfg, lease, e) = dhcpclient->dhcp(netdir, ifcctl, etherdir+"/addr", cfg, nil); # last is array of int options + if(e == nil){ + if(pcfg) + printcfg(cfg); + if(cfg.lease > 0 && monitor) + leasemon(lease.configs, pcfg); + break; + } + } + if(!retry) + err("failed to configure network: "+e); + sys->fprint(sys->fildes(2), "dhcp: failed to configure network: %s; retrying", e); + sys->sleep(RetryTime); + } +} + +leasemon(configs: chan of (ref Bootconf, string), pcfg: int) +{ + for(;;){ + (cfg, e) := <-configs; + if(e != nil) + sys->fprint(sys->fildes(2), "dhcp: %s", e); + if(pcfg) + printcfg(cfg); + } +} + +printcfg(cfg: ref Bootconf) +{ + sys->print("ip=%s ipmask=%s ipgw=%s iplease=%d\n", cfg.ip, cfg.ipmask, cfg.ipgw, cfg.lease); +} + +finddev(ifcdir: string): string +{ + fd := sys->open(ifcdir+"/status", Sys->OREAD); + if(fd == nil) + return nil; + buf := array[1024] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return nil; + (nf, l) := sys->tokenize(string buf[0:n], " \n"); + if(nf < 2){ + sys->werrstr("unexpected format for status file"); + return nil; + } + return hd tl l; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "dhcp: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/ip/mkfile b/appl/cmd/ip/mkfile new file mode 100644 index 00000000..a2238154 --- /dev/null +++ b/appl/cmd/ip/mkfile @@ -0,0 +1,30 @@ +<../../../mkconfig + +DIRS=\ + ppp\ +# nppp\ + +TARG=\ + bootpd.dis\ + dhcp.dis\ + ping.dis\ + rip.dis\ + tftpd.dis\ + virgild.dis\ + obootpd.dis\ + sntp.dis\ + +SYSMODULES=\ + attrdb.m\ + bufio.m\ + dhcp.m\ + draw.m\ + ether.m\ + ip.m\ + ipattr.m\ + sys.m\ + +DISBIN=$ROOT/dis/ip + +<$ROOT/mkfiles/mkdis +<$ROOT/mkfiles/mksubdirs diff --git a/appl/cmd/ip/nppp/mkfile b/appl/cmd/ip/nppp/mkfile new file mode 100644 index 00000000..0f803acd --- /dev/null +++ b/appl/cmd/ip/nppp/mkfile @@ -0,0 +1,24 @@ +<../../../../mkconfig + +TARG=\ + ppplink.dis\ + pppchat.dis\ + modem.dis\ + script.dis\ +# ppptest.dis\ + +MODULES=\ + modem.m\ + script.m\ + +SYSMODULES=\ + sys.m\ + draw.m\ + tk.m\ + dict.m\ + string.m\ + lock.m\ + +DISBIN=$ROOT/dis/ip/nppp + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/ip/nppp/modem.b b/appl/cmd/ip/nppp/modem.b new file mode 100644 index 00000000..f8c81396 --- /dev/null +++ b/appl/cmd/ip/nppp/modem.b @@ -0,0 +1,469 @@ +implement Modem; + +# +# Copyright © 1998-2001 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "lock.m"; + lock: Lock; + Semaphore: import lock; + +include "draw.m"; + +include "modem.m"; + +hangupcmd := "ATH0"; # was ATZH0 but some modem versions on Umec hung on ATZ + +# modem return codes +Ok, Success, Failure, Abort, Noise, Found: con iota; + +maxspeed: con 115200; + +# +# modem return messages +# +Msg: adt { + text: string; + code: int; +}; + +msgs: array of Msg = array [] of { + ("OK", Ok), + ("NO CARRIER", Failure), + ("ERROR", Failure), + ("NO DIALTONE", Failure), + ("BUSY", Failure), + ("NO ANSWER", Failure), + ("CONNECT", Success), +}; + +kill(pid: int) +{ + fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "kill") < 0) + sys->print("modem: can't kill %d: %r\n", pid); +} + +# +# prepare a modem port +# +openserial(d: ref Device): string +{ + d.data = nil; + d.ctl = nil; + + d.data = sys->open(d.local, Sys->ORDWR); + if(d.data == nil) + return sys->sprint("can't open %s: %r", d.local); + + d.ctl = sys->open(d.local+"ctl", Sys->ORDWR); + if(d.ctl == nil) + return sys->sprint("can't open %s: %r", d.local+"ctl"); + + d.speed = maxspeed; + d.avail = nil; + return nil; +} + +# +# shut down the monitor (if any) and return the connection +# + +Device.close(m: self ref Device): ref Sys->Connection +{ + if(m.pid != 0){ + kill(m.pid); + m.pid = 0; + } + if(m.data == nil) + return nil; + mc := ref sys->Connection(m.data, m.ctl, nil); + m.ctl = nil; + m.data = nil; + return mc; +} + +# +# Send a string to the modem +# + +Device.send(d: self ref Device, x: string): string +{ + a := array of byte x; + f := sys->write(d.data, a, len a); + if(f != len a) { + # let's attempt to close & reopen the modem + d.close(); + err := openserial(d); + if(err != nil) + return err; + f = sys->write(d.data,a, len a); + if(f < 0) + return sys->sprint("%r"); + if(f != len a) + return "short write"; + } + if(d.trace) + sys->print("->%s\n",x); + return nil; +} + +# +# apply a string of commands to modem & look for a response +# + +apply(d: ref Device, s: string, substr: string, secs: int): int +{ + m := Ok; + buf := ""; + for(i := 0; i < len s; i++){ + c := s[i]; + buf[len buf] = c; # assume no Unicode + if(c == '\r' || i == (len s -1)){ + if(c != '\r') + buf[len buf] = '\r'; + if(d.send(buf) != nil) + return Abort; + (m, nil) = readmsg(d, secs, substr); + buf = ""; + } + } + return m; +} + +# +# get modem into command mode if it isn't already +# +GUARDTIME: con 1100; # usual default for S12=50 in units of 1/50 sec; allow 100ms fuzz + +attention(d: ref Device): int +{ + for(i := 0; i < 3; i++){ + if(apply(d, hangupcmd, nil, 2) == Ok) + return Ok; + sys->sleep(GUARDTIME); + if(d.send("+++") != nil) + return Abort; + sys->sleep(GUARDTIME); + (nil, msg) := readmsg(d, 0, nil); + if(msg != nil && d.trace) + sys->print("status: %s\n", msg); + } + return Failure; +} + +# +# apply a command type +# + +applyspecial(d: ref Device, cmd: string): int +{ + if(cmd == nil) + return Failure; + return apply(d, cmd, nil, 2); +} + +# +# hang up any connections in progress and close the device +# +Device.onhook(d: self ref Device) +{ + # hang up the modem + monitoring(d); + if(attention(d) != Ok) + sys->print("modem: no attention\n"); + + # hangup the stream (eg, for ppp) and toggle the lines to the modem + if(d.ctl != nil) { + sys->fprint(d.ctl,"d0\n"); + sys->fprint(d.ctl,"r0\n"); + sys->fprint(d.ctl, "h\n"); # hangup on native serial + sys->sleep(250); + sys->fprint(d.ctl,"r1\n"); + sys->fprint(d.ctl,"d1\n"); + } + + d.close(); +} + +# +# does string s contain t anywhere? +# + +contains(s, t: string): int +{ + if(t == nil) + return 1; + if(s == nil) + return 0; + n := len t; + for(i := 0; i+n <= len s; i++) + if(s[i:i+n] == t) + return 1; + return 0; +} + +# +# read till we see a message or we time out +# +readmsg(d: ref Device, secs: int, substr: string): (int, string) +{ + found := 0; + msecs := secs*1000; + limit := 1000; # pretty arbitrary + s := ""; + + for(start := sys->millisec(); sys->millisec() <= start+msecs;){ + a := d.getinput(1); + if(len a == 0){ + if(limit){ + sys->sleep(1); + continue; + } + break; + } + if(a[0] == byte '\n' || a[0] == byte '\r' || limit == 0){ + if (len s) { + if (s[(len s)-1] == '\r') + s[(len s)-1] = '\n'; + sys->print("<-%s\n",s); + } + if(substr != nil && contains(s, substr)) + found = 1; + for(k := 0; k < len msgs; k++) + if(len s >= len msgs[k].text && + s[0:len msgs[k].text] == msgs[k].text){ + if(found) + return (Found, s); + return (msgs[k].code, s); + } + start = sys->millisec(); + s = ""; + continue; + } + s[len s] = int a[0]; + limit--; + } + s = "no response from modem"; + if(found) + return (Found, s); + + return (Noise, s); +} + +# +# get baud rate from a connect message +# + +getspeed(msg: string, speed: int): int +{ + p := msg[7:]; # skip "CONNECT" + while(p[0] == ' ' || p[0] == '\t') + p = p[1:]; + s := int p; + if(s <= 0) + return speed; + else + return s; +} + +# +# set speed and RTS/CTS modem flow control +# + +setspeed(d: ref Device, baud: int) +{ + if(d != nil && d.ctl != nil){ + sys->fprint(d.ctl, "b%d", baud); + sys->fprint(d.ctl, "m1"); + } +} + +monitoring(d: ref Device) +{ + # if no monitor then spawn one + if(d.pid == 0) { + pidc := chan of int; + spawn monitor(d, pidc, nil); + d.pid = <-pidc; + } +} + +# +# a process to read input from a modem. +# +monitor(d: ref Device, pidc: chan of int, errc: chan of string) +{ + err := openserial(d); + pidc <-= sys->pctl(0, nil); + if(err != nil && errc != nil) + errc <-= err; + a := array[Sys->ATOMICIO] of byte; + for(;;) { + d.lock.obtain(); + d.status = "Idle"; + d.remote = ""; + setspeed(d, d.speed); + d.lock.release(); + # shuttle bytes + while((n := sys->read(d.data, a, len a)) > 0){ + d.lock.obtain(); + if (len d.avail < Sys->ATOMICIO) { + na := array[len d.avail + n] of byte; + na[0:] = d.avail[0:]; + na[len d.avail:] = a[0:n]; + d.avail = na; + } + d.lock.release(); + } + # on an error, try reopening the device + d.data = nil; + d.ctl = nil; + err = openserial(d); + if(err != nil && errc != nil) + errc <-= err; + } +} + +# +# return up to n bytes read from the modem by monitor() +# +Device.getinput(d: self ref Device, n: int): array of byte +{ + if(d==nil || n <= 0) + return nil; + a: array of byte; + d.lock.obtain(); + if(len d.avail != 0){ + if(n > len d.avail) + n = len d.avail; + a = d.avail[0:n]; + d.avail = d.avail[n:]; + } + d.lock.release(); + return a; +} + +Device.getc(d: self ref Device, msec: int): int +{ + start := sys->millisec(); + while((b := d.getinput(1)) == nil) { + if (msec && sys->millisec() > start+msec) + return 0; + sys->sleep(1); + } + return int b[0]; +} + +init(): string +{ + sys = load Sys Sys->PATH; + lock = load Lock Lock->PATH; + if(lock == nil) + return sys->sprint("can't load %s: %r", Lock->PATH); + lock->init(); + return nil; +} + +Device.new(modeminfo: ref ModemInfo, trace: int): ref Device +{ + d := ref Device; + d.lock = Semaphore.new(); + d.local = modeminfo.path; + d.pid = 0; + d.speed = 0; + d.t = *modeminfo; + if(d.t.hangup == nil) + d.t.hangup = hangupcmd; + d.trace = trace | 1; # always trace for now + return d; +} + +# +# dial a number +# +Device.dial(d: self ref Device, number: string): string +{ + monitoring(d); + + # modem type should already be established, but just in case + if(d.trace) + sys->print("modem: attention\n"); + x := attention(d); + if (x != Ok && d.trace) + return "bad response from modem"; + # + # extended Hayes commands, meaning depends on modem + # + sys->print("modem: init\n"); + if(d.t.country != nil) + applyspecial(d, d.t.country); + + if(d.t.init != nil) + applyspecial(d, d.t.init); + + if(d.t.other != nil) + applyspecial(d, d.t.other); + + applyspecial(d, d.t.errorcorrection); + + compress := Abort; + if(d.t.mnponly != nil) + compress = applyspecial(d, d.t.mnponly); + if(d.t.compression != nil) + compress = applyspecial(d, d.t.compression); + + rateadjust := Abort; + if(compress != Ok) + rateadjust = applyspecial(d, d.t.rateadjust); + applyspecial(d, d.t.flowctl); + + # finally, dialout + if(d.trace) + sys->print("modem: dial\n"); + if((dt := d.t.dialtype) == nil) + dt = "ATDT"; + err := d.send(sys->sprint("%s%s\r", dt, number)); + if(err != nil){ + if(d.trace) + sys->print("modem: can't dial %s: %s\n", number, err); + return err; + } + + (i, msg) := readmsg(d, 120, nil); + if(i != Success){ + if(d.trace) + sys->print("modem: modem error reply: %s\n", msg); + return msg; + } + + connectspeed := getspeed(msg, d.speed); + + # change line rate if not compressing + if(rateadjust == Ok) + setspeed(d, connectspeed); + + if(d.ctl != nil){ + if(d != nil) + sys->fprint(d.ctl, "s%d", connectspeed); # set DCE speed (if device implements it) + sys->fprint(d.ctl, "c1"); # enable CD monitoring + } + + return nil; +} + +dumpa(a: array of byte): string +{ + s := ""; + for(i:=0; i<len a; i++){ + b := int a[i]; + if(b >= ' ' && b < 16r7f) + s[len s] = b; + else + s += sys->sprint("\\%.2x", b); + } + return s; +} diff --git a/appl/cmd/ip/nppp/modem.m b/appl/cmd/ip/nppp/modem.m new file mode 100644 index 00000000..6e84b0e3 --- /dev/null +++ b/appl/cmd/ip/nppp/modem.m @@ -0,0 +1,47 @@ +Modem: module +{ + PATH: con "/dis/ip/nppp/modem.dis"; + + ModemInfo: adt { + path: string; + init: string; + country: string; + other: string; + errorcorrection:string; + compression: string; + flowctl: string; + rateadjust: string; + mnponly: string; + dialtype: string; + hangup: string; + }; + + Device: adt { + lock: ref Lock->Semaphore; + # modem stuff + ctl: ref Sys->FD; + data: ref Sys->FD; + + local: string; + remote: string; + status: string; + speed: int; + t: ModemInfo; + trace: int; + + # input reader + avail: array of byte; + pid: int; + + new: fn(i: ref ModemInfo, trace: int): ref Device; + dial: fn(m: self ref Device, number: string): string; + getc: fn(m: self ref Device, msec: int): int; + getinput: fn(m: self ref Device, n: int): array of byte; + send: fn(m: self ref Device, x: string): string; + close: fn(m: self ref Device): ref Sys->Connection; + onhook: fn(m: self ref Device); + }; + + init: fn(): string; + +}; diff --git a/appl/cmd/ip/nppp/pppchat.b b/appl/cmd/ip/nppp/pppchat.b new file mode 100644 index 00000000..77202b18 --- /dev/null +++ b/appl/cmd/ip/nppp/pppchat.b @@ -0,0 +1,322 @@ +implement Dialupchat; + +# +# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + Point, Rect: import draw; + +include "tk.m"; + tk: Tk; + +include "wmlib.m"; + wmlib: Wmlib; + +include "translate.m"; + translate: Translate; + Dict: import translate; + dict: ref Dict; + +Dialupchat: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +# Dimension constant for ISP Connect window +WIDTH: con 300; +HEIGHT: con 58; + +LightGreen: con "#00FF80"; # colour for successful blob +Blobx: con 8; +Gapx: con 4; +BARW: con (Blobx+Gapx)*10; # Progress bar width +BARH: con 18; # Progress bar height +DIALQUANTA : con 1000; +ICONQUANTA : con 5000; + +pppquanta := DIALQUANTA; + +Maxstep: con 9; + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + tk = load Tk Tk->PATH; + wmlib = load Wmlib Wmlib->PATH; + wmlib->init(); + + translate = load Translate Translate->PATH; + if(translate != nil) { + translate->init(); + dictname := translate->mkdictname("", "pppchat"); + dicterr: string; + (dict, dicterr) = translate->opendict(dictname); + if(dicterr != nil) + sys->fprint(sys->fildes(2), "pppchat: can't open %s: %s\n", dictname, dicterr); + }else + sys->fprint(sys->fildes(2), "pppchat: can't load %s: %r\n", Translate->PATH); + + tkargs: string; + if(args != nil) { + tkargs = hd args; + args = tl args; + } + + sys->pctl(Sys->NEWPGRP, nil); + + pppfd := sys->open("/chan/pppctl", Sys->ORDWR); + if(pppfd == nil) + error(sys->sprint("can't open /chan/pppctl: %r")); + + (t, wmctl) := wmlib->titlebar(ctxt.screen, tkargs, X("Dialup Connection"), Wmlib->Hide); + + cmd := chan of string; + tk->namechan(t, cmd, "cmd"); + + pb := Progressbar.mk(t, ".f.prog.c", (BARW, BARH)); + + config_win := array[] of { + "frame .f", + "frame .f.prog", + "frame .f.b", + + pb.tkcreate(), + "pack .f.prog.c -pady 6 -side top", + + "label .f.stat -fg blue -text {"+X("Initialising connection...")+"}", + "pack .f.stat -side top -fill x -expand 1 -anchor n", + + "pack .f -side top -expand 1 -padx 5 -pady 3 -fill both -anchor w", + "pack .f.prog -side top -expand 1 -fill x", + "button .f.b.done -text {"+X("Cancel")+"} -command {send cmd cancel}", + "pack .f.b.done -side right -padx 1 -pady 1 -anchor s", + "button .f.b.retry -text {"+X("Retry")+"} -command {send cmd retry} -state disabled", + "pack .f.b.retry -side left -padx 1 -pady 1 -anchor s", + "pack .f.b -side top -expand 1 -fill x", + + "pack propagate . 0", + "update", + }; + + for(i := 0; i < len config_win; i++) + tkcmd(t, config_win[i]); + + connected := 0; + winmapped := 1; + timecount := 0; + xmin := 0; + x := 0; + turn := 0; + + pppquanta = DIALQUANTA; + ticks := chan of int; + spawn ppptimer(ticks); + + statuslines := chan of (string, string); + pids := chan of int; + spawn ctlreader(pppfd, pids, statuslines); + ctlpid := <-pids; + +Work: + for(;;) alt { + + s := <-wmctl => + if(s == "exit") + s = "task"; + if(s == "task"){ + spawn wmlib->titlectl(t, s); + continue; + } + wmlib->titlectl(t, s); + + press := <-cmd => + case press { + "cancel" or "disconnect" => + tkcmd(t, sys->sprint(".f.stat configure -text '%s", X("Disconnecting"))); + tkcmd(t, "update"); + if(sys->fprint(pppfd, "hangup") < 0){ + err := sys->sprint("%r"); + tkcmd(t, sys->sprint(".f.stat configure -text '%s: %s", X("Error disconnecting"), X(err))); + sys->fprint(sys->fildes(2), "pppchat: can't disconnect: %s\n", err); + } + break Work; + "retry" => + if(sys->fprint(pppfd, "connect") < 0){ + err := sys->sprint("%r"); + } + } + + <-ticks => + ticks <-= 1; + if(!connected){ + if(pb != nil){ + if((turn ^= 1) == 0) + pb.setcolour("white"); + else + pb.setcolour(LightGreen); + } + tkcmd(t, "raise .; update"); + } + + (status, err) := <-statuslines => + if(status == nil){ + status = "0 1 empty status"; + if(err != nil) + sys->print("pppchat: !%s\n", err); + } else + sys->print("pppchat: %s\n", status); + (nf, flds) := sys->tokenize(status, " \t\n"); +# for(i = 0; i < len status; i++) +# if(status[i] == ' ' || status[i] == '\t') { +# status = status[i+1:]; +# break; +# } + if(nf < 3) + break; + step := int hd flds; flds = tl flds; + nstep := int hd flds; flds = tl flds; + if(step < 0) + raise "pppchat: bad step"; + case hd flds { + "error:" => + tkcmd(t, ".f.stat configure -fg red -text '"+X(status)); + tkcmd(t, ".f.b.retry configure -state normal"); + tkcmd(t, "update"); + wmlib->unhide(); + winmapped = 1; + pb.stepto(step, "red"); + #break Work; + * => + pb.setcolour(LightGreen); + pb.stepto(step, LightGreen); + } + turn = 0; + statusmsg := X(status); + tkcmd(t, ".f.stat configure -text '"+statusmsg); + tkcmd(t, "raise .; update"); + + case hd flds { + "up" or "done" => + if(!connected){ + connected = 1; + } + pppquanta = ICONQUANTA; + + # display connection speed + if(tl flds != nil) + tkcmd(t, ".f.stat configure -text {"+statusmsg+" "+"SPEED"+" hd tl flds}"); + else + tkcmd(t, ".f.stat configure -text {"+statusmsg+"}"); + tkcmd(t, ".f.b.done configure -text Disconnect -command 'send cmd disconnect"); + tkcmd(t, "update"); + sys->sleep(2000); + tkcmd(t, "pack forget .f.prog; update"); + spawn wmlib->titlectl(t, "task"); + winmapped = 0; + } + tkcmd(t, "update"); + } + <-ticks; + ticks <-= 0; # stop ppptimer + kill(ctlpid); +} + +ppptimer(ticks: chan of int) +{ + do{ + sys->sleep(pppquanta); + ticks <-= 1; + }while(<-ticks); +} + +ctlreader(fd: ref Sys->FD, pidc: chan of int, lines: chan of (string, string)) +{ + pidc <-= sys->pctl(0, nil); + buf := array[128] of byte; + while((n := sys->read(fd, buf, len buf)) > 0) + lines <-= (string buf[0:n], nil); + if(n < 0) + lines <-= (nil, sys->sprint("%r")); + else + lines <-= (nil, nil); +} + +Progressbar: adt { + t: ref Tk->Toplevel; + canvas: string; + csize: Point; + blobs: list of string; + + mk: fn(t: ref Tk->Toplevel, canvas: string, csize: Point): ref Progressbar; + tkcreate: fn(pb: self ref Progressbar): string; + setcolour: fn(pb: self ref Progressbar, c: string); + stepto: fn(pb: self ref Progressbar, step: int, col: string); + destroy: fn(pb: self ref Progressbar); +}; + +Progressbar.mk(t: ref Tk->Toplevel, canvas: string, csize: Point): ref Progressbar +{ + return ref Progressbar(t, canvas, csize, nil); +} + +Progressbar.tkcreate(pb: self ref Progressbar): string +{ + return sys->sprint("canvas %s -width %d -height %d", pb.canvas, pb.csize.x, pb.csize.y); +} + +Progressbar.setcolour(pb: self ref Progressbar, colour: string) +{ + if(pb.blobs != nil) + tkcmd(pb.t, sys->sprint("%s itemconfigure %s -fill %s; update", pb.canvas, hd pb.blobs, colour)); +} + +Progressbar.stepto(pb: self ref Progressbar, step: int, col: string) +{ + for(nblob := len pb.blobs; nblob > step+1; nblob--){ + tkcmd(pb.t, sys->sprint("%s delete %s", pb.canvas, hd pb.blobs)); + pb.blobs = tl pb.blobs; + } + if(nblob == step+1) + return; + p := Point(step*(Blobx+Gapx), 0); + r := Rect(p, p.add((Blobx, pb.csize.y-2))); + pb.blobs = tkcmd(pb.t, sys->sprint("%s create rectangle %d %d %d %d -fill %s", pb.canvas, r.min.x,r.min.y, r.max.x,r.max.y, col)) :: pb.blobs; +} + +Progressbar.destroy(pb: self ref Progressbar) +{ + tk->cmd(pb.t, "destroy "+pb.canvas); # ignore errors +} + +tkcmd(t: ref Tk->Toplevel, s: string): string +{ + e := tk->cmd(t, s); + if(e != nil && e[0] == '!') + sys->print("pppchat: tk error: %s [%s]\n", e, s); + return e; +} + +kill(pid: int) +{ + if(pid > 0 && (fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE)) != nil) + sys->fprint(fd, "kill"); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "pppchat: %s\n", s); + raise "fail:error"; +} + +X(s: string): string +{ + if(dict != nil) + return dict.xlate(s); + return s; +} diff --git a/appl/cmd/ip/nppp/ppplink.b b/appl/cmd/ip/nppp/ppplink.b new file mode 100644 index 00000000..5f0e9686 --- /dev/null +++ b/appl/cmd/ip/nppp/ppplink.b @@ -0,0 +1,782 @@ +implement PPPlink; + +# +# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "arg.m"; + +include "cfgfile.m"; + cfg: CfgFile; + ConfigFile: import cfg; + +include "lock.m"; +include "modem.m"; +include "script.m"; + +include "sh.m"; + +include "translate.m"; + translate: Translate; + Dict: import translate; + dict: ref Dict; + +PPPlink: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +PPPInfo: adt { + ipaddr: string; + ipmask: string; + peeraddr: string; + maxmtu: string; + username: string; + password: string; +}; + +modeminfo: ref Modem->ModemInfo; +context: ref Draw->Context; +pppinfo: ref PPPInfo; +scriptinfo: ref Script->ScriptInfo; +isp_number: string; +lastCdir: ref Sys->Dir; # state of file when last read +netdir := "/net"; + +Packet: adt { + src: array of byte; + dst: array of byte; + data: array of byte; +}; + +DEFAULT_ISP_DB_PATH: con "/services/ppp/isp.cfg"; # contains pppinfo & scriptinfo +DEFAULT_MODEM_DB_PATH: con "/services/ppp/modem.cfg"; # contains modeminfo +MODEM_DB_PATH: con "modem.cfg"; # contains modeminfo +ISP_DB_PATH: con "isp.cfg"; # contains pppinfo & scriptinfo + +primary := 0; +framing := 1; + +Disconnected, Modeminit, Dialling, Modemup, Scriptstart, Scriptdone, Startingppp, Startedppp, Login, Linkup: con iota; +Error: con -1; + +Ignorems: con 10*1000; # time to ignore outgoing packets between dial attempts + +statustext := array[] of { +Disconnected => "Disconnected", +Modeminit => "Initializing Modem", +Dialling => "Dialling Service Provider", +Modemup => "Logging Into Network", +Scriptstart => "Executing Login Script", +Scriptdone => "Script Execution Complete", +Startingppp => "Logging Into Network", +Startedppp => "Logging Into Network", +Login => "Verifying Password", +Linkup => "Connected", +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: ppplink [-P] [-f] [-m mtu] [local [remote]]\n"); + raise "fail:usage"; +} + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + translate = load Translate Translate->PATH; + if(translate != nil) { + translate->init(); + dictname := translate->mkdictname("", "pppclient"); + (dict, nil) = translate->opendict(dictname); + } + mtu := 1450; + + arg := load Arg Arg->PATH; + if(arg == nil) + error(0, sys->sprint("can't load %s: %r", Arg->PATH)); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'm' => + if((s := arg->arg()) == nil || !(s[0]>='0' && s[0]<='9')) + usage(); + mtu = int s; + 'P' => + primary = 1; + 'f' => + framing = 0; + * => + usage(); + } + args = arg->argv(); + arg = nil; + localip := "10.9.8.7"; # should be something locally unique + fake := 1; + if(args != nil){ + fake = 0; + localip = hd args; + args = tl args; + } + + cerr := configinit(); + if(cerr != nil) + error(0, sys->sprint("can't configure: %s", cerr)); + context = ctxt; + + # make default (for now) + # if packet appears, start ppp and reset routing until it stops + + (cfd, dir, err) := getifc(); + if(err != nil) + error(0, err); + + if(sys->fprint(cfd, "bind pkt") < 0) + error(0, sys->sprint("can't bind pkt: %r")); + if(sys->fprint(cfd, "add %s 255.255.255.0 10.9.8.0 %d", localip, mtu) < 0) + error(0, sys->sprint("can't add ppp addresses: %r")); + if(primary && addroute("0", "0", localip) < 0) + error(0, sys->sprint("can't add default route: %r")); + dfd := sys->open(dir+"/data", Sys->ORDWR); + if(dfd == nil) + error(0, sys->sprint("can't open %s: %r", dir)); + + sys->pctl(Sys->NEWPGRP, nil); + + packets := chan of ref Packet; + spawn netreader(dfd, dir, localip, fake, packets); + + logger := chan of (int, string); + iocmd := sys->file2chan("/chan", "pppctl"); + if(iocmd == nil) + error(0, sys->sprint("can't create /chan/pppctl: %r")); + spawn servestatus(iocmd.read, logger); + + starteduser := 0; + lasttime := 0; + + for(;;) alt{ + (nil, data, nil, wc) := <-iocmd.write => # remote io control + if(wc == nil) + break; + (nil, flds) := sys->tokenize(string data, " \t"); + if(len flds > 1){ + case hd flds { + "cancel" or "disconnect" or "hangup" => + ; # ignore it + "connect" => + # start connection ... + ; + * => + wreply(wc, (0, "illegal request")); + continue; + } + } + wreply(wc, (len data, nil)); + + pkt := <-packets => + sys->print("ppplink: received packet %s->%s: %d bytes\n", ipa(pkt.src), ipa(pkt.dst), len pkt.data); + if(abs(sys->millisec()-lasttime) < Ignorems){ + sys->print("ppplink: ignored, not enough time elapsed yet between dial attempts\n"); + break; + } + (ok, stat) := sys->stat(ISP_DB_PATH); + if(ok < 0 || lastCdir == nil || !samefile(*lastCdir, stat)){ + cerr = configinit(); + if(cerr != nil){ + sys->print("ppplink: can't reconfigure: %s\n", cerr); + # use existing configuration + } + } + if(!starteduser){ + sync := chan of int; + spawn userinterface(sync); + starteduser = <-sync; + } + (ppperr, pppdir) := makeconnection(packets, logger, iocmd.write); + lasttime = sys->millisec(); + if(ppperr == nil){ + sys->print("ppplink: connected on %s\n", pppdir); + # converse ... +sys->sleep(120*1000); + }else{ + sys->print("ppplink: ppp connect error: %s\n", ppperr); + hangup(pppdir); + } + } +} + +servestatus(reader: chan of (int, int, int, Sys->Rread), updates: chan of (int, string)) +{ + statuspending := 0; + statusreq: (int, int, Sys->Rread); + step := Disconnected; + statuslist := statusline(step, step, nil) :: nil; + + for(;;) alt{ + (off, nbytes, fid, rc) := <-reader=> + if(rc == nil){ + statuspending = 0; + if(step == Disconnected) + statuslist = nil; + break; + } + if(statuslist == nil){ + if(statuspending){ + alt{ + rc <-= (nil, "pppctl file already in use") => ; + * => ; + } + break; + } + statusreq = (nbytes, fid, rc); + statuspending = 1; + break; + } + alt{ + rc <-= reads(hd statuslist, 0, nbytes) => + statuslist = tl statuslist; + * => ; + } + + (code, arg) := <-updates => + # convert to string + if(code != Error) + step = code; + status := statusline(step, code, arg); + if(code == Error) + step = Disconnected; + statuslist = appends(statuslist, status); + sys->print("status: %d %d %s\n", step, code, status); + if(statuspending){ + (nbytes, nil, rc) := statusreq; + statuspending = 0; + alt{ + rc <-= reads(hd statuslist, 0, nbytes) => + statuslist = tl statuslist; + * => + ; + } + } + } +} + +makeconnection(packets: chan of ref Packet, logger: chan of (int, string), writer: chan of (int, array of byte, int, Sys->Rwrite)): (string, string) +{ + result := chan of (string, string); + sync := chan of int; + spawn pppconnect(result, sync, logger); + pid := <-sync; + for(;;) alt{ + (err, pppdir) := <-result => + # pppconnect finished + return (err, pppdir); + + pkt := <-packets => + # ignore packets whilst connecting + sys->print("ppplink: ignored packet %s->%s: %d byten", ipa(pkt.src), ipa(pkt.dst), len pkt.data); + + (nil, data, nil, wc) := <-writer => # user control + if(wc == nil) + break; + (nil, flds) := sys->tokenize(string data, " \t"); + if(len flds > 1){ + case hd flds { + "connect" => + ; # ignore it + "cancel" or "disconnect" or "hangup"=> + kill(pid, "killgrp"); + wreply(wc, (len data, nil)); + return ("cancelled", nil); + * => + wreply(wc, (0, "illegal request")); + continue; + } + } + wreply(wc, (len data, nil)); + } +} + +wreply(wc: chan of (int, string), v: (int, string)) +{ + alt{ + wc <-= v => ; + * => ; + } +} + +appends(l: list of string, s: string): list of string +{ + if(l == nil) + return s :: nil; + return hd l :: appends(tl l, s); +} + +statusline(step: int, code: int, arg: string): string +{ + s: string; + if(code >= 0 && code < len statustext){ + n := "step"; + if(code == Linkup) + n = "connect"; + s = sys->sprint("%d %d %s %s", step, len statustext, n, X(statustext[code])); + }else + s = sys->sprint("%d %d error", step, len statustext); + if(arg != nil) + s += sys->sprint(": %s", arg); + return s; +} + +getifc(): (ref Sys->FD, string, string) +{ + clonefile := netdir+"/ipifc/clone"; + cfd := sys->open(clonefile, Sys->ORDWR); + if(cfd == nil) + return (nil, nil, sys->sprint("can't open %s: %r", clonefile)); + buf := array[32] of byte; + n := sys->read(cfd, buf, len buf); + if(n <= 0) + return (nil, nil, sys->sprint("can't read %s: %r", clonefile)); + return (cfd, netdir+"/ipifc/" + string buf[0:n], nil); +} + +addroute(addr, mask, gate: string): int +{ + fd := sys->open(netdir+"/iproute", Sys->OWRITE); + if(fd == nil) + return -1; + return sys->fprint(fd, "add %s %s %s", addr, mask, gate); +} + +# uchar vihl; /* Version and header length */ +# uchar tos; /* Type of service */ +# uchar length[2]; /* packet length */ +# uchar id[2]; /* ip->identification */ +# uchar frag[2]; /* Fragment information */ +# uchar ttl; /* Time to live */ +# uchar proto; /* Protocol */ +# uchar cksum[2]; /* Header checksum */ +# uchar src[4]; /* IP source */ +# uchar dst[4]; /* IP destination */ +IPhdrlen: con 20; + +netreader(dfd: ref Sys->FD, dir: string, localip: string, fake: int, outc: chan of ref Packet) +{ + buf := array [32*1024] of byte; + while((n := sys->read(dfd, buf, len buf)) > 0){ + if(n < IPhdrlen){ + sys->print("ppplink: received short packet: %d bytes\n", n); + continue; + } + pkt := ref Packet; + if(n < 9*1024){ + pkt.data = array[n] of byte; + pkt.data[0:] = buf[0:n]; + }else{ + pkt.data = buf[0:n]; + buf = array[32*1024] of byte; + } + pkt.src = pkt.data[12:]; + pkt.dst = pkt.data[16:]; + outc <-= pkt; + } + if(n < 0) + error(1, sys->sprint("packet interface read error: %r")); + else if(n == 0) + error(1, "packet interface: end of file"); +} + +ipa(a: array of byte): string +{ + if(len a < 4) + return "???"; + return sys->sprint("%d.%d.%d.%d", int a[0], int a[1], int a[2], int a[3]); +} + +reads(str: string, off, nbytes: int): (array of byte, string) +{ + bstr := array of byte str; + slen := len bstr; + if(off < 0 || off >= slen) + return (nil, nil); + if(off + nbytes > slen) + nbytes = slen - off; + if(nbytes <= 0) + return (nil, nil); + return (bstr[off:off+nbytes], nil); +} + +readppplog(log: chan of (int, string), errfile: string, pidc: chan of int) +{ + pidc <-= sys->pctl(0, nil); + src := sys->open(errfile, Sys->OREAD); + if(src == nil) + log <-= (Error, sys->sprint("can't open %s: %r", errfile)); + + buf := array[1024] of byte; + connected := 0; + lasterror := ""; + + while((count := sys->read(src, buf, len buf)) > 0) { + (nil, tokens) := sys->tokenize(string buf[:count],"\n"); + for(; tokens != nil; tokens = tl tokens) { + case hd tokens { + "no error" => + log <-= (Linkup, nil); + lasterror = nil; + connected = 1; + "permission denied" => + lasterror = X("Username or Password Incorrect"); + log <-= (Error, lasterror); + "write to hungup channel" => + lasterror = X("Remote Host Hung Up"); + log <-= (Error, lasterror); + * => + lasterror = X(hd tokens); + log <-= (Error, lasterror); + } + } + } + if(count == 0 && connected && lasterror == nil){ # should change ip/pppmedium.c instead? + #hangup(nil); + log <-= (Error, X("Lost Connection")); + } +} + +dialup(mi: ref Modem->ModemInfo, number: string, scriptinfo: ref Script->ScriptInfo, logchan: chan of (int, string)): (string, ref Sys->Connection) +{ + logchan <-= (Modeminit, nil); + + # open & init the modem + + modeminfo = mi; + modem := load Modem Modem->PATH; + if(modem == nil) + return (sys->sprint("can't load %s: %r", Modem->PATH), nil); + err := modem->init(); + if(err != nil) + return (sys->sprint("couldn't init modem: %s", err), nil); + Device: import modem; + d := Device.new(modeminfo, 1); + logchan <-= (Dialling, number); + err = d.dial(number); + if(err != nil){ + d.close(); + return (err, nil); + } + logchan <-= (Modemup, nil); + + # login script + + if(scriptinfo != nil) { + logchan <-= (Scriptstart, nil); + err = runscript(modem, d, scriptinfo); + if(err != nil){ + d.close(); + return (err, nil); + } + logchan <-= (Scriptdone, nil); + } + + mc := d.close(); + return (nil, mc); + +} + +startppp(logchan: chan of (int, string), pppinfo: ref PPPInfo): (string, string) +{ + (ifd, dir, err) := getifc(); + if(ifd == nil) + return (err, nil); + + sync := chan of int; + spawn readppplog(logchan, dir + "/err", sync); # unbind gives eof on err + <-sync; + + if(pppinfo.ipaddr == nil) + pppinfo.ipaddr = "-"; +# if(pppinfo.ipmask == nil) +# pppinfo.ipmask = "255.255.255.255"; + if(pppinfo.peeraddr == nil) + pppinfo.peeraddr = "-"; + if(pppinfo.maxmtu == nil) + pppinfo.maxmtu = "-"; +# if(pppinfo.maxmtu <= 0) +# pppinfo.maxmtu = mtu; +# if(pppinfo.maxmtu < 576) +# pppinfo.maxmtu = 576; + if(pppinfo.username == nil) + pppinfo.username = "-"; + if(pppinfo.password == nil) + pppinfo.password = "-"; + + ifc := "bind ppp "+modeminfo.path+" "+ pppinfo.ipaddr+" "+pppinfo.peeraddr+" "+pppinfo.maxmtu + +" "+string framing+" "+pppinfo.username+" "+pppinfo.password; + + if(sys->fprint(ifd, "%s", ifc) < 0) + return (sys->sprint("can't bind ppp to %s: %r", dir), nil); + + sys->print("ppplink: %s\n", ifc); + + return (nil, dir); +} + +runscript(modem: Modem, dev: ref Modem->Device, scriptinfo: ref Script->ScriptInfo): string +{ + script := load Script Script->PATH; + if(script == nil) + return sys->sprint("can't load %s: %r", Script->PATH); + err := script->init(modem); + if(err != nil) + return err; + return script->execute(dev, scriptinfo); +} + +hangup(pppdir: string) +{ + sys->print("ppplink: hangup...\n"); + if(pppdir != nil){ # shut down the PPP link + fd := sys->open(pppdir + "/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "unbind") < 0) + sys->print("ppplink: hangup: can't unbind ppp on %s: %r\n", pppdir); + fd = nil; + } + modem := load Modem Modem->PATH; + if(modem == nil) { + sys->print("ppplink: hangup: can't load %s: %r", Modem->PATH); + return; + } + err := modem->init(); + if(err != nil){ + sys->print("ppplink: hangup: couldn't init modem: %s", err); + return; + } + Device: import modem; + d := Device.new(modeminfo, 1); + if(d != nil){ + d.onhook(); + d.close(); + } +} + +kill(pid: int, msg: string) +{ + fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "%s", msg) < 0) + sys->print("pppclient: can't %s %d: %r\n", msg, pid); +} + +error(dokill: int, s: string) +{ + sys->fprint(sys->fildes(2), "ppplink: %s\n", s); + if(dokill) + kill(sys->pctl(0, nil), "killgrp"); + raise "fail:error"; +} + +X(s : string) : string +{ + if(dict != nil) + return dict.xlate(s); + return s; +} + +cfile(file: string): string +{ + if(len file > 0 && file[0] == '/') + return file; + return "/usr/"+user()+"/config/"+file; +} + +user(): string +{ + fd := sys->open("/dev/user", Sys->OREAD); + buf := array[64] of byte; + if(fd != nil && (n := sys->read(fd, buf, len buf)) > 0) + return string buf[0:n]; + return "inferno"; # hmmm. +} + +cfvalue(c: ref ConfigFile, key: string) :string +{ + s := ""; + for(values := c.getcfg(key); values != nil; values = tl values){ + if(s != "") + s[len s] = ' '; + s += hd values; + } + return s; +} + +configinit(): string +{ + cfg = load CfgFile CfgFile->PATH; + if(cfg == nil) + return sys->sprint("can't load %s: %r", CfgFile->PATH); + + # Modem Configuration + + modemdb := cfile(MODEM_DB_PATH); + cfg->verify(DEFAULT_MODEM_DB_PATH, modemdb); + modemcfg := cfg->init(modemdb); + if(modemcfg == nil) + return sys->sprint("can't open %s: %r", modemdb); + modeminfo = ref Modem->ModemInfo; + modeminfo.path = cfvalue(modemcfg, "PATH"); + modeminfo.init = cfvalue(modemcfg, "INIT"); + modeminfo.country = cfvalue(modemcfg, "COUNTRY"); + modeminfo.other = cfvalue(modemcfg, "OTHER"); + modeminfo.errorcorrection = cfvalue(modemcfg,"CORRECT"); + modeminfo.compression = cfvalue(modemcfg,"COMPRESS"); + modeminfo.flowctl = cfvalue(modemcfg,"FLOWCTL"); + modeminfo.rateadjust = cfvalue(modemcfg,"RATEADJ"); + modeminfo.mnponly = cfvalue(modemcfg,"MNPONLY"); + modeminfo.dialtype = cfvalue(modemcfg,"DIALING"); + if(modeminfo.dialtype!="ATDP") + modeminfo.dialtype="ATDT"; + + ispdb := cfile(ISP_DB_PATH); + cfg->verify(DEFAULT_ISP_DB_PATH, ispdb); + sys->print("cfg->init(%s)\n", ispdb); + + # ISP Configuration + pppcfg := cfg->init(ispdb); + if(pppcfg == nil) + return sys->sprint("can't read or create ISP configuration file %s: %r", ispdb); + (ok, stat) := sys->stat(ispdb); + if(ok >= 0) + lastCdir = ref stat; + + pppinfo = ref PPPInfo; + isp_number = cfvalue(pppcfg, "NUMBER"); + pppinfo.ipaddr = cfvalue(pppcfg,"IPADDR"); + pppinfo.ipmask = cfvalue(pppcfg,"IPMASK"); + pppinfo.peeraddr = cfvalue(pppcfg,"PEERADDR"); + pppinfo.maxmtu = cfvalue(pppcfg,"MAXMTU"); + pppinfo.username = cfvalue(pppcfg,"USERNAME"); + pppinfo.password = cfvalue(pppcfg,"PASSWORD"); + + info := pppcfg.getcfg("SCRIPT"); + if(info != nil) { + scriptinfo = ref Script->ScriptInfo; + scriptinfo.path = hd info; + scriptinfo.username = pppinfo.username; + scriptinfo.password = pppinfo.password; + } else + scriptinfo = nil; + + info = pppcfg.getcfg("TIMEOUT"); + if(info != nil) + scriptinfo.timeout = int (hd info); + cfg = nil; # unload it + + if(modeminfo.path == nil) + return "no modem device configured"; + if(isp_number == nil) + return "no telephone number configured for ISP"; + + return nil; +} + +isipaddr(a: string): int +{ + i, c, ac, np : int = 0; + + for(i = 0; i < len a; i++) { + c = a[i]; + if(c >= '0' && c <= '9') { + np = 10*np + c - '0'; + continue; + } + if(c == '.' && np) { + ac++; + if(np > 255) + return 0; + np = 0; + continue; + } + return 0; + } + return np && np < 256 && ac == 3; +} + +userinterface(sync: chan of int) +{ + pppgui := load Command "pppchat.dis"; + if(pppgui == nil){ + sys->fprint(sys->fildes(2), "ppplink: can't load %s: %r\n", "/dis/svc/nppp/pppchat.dis"); + # TO DO: should be optional + sync <-= 0; + } + + sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2}); + sync <-= sys->pctl(0, nil); + pppgui->init(context, "pppchat" :: nil); +} + +pppconnect(result: chan of (string, string), sync: chan of int, status: chan of (int, string)) +{ + sys->pctl(Sys->NEWPGRP|Sys->NEWFD, list of {0, 1, 2}); + sync <-= sys->pctl(0, nil); + pppdir: string; + (err, mc) := dialup(modeminfo, isp_number, scriptinfo, status); # mc keeps connection open until startppp binds it to ppp + if(err == nil){ + if(0 && (cfd := mc.cfd) != nil){ + sys->fprint(cfd, "m1"); # cts/rts flow control/fifo's on + sys->fprint(cfd, "q64000"); # increase queue size to 64k + sys->fprint(cfd, "n1"); # nonblocking writes on + sys->fprint(cfd, "r1"); # rts on + sys->fprint(cfd, "d1"); # dtr on + } + status <-= (Startingppp, nil); + (err, pppdir) = startppp(status, pppinfo); + if(err == nil){ + status <-= (Startedppp, nil); + result <-= (nil, pppdir); + return; + } + } + status <-= (Error, err); + result <-= (err, nil); +} + +getspeed(file: string): string +{ + return findrate("/dev/modemstat", "rcvrate" :: "baud" :: nil); +} + +findrate(file: string, opt: list of string): string +{ + fd := sys->open(file, sys->OREAD); + if(fd == nil) + return nil; + buf := array [1024] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 1) + return nil; + (nil, flds) := sys->tokenize(string buf[0:n], " \t\r\n"); + for(; flds != nil; flds = tl flds) + for(l := opt; l != nil; l = tl l) + if(hd flds == hd l) + return hd tl flds; + return nil; +} + +samefile(d1, d2: Sys->Dir): int +{ + return d1.dev==d2.dev && d1.dtype==d2.dtype && + d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers && + d1.mtime==d2.mtime; +} + +abs(n: int): int +{ + if(n < 0) + return -n; + return n; +} diff --git a/appl/cmd/ip/nppp/ppptest.b b/appl/cmd/ip/nppp/ppptest.b new file mode 100644 index 00000000..af8e16e0 --- /dev/null +++ b/appl/cmd/ip/nppp/ppptest.b @@ -0,0 +1,90 @@ +# Last change: R 24 May 2001 11:05 am +implement PPPTest; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "lock.m"; +include "modem.m"; +include "script.m"; +include "pppclient.m"; +include "pppgui.m"; + +PPPTest: module { + init: fn(nil: ref Draw->Context, args: list of string); +}; +usage() +{ + sys->print("ppptest device modem_init tel user password \n"); + sys->print("Example: ppptest /dev/modem atw2 4125678 rome xxxxxxxx\n"); + exit; + +} +init( ctxt: ref Draw->Context, argv: list of string ) +{ + sys = load Sys Sys->PATH; + + mi: Modem->ModemInfo; + pi: PPPClient->PPPInfo; + tel : string; +# si: Script->ScriptInfo; + argv = tl argv; + if(argv == nil) + usage(); + else + mi.path = hd argv; + + argv = tl argv; + if(argv == nil) + usage(); + else + mi.init = hd argv; + argv = tl argv; + if(argv == nil) + usage(); + else + tel = hd argv; + argv = tl argv; + if(argv == nil) + usage(); + else + pi.username = hd argv; + argv = tl argv; + if(argv==nil) + usage(); + else + pi.password = hd argv; + + + #si.path = "rdid.script"; + #si.username = "ericvh"; + #si.password = "foobar"; + #si.timeout = 60; + + + ppp := load PPPClient PPPClient->PATH; + + logger := chan of int; + + spawn ppp->connect( ref mi, tel, nil, ref pi, logger ); + + pppgui := load PPPGUI PPPGUI->PATH; + (respchan, err) := pppgui->init(ctxt, logger, ppp, nil); + if(err != nil){ + sys->print("ppptest: can't %s: %s\n", PPPGUI->PATH, err); + exit; + } + + event := 0; + while(1) { + event =<- respchan; + sys->print("GUI event received: %d\n",event); + if(event) { + sys->print("success"); + exit; + } else { + raise "fail: Couldn't connect to ISP"; + } + } +} diff --git a/appl/cmd/ip/nppp/script.b b/appl/cmd/ip/nppp/script.b new file mode 100644 index 00000000..d929ff7a --- /dev/null +++ b/appl/cmd/ip/nppp/script.b @@ -0,0 +1,171 @@ +implement Script; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "lock.m"; +include "modem.m"; + modem: Modem; + Device: import modem; + +include "script.m"; + +Scriptlim: con 32*1024; # should be enough for all + +init(mm: Modem): string +{ + sys = load Sys Sys->PATH; + modem = mm; + str = load String String->PATH; + if(str == nil) + return sys->sprint("can't load %s: %r", String->PATH); + return nil; +} + +execute(m: ref Modem->Device, scriptinfo: ref ScriptInfo): string +{ + if(scriptinfo.path != nil) { + if(m.trace) + sys->print("script: using %s\n",scriptinfo.path); + # load the script + err: string; + (scriptinfo.content, err) = scriptload(scriptinfo.path); + if(err != nil) + return err; + }else{ + if(m.trace) + sys->print("script: using inline script\n"); + } + + if(scriptinfo.timeout == 0) + scriptinfo.timeout = 20; + + tend := sys->millisec() + 1000*scriptinfo.timeout; + + for(conv := scriptinfo.content; conv != nil; conv = tl conv){ + e, s: string = nil; + p := hd conv; + if(len p == 0) + continue; + if(m.trace) + sys->print("script: %s\n",p); + if(p[0] == '-') { # just send + if(len p == 1) + continue; + s = p[1:]; + } else { + (n, esl) := sys->tokenize(p, "-"); + if(n > 0) { + e = hd esl; + esl = tl esl; + if(n > 1) + s = hd esl; + } + } + if(e != nil) { + if(match(m, special(e,scriptinfo), tend-sys->millisec()) == 0) { + if(m.trace) + sys->print("script: match failed\n"); + return "script failed"; + } + } + if(s != nil) + m.send(special(s, scriptinfo)); + } + if(m.trace) + sys->print("script: done\n"); + return nil; +} + +match(m: ref Modem->Device, s: string, msec: int): int +{ + for(;;) { + c := m.getc(msec); + if(c == '\r') + c = '\n'; + if(m.trace) + sys->print("%c",c); + if(c == 0) + return 0; + head: + while(c == s[0]) { + i := 1; + while(i < len s) { + c = m.getc(msec); + if(c == '\r') + c = '\n'; + if(m.trace) + sys->print("%c",c); + if(c == 0) + return 0; + if(c != s[i]) + continue head; + i++; + } + return 1; + } + if(c == '~') + return 1; # assume PPP for now + } +} + +# +# Expand special script sequences +# +special(s: string, scriptinfo: ref ScriptInfo): string +{ + if(s == "$username") # special variable + s = scriptinfo.username; + else if(s == "$password") + s = scriptinfo.password; + return deparse(s); +} + +deparse(s: string): string +{ + r: string = ""; + for(i:=0; i < len s; i++) { + c := s[i]; + if(c == '\\' && i+1 < len s) { + c = s[++i]; + case c { + 't' => c = '\t'; + 'n' => c = '\n'; + 'r' => c = '\r'; + 'b' => c = '\b'; + 'a' => c = '\a'; + 'v' => c = '\v'; + '0' => c = '\0'; + '$' => c = '$'; + 'u' => + if(i+4 < len s) { + i++; + (c, nil) = str->toint(s[i:i+4], 16); + i+=3; + } + } + } + r[len r] = c; + } + return r; +} + +scriptload(path: string): (list of string, string) +{ + dfd := sys->open(path, Sys->OREAD); + if(dfd == nil) + return (nil, sys->sprint("can't open script %s: %r", path)); + + b := array[Scriptlim] of byte; + n := sys->read(dfd, b, len b); + if(n < 0) + return (nil, sys->sprint("can't read script %s: %r", path)); + + (nil, script) := sys->tokenize(string b[0:n], "\n"); + return (script, nil); +} diff --git a/appl/cmd/ip/nppp/script.m b/appl/cmd/ip/nppp/script.m new file mode 100644 index 00000000..a1f66e06 --- /dev/null +++ b/appl/cmd/ip/nppp/script.m @@ -0,0 +1,15 @@ +Script: module +{ + PATH: con "/dis/ip/nppp/script.dis"; + + ScriptInfo: adt { + path: string; + content: list of string; + timeout: int; + username: string; + password: string; + }; + + init: fn(m: Modem): string; + execute: fn(m: ref Modem->Device, scriptinfo: ref ScriptInfo): string; +}; diff --git a/appl/cmd/ip/obootpd.b b/appl/cmd/ip/obootpd.b new file mode 100644 index 00000000..8795d672 --- /dev/null +++ b/appl/cmd/ip/obootpd.b @@ -0,0 +1,777 @@ +implement Bootpd; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "attrdb.m"; + attrdb: Attrdb; + Db, Dbentry: import attrdb; + +include "ip.m"; + ip: IP; + IPaddr, Udphdr: import ip; + +include "ether.m"; + ether: Ether; + +include "arg.m"; + +Bootpd: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; +debug: int; +sniff: int; +verbose: int; + +siaddr: array of byte; +sysname: string; +progname := "bootpd"; +net := "/net"; + +Udphdrsize: con IP->OUdphdrlen; + +NEED_HA: con 1; +NEED_IP: con 0; +NEED_BF: con 0; +NEED_SM: con 0; +NEED_GW: con 0; +NEED_FS: con 0; +NEED_AU: con 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + loadfail(Bufio->PATH); + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + loadfail(Attrdb->PATH); + attrdb->init(); + ip = load IP IP->PATH; + if(ip == nil) + loadfail(IP->PATH); + ip->init(); + ether = load Ether Ether->PATH; + if(ether == nil) + loadfail(Ether->PATH); + ether->init(); + + fname := "/services/bootp/db"; + verbose = 1; + sniff = 0; + debug = 0; + arg := load Arg Arg->PATH; + if(arg == nil) + raise "fail: load Arg"; + arg->init(args); + arg->setusage("bootpd [-dsqv] [-f file] [-x network]"); + progname = arg->progname(); + while((o := arg->opt()) != 0) + case o { + 'd' => debug++; + 's' => sniff = 1; debug = 255; + 'q' => verbose = 0; + 'v' => verbose = 1; + 'x' => net = arg->earg(); + 'f' => fname = arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + + sys->pctl(Sys->FORKFD|Sys->FORKNS, nil); + if(tabopen(fname)) + raise "fail: open database"; + + if(!sniff && (err := dbread()) != nil) + error(sys->sprint("error in %s: %s", fname, err)); + + addr := net+"/udp!*!67"; + if(debug) + sys->fprint(stderr, "bootp: announcing %s\n", addr); + (ok, c) := sys->announce(addr); + if(ok < 0) + error(sys->sprint("can't announce %s: %r", addr)); + get_sysname(); + get_ip(); + + if(sys->fprint(c.cfd, "headers") < 0) + error(sys->sprint("can't set headers mode: %r")); + sys->fprint(c.cfd, "oldheaders"); + + if(debug) + sys->fprint(stderr, "bootp: opening %s/data\n", c.dir); + c.dfd = sys->open(c.dir+"/data", sys->ORDWR); + if(c.dfd == nil) + error(sys->sprint("can't open %s/data: %r", c.dir)); + + spawn server(c); +} + +loadfail(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(stderr, "bootp: %s\n", s); + raise "fail:error"; +} + +server(c: Sys->Connection) +{ + buf := array[2048] of byte; + badread := 0; + for(;;) { + if(debug) + sys->fprint(stderr, "bootp: listening for bootp requests...\n"); + n := sys->read(c.dfd, buf, len buf); + if(n <0) { + if (badread++ > 10) + break; + continue; + } + badread = 0; + if(n < Udphdrsize) { + if(debug) + sys->fprint(stderr, "bootp: short Udphdr: %d bytes\n", n); + continue; + } + hdr := Udphdr.unpack(buf, Udphdrsize); + if(debug) + sys->fprint(stderr, "bootp: received request from udp!%s!%d\n", hdr.raddr.text(), hdr.rport); + if(n < Udphdrsize+300) { + if(debug) + sys->fprint(stderr, "bootp: short request of %d bytes\n", n - Udphdrsize); + continue; + } + + (err, bootp) := M2S(buf[Udphdrsize:]); + if(err != nil) { + if(debug) + sys->fprint(stderr, "bootp: M2S failed: %s\n", err); + continue; + } + if(debug >= 2) + ppkt(bootp); + if(sniff) + continue; + if(bootp.htype != byte 1 || bootp.hlen != byte 6) { + # if it isn't ether, we don't do it + if(debug) + sys->fprint(stderr, "bootp: hardware type not ether; ignoring.\n"); + continue; + } + if((err = dbread()) != nil) { + sys->fprint(stderr, "bootp: getreply: dbread failed: %s\n", err); + continue; + } + rec := lookup(bootp); + if(rec == nil) { + # we can't answer this request + if(debug) + sys->fprint(stderr, "bootp: cannot answer request.\n"); + continue; + } + if(debug){ + sys->fprint(stderr, "bootp: found a matching entry:\n"); + pinfbp(rec); + } + mkreply(bootp, rec); + if(verbose) sys->print("bootp: %s -> %s %s\n", ether->text(rec.ha), rec.hostname, iptoa(rec.ip)); + if(debug >= 2) { + sys->fprint(stderr, "bootp: reply message:\n"); + ppkt(bootp); + } + repl:= S2M(bootp); + + if(debug) + sys->fprint(stderr, "bootp: sending reply.\n"); + arpenter(iptoa(rec.ip), ether->text(rec.ha)); + send(repl); + } + sys->fprint(stderr, "bootp: %d read errors: %r\n", badread); +} + +arpenter(ip, ha: string) +{ + if(debug) sys->fprint(stderr, "bootp: arp: %s -> %s\n", ip, ha); + fd := sys->open(net+"/arp", Sys->OWRITE); + if(fd == nil) { + if(debug) + sys->fprint(stderr, "bootp: arp open failed: %r\n"); + return; + } + if(sys->fprint(fd, "add %s %s", ip, ha) < 0){ + if(debug) + sys->fprint(stderr, "bootp: error writing arp: %r\n"); + } +} + +get_sysname() +{ + fd := sys->open("/dev/sysname", sys->OREAD); + if(fd == nil) { + sysname = "anon"; + return; + } + buf := array[128] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) { + sysname = "anon"; + return; + } + sysname = string buf[0:n]; +} + +get_ip() +{ + siaddr = array[4] of { * => byte 0 }; + # get a local IP address by translating our sysname with cs(8) + fd := sys->open(net+"/cs", Sys->ORDWR); + if(fd == nil){ + if(debug) + sys->fprint(stderr, "bootp: cannot open %s/cs for reading: %r.\n", net); + return; + } + if(sys->fprint(fd, "net!%s!0", sysname) < 0){ + if(debug) + sys->fprint(stderr, "bootp: can't translate net!%s!0 via %s/cs: %r\n", sysname, net); + return; + } + sys->seek(fd, big 0, 0); + a := array[1024] of byte; + n := sys->read(fd, a, len a); + if(n < 0) { + if(debug) sys->fprint(stderr, "bootp: read from /net/cs: %r.\n"); + return; + } + reply := string a[0:n]; + if(debug) sys->fprint(stderr, "bootp: read %s from /net/cs\n", reply); + + (l, addr):= sys->tokenize(reply, " "); + if(l != 2) { + if(debug) sys->fprint(stderr, "bootp: bad format from cs\n"); + return; + } + (l, addr) = sys->tokenize(hd tl addr, "!"); + if(l < 2) { + if(debug) sys->fprint(stderr, "bootp: short addr from cs\n"); + return; + } + err:= ""; + (err, siaddr) = get_ipaddr(hd addr); + if(err != nil || siaddr == nil) { + if(debug) sys->fprint(stderr, "bootp: invalid local IP addr %s.\n", hd tl addr); + siaddr = array[4] of { * => byte 0 }; + }; + if(debug) sys->fprint(stderr, "bootp: local IP address is %s.\n", iptoa(siaddr)); +} + +# byte op; /* opcode */ +# byte htype; /* hardware type */ +# byte hlen; /* hardware address len */ +# byte hops; /* hops */ +# byte xid[4]; /* a random number */ +# byte secs[2]; /* elapsed snce client started booting */ +# byte pad[2]; +# byte ciaddr[4]; /* client IP address (client tells server) */ +# byte yiaddr[4]; /* client IP address (server tells client) */ +# byte siaddr[4]; /* server IP address */ +# byte giaddr[4]; /* gateway IP address */ +# byte chaddr[16]; /* client hardware address */ +# byte sname[64]; /* server host name (optional) */ +# byte file[128]; /* boot file name */ +# byte vend[128]; /* vendor-specific goo */ + +BootpPKT: adt +{ + op: byte; # Start of udp datagram + htype: byte; + hlen: byte; + hops: byte; + xid: int; + secs: int; + ciaddr: array of byte; + yiaddr: array of byte; + siaddr: array of byte; + giaddr: array of byte; + chaddr: array of byte; + sname: string; + file: string; + vend: array of byte; +}; + +InfBP: adt { + hostname: string; + + ha: array of byte; # hardware addr + ip: array of byte; # client IP addr + bf: array of byte; # boot file path + sm: array of byte; # subnet mask + gw: array of byte; # gateway IP addr + fs: array of byte; # file server IP addr + au: array of byte; # authentication server IP addr +}; + +records: array of ref InfBP; + +tabbio: ref Bufio->Iobuf; +tabname: string; +mtime: int; + +tabopen(fname: string): int +{ + if(sniff) return 0; + tabname = fname; + if((tabbio = bufio->open(tabname, bufio->OREAD)) == nil) { + sys->fprint(stderr, "bootp: cannot open %s: %r\n", tabname); + return 1; + } + return 0; +} + +send(msg: array of byte) +{ + if(debug) sys->fprint(stderr, "bootp: dialing udp!broadcast!68\n"); + (n, c) := sys->dial(net+"/udp!255.255.255.255!68", "67"); +# (n, c) := sys->dial(net+"/udp!255.255.255.255!68", "192.168.129.1!67"); + if(n < 0) { + sys->fprint(stderr, "bootp: send: error calling dial: %r\n"); + return; + } + if(debug) sys->fprint(stderr, "bootp: writing to %s/data\n", c.dir); + n = sys->write(c.dfd, msg, len msg); + if(n <=0) { + sys->fprint(stderr, "bootp: send: error writing to %s/data: %r\n", c.dir); + return; + } + if(debug) sys->fprint(stderr, "bootp: successfully wrote %d bytes to %s/data\n", n, c.dir); +} + +mkreply(bootp: ref BootpPKT, rec: ref InfBP) +{ + bootp.op = byte 2; # boot reply + bootp.yiaddr = rec.ip; + bootp.siaddr = siaddr; + bootp.giaddr = array[4] of { * => byte 0 }; + bootp.sname = sysname; + bootp.file = string rec.bf; + bootp.vend = array of byte sys->sprint("p9 %s %s %s %s", iptoa(rec.sm), iptoa(rec.fs), iptoa(rec.au), iptoa(rec.gw)); +} + +lookup(bootp: ref BootpPKT): ref InfBP +{ + for(i := 0; i < len records; i++) + if(eqa(bootp.chaddr[0:6], records[i].ha) || eqa(bootp.ciaddr, records[i].ip)) + return records[i]; + return nil; +} + +dbread(): string +{ + (n, dir) := sys->fstat(tabbio.fd); + if(n < 0) + return sys->sprint("cannot fstat %s: %r", tabname); + if(mtime == 0 || mtime != dir.mtime) { + if(bufio->tabbio.seek(big 0, Sys->SEEKSTART) < big 0) + return sys->sprint("error seeking to start of %s.", tabname); + mtime = dir.mtime; + lnum: int = 0; + trecs: list of ref InfBP; +LINES: while((line := bufio->tabbio.gets('\n')) != nil) { + lnum++; + if(line[0] == '#') # comment + continue LINES; + fields: list of string; + (n, fields) = sys->tokenize(line, ":\r\n"); + if(n <= 0) { # blank line or colons + if(len line > 0) { + sys->fprint(stderr, "bootp: %s: %d empty entry.\n", tabname, lnum); + } + continue LINES; + } + rec := ref InfBP; + rec.hostname = hd fields; + fields = tl fields; + err: string; +FIELDS: for(; fields != nil; fields = tl fields) { + field := hd fields; + if(len field <= len "xx=") { + sys->fprint(stderr, "bootp: %s:%d invalid field \"%s\" in entry for %s", + tabname, lnum, field, rec.hostname); + continue FIELDS; + } + err = nil; + case field[0:3] { + "ha=" => + if(rec.ha != nil) { + sys->fprint(stderr, + "bootp: warning: %s:%d hardware address redefined for %s.\n", + tabname, lnum, rec.hostname); + } + (err, rec.ha) = get_haddr(field[3:]); + "ip=" => + if(rec.ip != nil) { + sys->fprint(stderr, "bootp: warning: %s:%d IP address redefined for %s.\n", + tabname, lnum, rec.hostname); + } + (err, rec.ip) = get_ipaddr(field[3:]); + "bf=" => + if(rec.bf != nil) { + sys->fprint(stderr, "bootp: warning: %s:%d bootfile redefined for %s.\n", + tabname, lnum, rec.hostname); + } + (err, rec.bf) = get_path(field[3:]); + "sm=" => + if(rec.sm != nil) { + sys->fprint(stderr, "bootp: warning: %s:%d subnet mask redefined for %s.\n", + tabname, lnum, rec.hostname); + } + (err, rec.sm) = get_ipaddr(field[3:]); + "gw=" => + if(rec.gw != nil) { + sys->fprint(stderr, "bootp: warning: %s:%d gateway redefined for %s.\n", + tabname, lnum, rec.hostname); + } + (err, rec.gw) = get_ipaddr(field[3:]); + "fs=" => + if(rec.fs != nil) { + sys->fprint(stderr, "bootp: warning: %s:%d file server redefined for %s.\n", + tabname, lnum, rec.hostname); + } + (err, rec.fs) = get_ipaddr(field[3:]); + "au=" => + if(rec.au != nil) { + sys->fprint(stderr, + "bootp: warning: %s:%d authentication server redefined for %s.\n", + tabname, lnum, rec.hostname); + } + (err, rec.au) = get_ipaddr(field[3:]); + * => + sys->fprint(stderr, + "bootp: %s:%d invalid or unsupported tag \"%s\" in entry for %s.\n", + tabname, lnum, field[0:2], rec.hostname); + continue FIELDS; + } + if(err != nil) { + sys->fprint(stderr, + "bootp: %s:%d %s for %s.\nbootp: skipping entry for %s.\n", + tabname, lnum, err, rec.hostname, + rec.hostname); + continue LINES; + } + } + if(rec.ha == nil) { + if(NEED_HA) { + sys->fprint(stderr, "bootp: %s:%d no hardware address defined for %s.\n", + tabname, lnum, rec.hostname); + sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname); + continue LINES; + } + } + if(rec.ip == nil) { + if(NEED_IP) { + sys->fprint(stderr, "bootp: %s:%d no IP address defined for %s.\n", + tabname, lnum, rec.hostname); + sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname); + continue LINES; + } + } + if(rec.bf == nil) { + if(NEED_BF) { + sys->fprint(stderr, "bootp: %s:%d no bootfile defined for %s.\n", + tabname, lnum, rec.hostname); + sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname); + continue LINES; + } + } + if(rec.sm == nil) { + if(NEED_SM) { + sys->fprint(stderr, "bootp: %s:%d no subnet mask defined for %s.\n", + tabname, lnum, rec.hostname); + sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname); + continue LINES; + } + } + if(rec.gw == nil) { + if(NEED_GW) { + sys->fprint(stderr, "bootp: %s:%d no gateway defined for %s.\n", + tabname, lnum, rec.hostname); + sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname); + continue LINES; + } + } + if(rec.fs == nil) { + if(NEED_FS) { + sys->fprint(stderr, "bootp: %s:%d no file server defined for %s.\n", + tabname, lnum, rec.hostname); + sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname); + continue LINES; + } + } + if(rec.au == nil) { + if(NEED_AU) { + sys->fprint(stderr, + "bootp: %s:%d no authentication server defined for %s.\n", + tabname, lnum, rec.hostname); + sys->fprint(stderr, "bootp: skipping entry for %s.\n", rec.hostname); + continue LINES; + } + } + if(debug) pinfbp(rec); + trecs = rec :: trecs; + } + if(trecs == nil) { + sys->fprint(stderr, "bootp: no valid entries in %s.\n", tabname); + if(records != nil) { + sys->fprint(stderr, "bootp: reverting to previous state.\n"); + return nil; + } + return "no entries."; + } + records = array[len trecs] of ref InfBP; + for(n = len records; n > 0; trecs = tl trecs) + records[--n] = hd trecs; + } + return nil; +} + +get_haddr(str: string): (string, array of byte) +{ + addr := ether->parse(str); + if(addr == nil) + return (sys->sprint("invalid hardware address \"%s\"", str), nil); + return (nil, addr); +} + +get_ipaddr(str: string): (string, array of byte) +{ + (ok, a) := IPaddr.parse(str); + if(ok < 0) + return (sys->sprint("invalid address: %s", str), nil); + return (nil, a.v4()); +} + +get_path(str: string): (string, array of byte) +{ + if(str == nil) { + return ("nil path", nil); + } + path := array of byte str; + if(len path > 128) + return (sys->sprint("path too long (>128 bytes) \"%s...\"", string path[0:16]), nil); + return (nil, path); +} + +iptoa(addr: array of byte): string +{ + if(len addr != 4) + return "0.0.0.0"; + return sys->sprint("%d.%d.%d.%d", + int addr[0], + int addr[1], + int addr[2], + int addr[3]); +} + +dtoa(data: array of byte): string +{ + if(data == nil) + return nil; + result: string; + for(i:=0; i < len data; i++) + result += sys->sprint(".%d", int data[i]); + return result[1:]; +} + +bptohw(bp: ref BootpPKT): string +{ + l := int bp.hlen; + if(l > 0 && l < len bp.chaddr) + return ether->text(bp.chaddr[0:l]); + return ""; +} + +ctostr(cstr: array of byte): string +{ + for(i:=0; i<len cstr; i++) + if(cstr[i] == byte 0) + break; + return string cstr[0:i]; +} + +strtoc(s: string): array of byte +{ + as := array of byte s; + cs := array[1 + len as] of byte; + cs[0:] = as; + cs[len cs - 1] = byte 0; + return cs; +} + +ppkt(bootp: ref BootpPKT) +{ + sys->fprint(stderr, "BootpPKT {\n"); + sys->fprint(stderr, "\top == %d\n", int bootp.op); + sys->fprint(stderr, "\thtype == %d\n", int bootp.htype); + sys->fprint(stderr, "\thlen == %d\n", int bootp.hlen); + sys->fprint(stderr, "\thops == %d\n", int bootp.hops); + sys->fprint(stderr, "\txid == %d\n", bootp.xid); + sys->fprint(stderr, "\tsecs == %d\n", bootp.secs); + sys->fprint(stderr, "\tC client == %s\n", dtoa(bootp.ciaddr)); + sys->fprint(stderr, "\tY client == %s\n", dtoa(bootp.yiaddr)); + sys->fprint(stderr, "\tserver == %s\n", dtoa(bootp.siaddr)); + sys->fprint(stderr, "\tgateway == %s\n", dtoa(bootp.giaddr)); + sys->fprint(stderr, "\thwaddr == %s\n", bptohw(bootp)); + sys->fprint(stderr, "\thost == %s\n", bootp.sname); + sys->fprint(stderr, "\tfile == %s\n", bootp.file); + sys->fprint(stderr, "\tmagic == %s\n", magic(bootp.vend[0:4])); + if(magic(bootp.vend[0:4]) == "plan9") { + (n, strs) := sys->tokenize(string bootp.vend[4:], " \r\n"); + if(strs != nil) { + sys->fprint(stderr, "\t\tsm == %s\n", hd strs); + strs = tl strs; + } + if(strs != nil) { + sys->fprint(stderr, "\t\tfs == %s\n", hd strs); + strs = tl strs; + } + if(strs != nil) { + sys->fprint(stderr, "\t\tau == %s\n", hd strs); + strs = tl strs; + } + if(strs != nil) { + sys->fprint(stderr, "\t\tgw == %s\n", hd strs); + strs = tl strs; + } + } + sys->fprint(stderr, "}\n\n"); +} + +eqa(a1: array of byte, a2: array of byte): int +{ + if(len a1 != len a2) + return 0; + for(i := 0; i < len a1; i++) + if(a1[i] != a2[i]) + return 0; + return 1; +} + +magic(cookie: array of byte): string +{ + if(eqa(cookie, array[] of { byte 'p', byte '9', byte ' ', byte ' ' })) + return "plan9"; + if(eqa(cookie, array[] of { byte 99, byte 130, byte 83, byte 99 })) + return "rfc1048"; + if(eqa(cookie, array[] of { byte 'C', byte 'M', byte 'U', byte 0 })) + return "cmu"; + return dtoa(cookie); +} + +pinfbp(rec: ref InfBP) +{ + sys->fprint(stderr, "Bootp entry {\n"); + sys->fprint(stderr, "\tha == %s\n", ether->text(rec.ha)); + sys->fprint(stderr, "\tip == %s\n", dtoa(rec.ip)); + sys->fprint(stderr, "\tbf == %s\n", string rec.bf); + sys->fprint(stderr, "\tsm == %s\n", dtoa(rec.sm)); + sys->fprint(stderr, "\tgw == %s\n", dtoa(rec.gw)); + sys->fprint(stderr, "\tfs == %s\n", dtoa(rec.fs)); + sys->fprint(stderr, "\tau == %s\n", dtoa(rec.au)); + sys->fprint(stderr, "}\n"); +} + +M2S(data: array of byte): (string, ref BootpPKT) +{ + if(len data < 300) + return ("too short", nil); + + bootp := ref BootpPKT; + + bootp.op = data[0]; + bootp.htype = data[1]; + bootp.hlen = data[2]; + bootp.hops = data[3]; + bootp.xid = nhgetl(data[4:8]); + bootp.secs = nhgets(data[8:10]); + # data[10:12] unused + bootp.ciaddr = data[12:16]; + bootp.yiaddr = data[16:20]; + bootp.siaddr = data[20:24]; + bootp.giaddr = data[24:28]; + bootp.chaddr = data[28:44]; + bootp.sname = ctostr(data[44:108]); + bootp.file = ctostr(data[108:236]); + bootp.vend = data[236:300]; + + return (nil, bootp); +} + +S2M(bootp: ref BootpPKT): array of byte +{ + data := array[364] of { * => byte 0 }; + + data[0] = bootp.op; + data[1] = bootp.htype; + data[2] = bootp.hlen; + data[3] = bootp.hops; + data[4:] = nhputl(bootp.xid); + data[8:] = nhputs(bootp.secs); + # data[10:12] unused + data[12:] = bootp.ciaddr; + data[16:] = bootp.yiaddr; + data[20:] = bootp.siaddr; + data[24:] = bootp.giaddr; + data[28:] = bootp.chaddr; + data[44:] = array of byte bootp.sname; + data[108:] = array of byte bootp.file; + data[236:] = bootp.vend; + + return data; +} + +nhgetl(data: array of byte): int +{ + return (int data[0]<<24) | (int data[1]<<16) | + (int data[2]<<8) | int data[3]; +} + +nhgets(data: array of byte): int +{ + return (int data[0]<<8) | int data[1]; +} + +nhputl(value: int): array of byte +{ + return array[] of { + byte (value >> 24), + byte (value >> 16), + byte (value >> 8), + byte (value >> 0), + }; +} + +nhputs(value: int): array of byte +{ + return array[] of { + byte (value >> 8), + byte (value >> 0), + }; +} + diff --git a/appl/cmd/ip/ping.b b/appl/cmd/ip/ping.b new file mode 100644 index 00000000..a148c1e6 --- /dev/null +++ b/appl/cmd/ip/ping.b @@ -0,0 +1,369 @@ +implement Ping; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "ip.m"; + ip: IP; + IPaddr: import ip; + +include "timers.m"; + timers: Timers; + Timer: import timers; + +include "rand.m"; + rand: Rand; + +include "arg.m"; + +Ping: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Icmp: adt +{ + ttl: int; # time to live + src: IPaddr; + dst: IPaddr; + ptype: int; + code: int; + seq: int; + munged: int; + time: big; + + unpack: fn(b: array of byte): ref Icmp; +}; + +# packet types +EchoReply: con 0; +Unreachable: con 3; +SrcQuench: con 4; +EchoRequest: con 8; +TimeExceed: con 11; +Timestamp: con 13; +TimestampReply: con 14; +InfoRequest: con 15; +InfoReply: con 16; + +Nmsg: con 32; +Interval: con 1000; # ms + +Req: adt +{ + seq: int; # sequence number + time: big; # time sent + rtt: big; + ttl: int; + replied: int; +}; + +debug := 0; +quiet := 0; +lostonly := 0; +lostmsgs := 0; +rcvdmsgs := 0; +sum := big 0; +firstseq := 0; +addresses := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + rand = load Rand Rand->PATH; + timers = load Timers Timers->PATH; + ip = load IP IP->PATH; + ip->init(); + + + msglen := interval := 0; + nmsg := Nmsg; + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("ip/ping [-alq] [-s msgsize] [-i millisecs] [-n #pings] destination"); + while((o := arg->opt()) != 0) + case o { + 'l' => + lostonly++; + 'd' => + debug++; + 's' => + msglen = int arg->earg(); + 'i' => + interval = int arg->earg(); + 'n' => + nmsg = int arg->earg(); + 'a' => + addresses = 1; + 'q' => + quiet = 1; + } + if(msglen < 32) + msglen = 64; + if(msglen >= 65*1024) + msglen = 65*1024-1; + if(interval <= 0) + interval = Interval; + + args = arg->argv(); + if(args == nil) + arg->usage(); + arg = nil; + + sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil); + opentime(); + rand->init(int(nsec()/big 1000)); + + addr := netmkaddr(hd args, "icmp", "1"); + (ok, c) := sys->dial(addr, nil); + if(ok < 0){ + sys->fprint(sys->fildes(2), "ip/ping: can't dial %s: %r\n", addr); + raise "fail:dial"; + } + + sys->print("sending %d %d byte messages %d ms apart\n", nmsg, msglen, interval); + + done := chan of int; + reqs := chan of ref Req; + + spawn sender(c.dfd, msglen, interval, nmsg, done, reqs); + spid := <-done; + + pids := chan of int; + replies := chan [8] of ref Icmp; + spawn reader(c.dfd, msglen, replies, pids); + rpid := <-pids; + + tpid := 0; + timeout := chan of int; + requests: list of ref Req; +Work: + for(;;) alt{ + r := <-reqs => + requests = r :: requests; + ic := <-replies => + if(ic == nil){ + rpid = 0; + break Work; + } + if(ic.munged) + sys->print("corrupted reply\n"); + if(ic.ptype != EchoReply || ic.code != 0){ + sys->print("bad type/code %d/%d seq %d\n", + ic.ptype, ic.code, ic.seq); + continue; + } + requests = clean(requests, ic); + if(lostmsgs+rcvdmsgs == nmsg) + break Work; + <-done => + spid = 0; + # must be at least one message outstanding; wait for it + tpid = timers->init(Timers->Sec); + timeout = Timer.start((nmsg-lostmsgs-rcvdmsgs)*interval+5*Timers->Sec).timeout; + <-timeout => + break Work; + } + kill(rpid); + kill(spid); + kill(tpid); + + for(; requests != nil; requests = tl requests) + if((hd requests).replied == 0) + lostmsgs++; + + if(lostmsgs){ + sys->print("%d out of %d message(s) lost\n", lostmsgs, lostmsgs+rcvdmsgs); + raise "fail:lost messages"; + } +} + +kill(pid: int) +{ + if(pid) + sys->fprint(sys->open("#p/"+string pid+"/ctl", Sys->OWRITE), "kill"); +} + +SECOND: con big 1000000000; # nanoseconds +MINUTE: con big 60*SECOND; + +clean(l: list of ref Req, ip: ref Icmp): list of ref Req +{ + left: list of ref Req; + for(; l != nil; l = tl l){ + r := hd l; + if(ip.seq == r.seq){ + r.rtt = ip.time-r.time; + r.ttl = ip.ttl; + reply(r, ip); + } + if(ip.time-r.time > MINUTE){ + r.rtt = ip.time-r.time; + r.ttl = ip.ttl; + if(!r.replied) + lost(r, ip); + }else + left = r :: left; + } + return left; +} + +sender(fd: ref Sys->FD, msglen: int, interval: int, n: int, done: chan of int, reqs: chan of ref Req) +{ + + done <-= sys->pctl(0, nil); + + firstseq = rand->rand(65536) - n; # -n to ensure we don't exceed 16 bits + if(firstseq < 0) + firstseq = 0; + + buf := array[64*1024+512] of {* => byte 0}; + for(i := Odata; i < msglen; i++) + buf[i] = byte i; + buf[Otype] = byte EchoRequest; + buf[Ocode] = byte 0; + + seq := firstseq; + for(i = 0; i < n; i++){ + if(i != 0) + sys->sleep(interval); + ip->put2(buf, Oseq, seq); # order? + r := ref Req; + r.seq = seq; + r.replied = 0; + r.time = nsec(); + reqs <-= r; + if(sys->write(fd, buf, msglen) < msglen){ + sys->fprint(sys->fildes(2), "ping: write failed: %r\n"); + break; + } + seq++; + } + done <-= 1; +} + +reader(fd: ref Sys->FD, msglen: int, out: chan of ref Icmp, pid: chan of int) +{ + pid <-= sys->pctl(0, nil); + buf := array[64*1024+512] of byte; + while((n := sys->read(fd, buf, len buf)) > 0){ + now := nsec(); + if(n < msglen){ + sys->print("bad len %d/%d\n", n, msglen); + continue; + } + ic := Icmp.unpack(buf[0:n]); + ic.munged = 0; + for(i := Odata; i < msglen; i++) + if(buf[i] != byte i) + ic.munged++; + ic.time = now; + out <-= ic; + } + sys->print("read: %r\n"); + out <-= nil; +} + +reply(r: ref Req, ic: ref Icmp) +{ + rcvdmsgs++; + r.rtt /= big 1000; + sum += r.rtt; + if(!quiet && !lostonly){ + if(addresses) + sys->print("%ud: %s->%s rtt %bd µs, avg rtt %bd µs, ttl = %d\n", + r.seq-firstseq, + ic.src.text(), ic.dst.text(), + r.rtt, sum/big rcvdmsgs, r.ttl); + else + sys->print("%ud: rtt %bd µs, avg rtt %bd µs, ttl = %d\n", + r.seq-firstseq, + r.rtt, sum/big rcvdmsgs, r.ttl); + } + r.replied = 1; # TO DO: duplicates might be interesting +} + +lost(r: ref Req, ic: ref Icmp) +{ + if(!quiet){ + if(addresses) + sys->print("lost %ud: %s->%s avg rtt %bd µs\n", + r.seq-firstseq, + ic.src.text(), ic.dst.text(), + sum/big rcvdmsgs); + else + sys->print("lost %ud: avg rtt %bd µs\n", + r.seq-firstseq, + sum/big rcvdmsgs); + } + lostmsgs++; +} + +Ovihl: con 0; +Otos: con 1; +Olength: con 2; +Oid: con Olength+2; +Ofrag: con Oid+2; +Ottl: con Ofrag+2; +Oproto: con Ottl+1; +Oipcksum: con Oproto+1; +Osrc: con Oipcksum+2; +Odst: con Osrc+4; +Otype: con Odst+4; +Ocode: con Otype+1; +Ocksum: con Ocode+1; +Oicmpid: con Ocksum+2; +Oseq: con Oicmpid+2; +Odata: con Oseq+2; + +Icmp.unpack(b: array of byte): ref Icmp +{ + ic := ref Icmp; + ic.ttl = int b[Ottl]; + ic.src = IPaddr.newv4(b[Osrc:]); + ic.dst = IPaddr.newv4(b[Odst:]); + ic.ptype = int b[Otype]; + ic.code = int b[Ocode]; + ic.seq = ip->get2(b, Oseq); + ic.munged = 0; + ic.time = big 0; + return ic; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} + +timefd: ref Sys->FD; + +opentime() +{ + timefd = sys->open("/dev/time", Sys->OREAD); + if(timefd == nil){ + sys->fprint(sys->fildes(2), "ping: can't open /dev/time: %r\n"); + raise "fail:no time"; + } +} + +nsec(): big +{ + buf := array[64] of byte; + n := sys->pread(timefd, buf, len buf, big 0); + if(n <= 0) + return big 0; + return big string buf[0:n] * big 1000; +} diff --git a/appl/cmd/ip/ppp/mkfile b/appl/cmd/ip/ppp/mkfile new file mode 100644 index 00000000..193b8faf --- /dev/null +++ b/appl/cmd/ip/ppp/mkfile @@ -0,0 +1,27 @@ +<../../../../mkconfig + +TARG=\ + pppclient.dis\ + pppdial.dis\ + pppgui.dis\ + ppptest.dis\ + modem.dis\ + script.dis\ + +MODULES=\ + modem.m\ + pppclient.m\ + pppgui.m\ + script.m\ + +SYSMODULES=\ + sys.m\ + draw.m\ + tk.m\ + dict.m\ + string.m\ + lock.m\ + +DISBIN=$ROOT/dis/ip/ppp + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/ip/ppp/modem.b b/appl/cmd/ip/ppp/modem.b new file mode 100644 index 00000000..6085524a --- /dev/null +++ b/appl/cmd/ip/ppp/modem.b @@ -0,0 +1,468 @@ +implement Modem; + +include "sys.m"; + sys: Sys; + +include "lock.m"; + lock: Lock; + Semaphore: import lock; + +include "draw.m"; + +include "modem.m"; + +hangupcmd := "ATH0"; # was ATZH0 but some modem versions on Umec hung on ATZ (BUG: should be in modeminfo) + +# modem return codes +Ok, Success, Failure, Abort, Noise, Found: con iota; + +maxspeed: con 115200; + +# +# modem return messages +# +Msg: adt { + text: string; + code: int; +}; + +msgs: array of Msg = array [] of { + ("OK", Ok), + ("NO CARRIER", Failure), + ("ERROR", Failure), + ("NO DIALTONE", Failure), + ("BUSY", Failure), + ("NO ANSWER", Failure), + ("CONNECT", Success), +}; + +kill(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "kill") < 0) + sys->print("modem: can't kill %d: %r\n", pid); +} + +# +# prepare a modem port +# +openserial(d: ref Device) +{ + if (d==nil) { + raise "fail: device not initialized"; + return; + } + + d.data = nil; + d.ctl = nil; + + d.data = sys->open(d.local, Sys->ORDWR); + if(d.data == nil) { + raise "fail: can't open "+d.local; + return; + } + + d.ctl = sys->open(d.local+"ctl", Sys->ORDWR); + if(d.ctl == nil) { + raise "can't open "+d.local+"ctl"; + return; + } + + d.speed = maxspeed; + d.avail = nil; +} + +# +# shut down the monitor (if any) and return the connection +# + +close(m: ref Device): ref Sys->Connection +{ + if(m == nil) + return nil; + if(m.pid != 0){ + kill(m.pid); + m.pid = 0; + } + if(m.data == nil) + return nil; + mc := ref sys->Connection(m.data, m.ctl, nil); + m.ctl = nil; + m.data = nil; + return mc; +} + +# +# Send a string to the modem +# + +send(d: ref Device, x: string): int +{ + if (d == nil) + return -1; + + a := array of byte x; + f := sys->write(d.data, a, len a); + if (f < 0) { + # let's attempt to close & reopen the modem + close(d); + openserial(d); + f = sys->write(d.data,a, len a); + } + sys->print("->%s\n",x); + return f; +} + +# +# apply a string of commands to modem & look for a response +# + +apply(d: ref Device, s: string, substr: string, secs: int): int +{ + m := Ok; + buf := ""; + for(i := 0; i < len s; i++){ + c := s[i]; + buf[len buf] = c; # assume no Unicode + if(c == '\r' || i == (len s -1)){ + if(c != '\r') + buf[len buf] = '\r'; + if(send(d, buf) < 0) + return Abort; + (m, nil) = readmsg(d, secs, substr); + buf = ""; + } + } + return m; +} + +# +# get modem into command mode if it isn't already +# +GUARDTIME: con 1100; # usual default for S12=50 in units of 1/50 sec; allow 100ms fuzz + +attention(d: ref Device): int +{ + for(i := 0; i < 3; i++){ + if(apply(d, hangupcmd, nil, 2) == Ok) + return Ok; + sys->sleep(GUARDTIME); + if(send(d, "+++") < 0) + return Abort; + sys->sleep(GUARDTIME); + (nil, msg) := readmsg(d, 0, nil); + if(msg != nil) + sys->print("status: %s\n", msg); + } + return Failure; +} + +# +# apply a command type +# + +applyspecial(d: ref Device, cmd: string): int +{ + if(cmd == nil) + return Failure; + return apply(d, cmd, nil, 2); +} + +# +# hang up any connections in progress and close the device +# +onhook(d: ref Device) +{ + if(d == nil) + return; + + # hang up the modem + monitoring(d); + if(attention(d) != Ok) + sys->print("modem: no attention\n"); + + # hangup the stream (eg, for ppp) and toggle the lines to the modem + if(d.ctl != nil) { + sys->fprint(d.ctl,"d0\n"); + sys->fprint(d.ctl,"r0\n"); + sys->fprint(d.ctl, "h\n"); # hangup on native serial + sys->sleep(250); + sys->fprint(d.ctl,"r1\n"); + sys->fprint(d.ctl,"d1\n"); + } + + close(d); +} + +# +# does string s contain t anywhere? +# + +contains(s, t: string): int +{ + if(t == nil) + return 1; + if(s == nil) + return 0; + n := len t; + for(i := 0; i+n <= len s; i++) + if(s[i:i+n] == t) + return 1; + return 0; +} + +# +# read till we see a message or we time out +# +readmsg(d: ref Device, secs: int, substr: string): (int, string) +{ + if (d == nil) + return (Abort, "device not initialized"); + found := 0; + secs *= 1000; + limit := 1000; # pretty arbitrary + s := ""; + + for(start := sys->millisec(); sys->millisec() <= start+secs;){ + a := getinput(d,1); + if(len a == 0){ + if(limit){ + sys->sleep(1); + continue; + } + break; + } + if(a[0] == byte '\n' || a[0] == byte '\r' || limit == 0){ + if (len s) { + if (s[(len s)-1] == '\r') + s[(len s)-1] = '\n'; + sys->print("<-%s\n",s); + } + if(substr != nil && contains(s, substr)) + found = 1; + for(k := 0; k < len msgs; k++) + if(len s >= len msgs[k].text && + s[0:len msgs[k].text] == msgs[k].text){ + if(found) + return (Found, s); + return (msgs[k].code, s); + } + start = sys->millisec(); + s = ""; + continue; + } + s[len s] = int a[0]; + limit--; + } + s = "No response from modem"; + if(found) + return (Found, s); + + return (Noise, s); +} + +# +# get baud rate from a connect message +# + +getspeed(msg: string, speed: int): int +{ + p := msg[7:]; # skip "CONNECT" + while(p[0] == ' ' || p[0] == '\t') + p = p[1:]; + s := int p; + if(s <= 0) + return speed; + else + return s; +} + +# +# set speed and RTS/CTS modem flow control +# + +setspeed(d: ref Device, baud: int) +{ + if(d != nil && d.ctl != nil){ + sys->fprint(d.ctl, "b%d", baud); + sys->fprint(d.ctl, "m1"); + } +} + +dumpa(a: array of byte): string +{ + s := ""; + for(i:=0; i<len a; i++){ + b := int a[i]; + if(b >= ' ' && b < 16r7f) + s[len s] = b; + else + s += sys->sprint("\\%.2x", b); + } + return s; +} + +monitoring(d: ref Device) +{ + # if no monitor then spawn one + if(d.pid == 0) { + pidc := chan of int; + spawn monitor(d, pidc); + d.pid = <-pidc; + } +} + +# +# a process to read input from a modem. +# +monitor(d: ref Device, pidc: chan of int) +{ + openserial(d); + pidc <-= sys->pctl(0, nil); # pidc can be written once only. + a := array[Sys->ATOMICIO] of byte; + for(;;) { + d.lock.obtain(); + d.status = "Idle"; + d.remote = ""; + setspeed(d, d.speed); + d.lock.release(); + # shuttle bytes + while((n := sys->read(d.data, a, len a)) > 0){ + d.lock.obtain(); + if (len d.avail < Sys->ATOMICIO) { + na := array[len d.avail + n] of byte; + na[0:] = d.avail[0:]; + na[len d.avail:] = a[0:n]; + d.avail = na; + } + d.lock.release(); + } + # on an error, try reopening the device + d.data = nil; + d.ctl = nil; + openserial(d); + } +} + +# +# return up to n bytes read from the modem by monitor() +# +getinput(d: ref Device, n: int): array of byte +{ + if(d==nil || n <= 0) + return nil; + a: array of byte; + d.lock.obtain(); + if(len d.avail != 0){ + if(n > len d.avail) + n = len d.avail; + a = d.avail[0:n]; + d.avail = d.avail[n:]; + } + d.lock.release(); + return a; +} + +getc(m: ref Device, timo: int): int +{ + start := sys->millisec(); + while((b := getinput(m, 1)) == nil) { + if (timo && sys->millisec() > start+timo) + return 0; + sys->sleep(1); + } + return int b[0]; +} + +init(modeminfo: ref ModemInfo): ref Device +{ + if (sys == nil) { + sys = load Sys Sys->PATH; + lock = load Lock Lock->PATH; + if (lock == nil) { + raise "fail: Couldn't load lock module"; + return nil; + } + lock->init(); + } + + newdev := ref Device; + newdev.lock = Semaphore.new(); + newdev.local = modeminfo.path; + newdev.pid = 0; + newdev.t = modeminfo; + + return newdev; +} + + +# +# dial a number +# +dial(d: ref Device, number: string) +{ + if (d==nil) { + raise "fail: Device not initialized"; + return; + } + + monitoring(d); + + # modem type should already be established, but just in case + sys->print("Attention\n"); + x := attention(d); + if (x != Ok) + sys->print("Attention failed\n"); + # + # extended Hayes commands, meaning depends on modem (VGA all over again) + # + sys->print("Init\n"); + if(d.t.country != nil) + applyspecial(d, d.t.country); + + if(d.t.init != nil) + applyspecial(d, d.t.init); + + if(d.t.other != nil) + applyspecial(d, d.t.other); + + applyspecial(d, d.t.errorcorrection); + + compress := Abort; + if(d.t.mnponly != nil) + compress = applyspecial(d, d.t.mnponly); + if(d.t.compression != nil) + compress = applyspecial(d, d.t.compression); + + rateadjust := Abort; + if(compress != Ok) + rateadjust = applyspecial(d, d.t.rateadjust); + applyspecial(d, d.t.flowctl); + + # finally, dialout + sys->print("Dialing\n"); + if((dt := d.t.dialtype) == nil) + dt = "ATDT"; + if(send(d, sys->sprint("%s%s\r", dt, number)) < 0) { + raise "can't dial "+number; + return; + } + + (i, msg) := readmsg(d, 120, nil); + if(i != Success) { + raise "fail: "+msg; + return; + } + + connectspeed := getspeed(msg, d.speed); + + # change line rate if not compressing + if(rateadjust == Ok) + setspeed(d, connectspeed); + + if(d.ctl != nil){ + if(d != nil) + sys->fprint(d.ctl, "s%d", connectspeed); # set DCE speed (if device implements it) + sys->fprint(d.ctl, "c1"); # enable CD monitoring + } +} diff --git a/appl/cmd/ip/ppp/modem.m b/appl/cmd/ip/ppp/modem.m new file mode 100644 index 00000000..9a99acf8 --- /dev/null +++ b/appl/cmd/ip/ppp/modem.m @@ -0,0 +1,41 @@ +Modem: module +{ + PATH: con "/dis/ip/ppp/modem.dis"; + + ModemInfo: adt { + path: string; + init: string; + country: string; + other: string; + errorcorrection:string; + compression: string; + flowctl: string; + rateadjust: string; + mnponly: string; + dialtype: string; + }; + + Device: adt { + lock: ref Lock->Semaphore; + # modem stuff + ctl: ref Sys->FD; + data: ref Sys->FD; + + local: string; + remote: string; + status: string; + speed: int; + t: ref ModemInfo; + # input reader + avail: array of byte; + pid: int; + }; + + init: fn(i: ref ModemInfo): ref Device; + dial: fn( m: ref Device, number: string); + getc: fn(m: ref Device, timout: int): int; + getinput: fn(m: ref Device, n: int ): array of byte; + send: fn(m: ref Device, x: string): int; + close: fn(m: ref Device): ref Sys->Connection; + onhook: fn(m: ref Device); +}; diff --git a/appl/cmd/ip/ppp/pppclient.b b/appl/cmd/ip/ppp/pppclient.b new file mode 100644 index 00000000..be321b59 --- /dev/null +++ b/appl/cmd/ip/ppp/pppclient.b @@ -0,0 +1,216 @@ +implement PPPClient; + + +include "sys.m"; + sys : Sys; +include "draw.m"; + +include "lock.m"; +include "modem.m"; +include "script.m"; + +include "pppclient.m"; + +include "translate.m"; + translate : Translate; + Dict : import translate; + dict : ref Dict; + +# +# Globals (these will have to be removed if we are going multithreaded) +# + +pid := 0; +modeminfo: ref Modem->ModemInfo; +pppdir: string; + +ppplog(log: chan of int, errfile: string, pidc: chan of int) +{ + pidc <-= sys->pctl(0, nil); # set reset pid to our pid + src := sys->open(errfile, Sys->OREAD); + if (src == nil) + raise sys->sprint("fail: Couldn't open %s: %r", errfile); + + LOGBUFMAX: con 1024; + buf := array[LOGBUFMAX] of byte; + connected := 0; + + while ((count := sys->read(src, buf, LOGBUFMAX)) > 0) { + (n, toklist) := sys->tokenize(string buf[:count],"\n"); + for (;toklist != nil;toklist = tl toklist) { + case hd toklist { + "no error" => + log <-= s_SuccessPPP; + lasterror = nil; + connected = 1; + "permission denied" => + lasterror = X("Username or Password Incorrect"); + log <-= s_Error; + "write to hungup channel" => + lasterror = X("Remote Host Hung Up"); + log <-= s_Error; + * => + lasterror = X(hd toklist); + log <-= s_Error; + } + } + } + if(count == 0 && connected && lasterror == nil){ # should change ip/pppmedium.c instead? + lasterror = X("Lost Connection"); + log <-= s_Error; + } +} + +startppp(logchan: chan of int, pppinfo: ref PPPInfo) +{ + ifd := sys->open("/net/ipifc/clone", Sys->ORDWR); + if (ifd == nil) + raise "fail: Couldn't open /net/ipifc/clone"; + + buf := array[32] of byte; + n := sys->read(ifd, buf, len buf); + if(n <= 0) + raise "fail: can't read from /net/ipifc/clone"; + + pppdir = "/net/ipifc/" + string buf[0:n]; + pidc := chan of int; + spawn ppplog(logchan, pppdir + "/err", pidc); + pid = <-pidc; + logchan <-= s_StartPPP; + + if (pppinfo.ipaddr == nil) + pppinfo.ipaddr = "-"; +# if (pppinfo.ipmask == nil) +# pppinfo.ipmask = "255.255.255.255"; + if (pppinfo.peeraddr == nil) + pppinfo.peeraddr = "-"; + if (pppinfo.maxmtu == nil) + pppinfo.maxmtu = "512"; + if (pppinfo.username == nil) + pppinfo.username = "-"; + if (pppinfo.password == nil) + pppinfo.password = "-"; + framing := "1"; + + ifc := "bind ppp "+modeminfo.path+" "+ pppinfo.ipaddr+" "+pppinfo.peeraddr+" "+pppinfo.maxmtu + +" "+framing+" "+pppinfo.username+" "+pppinfo.password; + + # send the add command + if (sys->fprint(ifd, "%s", ifc) < 0) { + sys->print("pppclient: couldn't write %s/ctl: %r\n", pppdir); + raise "fail: Couldn't write /net/ipifc"; + return; + } +} + +connect(mi: ref Modem->ModemInfo, number: string, + scriptinfo: ref Script->ScriptInfo, pppinfo: ref PPPInfo, logchan: chan of int) +{ + sys = load Sys Sys->PATH; + + translate = load Translate Translate->PATH; + if (translate != nil) { + translate->init(); + dictname := translate->mkdictname("", "pppclient"); + (dict, nil) = translate->opendict(dictname); + } + if (pid != 0) # yikes we are already running + reset(); + + # create a new process group + pid = sys->pctl( Sys->NEWPGRP, nil); + + { + logchan <-= s_Initialized; + + # open & init the modem + modeminfo = mi; + modem := load Modem Modem->PATH; + if (modem == nil) { + raise "fail: Couldn't load modem module"; + return; + } + + modemdev := modem->init(modeminfo); + logchan <-= s_StartModem; + modem->dial(modemdev, number); + logchan <-= s_SuccessModem; + + # if script + if (scriptinfo != nil) { + script := load Script Script->PATH; + if (script == nil) { + raise "fail: Couldn't load script module"; + return; + } + logchan <-= s_StartScript; + script->execute(modem, modemdev, scriptinfo); + logchan <-= s_SuccessScript; + } + + mc := modem->close(modemdev); # keep connection open for ppp mode + modemdev = nil; + modem = nil; # unload modem module + + # if ppp + if (pppinfo != nil) + startppp(logchan, pppinfo); + else + logchan <-= s_Done; + } + exception e{ + "fail*" => + lasterror = e; + sys->print("PPPclient: fatal exception: %s\n", e); + logchan <-= s_Error; + kill(pid, "killgrp"); + exit; + } +} + +reset() +{ + sys->print("reset..."); + if(pid != 0){ + kill(pid, "killgrp"); + pid = 0; + } + + if(pppdir != nil){ # shut down the PPP link + fd := sys->open(pppdir + "/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "unbind") < 0) + sys->print("pppclient: can't unbind: %r\n"); + fd = nil; + pppdir = nil; + } + + modem := load Modem Modem->PATH; + if (modem == nil) { + raise "fail: Couldn't load modem module"; + return; + } + modemdev := modem->init(modeminfo); + if(modemdev != nil) + modem->onhook(modemdev); + modem = nil; + + # clear error buffer + lasterror = nil; +} + +kill(pid: int, msg: string) +{ + a := array of byte msg; + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->write(fd, a, len a) < 0) + sys->print("pppclient: can't %s %d: %r\n", msg, pid); +} + +# Translate a string + +X(s : string) : string +{ + if (dict== nil) return s; + return dict.xlate(s); +} + diff --git a/appl/cmd/ip/ppp/pppclient.m b/appl/cmd/ip/ppp/pppclient.m new file mode 100644 index 00000000..23396af4 --- /dev/null +++ b/appl/cmd/ip/ppp/pppclient.m @@ -0,0 +1,31 @@ + +PPPClient: module { + PATH: con "/dis/ip/ppp/pppclient.dis"; + + PPPInfo: adt { + ipaddr: string; + ipmask: string; + peeraddr: string; + maxmtu: string; + username: string; + password: string; + }; + + connect: fn( mi: ref Modem->ModemInfo, number: string, + scriptinfo: ref Script->ScriptInfo, + pppinfo: ref PPPInfo, logchan: chan of int); + reset: fn(); + + lasterror :string; + + s_Error: con -666; + s_Initialized, # Module Initialized + s_StartModem, # Modem Initialized + s_SuccessModem, # Modem Connected + s_StartScript, # Script Executing + s_SuccessScript, # Script Executed Sucessfully + s_StartPPP, # PPP Started + s_LoginPPP, # CHAP/PAP Authentication + s_SuccessPPP, # PPP Session Established + s_Done: con iota; # PPPClient Cleaningup & Exiting +}; diff --git a/appl/cmd/ip/ppp/pppdial.b b/appl/cmd/ip/ppp/pppdial.b new file mode 100644 index 00000000..ec689dc1 --- /dev/null +++ b/appl/cmd/ip/ppp/pppdial.b @@ -0,0 +1,283 @@ +implement PPPdial; + +# +# Module: ispservice +# Purpose: Simple PPP Dial-on-Demand +# Author: Eric Van Hensbergen (ericvh@lucent.com) +# +# Copyright © 1998-1999 Lucent Technologies Inc. All rights reserved. +# Revisions copyright © 2000-2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + +include "cfgfile.m"; + cfg: CfgFile; + ConfigFile: import cfg; + +include "lock.m"; +include "modem.m"; +include "script.m"; +include "pppclient.m"; + ppp: PPPClient; +include "pppgui.m"; + +PPPdial: module +{ + init: fn(nil: ref Draw->Context): string; + connect: fn(): string; +}; + +context: ref Draw->Context; +modeminfo: ref Modem->ModemInfo; +pppinfo: ref PPPClient->PPPInfo; +scriptinfo: ref Script->ScriptInfo; +isp_number: string; # should be part of pppinfo +lastCdir: ref Sys->Dir; # state of file when last read + +DEFAULT_ISP_DB_PATH: con "/services/ppp/isp.cfg"; # contains pppinfo & scriptinfo +DEFAULT_MODEM_DB_PATH: con "/services/ppp/modem.cfg"; # contains modeminfo +MODEM_DB_PATH: con "/usr/inferno/config/modem.cfg"; # contains modeminfo +ISP_DB_PATH: con "/usr/inferno/config/isp.cfg"; # contains pppinfo & scriptinfo +ISP_RETRIES: con 5; + +getcfgstring(c: ref ConfigFile, key: string) :string +{ + l := c.getcfg(key); + if (l == nil) + return nil; + for(ret := ""; l != nil; l = tl l) + ret+= " " + hd l; + + return ret[1:]; # trim the first space +} + +configinit() +{ + mi: Modem->ModemInfo; + pppi: PPPClient->PPPInfo; + info: list of string; + + cfg = load CfgFile CfgFile->PATH; + if (cfg == nil) + raise "fail: load CfgFile"; + + # Modem Configuration + + cfg->verify(DEFAULT_MODEM_DB_PATH, MODEM_DB_PATH); + modemcfg := cfg->init(MODEM_DB_PATH); + if (modemcfg == nil) + raise "fail: read: "+MODEM_DB_PATH; + modeminfo = ref mi; + + modeminfo.path = getcfgstring(modemcfg, "PATH"); + modeminfo.init = getcfgstring(modemcfg, "INIT"); + modeminfo.country = getcfgstring(modemcfg, "COUNTRY"); + modeminfo.other = getcfgstring(modemcfg, "OTHER"); + modeminfo.errorcorrection = getcfgstring(modemcfg,"CORRECT"); + modeminfo.compression = getcfgstring(modemcfg,"COMPRESS"); + modeminfo.flowctl = getcfgstring(modemcfg,"FLOWCTL"); + modeminfo.rateadjust = getcfgstring(modemcfg,"RATEADJ"); + modeminfo.mnponly = getcfgstring(modemcfg,"MNPONLY"); + modeminfo.dialtype = getcfgstring(modemcfg,"DIALING"); + if(modeminfo.dialtype!="ATDP") + modeminfo.dialtype="ATDT"; + + cfg->verify(DEFAULT_ISP_DB_PATH, ISP_DB_PATH); + (ok, stat) := sys->stat(ISP_DB_PATH); + if(ok >= 0) + lastCdir = ref stat; + sys->print("cfg->init(%s)\n", ISP_DB_PATH); + + # ISP Configuration + pppcfg := cfg->init(ISP_DB_PATH); + if (pppcfg == nil) + raise "fail: Couldn't load ISP configuration file: "+ISP_DB_PATH; + pppinfo = ref pppi; + isp_number = getcfgstring(pppcfg, "NUMBER"); + pppinfo.ipaddr = getcfgstring(pppcfg,"IPADDR"); + pppinfo.ipmask = getcfgstring(pppcfg,"IPMASK"); + pppinfo.peeraddr = getcfgstring(pppcfg,"PEERADDR"); + pppinfo.maxmtu = getcfgstring(pppcfg,"MAXMTU"); + pppinfo.username = getcfgstring(pppcfg,"USERNAME"); + pppinfo.password = getcfgstring(pppcfg,"PASSWORD"); + + info = pppcfg.getcfg("SCRIPT"); + if (info != nil) { + scriptinfo = ref Script->ScriptInfo; + scriptinfo.path = hd info; + scriptinfo.username = pppinfo.username; + scriptinfo.password = pppinfo.password; + } else + scriptinfo = nil; + + info = pppcfg.getcfg("TIMEOUT"); + if (info != nil) + scriptinfo.timeout = int (hd info); + + cfg = nil; # might as well unload it +} + +# +# Parts of the following two functions could be generalized +# + +isipaddr(a: string): int +{ + i, c, ac, np: int = 0; + + for(i = 0; i < len a; i++) { + c = a[i]; + if(c >= '0' && c <= '9') { + np = 10*np + c - '0'; + continue; + } + if (c == '.' && np) { + ac++; + if (np > 255) + return 0; + np = 0; + continue; + } + return 0; + } + return np && np < 256 && ac == 3; +} + +# check if there is an existing PPP connection +connected(): int +{ + ifd := sys->open("/net/ipifc", Sys->OREAD); + if(ifd == nil) + return 0; + + buf := array[1024] of byte; + + for(;;) { + (n, d) := sys->dirread(ifd); + if (n <= 0) + return 0; + for(i := 0; i < n; i++) + if(d[i].name[0] <= '9') { + sfd := sys->open("/net/ipifc/"+d[i].name+"/status", Sys->OREAD); + if (sfd == nil) + continue; + ns := sys->read(sfd, buf, len buf); + if (ns <= 0) + continue; + (nflds, flds) := sys->tokenize(string buf[0:ns], " \t\r\n"); + if(nflds < 4) + continue; + if (isipaddr(hd tl tl flds)) + return 1; + } + } +} + +# +# called once when loaded +# +init(ctxt: ref Draw->Context): string +{ + sys = load Sys Sys->PATH; + { + ppp = load PPPClient PPPClient->PATH; + if (ppp == nil) + raise "fail: Couldn't load ppp module"; + + # Contruct Config Tables During Init - may want to change later + # for multiple configs (Software Download Server versus ISP) + configinit(); + context = ctxt; + }exception e { + "fail:*" => + return e; + } + return nil; +} + +dialup_cancelled := 0; +connecting := 0; + +# +# called each time a translation is needed, to check that we're on line(!) +# eventually this will be replaced by a packet interface that does dial-on-demand +# +connect(): string +{ + { + dialup_cancelled = 0; + (ok, stat) := sys->stat(ISP_DB_PATH); + if (ok < 0 || lastCdir == nil || !samefile(*lastCdir, stat)) + configinit(); + errc := chan of string; + while(!connected()){ + if(!connecting) { + connecting = 1; + sync := chan of int; + spawn pppconnect(errc, sync); + <- sync; + return <-errc; + }else{ + sys->sleep(2500); + if (dialup_cancelled) + return "fail: dialup cancelled"; + } + } + }exception e{ + "fail:*" => + return e; + "*" => + sys->print("pppdial: caught exception: %s\n", e); + return "fail: internal error: "+e; + } + return nil; +} + +pppconnect(errc: chan of string, sync: chan of int) +{ + connecting = 1; + sys->pctl(Sys->NEWPGRP, nil); + sync <-= 0; + resp_chan: chan of int; + logger := chan of int; + pppgui := load PPPGUI PPPGUI->PATH; + for (count :=0; count < ISP_RETRIES; count++) { + resp_chan = pppgui->init(context, logger, ppp, nil); + spawn ppp->connect(modeminfo, isp_number, scriptinfo, pppinfo, logger); + x := <-resp_chan; + if (x > 0) { + if (x == 1) { + # alt needed in case calling process has been killed + alt { + errc <-= nil => ; + * => ; + } + } else { # user cancelled dial-in + dialup_cancelled = 1; + alt { + errc <-= "fail: dialup cancelled" => ; + * => ; + } + } + connecting = 0; + return; + } + # else connect failed, go around loop to try again + } + alt { + errc <-= "fail: dialup failed" => ; + * => ; + } + connecting = 0; +} + +samefile(d1, d2: Sys->Dir): int +{ + return d1.dev==d2.dev && d1.dtype==d2.dtype && + d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers && + d1.mtime==d2.mtime; +} diff --git a/appl/cmd/ip/ppp/pppgui.b b/appl/cmd/ip/ppp/pppgui.b new file mode 100644 index 00000000..40e7e3b4 --- /dev/null +++ b/appl/cmd/ip/ppp/pppgui.b @@ -0,0 +1,373 @@ +# +# Copyright © 1998 Lucent Technologies Inc. All rights reserved. +# Revisions copyright © 2000,2001 Vita Nuova Holdings Limited. All rights reserved. +# +# Originally Written by N. W. Knauft +# Adapted by E. V. Hensbergen (ericvh@lucent.com) +# Further adapted by Vita Nuova +# + +implement PPPGUI; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + +include "tk.m"; + tk: Tk; + +include "tkclient.m"; + tkclient: Tkclient; + +include "translate.m"; + translate: Translate; + Dict: import translate; + dict: ref Dict; + +include "lock.m"; +include "modem.m"; +include "script.m"; +include "pppclient.m"; + ppp: PPPClient; + +include "pppgui.m"; + +#Screen constants +BBG: con "#C0C0C0"; # Background color for button +PBG: con "#808080"; # Background color for progress bar +LTGRN: con "#00FF80"; # Color for progress bar +BARW: con 216; # Progress bar width +BARH: con " 9"; # Progress bar height +INCR: con 30; # Progress bar increment size +N_INCR: con 7; # Number of increments in progress bar width +BSIZE: con 25; # Icon button size +ISIZE: con BSIZE + 4; # Icon window size +DIALQUANTA : con 1000; +ICONQUANTA : con 5000; + +#Globals +pppquanta := DIALQUANTA; + +#Font +FONT: con "/fonts/lucidasans/unicode.6.font"; + +#Messages +stat_msgs := array[] of { + "Initializing Modem", + "Dialling Service Provider", + "Logging Into Network", + "Executing Login Script", + "Script Execution Complete", + "Logging Into Network", + "Verifying Password", + "Connected", + "", +}; + +config_icon := array[] of { + "button .btn -text X -width "+string BSIZE+" -height "+string BSIZE+" -command {send tsk open} -bg "+BBG, + "pack .btn", + + "pack propagate . no", + ". configure -bd 0", + ". unmap", + "update", +}; + + +# Create internet connect window, spawn event handler +init(ctxt: ref Draw->Context, stat: chan of int, pppmod: PPPClient, args: list of string): chan of int +{ + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + tk = load Tk Tk->PATH; + tkclient = load Tkclient Tkclient->PATH; + + if (draw == nil || tk == nil || tkclient == nil) { + sys->fprint(sys->fildes(2), "pppgui: can't load Draw or Tk: %r\n"); + return nil; + } + + translate = load Translate Translate->PATH; + if(translate != nil) { + translate->init(); + dictname := translate->mkdictname("", "pppgui"); + dicterr: string; + (dict, dicterr) = translate->opendict(dictname); + if(dicterr != nil) + sys->fprint(sys->fildes(2), "pppgui: can't open %s: %s\n", dictname, dicterr); + }else + sys->fprint(sys->fildes(2), "pppgui: can't load %s: %r\n", Translate->PATH); + ppp = pppmod; # set the global + + tkargs := ""; + + if (args != nil) { + tkargs = hd args; + args = tl args; + } else + tkargs="-x 340 -y 4"; + + tkclient->init(); + + (t, wmctl) := tkclient->toplevel(ctxt, tkargs, "PPP", Tkclient->Plain); + + config_win := array[] of { + "frame .f", + "frame .fprog", + + "canvas .cprog -bg "+PBG+" -bd 2 -width "+string BARW+" -height "+BARH+" -relief ridge", + "pack .cprog -in .fprog -pady 6", + + "label .stat -text {"+X("Initializing connection...")+"} -width 164 -font "+FONT, + "pack .stat -in .f -side left -fill y -anchor w", + + "button .done -text {"+X("Cancel")+"} -width 60 -command {send cmd cancel} -bg "+BBG+" -font "+FONT, + "pack .fprog -side bottom -expand 1 -fill x", + "pack .done -side right -padx 1 -pady 1 -fill y -anchor e", + "pack .f -side left -expand 1 -padx 5 -pady 3 -fill both -anchor w", + + "pack propagate . no", + ". configure -bd 2 -relief raised -width "+string WIDTH, + "update", + }; + + for(i := 0; i < len config_win; i++) + tk->cmd(t, config_win[i]); + + itkargs := ""; + if (args != nil) { + itkargs = hd args; + args = tl args; + } + tkclient->onscreen(t, nil); + tkclient->startinput(t, "ptr" :: nil); + + if (itkargs == "") { + x := int tk->cmd(t, ". cget x"); + y := int tk->cmd(t, ". cget y"); + x += WIDTH - ISIZE; + itkargs = "-x "+string x+" -y "+string y; + } + + (ticon, iconctl) := tkclient->toplevel(ctxt, itkargs, "PPP", Tkclient->Plain); + + for( i = 0; i < len config_icon; i++) + tk->cmd(ticon, config_icon[i]); + + tk->cmd(ticon, "image create bitmap Network -file network.bit -maskfile network.bit"); + tk->cmd(ticon, ".btn configure -image Network"); + tkclient->startinput(ticon, "ptr"::nil); + + chn := chan of int; + spawn handle_events(t, wmctl, ticon, iconctl, stat, chn); + return chn; +} + +ppp_timer(sync: chan of int, stat: chan of int) +{ + for(;;) { + sys->sleep(pppquanta); + alt { + <-sync => + return; + stat <-= -1 => + ; + } + } +} + +send(cmd: chan of string, msg: string) +{ + cmd <-= msg; +} + +# Process events and pass disconnect cmd to calling app +handle_events(t: ref Tk->Toplevel, wmctl: chan of string, ticon: ref Tk->Toplevel, iconctl: chan of string, stat, chn: chan of int) +{ + sys->pctl(Sys->NEWPGRP, nil); + cmd := chan of string; + tk->namechan(t, cmd, "cmd"); + + tsk := chan of string; + tk->namechan(ticon, tsk, "tsk"); + + connected := 0; + winmapped := 1; + timecount := 0; + xmin := 0; + x := 0; + + iocmd := sys->file2chan("/chan", "pppgui"); + if (iocmd == nil) { + sys->print("fail: pppgui: file2chan: /chan/pppgui: %r\n"); + return; + } + + pppquanta = DIALQUANTA; + sync_chan := chan of int; + spawn ppp_timer(sync_chan, stat); + +Work: + for(;;) alt { + s := <-t.ctxt.kbd => + tk->keyboard(t, s); + + s := <-t.ctxt.ptr => + tk->pointer(t, *s); + + s := <-t.ctxt.ctl or + s = <-t.wreq or + s = <-wmctl => + tkclient->wmctl(t, s); + + s := <-ticon.ctxt.kbd => + tk->keyboard(ticon, s); + s := <-ticon.ctxt.ptr => + tk->pointer(ticon, *s); + s := <-ticon.ctxt.ctl or + s = <-ticon.wreq or + s = <-iconctl => + tkclient->wmctl(ticon, s); + + (off, data, fid, wc) := <-iocmd.write => # remote io control + if (wc == nil) + break; + spawn send(cmd, string data[0:len data]); + wc <-= (len data, nil); + + (nil, nbytes, fid, rc) := <-iocmd.read => + if (rc != nil) + rc <-= (nil, "not readable"); + + press := <-cmd => + case press { + "cancel" or "disconnect" => + tk->cmd(t, ".stat configure -text 'Disconnecting..."); + tk->cmd(t, "update"); + ppp->reset(); + if (!connected) { + # other end may have gone away + alt { + chn <-= 666 => ; + * => ; + } + } + break Work; + * => ; + } + + prs := <-tsk => + case prs { + "open" => + tk->cmd(ticon, ". unmap; update"); + tk->cmd(t, ". map; raise .; update"); + winmapped = 1; + timecount = 0; + * => ; + } + + s := <-stat => + if (s == -1) { # just an update event + if(winmapped){ + if(!connected) { # increment status bar + if (x < xmin+INCR) { + x++; + tk->cmd(t, ".cprog create rectangle 0 0 "+string x + BARH+" -fill "+LTGRN); + } + }else{ + timecount++; + if(timecount > 1){ + winmapped = 0; + timecount = 0; + tk->cmd(t, ". unmap; update"); + tk->cmd(ticon, ". map; raise .; update"); + continue; + } + } + tk->cmd(t, "raise .; update"); + } else { + tk->cmd(ticon, "raise .; update"); + timecount = 0; + } + continue; + } + if (s == ppp->s_Error) { + tk->cmd(t, ".stat configure -text '"+ppp->lasterror); + if (!winmapped) { + tk->cmd(ticon, ". unmap; update"); + tk->cmd(t, ". map; raise ."); + } + tk->cmd(t, "update"); + sys->sleep(3000); + ppp->reset(); + if (!connected) + chn <-= 0; # Failure + break Work; + } + + if (s == ppp->s_Initialized) + tk->cmd(t,".cprog create rectangle 0 0 "+string BARW + BARH+" -fill "+PBG); + + x = xmin = s * INCR; + if (xmin > BARW) + xmin = BARW; + tk->cmd(t, ".cprog create rectangle 0 0 "+string xmin + BARH+" -fill "+LTGRN); + tk->cmd(t, "raise .; update"); + tk->cmd(t, ".stat configure -text '"+X(stat_msgs[s])); + + if (s == ppp->s_SuccessPPP || s == ppp->s_Done) { + if(!connected){ + chn <-= 1; + connected = 1; + } + pppquanta = ICONQUANTA; + + # find and display connection speed + speed := findrate("/dev/modemstat", "rcvrate" :: "baud" :: nil); + if(speed != nil) + tk->cmd(t, ".stat configure -text {"+X(stat_msgs[s])+" "+speed+" bps}"); + else + tk->cmd(t, ".stat configure -text {"+X(stat_msgs[s])+"}"); + tk->cmd(t, ".done configure -text Disconnect -command 'send cmd disconnect"); + tk->cmd(t, "update"); + sys->sleep(2000); + tk->cmd(t, ". unmap; pack forget .fprog; update"); + winmapped = 0; + tk->cmd(ticon, ". map; raise .; update"); + } + + tk->cmd(t, "update"); + } + sync_chan <-= 1; # stop ppp_timer +} + +findrate(file: string, opt: list of string): string +{ + fd := sys->open(file, sys->OREAD); + if(fd == nil) + return nil; + buf := array [1024] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 1) + return nil; + (nil, flds) := sys->tokenize(string buf[0:n], " \t\r\n"); + for(; flds != nil; flds = tl flds) + for(l := opt; l != nil; l = tl l) + if (hd flds == hd l) + return hd tl flds; + return nil; +} + + + +# Translate a string + +X(s : string) : string +{ + if (dict== nil) return s; + return dict.xlate(s); +} + diff --git a/appl/cmd/ip/ppp/pppgui.m b/appl/cmd/ip/ppp/pppgui.m new file mode 100644 index 00000000..af9ec574 --- /dev/null +++ b/appl/cmd/ip/ppp/pppgui.m @@ -0,0 +1,21 @@ +# +# Copyright © 1998 Lucent Technologies Inc. All rights reserved. +# Revisions copyright © 2000,2001 Vita Nuova Holdings Limited. All rights reserved. +# +# Originally Written by N. W. Knauft +# Adapted by E. V. Hensbergen (ericvh@lucent.com) +# Further adapted by Vita Nuova +# + +PPPGUI: module +{ + PATH: con "/dis/ip/ppp/pppgui.dis"; + + # Dimension constant for ISP Connect window + WIDTH: con 300; + HEIGHT: con 58; + + init: fn(ctxt: ref Draw->Context, stat: chan of int, + ppp: PPPClient, args: list of string): chan of int; +}; + diff --git a/appl/cmd/ip/ppp/ppptest.b b/appl/cmd/ip/ppp/ppptest.b new file mode 100644 index 00000000..e5dfced0 --- /dev/null +++ b/appl/cmd/ip/ppp/ppptest.b @@ -0,0 +1,86 @@ +# Last change: R 24 May 2001 11:05 am +implement PPPTest; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "lock.m"; +include "modem.m"; +include "script.m"; +include "pppclient.m"; +include "pppgui.m"; + +PPPTest: module { + init: fn(nil: ref Draw->Context, args: list of string); +}; +usage() +{ + sys->print("ppptest device modem_init tel user password \n"); + sys->print("Example: ppptest /dev/modem atw2 4125678 rome xxxxxxxx\n"); + exit; + +} +init( ctxt: ref Draw->Context, argv: list of string ) +{ + sys = load Sys Sys->PATH; + + mi: Modem->ModemInfo; + pi: PPPClient->PPPInfo; + tel : string; +# si: Script->ScriptInfo; + argv = tl argv; + if(argv == nil) + usage(); + else + mi.path = hd argv; + + argv = tl argv; + if(argv == nil) + usage(); + else + mi.init = hd argv; + argv = tl argv; + if(argv == nil) + usage(); + else + tel = hd argv; + argv = tl argv; + if(argv == nil) + usage(); + else + pi.username = hd argv; + argv = tl argv; + if(argv==nil) + usage(); + else + pi.password = hd argv; + + + #si.path = "rdid.script"; + #si.username = "ericvh"; + #si.password = "foobar"; + #si.timeout = 60; + + + ppp := load PPPClient PPPClient->PATH; + + logger := chan of int; + + spawn ppp->connect( ref mi, tel, nil, ref pi, logger ); + + pppgui := load PPPGUI PPPGUI->PATH; + respchan := pppgui->init( ctxt, logger,ppp, nil); + + event := 0; + while (1) { + event =<- respchan; + sys->print("GUI event received: %d\n",event); + if (event) { + sys->print("success"); + exit; + } else { + raise "fail: Couldn't connect to ISP"; + } + } +} diff --git a/appl/cmd/ip/ppp/script.b b/appl/cmd/ip/ppp/script.b new file mode 100644 index 00000000..8be184a4 --- /dev/null +++ b/appl/cmd/ip/ppp/script.b @@ -0,0 +1,168 @@ +implement Script; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "lock.m"; +include "modem.m"; + modem: Modem; + +include "script.m"; + +delim: con "-"; # expect-send delimiter +BUFSIZE: con (1024 * 32); + +execute( modmod: Modem, m: ref Modem->Device, scriptinfo: ref ScriptInfo ) +{ + sys= load Sys Sys->PATH; + str= load String String->PATH; + if (str == nil) { + raise "fail: couldn't load string module"; + return; + } + modem = modmod; + + if (scriptinfo.path != nil) { + sys->print("Executing Script %s\n",scriptinfo.path); + # load the script + scriptinfo.content = scriptload(scriptinfo.path); + } else { + sys->print("Executing Inline Script\n"); + } + + # Check for timeout variable + + if (scriptinfo.timeout == 0) + scriptinfo.timeout = 20; + + tend := sys->millisec() + 1000*scriptinfo.timeout; + + conv := scriptinfo.content; + + while (conv != nil) { + e, s: string = nil; + p := hd conv; + conv = tl conv; + if (len p == 0) + continue; + sys->print("script: %s\n",p); + if (p[0] == '-') { # just send + if (len p == 1) + continue; + s = p[1:]; + } else { + (n, esl) := sys->tokenize(p, delim); + if (n > 0) { + e = hd esl; + esl = tl esl; + if (n > 1) + s = hd esl; + } + } + if (e != nil) { + if (match(m, special(e,scriptinfo), tend-sys->millisec()) == 0) { + sys->print("script: match failed\n"); + raise "fail: Script Failed"; + return; + } + } + if (s != nil) + modem->send(m, special(s, scriptinfo)); + } + + sys->print("script: done!\n"); +} + +match(m: ref Modem->Device, s: string, timo: int): int +{ + for(;;) { + c := modem->getc(m, timo); + if (c == '\r') + c = '\n'; + sys->print("%c",c); + if (c == 0) + return 0; + head: + while(c == s[0]) { + i := 1; + while(i < len s) { + c = modem->getc(m, timo); + if (c == '\r') + c = '\n'; + sys->print("%c",c); + if(c == 0) + return 0; + if(c != s[i]) + continue head; + i++; + } + return 1; + } + if(c == '~') + return 1; # assume PPP for now + } +} + +# +# Expand special script sequences +# +special(s: string, scriptinfo: ref ScriptInfo ): string +{ + if (s == "$username") # special variable + s = scriptinfo.username; + else if (s == "$password") + s = scriptinfo.password; + + return deparse(s); +} + +deparse(s : string) : string +{ + r: string = ""; + for(i:=0; i < len s; i++) { + c := s[i]; + if (c == '\\' && i+1 < len s) { + c = s[++i]; + case c { + 't' => c = '\t'; + 'n' => c = '\n'; + 'r' => c = '\r'; + 'b' => c = '\b'; + 'a' => c = '\a'; + 'v' => c = '\v'; + '0' => c = '\0'; + '$' => c = '$'; + 'u' => + if (i+4 < len s) { + i++; + (c, nil) = str->toint(s[i:i+4], 16); + i+=3; + } + } + } + r[len r] = c; + } + return r; +} + +scriptload( path: string) :list of string +{ + dfd := sys->open(path, Sys->OREAD); + if (dfd == nil) { + raise "fail: Script file ("+path+") not found"; + return nil; + } + + scriptbuf := array[BUFSIZE] of byte; + scriptlen := sys->read(dfd, scriptbuf, len scriptbuf); + if(scriptlen < 0) + raise "fail: can't read script: "+sys->sprint("%r"); + + (nil, scriptlist) := sys->tokenize(string scriptbuf[0:scriptlen], "\n"); + return scriptlist; +} diff --git a/appl/cmd/ip/ppp/script.m b/appl/cmd/ip/ppp/script.m new file mode 100644 index 00000000..342d4d79 --- /dev/null +++ b/appl/cmd/ip/ppp/script.m @@ -0,0 +1,14 @@ +Script: module { + PATH: con "/dis/ip/ppp/script.dis"; + + ScriptInfo: adt { + path: string; + content: list of string; + timeout: int; + username: string; + password: string; + }; + + execute: fn( modem: Modem, m: ref Modem->Device, + scriptinfo: ref ScriptInfo ); +}; diff --git a/appl/cmd/ip/rip.b b/appl/cmd/ip/rip.b new file mode 100644 index 00000000..90c1b6ce --- /dev/null +++ b/appl/cmd/ip/rip.b @@ -0,0 +1,620 @@ +implement Rip; + +# basic RIP implementation +# understands v2, sends v1 + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "daytime.m"; + daytime: Daytime; + +include "ip.m"; + ip: IP; + IPaddr, Ifcaddr, Udphdr: import ip; + +include "attrdb.m"; + attrdb: Attrdb; + +include "arg.m"; + +Rip: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +# rip header: +# op[1] version[1] pad[2] + +Oop: con 0; # op: byte +Oversion: con 1; # version: byte +Opad: con 2; # 2 byte pad +Riphdrlen: con Opad+2; # op[1] version[1] mbz[2] + +# rip route entry: +# type[2] tag[2] addr[4] mask[4] nexthop[4] metric[4] + +Otype: con 0; # type[2] +Otag: con Otype+2; # tag[2] v2 or mbz v1 +Oaddr: con Otag+2; # addr[4] +Omask: con Oaddr+4; # mask[4] v2 or mbz v1 +Onexthop: con Omask+4; +Ometric: con Onexthop+4; # metric[4] +Ipdestlen: con Ometric+4; + +Maxripmsg: con 512; + +# operations +OpRequest: con 1; # want route +OpReply: con 2; # all or part of route table + +HopLimit: con 16; # defined by protocol as `infinity' +RoutesInPkt: con 25; # limit defined by protocol +RIPport: con 520; + +Expired: con 180; +Discard: con 240; + +OutputRate: con 60; # seconds between routing table transmissions + +NetworkCost: con 1; # assume the simple case + +Gateway: adt { + dest: IPaddr; + mask: IPaddr; + gateway: IPaddr; + metric: int; + valid: int; + changed: int; + local: int; + time: int; + + contains: fn(g: self ref Gateway, a: IPaddr): int; +}; + +netfd: ref Sys->FD; +routefd: ref Sys->FD; +AF_INET: con 2; + +routes: array of ref Gateway; +Routeinc: con 50; +defroute: ref Gateway; +debug := 0; +nochange := 0; +quiet := 1; +myversion := 1; # default protocol version +logfile := "iproute"; +netdir := "/net"; +now: int; +nets: list of ref Ifcaddr; +addrs: list of IPaddr; + +syslog(nil: int, nil: string, s: string) +{ + sys->print("rip: %s\n", s); +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + daytime = load Daytime Daytime->PATH; + ip = load IP IP->PATH; + ip->init(); + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("ip/rip [-d] [-r]"); + while((o := arg->opt()) != 0) + case o { + 'd' => debug++; + 'b' => quiet = 0; + '2' => myversion = 2; + 'n' => nochange = 1; + 'x' => netdir = arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args != nil) + quiet = 0; + for(; args != nil; args = tl args){ + (ok, a) := IPaddr.parse(hd args); + if(ok < 0) + fatal(sys->sprint("invalid address: %s", hd args)); + addrs = a :: addrs; + } + arg = nil; + + sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKNS, nil); + + whereami(); + addlocal(); + + routefd = sys->open(sys->sprint("%s/iproute", netdir), Sys->ORDWR); + if(routefd == nil) + fatal(sys->sprint("can't open %s/iproute: %r", netdir)); + readroutes(); + + syslog(0, logfile, "started"); + + netfd = riplisten(); + + # broadcast request for all routes + + if(!quiet){ + sendall(OpRequest, 0); + spawn sender(); + } + + # read routing requests + + buf := array[8192] of byte; + while((nb := sys->read(netfd, buf, len buf)) > 0){ + nb -= Riphdrlen + IP->Udphdrlen; + if(nb < 0) + continue; + uh := Udphdr.unpack(buf, IP->Udphdrlen); + hdr := buf[IP->Udphdrlen:]; + version := int hdr[Oversion]; + if(version < 1) + continue; + bp := buf[IP->Udphdrlen + Riphdrlen:]; + case int hdr[Oop] { + OpRequest => + # TO DO: transmit in response to request? only if something interesting to say... + ; + + OpReply => + # wrong source port? + if(uh.rport != RIPport) + continue; + # my own broadcast? + if(ismyaddr(uh.raddr)) + continue; + now = daytime->now(); + if(debug > 1) + sys->fprint(sys->fildes(2), "from %s:\n", uh.raddr.text()); + for(; (nb -= Ipdestlen) >= 0; bp = bp[Ipdestlen:]) + unpackroute(bp, version, uh.raddr); + * => + if(debug) + sys->print("rip: unexpected op: %d\n", int hdr[Oop]); + } + } +} + +whereami() +{ + for(ifcs := ip->readipifc(netdir, -1).t0; ifcs != nil; ifcs = tl ifcs) + for(al := (hd ifcs).addrs; al != nil; al = tl al){ + ifa := hd al; + if(!ifa.ip.isv4()) + continue; + # how to tell broadcast? must be told? actually, it's in /net/iproute + nets = ifa :: nets; + } +} + +ismyaddr(a: IPaddr): int +{ + for(l := nets; l != nil; l = tl l) + if((hd l).ip.eq(a)) + return 1; + return 0; +} + +addlocal() +{ + for(l := nets; l != nil; l = tl l){ + ifc := hd l; + g := lookup(ifc.net); + g.valid = 1; + g.local = 1; + g.gateway = ifc.ip; + g.mask = ifc.mask; + g.metric = NetworkCost; + g.time = 0; + g.changed = 1; + if(debug) + syslog(0, logfile, sys->sprint("Existing: %s & %s -> %s", g.dest.text(), g.mask.masktext(), g.gateway.text())); + } +} + +# +# record any existing routes +# +readroutes() +{ + now = daytime->now(); + b := bufio->fopen(routefd, Sys->OREAD); + while((l := b.gets('\n')) != nil){ + (nf, flds) := sys->tokenize(l, " \t"); + if(nf >= 5){ + flags := hd tl tl tl flds; + if(flags == nil || flags[0] != '4' || contains(flags, "ibum")) + continue; + g := lookup(parseip(hd flds)); + g.mask = parsemask(hd tl flds); + g.gateway = parseip(hd tl tl flds); + g.metric = HopLimit; + g.time = now; + g.changed = 1; + if(debug) + syslog(0, logfile, sys->sprint("Existing: %s & %s -> %s", g.dest.text(), g.mask.masktext(), g.gateway.text())); + if(iszero(g.dest) && iszero(g.mask)){ + defroute = g; + g.local = 1; + }else if(defroute != nil && g.dest.eq(defroute.gateway)) + continue; + else + g.local = !ismyaddr(g.gateway); + } + } +} + +unpackroute(b: array of byte, version: int, gwa: IPaddr) +{ + # check that it's an IP route, valid metric, MBZ fields zero + + if(b[0] != byte 0 || b[1] != byte AF_INET){ + if(debug > 1) + sys->fprint(sys->fildes(2), "\t-- unknown address type %x,%x\n", int b[0], int b[1]); + return; + } + dest := IPaddr.newv4(b[Oaddr:]); + mask: IPaddr; + if(version == 1){ + # check MBZ fields + if(ip->get2(b, 2) | ip->get4(b, Omask) | ip->get4(b, Onexthop)){ + if(debug > 1) + sys->fprint(sys->fildes(2), "\t-- non-zero MBZ\n"); + return; + } + mask = maskgen(dest); + }else if(version == 2){ + if(ip->get4(b, Omask)) + mask = IPaddr.newv4(b[Omask:]); + else + mask = maskgen(dest); + if(ip->get4(b, Onexthop)) + gwa = IPaddr.newv4(b[Onexthop:]); + } + metric := ip->get4(b, Ometric); + if(debug > 1) + sys->fprint(sys->fildes(2), "\t%s %d\n", dest.text(), metric); + if(metric <= 0 || metric > HopLimit) + return; + + # 1058/3.4.2: response processing + # ignore route if IP address is: + # class D or E + # net 0 (except perhaps 0.0.0.0) + # net 127 + # broadcast address (all 1s host part) + # we allow host routes + + if(dest.ismulticast() || dest.a[0] == byte 0 || dest.a[0] == byte 16r7F){ + if(debug > 1) + sys->fprint(sys->fildes(2), "\t%s %d invalid addr\n", dest.text(), metric); + return; + } + if(isbroadcast(dest, mask)){ + if(debug > 1) + sys->fprint(sys->fildes(2), "\t%s & %s -> broadcast\n", dest.text(), mask.masktext()); + return; + } + + # update the metric min(metric+NetworkCost, HopLimit) + + metric += NetworkCost; + if(metric > HopLimit) + metric = HopLimit; + + updateroute(dest, mask, gwa, metric); +} + +updateroute(dest, mask, gwa: IPaddr, metric: int) +{ + # RFC1058 rules page 27-28, with optional replacement of expiring routes + r := lookup(dest); + if(r.valid){ + if(r.local) + return; # local, don't touch + if(r.gateway.eq(gwa)){ + if(metric != HopLimit){ + r.metric = metric; + r.time = now; + }else{ + # metric == HopLimit + if(r.metric != HopLimit){ + r.metric = metric; + r.changed = 1; + r.time = now - (Discard-120); + delroute(r); # don't use it for routing + # route remains valid but advertised with metric HopLimit + } else if(now >= r.time+Discard){ + delroute(r); # finally dead + r.valid = 0; + r.changed = 1; + } + } + }else if(metric < r.metric || + metric != HopLimit && metric == r.metric && now > r.time+Expired/2){ + delroute(r); + r.metric = metric; + r.gateway = gwa; + r.time = now; + addroute(r); + } + } else if(metric < HopLimit){ # new entry + + # 1058/3.4.2: don't add route-to-host if host is on net/subnet + # for which we have at least as good a route + + if(!mask.eq(ip->allbits) || + ((pr := findroute(dest)) == nil || metric <= pr.metric)){ + r.valid = 1; + r.changed = 1; + r.time = now; + r.metric = metric; + r.dest = dest; + r.mask = mask; + r.gateway = gwa; + addroute(r); + } + } +} + +sender() +{ + for(;;){ + sys->sleep(OutputRate*1000); # could add some random fizz + sendall(OpReply, 1); + } +} + +onlist(a: IPaddr, l: list of IPaddr): int +{ + for(; l != nil; l = tl l) + if(a.eq(hd l)) + return 1; + return 0; +} + +sendall(op: int, changes: int) +{ + for(l := nets; l != nil; l = tl l){ + if(addrs != nil && !onlist((hd l).net, addrs)) + continue; + a := (hd l).net.copy(); + b := (ip->allbits).maskn((hd l).mask); + for(i := 0; i < len a.a; i++) + a.a[i] |= b.a[i]; + sendroutes(hd l, a, op, changes); + } + for(i := 0; i < len routes; i++) + if((r := routes[i]) != nil) + r.changed = 0; +} + +zeroentry := array[Ipdestlen] of {* => byte 0}; + +sendroutes(ifc: ref Ifcaddr, dst: IPaddr, op: int, changes: int) +{ + if(debug > 1) + sys->print("rip: send %s\n", dst.text()); + buf := array[Maxripmsg+IP->Udphdrlen] of byte; + hdr := Udphdr.new(); + hdr.lport = hdr.rport = RIPport; + hdr.raddr = dst; # needn't copy + hdr.pack(buf, IP->Udphdrlen); + o := IP->Udphdrlen; + buf[o] = byte op; + buf[o+1] = byte myversion; + buf[o+2] = byte 0; + buf[o+3] = byte 0; + o += Riphdrlen; + rips := buf[IP->Udphdrlen+Riphdrlen:]; + if(op == OpRequest){ + buf[o:] = zeroentry; + ip->put4(buf, o+Ometric, HopLimit); + o += Ipdestlen; + } else { + # send routes + for(i:=0; i<len routes; i++){ + r := routes[i]; + if(r == nil || !r.valid || changes && !r.changed) + continue; + if(r == defroute) + continue; + if(r.dest.eq(ifc.net) || isonnet(r.dest, ifc)) + continue; + netmask := r.dest.classmask(); + subnet := !r.mask.eq(netmask); + if(myversion < 2 && !r.mask.eq(ip->allbits)){ + # if not a host route, don't let a subnet route leave its net + if(subnet && !netmask.eq(ifc.ip.classmask())) + continue; + } + if(o+Ipdestlen > IP->Udphdrlen+Maxripmsg){ + if(sys->write(netfd, buf, o) < 0) + sys->fprint(sys->fildes(2), "RIP write failed: %r\n"); + o = IP->Udphdrlen + Riphdrlen; + } + buf[o:] = zeroentry; + ip->put2(buf, o+Otype, AF_INET); + buf[o+Oaddr:] = r.dest.v4(); + ip->put4(buf, o+Ometric, r.metric); + if(myversion == 2 && subnet) + buf[o+Omask:] = r.mask.v4(); + o += Ipdestlen; + } + } + if(o > IP->Udphdrlen+Riphdrlen && sys->write(netfd, buf, o) < 0) + sys->fprint(sys->fildes(2), "rip: network write to %s failed: %r\n", dst.text()); +} + +lookup(addr: IPaddr): ref Gateway +{ + avail := -1; + for(i:=0; i<len routes; i++){ + g := routes[i]; + if(g == nil || !g.valid){ + if(avail < 0) + avail = i; + continue; + } + if(g.dest.eq(addr)) + return g; + } + if(avail < 0){ + avail = len routes; + a := array[len routes+Routeinc] of ref Gateway; + a[0:] = routes; + routes = a; + } + if((g := routes[avail]) == nil){ + g = ref Gateway; + routes[avail] = g; + g.valid = 0; + } + g.dest = addr; + return g; +} + +findroute(a: IPaddr): ref Gateway +{ + pr: ref Gateway; + for(i:=0; i<len routes; i++){ + r := routes[i]; + if(r == nil || !r.valid) + continue; + if(r.contains(a) && (pr == nil || !maskle(r.mask, pr.mask))) + pr = r; # more specific mask + } + return pr; +} + +maskgen(addr: IPaddr): IPaddr +{ + net: ref Ifcaddr; + for(l := nets; l != nil; l = tl l){ + ifc := hd l; + if(isonnet(addr, ifc) && + (net == nil || maskle(ifc.mask, net.mask))) # less specific mask? + net = ifc; + } + if(net != nil) + return net.mask; + return addr.classmask(); +} + +isonnet(a: IPaddr, n: ref Ifcaddr): int +{ + return a.mask(n.mask).eq(n.net); +} + +isbroadcast(a: IPaddr, mask: IPaddr): int +{ + h := a.maskn(mask); # host part + hm := (ip->allbits).maskn(mask); # host part of mask + return h.eq(hm); +} + +iszero(a: IPaddr): int +{ + return a.eq(ip->v4noaddr) || a.eq(ip->noaddr); +} + +maskle(a, b: IPaddr): int +{ + return a.mask(b).eq(a); +} + +# +# add ipdest mask gateway +# add 0.0.0.0 0.0.0.0 gateway (default) +# delete ipdest mask +# +addroute(g: ref Gateway) +{ + if(iszero(g.mask) && iszero(g.dest)) + g.valid = 0; # don't change default route + else if(defroute != nil && defroute.gateway.eq(g.gateway)){ + if(debug) + syslog(0, logfile, sys->sprint("default %s %s", g.dest.text(), g.mask.text())); # don't need a new entry + g.valid = 1; + g.changed = 1; + } else { + if(debug) + syslog(0, logfile, sys->sprint("add %s %s %s", g.dest.text(), g.mask.text(), g.gateway.text())); + if(nochange || sys->fprint(routefd, "add %s %s %s", g.dest.text(), g.mask.text(), g.gateway.text()) > 0){ + g.valid = 1; + g.changed = 1; + } + } +} + +delroute(g: ref Gateway) +{ + if(debug) + syslog(0, logfile, sys->sprint("delete %s %s", g.dest.text(), g.mask.text())); + if(!nochange) + sys->fprint(routefd, "delete %s %s", g.dest.text(), g.mask.text()); +} + +parseip(s: string): IPaddr +{ + (ok, a) := IPaddr.parse(s); + if(ok < 0) + raise "bad route"; + return a; +} + +parsemask(s: string): IPaddr +{ + (ok, a) := IPaddr.parsemask(s); + if(ok < 0) + raise "bad route"; + return a; +} + +contains(s: string, t: string): int +{ + for(i := 0; i < len s; i++) + for(j := 0; j < len t; j++) + if(s[i] == t[j]) + return 1; + return 0; +} + +Gateway.contains(g: self ref Gateway, a: IPaddr): int +{ + return g.dest.eq(a.mask(g.mask)); +} + +riplisten(): ref Sys->FD +{ + addr := sys->sprint("%s/udp!*!rip", netdir); + (ok, c) := sys->announce(addr); + if(ok < 0) + fatal(sys->sprint("can't announce %s: %r", addr)); + if(sys->fprint(c.cfd, "headers") < 0) + fatal(sys->sprint("can't set udp headers: %r")); + fd := sys->open(c.dir+"/data", Sys->ORDWR); + if(fd == nil) + fatal(sys->sprint("can't open %s: %r", c.dir+"/data")); + return fd; +} + +fatal(s: string) +{ + syslog(0, logfile, s); + raise "fail:error"; +} diff --git a/appl/cmd/ip/sntp.b b/appl/cmd/ip/sntp.b new file mode 100644 index 00000000..067d857d --- /dev/null +++ b/appl/cmd/ip/sntp.b @@ -0,0 +1,313 @@ +implement Sntp; + +# +# rfc1361 (simple network time protocol) +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "ip.m"; + ip: IP; + IPaddr: import ip; + +include "timers.m"; + timers: Timers; + Timer: import timers; + +include "arg.m"; + +Sntp: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +debug := 0; + +Retries: con 4; +Delay: con 3*1000; # milliseconds + +SNTP: adt { + li: int; + vn: int; + mode: int; + stratum: int; # level of local clock + poll: int; # log2(maximum interval in seconds between successive messages) + precision: int; # log2(seconds precision of local clock) [eg, -6 for mains, -18 for microsec] + rootdelay: int; # round trip delay in seconds to reference (16:16 fraction) + dispersion: int; # maximum error relative to primary reference + clockid: string; # reference clock identifier + reftime: big; # local time at which clock last set/corrected + orgtime: big; # local time at which client transmitted request + rcvtime: big; # time at which request arrived at server + xmttime: big; # time server transmitted reply + auth: array of byte; # auth field (ignored by this implementation) + + new: fn(vn, mode: int): ref SNTP; + pack: fn(s: self ref SNTP): array of byte; + unpack: fn(a: array of byte): ref SNTP; +}; +SNTPlen: con 4+3*4+4*8; + +Version: con 1; # accepted by version 2 and version 3 servers +Stratum: con 0; +Poll: con 0; +LI: con 0; +Symmetric: con 2; +ClientMode: con 3; +ServerMode: con 4; +Epoch: con big 86400*big (365*70 + 17); # seconds between 1 Jan 1900 and 1 Jan 1970 + +Microsec: con big 100000; + +server := "$ntp"; +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + ip = load IP IP->PATH; + timers = load Timers Timers->PATH; + + ip->init(); + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("sntp [-d] [server]"); + + doset := 1; + while((o := arg->opt()) != 0) + case o { + 'd' => debug++; + 'i' => doset = 0; + * => arg->usage(); + } + args = arg->argv(); + if(len args > 1) + arg->usage(); + arg = nil; + + if(args != nil) + server = hd args; + + sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil); + stderr = sys->fildes(2); + timers->init(100); + + (ok, conn) := sys->dial(netmkaddr(server, "udp", "ntp"), nil); + if(ok < 0){ + sys->fprint(stderr, "sntp: can't dial %s: %r\n", server); + raise "fail:dial"; + } + + replies := chan of ref SNTP; + spawn reader(conn.dfd, replies); + + for(i:=0; i<Retries; i++){ + request := SNTP.new(Version, ClientMode); + request.poll = 6; + request.orgtime = (big time() + Epoch)<<32; + b := request.pack(); + if(sys->write(conn.dfd, b, len b) != len b){ + sys->fprint(stderr, "sntp: UDP write failed: %r\n"); + continue; + } + t := Timer.start(Delay); + alt{ + reply := <-replies => + t.stop(); + if(reply == nil) + quit("read error"); + if(debug){ + sys->fprint(stderr, "LI = %d, version = %d, mode = %d\n", reply.li, reply.vn, reply.mode); + if(reply.stratum == 1) + sys->fprint(stderr, "stratum = 1 (%s), ", reply.clockid); + else + sys->fprint(stderr, "stratum = %d, ", reply.stratum); + sys->fprint(stderr, "poll = %d, prec = %d\n", reply.poll, reply.precision); + sys->fprint(stderr, "rootdelay = %d, dispersion = %d\n", reply.rootdelay, reply.dispersion); + } + if(reply.vn == 0 || reply.vn > 3) + continue; # unsupported version, ignored + if(reply.mode >= 6 || reply.mode == ClientMode) + continue; + now := ((reply.xmttime>>32)&16rFFFFFFFF) - Epoch; + if(now <= big 1120000000) + continue; + if(reply.li == 3 || reply.stratum == 0) # unsynchronised + sys->fprint(stderr, "sntp: time server not synchronised to reference time\n"); + if(debug) + sys->print("%bd\n", now); + if(doset){ + settime("#r/rtc", now); + settime("/dev/time", now * Microsec); + } + quit(nil); + <-t.timeout => + continue; + } + } + sys->fprint(sys->fildes(2), "sntp: no response from server %s\n", server); + quit("timeout"); +} + +reader(fd: ref Sys->FD, replies: chan of ref SNTP) +{ + for(;;){ + buf := array[512] of byte; + nb := sys->read(fd, buf, len buf); + if(nb <= 0) + break; + reply := SNTP.unpack(buf[0:nb]); + if(reply == nil){ + # ignore bad replies + if(debug) + sys->fprint(stderr, "sntp: invalid reply (len %d)\n", nb); + continue; + } + replies <-= reply; + } + if(debug) + sys->fprint(stderr, "sntp: UDP read failed: %r\n"); + replies <-= nil; +} + +quit(s: string) +{ + pid := sys->pctl(0, nil); + timers->shutdown(); + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "killgrp"); + if(s != nil) + raise "fail:"+s; + exit; +} + +time(): int +{ + fd := sys->open("#r/rtctime", Sys->OREAD); + if(fd == nil){ + fd = sys->open("/dev/time", Sys->OREAD); + if(fd == nil) + return 0; + } + b := array[128] of byte; + n := sys->read(fd, b, len b); + if(n <= 0) + return 0; + return int (big string b[0:n] / big 1000000); +} + +settime(f: string, t: big) +{ + fd := sys->open(f, Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "%bd", t); +} + +get8(a: array of byte, i: int): big +{ + b := big ip->get4(a, i+4) & 16rFFFFFFFF; + return (big ip->get4(a, i) << 32) | b; +} + +put8(a: array of byte, o: int, v: big) +{ + ip->put4(a, o, int (v>>32)); + ip->put4(a, o+4, int v); +} + +SNTP.unpack(a: array of byte): ref SNTP +{ + if(len a < SNTPlen) + return nil; + s := ref SNTP; + mode := int a[0]; + s.li = mode>>6; + s.vn = (mode>>3); + s.mode = mode & 3; + s.stratum = int a[1]; + s.poll = int a[2]; + if(s.poll & 16r80) + s.poll |= ~0 << 8; + s.precision = int a[3]; + if(s.precision & 16r80) + s.precision |= ~0 << 8; + s.rootdelay = ip->get4(a, 4); + s.dispersion = ip->get4(a, 8); + if(s.stratum <= 1){ + for(i := 12; i < 16; i++) + if(a[i] == byte 0) + break; + s.clockid = string a[12:i]; + }else + s.clockid = sys->sprint("%d.%d.%d.%d", int a[12], int a[13], int a[14], int a[15]); + s.reftime = get8(a, 16); + s.orgtime = get8(a, 24); + s.rcvtime = get8(a, 32); + s.xmttime = get8(a, 40); + if(len a > SNTPlen) + s.auth = a[48:]; + return s; +} + +SNTP.pack(s: self ref SNTP): array of byte +{ + a := array[SNTPlen + len s.auth] of byte; + a[0] = byte ((s.li<<6) | (s.vn<<3) | s.mode); + a[1] = byte s.stratum; + a[2] = byte s.poll; + a[3] = byte s.precision; + ip->put4(a, 4, s.rootdelay); + ip->put4(a, 8, s.dispersion); + ip->put4(a, 12, 0); # clockid field + if(s.clockid != nil){ + if(s.stratum <= 1){ + b := array of byte s.clockid; + for(i := 0; i < len b && i < 4; i++) + a[12+i] = b[i]; + }else + a[12:] = IPaddr.parse(s.clockid).t1.v4(); + } + put8(a, 16, s.reftime); + put8(a, 24, s.orgtime); + put8(a, 32, s.rcvtime); + put8(a, 40, s.xmttime); + if(s.auth != nil) + a[48:] = s.auth; + return a; +} + +SNTP.new(vn, mode: int): ref SNTP +{ + s := ref SNTP; + s.vn = vn; + s.mode = mode; + s.li = 0; + s.stratum = 0; + s.poll = 0; + s.precision = 0; + s.clockid = nil; + s.reftime = big 0; + s.orgtime = big 0; + s.rcvtime = big 0; + s.xmttime = big 0; + return s; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/ip/tftpd.b b/appl/cmd/ip/tftpd.b new file mode 100644 index 00000000..12411078 --- /dev/null +++ b/appl/cmd/ip/tftpd.b @@ -0,0 +1,514 @@ +implement Tftpd; + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; + +include "draw.m"; + +include "arg.m"; + +include "ip.m"; + ip: IP; + IPaddr, Udphdr: import ip; + +Tftpd: module +{ + init: fn (nil: ref Draw->Context, argv: list of string); +}; + +dir:= "/services/tftpd"; +net:= "/net"; + +Tftp_READ: con 1; +Tftp_WRITE: con 2; +Tftp_DATA: con 3; +Tftp_ACK: con 4; +Tftp_ERROR: con 5; + +Segsize: con 512; + +dbg := 0; +restricted := 0; +port := 69; + +Udphdrsize: con IP->OUdphdrlen; + +tftpcon: Sys->Connection; +tftpreq: ref Sys->FD; + +dokill(pid: int, scope: string) +{ + fd := sys->open("/prog/" + string pid + "/ctl", sys->OWRITE); + if(fd == nil) + fd = sys->open("#p/" + string pid + "/ctl", sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill%s", scope); +} + +kill(pid: int) { dokill(pid, ""); } +killgrp(pid: int) { dokill(pid, "grp"); } +killme() { kill(sys->pctl(0,nil)); } +killus() { killgrp(sys->pctl(0,nil)); } + +DBG(s: string) +{ + if(dbg) + sys->fprint(stderr, "tfptd: %d: %s\n", sys->pctl(0,nil), s); +} + +false, true: con iota; + +Timer: adt { + KILL: con -1; + ALARM: con -2; + RETRY: con -3; + sig: chan of int; + create: fn(): ref Timer; + destroy: fn(t: self ref Timer); + set: fn(t: self ref Timer, msec, nretry: int); + + ticker: fn(t: self ref Timer); + ticking: int; + wakeup: int; + timeout: int; + nretry: int; +}; + +Timer.create(): ref Timer +{ + t := ref Timer; + t.wakeup = 0; + t.ticking = false; + t.sig = chan of int; + return t; +} + +Timer.destroy(t: self ref Timer) +{ + DBG("Timer.destroy"); + alt { + t.sig <-= t.KILL => + DBG("sent final msg"); + * => + DBG("couldn't send final msg"); + } + DBG("Timer.destroy done"); +} + +Timer.ticker(t: self ref Timer) +{ + DBG("spawn: ticker"); + t.ticking = true; + while(t.wakeup > sys->millisec()) { + DBG("Timer.ticker sleeping for " + +string (t.wakeup-sys->millisec())); + sys->sleep(t.wakeup-sys->millisec()); + } + if(t.wakeup) { + DBG("Timer.ticker wakeup"); + if(t.nretry) { + alt { t.sig <-= t.RETRY => ; } + t.ticking = false; + t.set(t.timeout, t.nretry-1); + } else + alt { t.sig <-= t.ALARM => ; } + } + t.ticking = false; + DBG("unspawn: ticker"); +} + +Timer.set(t: self ref Timer, msec, nretry: int) +{ + DBG(sys->sprint("Timer.set(%d, %d)", msec, nretry)); + if(msec == 0) { + t.wakeup = 0; + t.timeout = 0; + t.nretry = 0; + } else { + t.wakeup = sys->millisec()+msec; + t.timeout = msec; + t.nretry = nretry; + if(!t.ticking) + spawn t.ticker(); + } +} + +killer(c: chan of int, pgid: int) +{ + DBG("spawn: killer"); + cmd := <- c; + DBG(sys->sprint("killer has awakened (flag=%d)", cmd)); + if(cmd == Timer.ALARM) { + killgrp(pgid); + DBG(sys->sprint("group %d has been killed", pgid)); + } + DBG("unspawn killer"); +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->NEWPGRP|Sys->FORKFD|Sys->FORKNS, nil); + stderr = sys->fildes(2); + + arg := load Arg Arg->PATH; + if(arg == nil) + fatal("can't load Arg"); + + arg->init(args); + arg->setusage("tftpd [-dr] [-p port] [-h homedir] [-x network-dir]"); + while((o := arg->opt()) != 0) + case o { + 'd' => dbg++; + 'h' => dir = arg->earg(); + 'r' => restricted = 1; + 'p' => port = int arg->earg(); + 'x' => net = arg->earg(); + * => arg->usage(); + } + args =arg->argv(); + if(args != nil){ + net = hd args; + args = tl args; + } + if(args != nil) + arg->usage(); + arg = nil; + + ip = load IP IP->PATH; + if(ip == nil) + fatal(sys->sprint("can't load %s: %r", IP->PATH)); + ip->init(); + + if(sys->chdir(dir) < 0) + fatal("can't chdir to " + dir); + + spawn mainthing(); +} + +mainthing() +{ + DBG("spawn: mainthing"); + bigbuf := array[32768] of byte; + + openlisten(); + setuser(); + for(;;) { + dlen := sys->read(tftpreq, bigbuf, len bigbuf); + if(dlen < 0) + fatal("listen"); + if(dlen < Udphdrsize) + continue; + + hdr := Udphdr.unpack(bigbuf, Udphdrsize); + + raddr := sys->sprint("%s/udp!%s!%d", net, hdr.raddr.text(), hdr.rport); + + DBG(sys->sprint("raddr=%s", raddr)); + (err, cx) := sys->dial(raddr, nil); + if(err < 0) + fatal("dialing "+raddr); + +# showbuf("bigbuf", bigbuf[0:dlen]); + + op := ip->get2(bigbuf, Udphdrsize); + mbuf := bigbuf[Udphdrsize+2:dlen]; # get past Udphdr and op + dlen -= 14; + + case op { + Tftp_READ or Tftp_WRITE => + ; + Tftp_ERROR => + DBG("tftp error"); + continue; + * => + nak(cx.dfd, 4, "Illegal TFTP operation"); + continue; + } + +# showbuf("mbuf", mbuf[0:dlen]); + + i := 0; + while(dlen > 0 && mbuf[i] != byte 0) { + dlen--; + i++; + } + + p := i++; + dlen--; + while(dlen > 0 && mbuf[i] != byte 0) { + dlen--; + i++; + } + + path := string mbuf[0:p]; + mode := string mbuf[p+1:i]; + DBG(sys->sprint("path = %s, mode = %s", path, mode)); + + if(dlen == 0) { + nak(cx.dfd, 0, "bad tftpmode"); + continue; + } + + if(restricted && dodgy(path)){ + nak(cx.dfd, 4, "Permission denied"); + continue; + } + + if(op == Tftp_READ) + spawn sendfile(cx.dfd, path, mode); + else + spawn recvfile(cx.dfd, path, mode); + } +} + +dodgy(path: string): int +{ + n := len path; + nd := len dir; + if(n == 0 || + path[0] == '#' || + path[0] == '/' && (n < nd+1 || path[0:nd] != dir || path[nd] != '/')) + return 1; + (nil, flds) := sys->tokenize(path, "/"); + for(; flds != nil; flds = tl flds) + if(hd flds == "..") + return 1; + return 0; +} + +showbuf(msg: string, b: array of byte) +{ + sys->fprint(stderr, "%s: size %d: ", msg, len b); + for(i:=0; i<len b; i++) + sys->fprint(stderr, "%.2ux ", int b[i]); + sys->fprint(stderr, "\n"); + for(i=0; i<len b; i++) + if(int b[i] >= 32 && int b[i] <= 126) + sys->fprint(stderr, " %c", int b[i]); + else + sys->fprint(stderr, " ."); + sys->fprint(stderr, "\n"); +} + +sendblock(sig: chan of int, buf: array of byte, net: ref sys->FD, ksig: chan of int) +{ + DBG("spawn: sendblocks"); + nbytes := 0; + loop: for(;;) { + DBG("sendblock: waiting for cmd"); + cmd := <- sig; + DBG(sys->sprint("sendblock: cmd=%d", cmd)); + case cmd { + Timer.KILL => + DBG("sendblock: killed"); + return; + Timer.RETRY => + ; + Timer.ALARM => + DBG("too many retries"); + break loop; + * => + nbytes = cmd; + } +# showbuf("sendblock", buf[0:nbytes]); + ret := sys->write(net, buf, 4+nbytes); + DBG(sys->sprint("ret=%d", ret)); + + if(ret < 0) { + ksig <-= Timer.ALARM; + fatal("tftp: network write error"); + } + if(ret != 4+nbytes) + return; + } + DBG("sendblock: exiting"); + alt { ksig <-= Timer.ALARM => ; } + DBG("unspawn: sendblocks"); +} + +sendfile(net: ref sys->FD, name: string, mode: string) +{ + + DBG(sys->sprint("spawn: sendfile: name=%s mode=%s", name, mode)); + + pgrp := sys->pctl(Sys->NEWPGRP, nil); + ack := array[1024] of byte; + if(name == "") { + nak(net, 0, "not in our database"); + return; + } + + file := sys->open(name, Sys->OREAD); + if(file == nil) { + DBG(sys->sprint("open failed: %s", name)); + errbuf := sys->sprint("%r"); + nak(net, 0, errbuf); + return; + } + DBG(sys->sprint("opened %s", name)); + + block := 0; + timer := Timer.create(); + ksig := chan of int; + buf := array[4+Segsize] of byte; + + spawn killer(ksig, pgrp); + spawn sendblock(timer.sig, buf, net, ksig); + + mainloop: for(;;) { + block++; + buf[0:] = array[] of {byte 0, byte Tftp_DATA, + byte (block>>8), byte block}; + n := sys->read(file, buf[4:], len buf-4); + DBG(sys->sprint("n=%d", n)); + if(n < 0) { + errbuf := sys->sprint("%r"); + nak(net, 0, errbuf); + break; + } + DBG(sys->sprint("signalling write of %d to block %d", n, block)); + timer.sig <-= n; + for(rxl := 0; rxl < 10; rxl++) { + + timer.set(1000, 15); + al := sys->read(net, ack, len ack); + timer.set(0, 0); + if(al < 0) { + timer.sig <-= Timer.ALARM; + break; + } + op := (int ack[0]<<8) | int ack[1]; + if(op == Tftp_ERROR) + break mainloop; + ackblock := (int ack[2]<<8) | int ack[3]; + DBG(sys->sprint("got ack: block=%d ackblock=%d", + block, ackblock)); + if(ackblock == block) + break; + if(ackblock == 16rffff) { + block--; + break; + } + } + if(n < len buf-4) + break; + } + timer.destroy(); + ksig <-= Timer.KILL; +} + +recvfile(fd: ref sys->FD, name: string, mode: string) +{ + DBG(sys->sprint("spawn: recvfile: name=%s mode=%s", name, mode)); + + pgrp := sys->pctl(Sys->NEWPGRP, nil); + + file := sys->create(name, sys->OWRITE, 8r666); + if(file == nil) { + errbuf := sys->sprint("%r"); + nak(fd, 0, errbuf); + return; + } + + block := 0; + ack(fd, block); + block++; + + buf := array[8+Segsize] of byte; + timer := Timer.create(); + spawn killer(timer.sig, pgrp); + + for(;;) { + timer.set(15000, 0); + DBG(sys->sprint("reading block %d", block)); + n := sys->read(fd, buf, len buf); + DBG(sys->sprint("read %d bytes", n)); + timer.set(0, 0); + + if(n < 0) + break; + op := int buf[0]<<8 | int buf[1]; + if(op == Tftp_ERROR) + break; + +# showbuf("got", buf[0:n]); + n -= 4; + inblock := int buf[2]<<8 | int buf[3]; +# showbuf("hdr", buf[0:4]); + if(op == Tftp_DATA) { + if(inblock == block) { + ret := sys->write(file, buf[4:], n); + if(ret < 0) { + errbuf := sys->sprint("%r"); + nak(fd, 0, errbuf); + break; + } + block++; + } + if(inblock < block) { + ack(fd, inblock); + DBG(sys->sprint("ok: inblock=%d block=%d", + inblock, block)); + } else + DBG(sys->sprint("FAIL: inblock=%d block=%d", + inblock, block)); + ack(fd, 16rffff); + if(n < 512) + break; + } + } + timer.destroy(); +} + +ack(fd: ref Sys->FD, block: int) +{ + buf := array[] of {byte 0, byte Tftp_ACK, byte (block>>8), byte block}; +# showbuf("ack", buf); + if(sys->write(fd, buf, 4) < 0) + fatal("write ack"); +} + + +nak(fd: ref Sys->FD, code: int, msg: string) +{ +sys->print("nak: %s\n", msg); + buf := array[128] of {byte 0, byte Tftp_ERROR, byte 0, byte code}; + bmsg := array of byte msg; + buf[4:] = bmsg; + buf[4+len bmsg] = byte 0; + if(sys->write(fd, buf, 4+len bmsg+1) < 0) + fatal("write nak"); +} + +fatal(msg: string) +{ + sys->fprint(stderr, "tftpd: %s: %r\n", msg); + killus(); + raise "fail:error"; +} + +openlisten() +{ + name := net+"/udp!*!" + string port; + err := 0; + (err, tftpcon) = sys->announce(name); + if(err < 0) + fatal("can't announce "+name); + if(sys->fprint(tftpcon.cfd, "headers") < 0) + fatal("can't set header mode"); + sys->fprint(tftpcon.cfd, "oldheaders"); + + tftpreq = sys->open(tftpcon.dir+"/data", sys->ORDWR); + if(tftpreq == nil) + fatal("open udp data"); +} + +setuser() +{ + f := sys->open("/dev/user", sys->OWRITE); + if(f != nil) + sys->fprint(f, "none"); +} + diff --git a/appl/cmd/ip/virgild.b b/appl/cmd/ip/virgild.b new file mode 100644 index 00000000..29ebba67 --- /dev/null +++ b/appl/cmd/ip/virgild.b @@ -0,0 +1,127 @@ +implement Virgild; + +include "sys.m"; +sys: Sys; + +include "draw.m"; + +include "ip.m"; + +Virgild: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +Udphdrsize: con IP->OUdphdrlen; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + + stderr = sys->fildes(2); + + sys->pctl(Sys->FORKNS|Sys->FORKFD, nil); + if(sys->chdir("/lib/ndb") < 0){ + sys->fprint(stderr, "virgild: no database\n"); + return; + } + + for(;;sys->sleep(10*1000)){ + fd := openlisten(); + if(fd == nil) + return; + + buf := array[512] of byte; + for(;;){ + n := sys->read(fd, buf, len buf); + if(n <= Udphdrsize){ + break; + } + if(n <= Udphdrsize+1) + continue; + + # dump any cruft after the question + for(i := Udphdrsize; i < n; i++){ + c := int buf[i]; + if(c == ' ' || c == 0 || c == '\n') + break; + } + + answer := query(string buf[Udphdrsize:i]); + if(answer == nil) + continue; + + # reply + r := array of byte answer; + if(len r > len buf - Udphdrsize) + continue; + buf[Udphdrsize:] = r; + sys->write(fd, buf, Udphdrsize+len r); + } + fd = nil; + } +} + +openlisten(): ref Sys->FD +{ + (ok, c) := sys->announce("udp!*!virgil"); + if(ok < 0){ + sys->fprint(stderr, "virgild: can't open port: %r\n"); + return nil; + } + + if(sys->fprint(c.cfd, "headers") <= 0){ + sys->fprint(stderr, "virgild: can't set headers: %r\n"); + return nil; + } + sys->fprint(c.cfd, "oldheaders"); + + c.dfd = sys->open(c.dir+"/data", Sys->ORDWR); + if(c.dfd == nil) { + sys->fprint(stderr, "virgild: can't open data file\n"); + return nil; + } + return c.dfd; +} + +# +# query is userid?question +# +# for now, we're ignoring userid +# +query(request: string): string +{ + (n, l) := sys->tokenize(request, "?"); + if(n < 2){ + sys->fprint(stderr, "virgild: bad request %s %d\n", request, n); + return nil; + } + + # + # until we have something better, ask cs + # to translate, make the request look cs-like + # + fd := sys->open("/net/cs", Sys->ORDWR); + if(fd == nil){ + sys->fprint(stderr, "virgild: can't open /net/cs - %r\n"); + return nil; + } + q := array of byte ("tcp!" + hd(tl l) + "!1000"); + if(sys->write(fd, q, len q) < 0){ + sys->fprint(stderr, "virgild: can't write /net/cs - %r: %s\n", string q); + return nil; + } + sys->seek(fd, big 0, 0); + buf := array[512-Udphdrsize-len request-1] of byte; + n = sys->read(fd, buf, len buf); + if(n <= 0){ + sys->fprint(stderr, "virgild: can't read /net/cs - %r\n"); + return nil; + } + + (nil, l) = sys->tokenize(string buf[0:n], " \t"); + (nil, l) = sys->tokenize(hd(tl l), "!"); + return request + "=" + hd l; +} diff --git a/appl/cmd/irtest.b b/appl/cmd/irtest.b new file mode 100644 index 00000000..3d0260c7 --- /dev/null +++ b/appl/cmd/irtest.b @@ -0,0 +1,70 @@ +implement Irtest; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "ir.m"; + ir: Ir; + +Irtest: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, nil: list of string) +{ + x := chan of int; + p := chan of int; + + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + ir = load Ir Ir->PATH; + if(ir == nil) + ir = load Ir Ir->SIMPATH; + if(ir == nil) { + sys->fprint(stderr, "load ir: %r\n"); + return; + } + + if(ir->init(x,p) != 0) { + sys->fprint(stderr, "Ir->init: %r\n"); + return; + } + <-p; + + names := array[] of { + "Zero", + "One", + "Two", + "Three", + "Four", + "Five", + "Six", + "Seven", + "Eight", + "Nine", + "ChanUP", + "ChanDN", + "VolUP", + "VolDN", + "FF", + "Rew", + "Up", + "Dn", + "Select", + "Power", + }; + + while((c := <-x) != Ir->EOF){ + c = ir->translate(c); + if(c == ir->Error) + sys->print("Error\n"); + else if(c >= len names) + sys->print("unknown %d\n", c); + else + sys->print("%s\n", names[c]); + } +} diff --git a/appl/cmd/itest.b b/appl/cmd/itest.b new file mode 100644 index 00000000..aa24e4de --- /dev/null +++ b/appl/cmd/itest.b @@ -0,0 +1,478 @@ +implement Itest; + +include "sys.m"; + sys: Sys; +include "string.m"; + str: String; +include "draw.m"; +include "daytime.m"; + daytime: Daytime; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "readdir.m"; + readdir: Readdir; +include "arg.m"; +include "itslib.m"; + S_INFO, S_WARN, S_ERROR, S_FATAL, S_STIME, S_ETIME: import Itslib; +include "env.m"; + env: Env; +include "sh.m"; + +SUMFILE: con "summary"; +MSGFILE: con "msgs"; +README: con "README"; + +configfile := ""; +cflag := -1; +verbosity := 3; +repcount := 1; +recroot := ""; +display_stderr := 0; +display_stdout := 0; +now := 0; + +stdout: ref Sys->FD; +stderr: ref Sys->FD; +context: ref Draw->Context; + +Test: adt { + spec: string; + fullspec: string; + cmd: Command; + recdir: string; + stdout: string; + stderr: string; + nruns: int; + nwarns: int; + nerrors: int; + nfatals: int; + failed: int; +}; + + +Itest: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + + + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stdout = sys->fildes(1); + stderr = sys->fildes(2); + context = ctxt; + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + nomod(Daytime->PATH); + str = load String String->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + nomod(Bufio->PATH); + if(str == nil) + nomod(String->PATH); + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + nomod(Readdir->PATH); + env = load Env Env->PATH; + if(env == nil) + nomod(Env->PATH); + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 'c' => cflag = toint("c", arg->arg(), 0, 9); + 'e' => display_stderr++; + 'o' => display_stdout++; + 'r' => repcount = toint("r", arg->arg(), 0, -1); + 'v' => verbosity = toint("v", arg->arg(), 0, 9); + 'C' => configfile = arg->arg(); + 'R' => recroot = arg->arg(); + * => usage(); + } + args = arg->argv(); + arg = nil; + testlist : array of ref Test; + if (args != nil) + testlist = arg_tests(args); + else if (configfile != "") + testlist = config_tests(configfile); + if (testlist == nil) + fatal("No tests to run"); + sys->pctl(Sys->FORKENV, nil); + if (env->setenv(Itslib->ENV_VERBOSITY, string verbosity)) + fatal("Failed to set environment variable " + Itslib->ENV_VERBOSITY); + if (repcount) + reps := string repcount; + else + reps = "infinite"; + if (len testlist == 1) ts := ""; + else ts = "s"; + if (repcount == 1) rs := ""; + else rs = "s"; + mreport(0, S_INFO, 2, sys->sprint("Starting tests - %s run%s of %d test%s", reps, rs, len testlist, ts)); + run := big 1; + tlist := testlist; + if (recroot != nil) + recn := highest(recroot) + 1; + while (repcount == 0 || run <= big repcount) { + mreport(1, S_INFO, 3, sys->sprint("Starting run %bd", run)); + for (i:=0; i<len testlist; i++) { + t := testlist[i]; + if (recroot != nil) { + t.recdir = sys->sprint("%s/%d", recroot, recn++); + mreport(2, S_INFO, 3, sys->sprint("Recording in %s", t.recdir)); + rfd := sys->create(t.recdir, Sys->OREAD, Sys->DMDIR | 8r770); + if (rfd == nil) + fatal(sys->sprint("Failed to create directory %s: %r\n", t.recdir)); + rfd = nil; + } + runtest(t); + } + mreport(1, S_INFO, 3, sys->sprint("Finished run %bd", run)); + run++; + } + mreport(0, S_INFO, 2, "Finished tests"); +} + +usage() +{ + sys->fprint(stderr, "Usage itest [-eo] [-c cflag] [-r count] [-v vlevel] [-C cfile] [-R recroot] [testdir ...]\n"); + raise "fail: usage"; +} + +fatal(s: string) +{ + sys->fprint(stderr, "%s\n", s); + raise "fail: error"; +} + +nomod(mod: string) +{ + sys->fprint(stderr, "Failed to load %s\n", mod); + raise "fail: module"; +} + +toint(opt, s: string, min, max: int): int +{ + if (len s == 0 || str->take(s, "[0-9]+-") != s) + fatal(sys->sprint("no value specified for option %s", opt)); + v := int s; + if (v < min) + fatal(sys->sprint("option %s value is less than minimum of %d: %d", opt, v, min)); + if (max != -1 && v > max) + fatal(sys->sprint("option %s value is greater than maximum of %d: %d", opt, v, max)); + return v; +} + +arg_tests(args: list of string): array of ref Test +{ + al := len args; + ta := array[al] of ref Test; + for (i:=0; i<al; i++) { + tspec := hd args; + args = tl args; + ta[i] = ref Test(tspec, "", nil, "", "", "", 0, 0, 0, 0, 0); + tcheck(ta[i]); + } + return ta; +} + +config_tests(cf: string): array of ref Test +{ + cl := linelist(cf); + if (cl == nil) + fatal("No tests in config file"); + al := len cl; + ta := array[al] of ref Test; + for (i:=0; i<al; i++) { + tspec := hd cl; + cl = tl cl; + ta[i] = ref Test(tspec, "", nil, "", "", "", 0, 0, 0, 0, 0); + tcheck(ta[i]); + } + return ta; + +} + +highest(path: string): int +{ + (da, nd) := readdir->init(path, Readdir->NAME); + high := 0; + for (i:=0; i<nd; i++) { + n := int da[i].name; + if (n > high) + high = n; + } + return high; +} + +tcheck(t: ref Test): int +{ + td := t.spec; + if (!checkdir(td)) { + fatal(sys->sprint("Failed to find test %s\n", td)); + return 0; + } + tf1 := t.spec + "/t.sh"; + tf2 := t.spec + "/t.dis"; + if (checkexec(tf1)) { + t.fullspec = tf1; + return 1; + } + if (checkexec(tf2)) { + t.fullspec = tf2; + return 1; + } + fatal(sys->sprint("Could not find executable files %s or %s\n", tf1, tf2)); + return 0; +} + +checkdir(d: string): int +{ + (ok, dir) := sys->stat(d); + if (ok != 0 || ! dir.qid.qtype & Sys->QTDIR) + return 0; + return 1; +} + +checkexec(d: string): int +{ + (ok, dir) := sys->stat(d); + if (ok != 0 || ! dir.mode & 8r100) + return 0; + return 1; +} + + +set_cflag(f: int) +{ + wfile("/dev/jit", string f, 0); + +} + +runtest(t: ref Test) +{ + if (t.failed) + return; + path := t.fullspec; + if (cflag != -1) { + mreport(0, S_INFO, 7, sys->sprint("Setting cflag to %d", cflag)); + set_cflag(cflag); + } + readme := t.spec + "/" + README; + mreport(2, S_INFO, 3, sys->sprint("Starting test %s cflag=%s", t.spec, rfile("/dev/jit"))); + if (verbosity > 8) + display_file(readme); + sync := chan of int; + spawn monitor(t, sync); + pid := <-sync; +} + +monitor(t: ref Test, sync: chan of int) +{ + pid := sys->pctl(Sys->FORKFD|Sys->FORKNS|Sys->FORKENV|Sys->NEWPGRP, nil); + pa := array[2] of ref Sys->FD; + if (sys->pipe(pa)) + fatal("Failed to set up pipe"); + if (env->setenv(Itslib->ENV_MFD, string pa[0].fd)) + fatal("Failed to set environment variable " + Itslib->ENV_MFD); + mlfd: ref Sys->FD; + if (t.recdir != nil) { + mfile := t.recdir+"/"+MSGFILE; + mlfd = sys->create(mfile, Sys->OWRITE, 8r660); + if (mlfd == nil) + fatal(sys->sprint("Failed to create %s: %r'\n", mfile)); + t.stdout = t.recdir+"/stdout"; + t.stderr = t.recdir+"/stderr"; + } else { + t.stdout = "/tmp/itest.stdout"; + t.stderr = "/tmp/itest.stderr"; + } + cf := int rfile("/dev/jit"); + stime := sys->millisec(); + swhen := daytime->now(); + etime := -1; + rsync := chan of int; + spawn runit(t.fullspec, t.stdout, t.stderr, t.spec, pa[0], rsync); + rpid := <-rsync; + pa[0] = nil; + (nwarns, nerrors, nfatals) := (0, 0, 0); + while (1) { + mbuf := array[Sys->ATOMICIO] of byte; + n := sys->read(pa[1], mbuf, len mbuf); + if (n <= 0) break; + msg := string mbuf[:n]; + sev := int msg[0:1]; + verb := int msg[1:2]; + body := msg[2:]; + if (sev == S_STIME) + stime = int body; + else if (sev == S_ETIME) + etime = int body; + else { + if (sev == S_WARN) { + nwarns++; + t.nwarns++; + } + else if (sev == S_ERROR) { + nerrors++; + t.nerrors++; + } + else if (sev == S_FATAL) { + nfatals++; + t.nfatals++; + } + mreport(3, sev, verb, sys->sprint("%s: %s", severs(sev), body)); + } + if (mlfd != nil) + sys->fprint(mlfd, "%d:%s", now, msg); + } + if (etime < 0) { + etime = sys->millisec(); + if (mlfd != nil) + sys->fprint(mlfd, "%d:%s", now, sys->sprint("%d0%d\n", S_ETIME, etime)); + } + elapsed := etime-stime; + errsum := sys->sprint("WRN:%d ERR:%d FTL:%d", nwarns, nerrors, nfatals); + mreport(2, S_INFO, 3, sys->sprint("Finished test %s after %dms - %s", t.spec, elapsed, errsum)); + if (t.recdir != "") { + wfile(t.recdir+"/"+SUMFILE, sys->sprint("%d %d %d %s\n", swhen, elapsed, cf, t.fullspec), 1); + } + if (display_stdout) { + mreport(2, 0, 0, "Stdout from test:"); + display_file(t.stdout); + } + if (display_stderr) { + mreport(2, 0, 0, "Stderr from test:"); + display_file(t.stderr); + } + sync <-= pid; +} + +runit(fullspec, sofile, sefile, tpath: string, mfd: ref Sys->FD, sync: chan of int) +{ + pid := sys->pctl(Sys->NEWFD|Sys->FORKNS, mfd.fd::nil); + o, e: ref Sys->FD; + o = sys->create(sofile, Sys->OWRITE, 8r660); + if (o == nil) + treport(mfd, S_ERROR, 0, "Failed to open stdout: %r\n"); + else + sys->dup(o.fd, 1); + o = nil; + e = sys->create(sefile, Sys->OWRITE, 8r660); + if (e == nil) + treport(mfd, S_ERROR, 0, "Failed to open stderr: %r\n"); + else + sys->dup(e.fd, 2); + e = nil; + sync <-= pid; + args := list of {fullspec}; + if (fullspec[len fullspec-1] == 's') + cmd := load Command fullspec; + else { + cmd = load Command "/dis/sh.dis"; + args = fullspec :: args; + } + if (cmd == nil) { + treport(mfd, S_FATAL, 0, sys->sprint("Failed to load Command from %s", "/dis/sh.dis")); + return; + } + if (sys->chdir(tpath)) + treport(mfd, S_FATAL, 0, "Failed to cd to " + tpath); + { + cmd->init(context, args); + } exception ex { + "*" => + treport(mfd, S_FATAL, 0, sys->sprint("Exception %s in test %s", ex, fullspec)); + } +} + +severs(sevs: int): string +{ + SEVMAP := array[] of {"INF", "WRN", "ERR", "FTL"}; + if (sevs >= len SEVMAP) + sstr := "UNK"; + else + sstr = SEVMAP[sevs]; + return sstr; +} + + +rfile(file: string): string +{ + fd := sys->open(file, Sys->OREAD); + if (fd == nil) return nil; + buf := array[Sys->ATOMICIO] of byte; + n := sys->read(fd, buf, len buf); + return string buf[:n]; +} + + +wfile(file: string, text: string, create: int): int +{ + if (create) + fd := sys->create(file, Sys->OWRITE, 8r660); + else + fd = sys->open(file, Sys->OWRITE); + if (fd == nil) { + sys->fprint(stderr, "Failed to open %s: %r\n", file); + return 0; + } + a := array of byte text; + al := len a; + if (sys->write(fd, a, al) != al) { + sys->fprint(stderr, "Failed to write to %s: %r\n", file); + return 0; + } + fd = nil; + return 1; +} + +linelist(file: string): list of string +{ + bf := bufio->open(file, Bufio->OREAD); + if (bf == nil) + return nil; + cl : list of string; + while ((line := bf.gets('\n')) != nil) { + if (line[len line -1] == '\n') + line = line[:len line - 1]; + cl = line :: cl; + } + bf = nil; + return cl; +} + +display_file(file: string) +{ + bf := bufio->open(file, Bufio->OREAD); + if (bf == nil) + return; + while ((line := bf.gets('\n')) != nil) { + sys->print(" %s", line); + } +} + +mreport(indent: int, sev: int, verb: int, msg: string) +{ + now = daytime->now(); + tm := daytime->local(now); + time := sys->sprint("%4d%02d%02d %02d:%02d:%02d", tm.year+1900, tm.mon-1, tm.mday, tm.hour, tm.min, tm.sec); + pad := "---"[:indent]; + term := ""; + if (len msg && msg[len msg-1] != '\n') + term = "\n"; + if (sev || verb <= verbosity) + sys->print("%s %s%s%s", time, pad, msg, term); +} + + +treport(mfd: ref Sys->FD, sev: int, verb: int, msg: string) +{ + sys->fprint(mfd, "%d%d%s\n", sev, verb, msg); +} diff --git a/appl/cmd/itreplay.b b/appl/cmd/itreplay.b new file mode 100644 index 00000000..bba2f591 --- /dev/null +++ b/appl/cmd/itreplay.b @@ -0,0 +1,230 @@ +implement Itreplay; + +include "sys.m"; + sys: Sys; +include "string.m"; + str: String; +include "draw.m"; +include "daytime.m"; + daytime: Daytime; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "readdir.m"; + readdir: Readdir; +include "arg.m"; +include "itslib.m"; + S_INFO, S_WARN, S_ERROR, S_FATAL, S_STIME, S_ETIME: import Itslib; + +SUMFILE: con "summary"; +MSGFILE: con "msgs"; + +verbosity := 3; +display_stderr := 0; +display_stdout := 0; + +stderr: ref Sys->FD; + + +Itreplay: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + + + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + nomod(Daytime->PATH); + str = load String String->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + nomod(Bufio->PATH); + if(str == nil) + nomod(String->PATH); + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + nomod(Readdir->PATH); + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 'e' => display_stderr++; + 'o' => display_stdout++; + 'v' => verbosity = toint("v", arg->arg(), 0, 9); + * => usage(); + } + recdirl := arg->argv(); + arg = nil; + if (recdirl == nil) + usage(); + while (recdirl != nil) { + dir := hd recdirl; + recdirl = tl recdirl; + replay(dir); + } +} + +usage() +{ + sys->fprint(stderr, "Usage: itreplay [-eo] [-v verbosity] recorddir ...\n"); + raise "fail: usage"; + exit; +} + +fatal(s: string) +{ + sys->fprint(stderr, "%s\n", s); + raise "fail: error"; + exit; +} + +nomod(mod: string) +{ + sys->fprint(stderr, "Failed to load %s\n", mod); + raise "fail: module"; + exit; +} + +toint(opt, s: string, min, max: int): int +{ + if (len s == 0 || str->take(s, "[0-9]+-") != s) + fatal(sys->sprint("no value specified for option %s", opt)); + v := int s; + if (v < min) + fatal(sys->sprint("option %s value is less than minimum of %d: %d", opt, v, min)); + if (max != -1 && v > max) + fatal(sys->sprint("option %s value is greater than maximum of %d: %d", opt, v, max)); + return v; +} + +replay(dir: string) +{ + sl := linelist(dir+"/"+SUMFILE); + if (sl == nil) { + sys->fprint(stderr, "No summary file in %s\n", dir); + return; + } + sline := hd sl; + (n, toks) := sys->tokenize(sline, " "); + if (n < 4) { + sys->fprint(stderr, "Bad summary file in %s\n", dir); + return; + } + when := int hd toks; + toks = tl toks; + elapsed := int hd toks; + toks = tl toks; + cflag := int hd toks; + toks = tl toks; + testspec := hd toks; + mreport(1, when, 0, 2, sys->sprint("Processing %s: test %s ran in %dms with cflag=%d\n", dir, testspec, elapsed, cflag)); + replay_msgs(dir+"/"+MSGFILE, testspec, cflag); + if (display_stdout) { + mreport(2, 0, 0, 0, "Stdout from test:"); + display_file(dir+"/stdout"); + } + if (display_stderr) { + mreport(2, 0, 0, 0, "Stderr from test:"); + display_file(dir+"/stderr"); + } +} + + +replay_msgs(mfile: string, tspec: string, cflag: int) +{ + mf := bufio->open(mfile, Bufio->OREAD); + if (mf == nil) + return; + (nwarns, nerrors, nfatals) := (0, 0, 0); + stime := 0; + etime := 0; + while ((line := mf.gets('\n')) != nil) { + (whens, rest) := str->splitl(line, ":"); + when := int whens; + msg := rest[1:]; + sev := int msg[0:1]; + verb := int msg[1:2]; + body := msg[2:]; + if (sev == S_STIME) { + stime = int body; + mreport(2, when, 0, 3, sys->sprint("Starting test %s cflag=%d", tspec, cflag)); + } + else if (sev == S_ETIME) { + uetime := int body; + elapsed := uetime-stime; + errsum := sys->sprint("WRN:%d ERR:%d FTL:%d", nwarns, nerrors, nfatals); + mreport(2, when+(int body-stime)/1000, 0, 3, sys->sprint("Finished test %s after %dms - %s", tspec, elapsed, errsum)); + } + else { + if (sev == S_WARN) { + nwarns++; + } + else if (sev == S_ERROR) { + nerrors++; + } + else if (sev == S_FATAL) { + nfatals++; + } + mreport(3, when, sev, verb, sys->sprint("%s: %s", severs(sev), body)); + } + } +} + +linelist(file: string): list of string +{ + bf := bufio->open(file, Bufio->OREAD); + if (bf == nil) + return nil; + cl : list of string; + while ((line := bf.gets('\n')) != nil) { + if (line[len line -1] == '\n') + line = line[:len line - 1]; + cl = line :: cl; + } + bf = nil; + return cl; +} + +display_file(file: string) +{ + bf := bufio->open(file, Bufio->OREAD); + if (bf == nil) + return; + while ((line := bf.gets('\n')) != nil) { + sys->print(" %s", line); + } +} + + +severs(sevs: int): string +{ + SEVMAP := array[] of {"INF", "WRN", "ERR", "FTL"}; + if (sevs >= len SEVMAP) + sstr := "UNK"; + else + sstr = SEVMAP[sevs]; + return sstr; +} + + +mreport(indent: int, when: int, sev: int, verb: int, msg: string) +{ + time := ""; + if (when) { + tm := daytime->local(when); + time = sys->sprint("%4d%02d%02d %02d:%02d:%02d", tm.year+1900, tm.mon-1, tm.mday, tm.hour, tm.min, tm.sec); + } + pad := "---"[:indent]; + term := ""; + if (len msg && msg[len msg-1] != '\n') + term = "\n"; + if (sev || verb <= verbosity) + sys->print("%-17s %s%s%s", time, pad, msg, term); +} diff --git a/appl/cmd/kill.b b/appl/cmd/kill.b new file mode 100644 index 00000000..7ff5a39a --- /dev/null +++ b/appl/cmd/kill.b @@ -0,0 +1,146 @@ +implement Kill; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; + +Kill: module { + init: fn(nil: ref Draw->Context, args: list of string); +}; + +stderr: ref Sys->FD; + +usage() +{ + sys->fprint(stderr, "usage: kill [-g] pid|module [...]\n"); + raise "fail: usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + arg := load Arg Arg->PATH; + if(arg == nil){ + sys->fprint(stderr, "kill: cannot load %s: %r\n", Arg->PATH); + raise "fail:load"; + } + + msg := array of byte "kill"; + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 'g' => + msg = array of byte "killgrp"; + * => + usage(); + } + + argv := arg->argv(); + arg = nil; + if(argv == nil) + usage(); + n := 0; + for(v := argv; v != nil; v = tl v) { + s := hd v; + if (s == nil) + usage(); + if(s[0] >= '0' && s[0] <= '9') + n += killpid(s, msg, 1); + else + n += killmod(s, msg); + } + if (n == 0 && argv != nil) + raise "fail:nothing killed"; +} + +killpid(pid: string, msg: array of byte, sbok: int): int +{ + fd := sys->open("/prog/"+pid+"/ctl", sys->OWRITE); + if(fd == nil) { + err := sys->sprint("%r"); + elen := len err; + if(sbok || err != "thread exited" && elen >= 14 && err[elen-14:] != "does not exist") + sys->fprint(stderr, "kill: cannot open /prog/%s/ctl: %r\n", pid); + return 0; + } + + n := sys->write(fd, msg, len msg); + if(n < 0) { + err := sys->sprint("%r"); + elen := len err; + if(sbok || err != "thread exited") + sys->fprint(stderr, "kill: cannot kill %s: %r\n", pid); + return 0; + } + return 1; +} + +killmod(mod: string, msg: array of byte): int +{ + fd := sys->open("/prog", sys->OREAD); + if(fd == nil) { + sys->fprint(stderr, "kill: open /prog: %r\n"); + return 0; + } + + pids: list of string; + for(;;) { + (n, d) := sys->dirread(fd); + if(n <= 0) { + if (n < 0) + sys->fprint(stderr, "kill: read /prog: %r\n"); + break; + } + + for(i := 0; i < n; i++) + if (killmatch(d[i].name, mod)) + pids = d[i].name :: pids; + } + if (pids == nil) { + sys->fprint(stderr, "kill: cannot find %s\n", mod); + return 0; + } + n := 0; + for (; pids != nil; pids = tl pids) + if (killpid(hd pids, msg, 0)) { + sys->print("%s ", hd pids); + n++; + } + if (n > 0) + sys->print("\n"); + return n; +} + +killmatch(dir, mod: string): int +{ + status := "/prog/"+dir+"/status"; + fd := sys->open(status, sys->OREAD); + if(fd == nil) + return 0; + buf := array[512] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) { + err := sys->sprint("%r"); + if(err != "thread exited") + sys->fprint(stderr, "kill: cannot read %s: %s\n", status, err); + return 0; + } + + # module name is last field + (nil, fields) := sys->tokenize(string buf[0:n], " "); + for(s := ""; fields != nil; fields = tl fields) + s = hd fields; + + # strip builtin module, e.g. Sh[$Sys] + for(i := 0; i < len s; i++) { + if(s[i] == '[') { + s = s[0:i]; + break; + } + } + + return s == mod; +} diff --git a/appl/cmd/lc.b b/appl/cmd/lc.b new file mode 100644 index 00000000..de5ec579 --- /dev/null +++ b/appl/cmd/lc.b @@ -0,0 +1,156 @@ +implement Lc; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "readdir.m"; + readdir: Readdir; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +Lc: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +t_FILE, t_DIR, t_NUMTYPES: con iota; +columns := 65; +stderr: ref Sys->FD; +stdout: ref Iobuf; + +usage() +{ + sys->fprint(stderr, "usage: lc [-df] [-c columns] [file ...]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + readdir = load Readdir Readdir->PATH; + if (readdir == nil) { + sys->fprint(stderr, "lc: cannot load %s: %r\n", Readdir->PATH); + raise "fail:bad module"; + } + bufio = load Bufio Bufio->PATH; + stdout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + if (bufio == nil) { + sys->fprint(stderr, "lc: cannot load %s: %r\n", Bufio->PATH); + raise "fail:bad module"; + } + if (argv == nil) + return; + argv = tl argv; + flags := 0; +loop: while (argv != nil && hd argv != nil && (hd argv)[0] == '-') { + s := (hd argv)[1:]; + argv = tl argv; + flagloop: for (; s != nil; s = s[1:]) { + case s[0] { + '-' => + break loop; + 'd' => + flags |= 1 << t_DIR; + 'f' => + flags |= 1 << t_FILE; + 'c' => + if (len s > 1) { + columns = int s[1:]; + break flagloop; + } + if (argv == nil) + usage(); + columns = int hd argv; + argv = tl argv; + * => + usage(); + } + } + } + + headings := 0; + if (flags == 0) { + flags = (1<<t_DIR)|(1<<t_FILE); + headings = 1; + } + if (argv == nil) + argv = "." :: nil; + multi := tl argv != nil; + nondir: list of string; + for (; argv != nil; argv = tl argv) { + dname := hd argv; + (ok, dir) := sys->stat(dname); + if(ok < 0) { + sys->fprint(stderr, "lc: can't stat %s: %r\n", hd argv); + continue; + } + if (dir.mode & Sys->DMDIR) { + (d, n) := readdir->init(hd argv, Readdir->NAME | Readdir->COMPACT); + if (n < 0) + sys->fprint(stderr, "lc: cannot read %s: %r\n", hd argv); + else { + indent := 0; + if (multi && headings) { + stdout.puts(hd argv + "/\n"); + indent = 2; + } + l: list of string = nil; + for (i := 0; i < n; i++) { + s := d[i].name; + if (!headings && dname != ".") + s = dname + "/" + s; + if (d[i].mode & Sys->DMDIR) { + if (flags & (1<<t_DIR)) + l = s + "/" :: l; + } else if (flags & (1<<t_FILE)) + l = s :: l; + } + d = nil; + lc(l, indent); + } + } else if (flags & (1 << t_FILE)) + nondir = dname :: nondir; + } + lc(nondir, 0); + stdout.close(); +} + +lc(dl: list of string, indent: int) +{ + a := array[len dl] of string; + j := len a - 1; + maxwidth := 0; + for (; dl != nil; dl = tl dl) { + s := hd dl; + a[j--] = s; + if (len s > maxwidth) + maxwidth = len s; + } + outcols(a, maxwidth, indent); +} + +outcols(stuff: array of string, maxwidth, indent: int) +{ + num := len stuff; + cols := columns - indent; + numcols := cols / (maxwidth + 1); + colwidth: int; + if (numcols == 0) { + numcols = 1; + colwidth = maxwidth; + } else + colwidth = cols / numcols; + numrows := (num + numcols - 1) / numcols; + + for (i := 0; i < numrows; i++) { + if (indent) + stdout.puts(sys->sprint("%*s", indent, "")); + for (j := i; j < num; j += numrows) { + if (j + numrows < num) + stdout.puts(sys->sprint("%*.*s", -colwidth, colwidth, stuff[j])); + else + stdout.puts(sys->sprint("%.*s\n", colwidth, stuff[j])); + } + } +} diff --git a/appl/cmd/lego/clock.b b/appl/cmd/lego/clock.b new file mode 100644 index 00000000..3b3c3e50 --- /dev/null +++ b/appl/cmd/lego/clock.b @@ -0,0 +1,214 @@ +implement Clock; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + Point, Rect: import draw; + +include "math.m"; + math: Math; + sqrt, atan2, hypot, Degree: import math; + +include "tk.m"; + tk: Tk; + top: ref Tk->Toplevel; + +include "tkclient.m"; + tkclient: Tkclient; + +Clock: module { + init: fn(ctxt: ref Draw->Context, argl: list of string); +}; + +cmds := array[] of { + "bind . <Configure> {send win resize}", + "canvas .face -height 200 -width 200 -bg yellow", + "bind .face <ButtonPress> {send ptr %x %y}", + "bind .face <ButtonRelease> {send ptr release}", + "pack .face -expand yes -fill both", + "button .reset -text Reset -command {send win reset}", + "pack .reset -after .Wm_t.title -side right -fill y", + "pack propagate . no", +}; + +init(ctxt: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + math = load Math Math->PATH; + tk = load Tk Tk->PATH; + tkclient = load Tkclient Tkclient->PATH; + tkclient->init(); + + sys->pctl(Sys->NEWPGRP, nil); + + clockface := sys->open("/chan/clockface", Sys->ORDWR); + if (clockface == nil) { + sys->print("open /chan/clockface failed: %r\n"); + raise "fail:clockface"; + } + tock := chan of string; + spawn readme(clockface, tock); + + titlech: chan of string; + (top, titlech) = tkclient->toplevel(ctxt, "hh:mm", "", Tkclient->Appl); + win := chan of string; + ptr := chan of string; + tk->namechan(top, win, "win"); + tk->namechan(top, ptr, "ptr"); + for(i:=0; i<len cmds; i++) + tk->cmd(top, cmds[i]); + tkclient->onscreen(top, nil); + tkclient->startinput(top, "ptr"::nil); + drawface(); + spawn hands(ptr, clockface); + + for (;;) alt { + s := <-top.ctxt.kbd => + tk->keyboard(top, s); + s := <-top.ctxt.ptr => + tk->pointer(top, *s); + s := <-top.ctxt.ctl or + s = <-top.wreq or + s = <-titlech => + tkclient->wmctl(top, s); + msg := <-win => + case msg { + "resize" => drawface(); + "reset" => sys->fprint(clockface, "reset"); + } + nowis := <-tock => + (n, toks) := sys->tokenize(nowis, ":"); + if (n == 2) { + (hour, minute) = (int hd toks, int hd tl toks); + setclock(); + } + } +} + +readme(fd: ref Sys->FD, ch: chan of string) +{ + buf := array[64] of byte; + while ((n := sys->read(fd, buf, len buf)) > 0) { + if (buf[n-1] == byte '\n') + n--; + ch <-= string buf[:n]; + } + ch <-= "99:99"; +} + +hour, minute: int; +center, focus: Point; +major: int; + +Frim: con .98; +Fminute: con .90; +Fhour: con .45; +Fnub: con .05; + +hands(ptr: chan of string, fd: ref Sys->FD) +{ + for (;;) { + pos := <-ptr; + p := s2p(pos); + hand := ""; + if (elinside(p, Fnub)) + hand = nil; + else if (elinside(p, Fhour)) + hand = "hour"; + else if (elinside(p, Fminute)) + hand = "minute"; + + do { + p = s2p(pos).sub(center); + angle := int (atan2(real -p.y, real p.x) / Degree); + if (hand != nil) + tkc(".face itemconfigure "+hand+" -start "+string angle+"; update"); + case hand { + "hour" => hour = ((360+90-angle) / 30) % 12; + "minute" => minute = ((360+90-angle) / 6) % 60; + } + } while ((pos = <-ptr) != "release"); + if (hand != nil) + sys->fprint(fd, "%d:%d\n", hour, minute); + } +} + +drawface() +{ + elparms(); + tkc(sys->sprint(".face configure -scrollregion {0 0 %d %d}", 2*center.x, 2*center.y)); + tkc(".face delete all"); + tkc(".face create oval "+elrect(Frim)+" -fill fuchsia -outline aqua -width 2"); + for (a := 0; a < 360; a += 30) + tkc(".face create arc "+elrect(Frim)+" -fill aqua -outline aqua -width 2 -extent 1 -start "+string a); + tkc(".face create oval "+elrect(Fminute)+" -fill fuchsia -outline fuchsia"); + tkc(".face create oval "+elrect(Fnub)+" -fill aqua -outline aqua"); + tkc(".face create arc "+elrect(Fhour)+" -fill aqua -outline aqua -width 6 -extent 1 -tags hour"); + tkc(".face create arc "+elrect(Fminute)+" -fill aqua -outline aqua -width 2 -extent 1 -tags minute"); + setclock(); +} + +setclock() +{ + tkc(".face itemconfigure hour -start "+string (90 - 30*(hour%12) - minute/2)); + tkc(".face itemconfigure minute -start "+string (90 - 6*minute)); + tkc(sys->sprint(".Wm_t.title configure -text {%d:%.2d}", (hour+11)%12+1, minute)); + tkc("update"); +} + +elparms() +{ + center = (int tkc(".face cget actwidth") / 2, int tkc(".face cget actheight") / 2); + dist := center.x*center.x - center.y*center.y; + if (dist > 0) { + major = 2 * center.x; + focus = (int sqrt(real dist), 0); + } else { + major = 2 * center.y; + focus = (0, int sqrt(real -dist)); + } +} + +elinside(p: Point, frac: real): int +{ + foc := mulf(focus, frac); + d := dist(p, center.add(foc)) + dist(p, center.sub(foc)); + return (d < frac * real major); +} + +elrect(frac: real): string +{ + inset := mulf(center, 1.-frac); + r := Rect(inset, center.mul(2).sub(inset)); + return sys->sprint("%d %d %d %d", r.min.x, r.min.y, r.max.x, r.max.y); +} + +mulf(p: Point, f: real): Point +{ + return (int (f * real p.x), int (f * real p.y)); +} + +dist(p, q: Point): real +{ + p = p.sub(q); + return hypot(real p.x, real p.y); +} + +s2p(s: string): Point +{ + (nil, xy) := sys->tokenize(s, " "); + if (len xy != 2) + return (0, 0); + return (int hd xy, int hd tl xy); +} + +tkc(msg: string): string +{ + ret := tk->cmd(top, msg); + if (ret != nil && ret[0] == '!') + sys->print("tk error? %s → %s\n", msg, ret); + return ret; +} diff --git a/appl/cmd/lego/clockface.b b/appl/cmd/lego/clockface.b new file mode 100644 index 00000000..6ba6069b --- /dev/null +++ b/appl/cmd/lego/clockface.b @@ -0,0 +1,384 @@ +# Model 1 +implement Clockface; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +Clockface: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +hmpath: con "motor/0"; # hour-hand motor +mmpath: con "motor/2"; # minute-hand motor +allmpath: con "motor/012"; # all motors (for stopall msg) + +hbpath: con "sensor/0"; # hour-hand sensor +mbpath: con "sensor/2"; # minute-hand sensor +lspath: con "sensor/1"; # light sensor; + +ONTHRESH: con 780; # light sensor thresholds +OFFTHRESH: con 740; +NCLICKS: con 120; +MINCLICKS: con 2; # min number of clicks required to stop a motor + +Hand: adt { + motor: ref Sys->FD; + sensor: ref Sys->FD; + fwd: array of byte; + rev: array of byte; + stop: array of byte; + pos: int; + time: int; +}; + +lightsensor: ref Sys->FD; +allmotors: ref Sys->FD; +hourhand: ref Hand; +minutehand: ref Hand; +timedata: array of byte; +readq: list of Sys->Rread; +verbose := 0; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + + argv = tl argv; + if (len argv > 0 && hd argv == "-v") { + verbose++; + argv = tl argv; + } + if (len argv != 1) { + sys->print("usage: [-v] legodir\n"); + raise "fail:usage"; + } + legodir := hd argv + "/"; + + # set up our control file + f2c := sys->file2chan("/chan", "clockface"); + if (f2c == nil) { + sys->print("cannot create clockface channel: %r\n"); + return; + } + + # get the motor files + log("opening motor files"); + hm := sys->open(legodir + hmpath, Sys->OWRITE); + mm := sys->open(legodir +mmpath, Sys->OWRITE); + allmotors = sys->open(legodir + allmpath, Sys->OWRITE); + if (hm == nil || mm == nil || allmotors == nil) { + sys->print("cannot open motor files\n"); + raise "fail:error"; + } + + # get the sensor files + log("opening sensor files"); + hb := sys->open(legodir + hbpath, Sys->ORDWR); + mb := sys->open(legodir + mbpath, Sys->ORDWR); + lightsensor = sys->open(legodir + lspath, Sys->ORDWR); + + if (hb == nil || mb == nil) { + sys->print("cannot open sensor files\n"); + raise "fail:error"; + } + + hourhand = ref Hand(hm, hb, array of byte "r7", array of byte "f7", array of byte "s7", 0, 00); + minutehand = ref Hand(mm, mb, array of byte "f7", array of byte "r7", array of byte "s7", 0, 00); + + log("setting sensor types"); + setsensortypes(hourhand, minutehand, lightsensor); + + # get the hands to 12 o'clock + reset(); + log(sys->sprint("H %d, M %d", hourhand.pos, minutehand.pos)); + spawn srvlink(f2c); +} + +srvlink(f2c: ref Sys->FileIO) +{ + tick := chan of int; + spawn eggtimer(tick); + + for (;;) alt { + (nil, count, fid, rc) := <-f2c.read => + if (rc == nil) { + close(fid); + continue; + } + if (count < len timedata) { + rc <-= (nil, "read too small"); + continue; + } + if (open(fid)) + readq = rc :: readq; + else + rc <-= (timedata, nil); + + (nil, data, fid, wc) := <-f2c.write => + if (wc == nil) { + close(fid); + continue; + } + (nil, toks) := sys->tokenize(string data, ": \t\n"); + if (len toks == 2) { + wc <-= (len data, nil); + hourhand.time = int hd toks % 12; + minutehand.time = int hd tl toks % 60; + sethands(); + } else if (len toks == 1 && hd toks == "reset") { + wc <-= (len data, nil); + reset(); + } else + wc <-= (0, "syntax is hh:mm or `reset'"); + + <-tick => + if (++minutehand.time == 60) { + minutehand.time = 0; + hourhand.time++; + hourhand.time %= 12; + } + sethands(); + } +} + +readers: list of int; + +open(fid: int): int +{ + for (rlist := readers; rlist != nil; rlist = tl rlist) + if (hd rlist == fid) + return 1; + readers = fid :: readers; + return 0; +} + +close(fid: int) +{ + rlist: list of int; + for (; readers != nil; readers = tl readers) + if (hd readers != fid) + rlist = hd readers :: rlist; + readers = rlist; +} + +eggtimer(tick: chan of int) +{ + next := sys->millisec(); + for (;;) { + next += 60*1000; + sys->sleep(next - sys->millisec()); + tick <-= 1; + } +} + +clicks(): (int, int) +{ + h := hourhand.time; + m := minutehand.time; + h = ((h * NCLICKS) / 12) + ((m * NCLICKS) / (12 * 60)); + m = (m * NCLICKS) / 60; + return (h, m); +} + +sethands() +{ + timedata = array of byte sys->sprint("%2d:%.2d\n", (hourhand.time+11) % 12 + 1, minutehand.time); + for (; readq != nil; readq = tl readq) + alt { + (hd readq) <-= (timedata, nil) => ; + * => ; + } + + (hclk, mclk) := clicks(); + for (i := 0; i < 6; i++) { + hdelta := clickdistance(hourhand.pos, hclk, NCLICKS); + mdelta := clickdistance(minutehand.pos, mclk, NCLICKS); + if (hdelta != 0) + sethand(hourhand, hdelta); + else if (mdelta != 0) + sethand(minutehand, mdelta); + else + break; + } + releaseall(); +} + +clickdistance(start, stop, mod: int): int +{ + if (start > stop) + stop += mod; + d := (stop - start) % mod; + if (d > mod/2) + d -= mod; + return d; +} + +setsensortypes(h1, h2: ref Hand, ls: ref Sys->FD) +{ + button := array of byte "b0"; + light := array of byte "l0"; + sys->write(h1.sensor, button, len button); + sys->write(h2.sensor, button, len button); + sys->write(ls, light, len light); +} + +HOUR_ADJUST: con 1; +MINUTE_ADJUST: con 2; + +reset() +{ + # run the motors until hands are well away from 12 o'clock (below threshold) + + val := readsensor(lightsensor); + if (val > OFFTHRESH) { + triggered := chan of int; + log("wait for hands clear of light sensor"); + spawn lightwait(triggered, lightsensor, 0); + forward(minutehand); + reverse(hourhand); + val = <-triggered; + stopall(); + log("sensor "+string val); + } + + resethand(hourhand); + hourhand.pos += HOUR_ADJUST; + resethand(minutehand); + minutehand.pos += MINUTE_ADJUST; + sethands(); +} + +sethand(hand: ref Hand, delta: int) +{ + triggered := chan of int; + dir := 1; + if (delta < 0) { + dir = -1; + delta = -delta; + } + if (delta > MINCLICKS) { + spawn handwait(triggered, hand, delta - MINCLICKS); + if (dir > 0) + forward(hand); + else + reverse(hand); + <-triggered; + stop(hand); + hand.pos += dir * readsensor(hand.sensor); + } else { + startval := readsensor(hand.sensor); + if (dir > 0) + forward(hand); + else + reverse(hand); + stop(hand); + hand.pos += dir * (readsensor(hand.sensor) - startval); + } + if (hand.pos < 0) + hand.pos += NCLICKS; + hand.pos %= NCLICKS; +} + +resethand(hand: ref Hand) +{ + triggered := chan of int; + val: int; + + # run the hand until the light sensor is above threshold + log("running hand until light sensor activated"); + spawn lightwait(triggered, lightsensor, 1); + forward(hand); + val = <-triggered; + stop(hand); + log("sensor "+string val); + + startclick := readsensor(hand.sensor); + + # advance until light sensor drops below threshold + log("running hand until light sensor clear"); + spawn lightwait(triggered, lightsensor, 0); + forward(hand); + val = <-triggered; + stop(hand); + log("sensor "+string val); + + stopclick := readsensor(hand.sensor); + nclicks := stopclick - startclick; + log(sys->sprint("startpos %d, endpos %d (nclicks %d)", startclick, stopclick, nclicks)); + + hand.pos = nclicks/2; +} + +stop(hand: ref Hand) +{ + sys->seek(hand.motor, big 0, Sys->SEEKSTART); + sys->write(hand.motor, hand.stop, len hand.stop); +} + +stopall() +{ + msg := array of byte "s0s0s0"; + sys->seek(allmotors, big 0, Sys->SEEKSTART); + sys->write(allmotors, msg, len msg); +} + +releaseall() +{ + msg := array of byte "F0F0F0"; + sys->seek(allmotors, big 0, Sys->SEEKSTART); + sys->write(allmotors, msg, len msg); +} + +forward(hand: ref Hand) +{ + sys->seek(hand.motor, big 0, Sys->SEEKSTART); + sys->write(hand.motor, hand.fwd, len hand.fwd); +} + +reverse(hand: ref Hand) +{ + sys->seek(hand.motor, big 0, Sys->SEEKSTART); + sys->write(hand.motor, hand.rev, len hand.rev); +} + +readsensor(fd: ref Sys->FD): int +{ + buf := array[4] of byte; + sys->seek(fd, big 0, Sys->SEEKSTART); + n := sys->read(fd, buf, len buf); + if (n <= 0) + return -1; + return int string buf[:n]; +} + +handwait(reply: chan of int, hand: ref Hand, clicks: int) +{ + blk := array of byte ("b" + string clicks); + log("handwait "+string blk); + sys->seek(hand.sensor, big 0, Sys->SEEKSTART); + if (sys->write(hand.sensor, blk, len blk) != len blk) + sys->print("handwait write error: %r\n"); + reply <-= readsensor(hand.sensor); +} + +lightwait(reply: chan of int, fd: ref Sys->FD, on: int) +{ + thresh := ""; + if (on) + thresh = "l>" + string ONTHRESH; + else + thresh = "l<" + string OFFTHRESH; + blk := array of byte thresh; + log("lightwait "+string blk); + sys->seek(fd, big 0, Sys->SEEKSTART); + sys->write(fd, blk, len blk); + reply <-= readsensor(fd); +} + +log(msg: string) +{ + if (verbose) + sys->print("%s\n", msg); +} diff --git a/appl/cmd/lego/firmdl.b b/appl/cmd/lego/firmdl.b new file mode 100644 index 00000000..718282d0 --- /dev/null +++ b/appl/cmd/lego/firmdl.b @@ -0,0 +1,294 @@ +implement RcxFirmdl; + +include "sys.m"; +include "draw.m"; +include "bufio.m"; +include "rcxsend.m"; + +RcxFirmdl : module { + init : fn (ctxt : ref Draw->Context, argv : list of string); +}; + +sys : Sys; +bufio : Bufio; +rcx : RcxSend; +me : int; + +Iobuf : import bufio; + +Image : adt { + start : int; + offset : int; + length : int; + data : array of byte; +}; + +DL_HDR : con 5; # download packet hdr size +DL_DATA : con 16rc8; # download packet payload size + +init(nil : ref Draw->Context, argv : list of string) +{ + sys = load Sys Sys->PATH; + me = sys->pctl(Sys->NEWPGRP, nil); + + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + error(sys->sprint("cannot load bufio module: %r")); + rcx = load RcxSend RcxSend->PATH; #"rcxsend.dis"; + if (rcx == nil) + error(sys->sprint("cannot load rcx module: %r")); + + argv = tl argv; + if (len argv != 2) + error("usage: portnum file"); + + portnum := int hd argv; + file := hd tl argv; + + img := getimage(file); + cksum := sum(img.data[0:img.length]); + sys->print("length %.4x start %.4x \n", img.length, img.start); + + err := rcx->init(portnum, 1); + if (err != nil) + error(err); + + # delete firmware + sys->print("delete firmware\n"); + reply : array of byte; + rmfirm := array [] of {byte 16r65, byte 1, byte 3, byte 5, byte 7, byte 11}; + reply = rcx->send(rmfirm, len rmfirm, 1); + if (reply == nil) + error("delete firmware failed"); + chkreply(reply, array [] of {byte 16r92}, "delete firmware"); + + # start download + sys->print("start download\n"); + dlstart := array [] of {byte 16r75, + byte (img.start & 16rff), + byte ((img.start>>8) & 16rff), + byte (cksum & 16rff), + byte ((cksum>>8) & 16rff), + byte 0, + }; + reply = rcx->send(dlstart, len dlstart, 2); + chkreply(reply,array [] of {byte 16r82, byte 0}, "start download"); + + # send the image + data := array [DL_HDR+DL_DATA+1] of byte; # hdr + data + 1 byte cksum + seqnum := 1; + step := DL_DATA; + for (i := 0; i < img.length; i += step) { + data[0] = byte 16r45; + if (seqnum & 1) + # alternate ops have bit 4 set + data[0] |= byte 16r08; + if (i + step > img.length) { + step = img.length - i; + seqnum = 0; + } + sys->print("."); + data[1] = byte (seqnum & 16rff); + data[2] = byte ((seqnum >> 8) & 16rff); + data[3] = byte (step & 16rff); + data[4] = byte ((step >> 8) & 16rff); + data[5:] = img.data[i:i+step]; + data[5+step] = byte (sum(img.data[i:i+step]) & 16rff); + reply = rcx->send(data, DL_HDR+step+1, 2); + chkreply(reply, array [] of {byte 16rb2, byte 0}, "tx data"); + seqnum++; + } + + # unlock firmware + sys->print("\nunlock firmware\n"); + ulfirm := array [] of {byte 16ra5, byte 'L', byte 'E', byte 'G', byte 'O', byte 174}; + reply = rcx->send(ulfirm, len ulfirm, 26); + chkreply(reply, array [] of {byte 16r52}, "unlock firmware"); + sys->print("result: %s\n", string reply[1:]); + + # all done, tidy up + killgrp(me); +} + +chkreply(got, expect : array of byte, err : string) +{ + if (got == nil || len got < len expect) + error(err + ": short reply"); + # RCX sometimes sets bit 3 of 'opcode' byte to prevent + # headers with same opcode having exactly same value - mask out + got[0] &= byte 16rf7; + + for (i := 0; i < len expect; i++) + if (got[i] != expect[i]) { + hexdump(got); + error(sys->sprint("%s: reply mismatch at %d", err, i)); + } +} + +error(msg : string) +{ + sys->print("%s\n", msg); + killgrp(me); +} + +killgrp(pid : int) +{ + pctl := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE); + if (pctl != nil) { + poison := array of byte "killgrp"; + sys->write(pctl, poison, len poison); + } + exit; +} + +sum(data : array of byte) : int +{ + t := 0; + for (i := 0; i < len data; i++) + t += int data[i]; + return t; +} + +hexdump(data : array of byte) +{ + for (i := 0; i < len data; i++) + sys->print("%.2x ", int data[i]); + sys->print("\n"); +} + +IMGSTART : con 16r8000; +IMGLEN : con 16r4c00; +getimage(path : string) : ref Image +{ + img := ref Image (IMGSTART, IMGSTART, 0, array [IMGLEN] of {* => byte 0}); + iob := bufio->open(path, Sys->OREAD); + if (iob == nil) + error(sys->sprint("cannot open %s: %r", path)); + + lnum := 0; + while ((s := iob.gets('\n')) != nil) { + lnum++; + slen := len s; + # trim trailing space + while (slen > 0) { + ch := s[slen -1]; + if (ch == ' ' || ch == '\r' || ch == '\n') { + slen--; + continue; + } + break; + } + # ignore blank lines + if (slen == 0) + continue; + + if (slen < 10) + # STNNAAAACC + error("short S-record: line " + string lnum); + + s = s[0:slen]; + t := s[1]; + if (s[0] != 'S' || t < '0' || t > '9') + error("bad S-record format: line " + string lnum); + + data := hex2bytes(s[2:]); + if (data == nil) + error("bad chars in S-record: line " + string lnum); + + count := int data[0]; + cksum := int data[len data - 1]; + if (count != len data -1) + error("S-record length mis-match: line " + string lnum); + + if (sum(data[0:len data -1]) & 16rff != 16rff) + error("bad S-record checksum: line " + string lnum); + + alen : int; + case t { + '0' => + # addr[2] mname[10] ver rev desc[18] cksum + continue; + '1' => + # 16-bit address, data + alen = 2; + '2' => + # 24-bit address, data + alen = 3; + '3' => + # 32-bit address, data + alen = 4; + '4' => + # extension record + error("bad S-record type: line " + string lnum); + '5' => + # data record count - ignore + continue; + '6' => + # unused - ignore + continue; + '7' => + img.start = wordval(data, 1, 4); + continue; + '8' => + img.start = wordval(data, 1, 3); + continue; + '9' => + img.start = wordval(data, 1, 2); + continue; + } + addr := wordval(data, 1, alen) - img.offset; + if (addr < 0 || addr > len img.data) + error("S-record address out of range: line " + string lnum); + img.data[addr:] = data[1+alen:1+count]; + img.length = max(img.length, addr + count -(alen +1)); + } + iob.close(); + return img; +} + +wordval(src : array of byte, s, l : int) : int +{ + r := 0; + for (i := 0; i < l; i++) { + r <<= 8; + r += int src[s+i]; + } + return r; +} + +max(a, b : int) : int +{ + if (a > b) + return a; + return b; +} + +hex2bytes(s : string) : array of byte +{ + slen := len s; + if (slen & 1) + # should be even + return nil; + data := array [slen/2] of byte; + six := 0; + dix := 0; + while (six < slen) { + d1 := hexdigit(s[six++]); + d2 := hexdigit(s[six++]); + if (d1 == -1 || d2 == -1) + return nil; + data[dix++] = byte ((d1 << 4) + d2); + } + return data; +} + +hexdigit(h : int) : int +{ + if (h >= '0' && h <= '9') + return h - '0'; + if (h >= 'A' && h <= 'F') + return 10 + h - 'A'; + if (h >= 'a' && h <= 'f') + return 10 + h - 'a'; + return -1; +} diff --git a/appl/cmd/lego/link.b b/appl/cmd/lego/link.b new file mode 100644 index 00000000..5c6b30d0 --- /dev/null +++ b/appl/cmd/lego/link.b @@ -0,0 +1,603 @@ +implement LegoLink; + +include "sys.m"; +include "draw.m"; +include "timers.m"; +include "rcxsend.m"; + +LegoLink : module { + init : fn (ctxt : ref Draw->Context, argv : list of string); +}; + +POLLDONT : con 0; +POLLNOW : con 16r02; +POLLDO : con 16r04; + +sys : Sys; +timers : Timers; +Timer : import timers; +datain : chan of array of byte; +errormsg : string; + +init(nil : ref Draw->Context, argv : list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->NEWPGRP, nil); + + argv = tl argv; + if (len argv != 1) { + sys->print("usage: lego/link portnum\n"); + return; + } + + timers = load Timers Timers->PATH; #"timers.dis"; + if (timers == nil) { + sys->print("cannot load timers module: %r\n"); + return; + } + portnum := int hd argv; + (rdfd, wrfd, err) := serialport(portnum); + if (err != nil) { + sys->print("%s\n", err); + return; + } + + # set up our mount file + if (sys->bind("#s", "/net", Sys->MBEFORE) == -1) { + sys->print("failed to bind srv device: %r\n"); + return; + } + f2c := sys->file2chan("/net", "legolink"); + if (f2c == nil) { + sys->print("cannot create legolink channel: %r\n"); + return; + } + + datain = chan of array of byte; + send := chan of array of byte; + recv := chan of array of byte; + timers->init(50); + spawn reader(rdfd, datain); + consume(); + spawn protocol(wrfd, send, recv); + spawn srvlink(f2c, send, recv); +} + +srvlink(f2c : ref Sys->FileIO, send, recv : chan of array of byte) +{ + me := sys->pctl(0, nil); + rdfid := -1; + wrfid := -1; + buffer := array [256] of byte; + bix := 0; + + rdblk := chan of (int, int, int, Sys->Rread); + readreq := rdblk; + wrblk := chan of (int, array of byte, int, Sys->Rwrite); + writereq := f2c.write; + wrreply : Sys->Rwrite; + sendblk := chan of array of byte; + sendchan := sendblk; + senddata : array of byte; + + for (;;) alt { + data := <- recv => + # got some data from brick, nil for error + if (data == nil) { + # some sort of error + if (wrreply != nil) { + wrreply <- = (0, errormsg); + } + killgrp(me); + } + if (bix + len data > len buffer) { + newb := array [bix + len data + 256] of byte; + newb[0:] = buffer; + buffer = newb; + } + buffer[bix:] = data; + bix += len data; + readreq = f2c.read; + + (offset, count, fid, rc) := <- readreq => + if (rdfid == -1) + rdfid = fid; + if (fid != rdfid) { + if (rc != nil) + rc <- = (nil, "file in use"); + continue; + } + if (rc == nil) { + rdfid = -1; + continue; + } + if (errormsg != nil) { + rc <- = (nil, errormsg); + killgrp(me); + } + # reply with what we've got + if (count > bix) + count = bix; + rdata := array [count] of byte; + rdata[0:] = buffer[0:count]; + buffer[0:] = buffer[count:bix]; + bix -= count; + if (bix == 0) + readreq = rdblk; + alt { + rc <- = (rdata, nil)=> + ; + * => + ; + } + + (offset, data, fid, wc) := <- writereq => + if (wrfid == -1) + wrfid = fid; + if (fid != wrfid) { + if (wc != nil) + wc <- = (0, "file in use"); + continue; + } + if (wc == nil) { + wrfid = -1; + continue; + } + if (errormsg != nil) { + wc <- = (0, errormsg); + killgrp(me); + } + senddata = data; + sendchan = send; + wrreply = wc; + writereq = wrblk; + + sendchan <- = senddata => + alt { + wrreply <- = (len senddata, nil) => + ; + * => + ; + } + wrreply = nil; + sendchan = sendblk; + writereq = f2c.write; + } +} + +killgrp(pid : int) +{ + pctl := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE); + if (pctl != nil) { + poison := array of byte "killgrp"; + sys->write(pctl, poison, len poison); + } + exit; +} + +serialport(port : int) : (ref Sys->FD, ref Sys->FD, string) +{ + serport := "/dev/eia" + string port; + serctl := serport + "ctl"; + + rfd := sys->open(serport, Sys->OREAD); + if (rfd == nil) + return (nil, nil, sys->sprint("cannot read %s: %r", serport)); + wfd := sys->open(serport, Sys->OWRITE); + if (wfd == nil) + return (nil, nil, sys->sprint("cannot write %s: %r", serport)); + ctlfd := sys->open(serctl, Sys->OWRITE); + if (ctlfd == nil) + return (nil, nil, sys->sprint("cannot open %s: %r", serctl)); + + config := array [] of { + "b2400", + "l8", + "po", + "m0", + "s1", + "d1", + "r1", + }; + + for (i := 0; i < len config; i++) { + cmd := array of byte config[i]; + if (sys->write(ctlfd, cmd, len cmd) <= 0) + return (nil, nil, sys->sprint("serial config (%s): %r", config[i])); + } + return (rfd, wfd, nil); +} + +# reader and nbread as in rcxsend.b +reader(fd : ref Sys->FD, out : chan of array of byte) +{ + # with buf size of 1 there is no need + # for overrun code in nbread() + + buf := array [1] of byte; + for (;;) { + n := sys->read(fd, buf, len buf); + if (n <= 0) + break; + data := array [n] of byte; + data[0:] = buf[0:n]; + out <- = data; + } + out <- = nil; +} + +overrun : array of byte; + +nbread(ms, n : int) : array of byte +{ + ret := array[n] of byte; + tot := 0; + if (overrun != nil) { + if (n < len overrun) { + ret[0:] = overrun[0:n]; + overrun = overrun[n:]; + return ret; + } + ret[0:] = overrun; + tot += len overrun; + overrun = nil; + } + tmr := timers->new(ms, 0); +loop: + while (tot < n) { + tmr.reset(); + alt { + data := <- datain => + if (data == nil) + break loop; + dlen := len data; + if (dlen > n - tot) { + dlen = n - tot; + overrun = data[dlen:]; + } + ret[tot:] = data[0:dlen]; + tot += dlen; + <- tmr.tick => + # reply timeout; + break loop; + } + } + tmr.destroy(); + if (tot == 0) + return nil; + return ret[0:tot]; +} + +consume() +{ + while (nbread(300, 1024) != nil) + ; +} + +# fd: connection to remote client +# send: from local to remote +# recv: from remote to local +protocol(fd : ref Sys->FD, send, recv : chan of array of byte) +{ + seqnum := 0; + towerdown := timers->new(1500, 0); + starttower := 1; + tmr := timers->new(250, 0); + + for (;;) { + data : array of byte = nil; + # get data to send + alt { + data = <- send => + ; + <- tmr.tick => + data = nil; + <- towerdown.tick => + starttower = 1; + continue; + } + + poll := POLLNOW; + while (poll == POLLNOW) { + reply : array of byte; + (reply, poll, errormsg) = datasend(fd, seqnum++, data, starttower); + starttower = 0; + towerdown.reset(); + if (errormsg != nil) { +sys->print("protocol: send error: %s\n", errormsg); + tmr.destroy(); + recv <- = nil; + return; + } + if (reply != nil) { + recv <- = reply; + } + if (poll == POLLNOW) { + # quick check to see if we have any more data + alt { + data = <- send => + ; + * => + data = nil; + } + } + } + if (poll == POLLDO) + tmr.reset(); + else + tmr.cancel(); + } +} + +TX_HDR : con 3; +DL_HDR : con 5; # 16r45 seqLSB seqMSB lenLSB lenMSB +DL_CKSM : con 1; +LN_HDR : con 1; +LN_JUNK : con 2; +LN_LEN : con 2; +LN_RXLEN : con 2; +LN_POLLMASK : con 16r06; +LN_COMPMASK : con 16r08; + + +# send a message (may be empty) +# wait for the reply +# returns (data, poll request, error) + +datasend(wrfd : ref Sys->FD, seqnum : int, data : array of byte, startup : int) : (array of byte, int, string) +{ +if (startup) { + dummy := array [] of { byte 255, byte 0, byte 255, byte 0}; + sys->write(wrfd, dummy, len dummy); + nbread(100, 100); +} + seqnum = seqnum & 1; + docomp := 0; + if (data != nil) { + comp := rlencode(data); + if (len comp < len data) { + docomp = 1; + data = comp; + } + } + + # construct the link-level data packet + # DL_HDR LN_HDR data cksum + # last byte of data is stored in cksum byte + llen := LN_HDR + len data; + blklen := LN_LEN + llen - 1; # llen includes cksum + ldata := array [DL_HDR + blklen + 1] of byte; + + # DL_HDR + if (seqnum == 0) + ldata[0] = byte 16r45; + else + ldata[0] = byte 16r4d; + ldata[1] = byte 0; # blk number LSB + ldata[2] = byte 0; # blk number MSB + ldata[3] = byte (blklen & 16rff); # blk length LSB + ldata[4] = byte ((blklen >> 8) & 16rff); # blk length MSB + + # LN_LEN + ldata[5] = byte (llen & 16rff); + ldata[6] = byte ((llen>>8) & 16rff); + # LN_HDR + lhval := byte 0; + if (seqnum == 1) + lhval |= byte 16r01; + if (docomp) + lhval |= byte 16r08; + + ldata[7] = lhval; + + # data (+cksum) + ldata[8:] = data; + + # construct the rcx data packet + # TX_HDR (dn ~dn) cksum ~cksum + rcxlen := TX_HDR + 2*(len ldata + 1); + rcxdata := array [rcxlen] of byte; + + rcxdata[0] = byte 16r55; + rcxdata[1] = byte 16rff; + rcxdata[2] = byte 16r00; + rcix := TX_HDR; + cksum := 0; + for (i := 0; i < len ldata; i++) { + b := ldata[i]; + rcxdata[rcix++] = b; + rcxdata[rcix++] = ~b; + cksum += int b; + } + rcxdata[rcix++] = byte (cksum & 16rff); + rcxdata[rcix++] = byte (~cksum & 16rff); + + # send it + err : string; + reply : array of byte; + for (try := 0; try < 8; try++) { + if (err != nil) + sys->print("Try %d (lasterr %s)\n", try, err); + err = ""; + step := 8; + for (i = 0; err == nil && i < rcxlen; i += step) { + if (i + step > rcxlen) + step = rcxlen -i; + if (sys->write(wrfd, rcxdata[i:i+step], step) != step) { + return (nil, 0, "hangup"); + } + + # get the echo + reply = nbread(300, step); + if (reply == nil || len reply != step) + # short echo + err = "tower not responding"; + + # check the echo + for (ei := 0; err == nil && ei < step; ei++) { + if (reply[ei] != rcxdata[i+ei]) + # echo mis-match + err = "serial comms error"; + } + } + if (err != nil) { + consume(); + continue; + } + + # wait for a reply + replen := TX_HDR + LN_JUNK + 2*LN_RXLEN; + reply = nbread(300, replen); + if (reply == nil || len reply != replen) { + err = "brick not responding"; + consume(); + continue; + } + if (reply[0] != byte 16r55 || reply[1] != byte 16rff || reply[2] != byte 0 + || reply[5] != ~reply[6] || reply[7] != ~reply[8]) { + err = "bad reply from brick"; + consume(); + continue; + } + # reply[3] and reply [4] are junk, ~junk + # put on front of msg by rcx rom + replen = int reply[5] + ((int reply[7]) << 8) + 1; + cksum = int reply[3] + int reply[5] + int reply[7]; + reply = nbread(200, replen * 2); + if (reply == nil || len reply != replen * 2) { + err = "short reply from brick"; + consume(); + continue; + } + cksum += int reply[0]; + for (i = 1; i < replen; i++) { + reply[i] = reply[2*i]; + cksum += int reply[i]; + } + cksum -= int reply[replen-1]; + if (reply[replen-1] != byte (cksum & 16rff)) { + err = "bad checksum from brick"; + consume(); + continue; + } + if ((reply[0] & byte 1) != byte (seqnum & 1)) { + # seqnum error + # we have read everything, don't bother with consume() + err = "bad seqnum from brick"; + continue; + } + + # TADA! we have a valid message + mdata : array of byte; + lnhdr := int reply[0]; + poll := lnhdr & LN_POLLMASK; + if (replen > 2) { + # more than just hdr and cksum + if (lnhdr & LN_COMPMASK) { + mdata = rldecode(reply[1:replen-1]); + if (mdata == nil) { + err = "bad brick msg compression"; + continue; + } + } else { + mdata = array [replen - 2] of byte; + mdata[0:] = reply[1:replen-1]; + } + } + return (mdata, poll, nil); + } + return (nil, 0, err); +} + + +rlencode(data : array of byte) : array of byte +{ + srcix := 0; + outix := 0; + out := array [64] of byte; + val := 0; + nextval := -1; + n0 := 0; + + while (srcix < len data || nextval != -1) { + if (nextval != -1) { + val = nextval; + nextval = -1; + } else { + val = int data[srcix]; + if (val == 16r88) + nextval = 0; + if (val == 0) { + n0++; + srcix++; + if (srcix < len data && n0 < 16rff + 2) + continue; + } + case n0 { + 0 => + srcix++; + 1 => + val = 0; + nextval = -1; + n0 = 0; + 2 => + val = 0; + nextval = 0; + n0 = 0; + * => + val = 16r88; + nextval = (n0-2); + n0 = 0; + } + } + if (outix >= len out) { + newout := array [2 * len out] of byte; + newout[0:] = out; + out = newout; + } + out[outix++] = byte val; + } + return out[0:outix]; +} + +rldecode(data : array of byte) : array of byte +{ + srcix := 0; + outix := 0; + out := array [64] of byte; + + n0 := 0; + val := 0; + while (srcix < len data || n0 > 0) { + if (n0 > 0) + n0--; + else { + val = int data[srcix++]; + if (val == 16r88) { + if (srcix >= len data) + # bad encoding + return nil; + n0 = int data[srcix++]; + if (n0 > 0) { + n0 += 2; + val = 0; + continue; + } + } + } + if (outix >= len out) { + newout := array [2 * len out] of byte; + newout[0:] = out; + out = newout; + } + out[outix++] = byte val; + } + return out[0:outix]; +} + +hexdump(data : array of byte) +{ + for (i := 0; i < len data; i++) + sys->print("%.2x ", int data[i]); + sys->print("\n"); +} diff --git a/appl/cmd/lego/mkfile b/appl/cmd/lego/mkfile new file mode 100644 index 00000000..b0e3dddb --- /dev/null +++ b/appl/cmd/lego/mkfile @@ -0,0 +1,23 @@ +<../../../mkconfig + +TARG=\ + clock.dis\ + clockface.dis\ + firmdl.dis\ + link.dis\ + rcxsend.dis\ + send.dis\ + timers.dis\ + +SYSMODULES=\ + sys.m\ + draw.m\ + bufio.m\ + +MODULES=\ + rcxsend.m\ + timers.m\ + +DISBIN=$ROOT/dis/lego + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/lego/rcxsend.b b/appl/cmd/lego/rcxsend.b new file mode 100644 index 00000000..402187e1 --- /dev/null +++ b/appl/cmd/lego/rcxsend.b @@ -0,0 +1,240 @@ +implement RcxSend; + +include "sys.m"; +include "timers.m"; +include "rcxsend.m"; + +sys : Sys; +timers : Timers; +Timer : import timers; +datain : chan of array of byte; +debug : int; +rpid : int; +wrfd : ref Sys->FD; + +TX_HDR : con 3; +TX_CKSM : con 2; + +init(portnum, dbg : int) : string +{ + debug = dbg; + sys = load Sys Sys->PATH; + timers = load Timers Timers->PATH; #"timers.dis"; + if (timers == nil) + return sys->sprint("cannot load timer module: %r"); + + rdfd : ref Sys->FD; + err : string; + (rdfd, wrfd, err) = serialport(portnum); + if (err != nil) + return err; + + timers->init(50); + pidc := chan of int; + datain = chan of array of byte; + spawn reader(pidc, rdfd, datain); + rpid = <- pidc; + consume(); + return nil; +} + +reader(pidc : chan of int, fd : ref Sys->FD, out : chan of array of byte) +{ + pidc <- = sys->pctl(0, nil); + + # with buf size of 1 there is no need + # for overrun code in nbread() + + buf := array [1] of byte; + for (;;) { + n := sys->read(fd, buf, len buf); + if (n <= 0) + break; + data := array [n] of byte; + data[0:] = buf[0:n]; + out <- = data; + } + if (debug) + sys->print("Reader error\n"); +} + +send(data : array of byte, n, rlen: int) : array of byte +{ + # 16r55 16rff 16r00 (d[i] ~d[i])*n cksum ~cksum + obuf := array [TX_HDR + (2*n ) + TX_CKSM] of byte; + olen := 0; + obuf[olen++] = byte 16r55; + obuf[olen++] = byte 16rff; + obuf[olen++] = byte 16r00; + cksum := 0; + for (i := 0; i < n; i++) { + obuf[olen++] = data[i]; + obuf[olen++] = ~data[i]; + cksum += int data[i]; + } + obuf[olen++] = byte (cksum & 16rff); + obuf[olen++] = byte (~cksum & 16rff); + + needr := rlen; + if (rlen > 0) + needr = TX_HDR + (2 * rlen) + TX_CKSM; + for (try := 0; try < 5; try++) { + ok := 1; + err := ""; + reply : array of byte; + + step := 8; + for (i = 0; ok && i < olen; i += step) { + if (i + step > olen) + step = olen -i; + if (sys->write(wrfd, obuf[i:i+step], step) != step) { + if (debug) + sys->print("serial tx error: %r\n"); + return nil; + } + + # get the echo + reply = nbread(200, step); + if (reply == nil || len reply != step) { + err = "short echo"; + ok = 0; + } + + # check the echo + for (ei := 0; ok && ei < step; ei++) { + if (reply[ei] != obuf[i+ei]) { + err = "bad echo"; + ok = 0; + } + } + } + + # get the reply + if (ok) { + if (needr == 0) + return nil; + if (needr == -1) { + # just get what we can + needr = TX_HDR + TX_CKSM; + reply = nbread(300, 1024); + } else { + reply = nbread(200, needr); + } + if (len reply < needr) { + err = "short reply"; + ok = 0; + } + } + # check the reply + if (ok && reply[0] == byte 16r55 && reply[1] == byte 16rff && reply[2] == byte 0) { + cksum := int reply[len reply -TX_CKSM]; + val := reply[TX_HDR:len reply -TX_CKSM]; + r := array [len val / 2] of byte; + sum := 0; + for (i = 0; i < len r; i++) { + r[i] = val[i*2]; + sum += int r[i]; + } + if (cksum == (sum & 16rff)) { + return r; + } + ok = 0; + err = "bad cksum"; + } else if (ok) { + ok = 0; + err = "reply header error"; + } + if (debug && ok == 0 && err != nil) { + sys->print("try %d %s: ", try, err); + hexdump(reply); + } + consume(); + } + return nil; +} + +overrun : array of byte; + +nbread(ms, n : int) : array of byte +{ + ret := array[n] of byte; + tot := 0; + if (overrun != nil) { + if (n < len overrun) { + ret[0:] = overrun[0:n]; + overrun = overrun[n:]; + return ret; + } + ret[0:] = overrun; + tot += len overrun; + overrun = nil; + } + tmr := timers->new(ms, 0); +loop: + while (tot < n) { + tmr.reset(); + alt { + data := <- datain => + dlen := len data; + if (dlen > n - tot) { + dlen = n - tot; + overrun = data[dlen:]; + } + ret[tot:] = data[0:dlen]; + tot += dlen; + <- tmr.tick => + # reply timeout; + break loop; + } + } + tmr.destroy(); + if (tot == 0) + return nil; + return ret[0:tot]; +} + +consume() +{ + while (nbread(300, 1024) != nil) + ; +} + +serialport(port : int) : (ref Sys->FD, ref Sys->FD, string) +{ + serport := "/dev/eia" + string port; + serctl := serport + "ctl"; + + rfd := sys->open(serport, Sys->OREAD); + if (rfd == nil) + return (nil, nil, sys->sprint("cannot read %s: %r", serport)); + wfd := sys->open(serport, Sys->OWRITE); + if (wfd == nil) + return (nil, nil, sys->sprint("cannot write %s: %r", serport)); + ctlfd := sys->open(serctl, Sys->OWRITE); + if (ctlfd == nil) + return (nil, nil, sys->sprint("cannot open %s: %r", serctl)); + + config := array [] of { + "b2400", + "l8", + "po", + "m0", + "s1", + "d1", + "r1", + }; + + for (i := 0; i < len config; i++) { + cmd := array of byte config[i]; + if (sys->write(ctlfd, cmd, len cmd) <= 0) + return (nil, nil, sys->sprint("serial config (%s): %r", config[i])); + } + return (rfd, wfd, nil); +} +hexdump(data : array of byte) +{ + for (i := 0; i < len data; i++) + sys->print("%.2x ", int data[i]); + sys->print("\n"); +} + diff --git a/appl/cmd/lego/rcxsend.m b/appl/cmd/lego/rcxsend.m new file mode 100644 index 00000000..f62087db --- /dev/null +++ b/appl/cmd/lego/rcxsend.m @@ -0,0 +1,6 @@ +RcxSend : module { + PATH: con "/dis/lego/rcxsend.dis"; + + init: fn (pnum, dbg : int) : string; + send : fn (data : array of byte, slen, rlen : int) : array of byte; +};
\ No newline at end of file diff --git a/appl/cmd/lego/send.b b/appl/cmd/lego/send.b new file mode 100644 index 00000000..e83861c3 --- /dev/null +++ b/appl/cmd/lego/send.b @@ -0,0 +1,86 @@ +implement Send; + +include "sys.m"; +include "draw.m"; +include "rcxsend.m"; + +Send : module { + init : fn (ctxt : ref Draw->Context, argv : list of string); +}; + +sys : Sys; +rcx : RcxSend; +me : int; + +init(nil : ref Draw->Context, argv : list of string) +{ + sys = load Sys Sys->PATH; + me = sys->pctl(Sys->NEWPGRP, nil); + + rcx = load RcxSend "rcxsend.dis"; + if (rcx == nil) + error(sys->sprint("cannot load rcx module: %r")); + + argv = tl argv; + if (len argv < 2) + error("usage: send portnum XX..."); + + portnum := int hd argv; + argv = tl argv; + + cmd := array [len argv] of byte; + for (i := 0; i < len cmd; i++) { + arg := hd argv; + argv = tl argv; + if (arg == nil || len arg > 2) + error(sys->sprint("bad arg %s\n", arg)); + d1, d2 : int = 0; + d2 = hexdigit(arg[0]); + if (len arg == 2) { + d1 = d2; + d2 = hexdigit(arg[1]); + } + if (d1 == -1 || d2 == -1) + error(sys->sprint("bad arg %s\n", arg)); + cmd[i] = byte ((d1 << 4) + d2); + } + + rcx->init(portnum, 1); + reply := rcx->send(cmd, len cmd, -1); + hexdump(reply); + killgrp(me); +} + +hexdigit(h : int) : int +{ + if (h >= '0' && h <= '9') + return h - '0'; + if (h >= 'A' && h <= 'F') + return 10 + h - 'A'; + if (h >= 'a' && h <= 'f') + return 10 + h - 'a'; + return -1; +} + +error(msg : string) +{ + sys->print("%s\n", msg); + killgrp(me); +} + +killgrp(pid : int) +{ + pctl := sys->open("/prog/" + string pid + "/ctl", Sys->OWRITE); + if (pctl != nil) { + poison := array of byte "killgrp"; + sys->write(pctl, poison, len poison); + } + exit; +} + +hexdump(data : array of byte) +{ + for (i := 0; i < len data; i++) + sys->print("%.2x ", int data[i]); + sys->print("\n"); +} diff --git a/appl/cmd/lego/timers.b b/appl/cmd/lego/timers.b new file mode 100644 index 00000000..67e08dec --- /dev/null +++ b/appl/cmd/lego/timers.b @@ -0,0 +1,263 @@ +# Chris Locke. June 2000 + +# TODO: for auto-repeat timers don't set up a new sender +# if there is already a pending sender for that timer. + +implement Timers; + +include "sys.m"; +include "timers.m"; + +RealTimer : adt { + t : ref Timer; + nticks : int; + rep : int; + nexttick: big; + tick : chan of int; + sender : int; +}; + +Sender : adt { + tid : int; + idle : int; # set by sender() when done, reset by main when about to assign work + ctl : chan of chan of int; +}; + +sys : Sys; +acquire : chan of int; +timers := array [4] of ref RealTimer; +senders := array [4] of ref Sender; +curtick := big 0; +tickres : int; + +init(res : int) +{ + sys = load Sys Sys->PATH; + acquire = chan of int; + tickres = res; + spawn main(); +} + +new(ms, rep : int) : ref Timer +{ + acquire <- = 1; + t := do_new(ms, rep); + <- acquire; + return t; +} + +Timer.destroy(t : self ref Timer) +{ + acquire <- = 1; + do_destroy(t); + <- acquire; +} + +Timer.reset(t : self ref Timer) +{ + acquire <- = 1; + do_reset(t); + <- acquire; +} + +Timer.cancel(t : self ref Timer) +{ + acquire <- = 1; + do_cancel(t); + <- acquire; +} + +# only call under lock +# +realtimer(t : ref Timer) : ref RealTimer +{ + if (t.id < 0 || t.id >= len timers) + return nil; + if (timers[t.id] == nil) + return nil; + if (timers[t.id].t != t) + return nil; + return timers[t.id]; +} + + +# called under lock +# +do_destroy(t : ref Timer) +{ + rt := realtimer(t); + if (rt == nil) + return; + clearsender(rt, t.id); + timers[t.id] = nil; +} + +# called under lock +# +do_reset(t : ref Timer) +{ + rt := realtimer(t); + if (rt == nil) + return; + clearsender(rt, t.id); + rt.nexttick = curtick + big (rt.nticks); + startclk = 1; +} + +# called under lock +# +do_cancel(t : ref Timer) +{ + rt := realtimer(t); + if (rt == nil) + return; + clearsender(rt, t.id); + rt.nexttick = big 0; +} + +# only call under lock +# +clearsender(rt : ref RealTimer, tid : int) +{ + # check to see if there is a sender trying to deliver tick + if (rt.sender != -1) { + sender := senders[rt.sender]; + rt.sender = -1; + if (sender.tid == tid && !sender.idle) { + # receive the tick to clear the busy state + alt { + <- rt.tick => + ; + * => + ; + } + } + } +} + +# called under lock +do_new(ms, rep : int) : ref Timer +{ + # find free slot + for (i := 0; i < len timers; i++) + if (timers[i] == nil) + break; + if (i == len timers) { + # grow the array + newtimers := array [len timers * 2] of ref RealTimer; + newtimers[0:] = timers; + timers = newtimers; + } + tick := chan of int; + t := ref Timer(i, tick); + nticks := ms / tickres; + if (nticks == 0) + nticks = 1; + rt := ref RealTimer(t, nticks, rep, big 0, tick, -1); + timers[i] = rt; + return t; +} + +startclk : int; +stopclk : int; + +main() +{ + clktick := chan of int; + clkctl := chan of int; + clkstopped := 1; + spawn ticker(tickres, clkctl, clktick); + + for (;;) alt { + <- acquire => + # Locking + acquire <- = 1; + + if (clkstopped && startclk) { + clkstopped = 0; + startclk = 0; + clkctl <- = 1; + } + + t := <- clktick => + if (t == 0) { + stopclk = 0; + if (startclk) { + startclk = 0; + clkctl <- = 1; + } else { + clkstopped = 1; + continue; + } + } + curtick++; + npend := 0; + for (i := 0; i < len timers; i++) { + rt := timers[i]; + if (rt == nil) + continue; + if (rt.nexttick == big 0) + continue; + if (rt.nexttick > curtick) { + npend++; + continue; + } + # Timeout - arrange to send the tick + if (rt.rep) { + rt.nexttick = curtick + big rt.nticks; + npend++; + } else + rt.nexttick = big 0; + si := getsender(); + s := senders[si]; + s.tid = i; + s.idle = 0; + rt.sender = si; + s.ctl <- = rt.tick; + + } + if (!npend) + stopclk = 1; + } +} + +getsender() : int +{ + for (i := 0; i < len senders; i++) { + s := senders[i]; + if (s == nil || s.idle == 1) + break; + } + if (i == len senders) { + newsenders := array [len senders * 2] of ref Sender; + newsenders[0:] = senders; + senders = newsenders; + } + if (senders[i] == nil) { + s := ref Sender (-1, 1, chan of chan of int); + spawn sender(s); + senders[i] = s; + } + return i; +} + +sender(me : ref Sender) +{ + for (;;) { + tickch := <- me.ctl; + tickch <- = 1; + me.idle = 1; + } +} + +ticker(ms : int, start, tick : chan of int) +{ + for (;;) { + <- start; + while (!stopclk) { + sys->sleep(ms); + tick <- = 1; + } + tick <- = 0; + } +} diff --git a/appl/cmd/lego/timers.m b/appl/cmd/lego/timers.m new file mode 100644 index 00000000..5cc2b731 --- /dev/null +++ b/appl/cmd/lego/timers.m @@ -0,0 +1,17 @@ +Timers : module{ + PATH: con "/dis/lego/timers.dis"; + + Timer : adt { + id : int; + tick : chan of int; + + reset : fn (t : self ref Timer); + cancel : fn (t : self ref Timer); + destroy : fn (t : self ref Timer); + }; + + init : fn (res : int); + new : fn(ms, rep : int) : ref Timer; +}; + + diff --git a/appl/cmd/limbo/arg.m b/appl/cmd/limbo/arg.m new file mode 100644 index 00000000..212752c1 --- /dev/null +++ b/appl/cmd/limbo/arg.m @@ -0,0 +1,50 @@ +Arg: adt +{ + argv: list of string; + c: int; + opts: string; + + init: fn(argv: list of string): ref Arg; + opt: fn(arg: self ref Arg): int; + arg: fn(arg: self ref Arg): string; +}; + +Arg.init(argv: list of string): ref Arg +{ + if(argv != nil) + argv = tl argv; + return ref Arg(argv, 0, nil); +} + +Arg.opt(arg: self ref Arg): int +{ + if(arg.opts != ""){ + arg.c = arg.opts[0]; + arg.opts = arg.opts[1:]; + return arg.c; + } + if(arg.argv == nil) + return arg.c = 0; + arg.opts = hd arg.argv; + if(len arg.opts < 2 || arg.opts[0] != '-') + return arg.c = 0; + arg.argv = tl arg.argv; + if(arg.opts == "--") + return arg.c = 0; + arg.c = arg.opts[1]; + arg.opts = arg.opts[2:]; + return arg.c; +} + +Arg.arg(arg: self ref Arg): string +{ + s := arg.opts; + arg.opts = ""; + if(s != "") + return s; + if(arg.argv == nil) + return ""; + s = hd arg.argv; + arg.argv = tl arg.argv; + return s; +} diff --git a/appl/cmd/limbo/asm.b b/appl/cmd/limbo/asm.b new file mode 100644 index 00000000..3788d016 --- /dev/null +++ b/appl/cmd/limbo/asm.b @@ -0,0 +1,263 @@ +asmentry(e: ref Decl) +{ + if(e == nil) + return; + bout.puts("\tentry\t"+string e.pc.pc+", "+string e.desc.id+"\n"); +} + +asmmod(m: ref Decl) +{ + bout.puts("\tmodule\t"); + bout.puts(m.sym.name); + bout.putc('\n'); + for(m = m.ty.tof.ids; m != nil; m = m.next){ + case m.store{ + Dglobal => + bout.puts("\tlink\t-1,-1,0x"+hex(sign(m), 0)+",\".mp\"\n"); + Dfn => + bout.puts("\tlink\t"+string m.desc.id+","+string m.pc.pc+",0x"+string hex(sign(m), 0)+",\""); + if(m.dot.ty.kind == Tadt) + bout.puts(m.dot.sym.name+"."); + bout.puts(m.sym.name+"\"\n"); + } + } +} + +asmpath() +{ + bout.puts("\tsource\t\"" + srcpath() + "\"\n"); +} + +asmdesc(d: ref Desc) +{ + for(; d != nil; d = d.next){ + bout.puts("\tdesc\t$"+string d.id+","+string d.size+",\""); + e := d.nmap; + m := d.map; + for(i := 0; i < e; i++) + bout.puts(hex(int m[i], 2)); + bout.puts("\"\n"); + } +} + +asmvar(size: int, d: ref Decl) +{ + bout.puts("\tvar\t@mp," + string size + "\n"); + + for(; d != nil; d = d.next) + if(d.store == Dglobal && d.init != nil) + asminitializer(d.offset, d.init); +} + +asmldt(size: int, d: ref Decl) +{ + bout.puts("\tldts\t@ldt," + string size + "\n"); + + for(; d != nil; d = d.next) + if(d.store == Dglobal && d.init != nil) + asminitializer(d.offset, d.init); +} + +asminitializer(offset: int, n: ref Node) +{ + wild: ref Node; + c: ref Case; + lab: Label; + id: ref Decl; + i, e: int; + + case n.ty.kind{ + Tbyte => + bout.puts("\tbyte\t@mp+"+string offset+","+string(int n.c.val & 16rff)+"\n"); + Tint or + Tfix => + bout.puts("\tword\t@mp+"+string offset+","+string(int n.c.val)+"\n"); + Tbig => + bout.puts("\tlong\t@mp+"+string offset+","+string n.c.val+" # "+string bhex(n.c.val, 16)+"\n"); + Tstring => + asmstring(offset, n.decl.sym); + Treal => + fs := ""; + ba := array[8] of byte; + export_real(ba, array[] of {n.c.rval}); + for(i = 0; i < 8; i++) + fs += hex(int ba[i], 2); + bout.puts("\treal\t@mp+"+string offset+","+string n.c.rval+" # "+fs+"\n"); + Tadt or + Tadtpick or + Ttuple => + id = n.ty.ids; + for(n = n.left; n != nil; n = n.right){ + asminitializer(offset + id.offset, n.left); + id = id.next; + } + Tcase => + c = n.ty.cse; + bout.puts("\tword\t@mp+"+string offset+","+string c.nlab); + for(i = 0; i < c.nlab; i++){ + lab = c.labs[i]; + bout.puts(","+string(int lab.start.c.val)+","+string(int lab.stop.c.val+1)+","+string(lab.inst.pc)); + } + if(c.iwild != nil) + bout.puts(","+string c.iwild.pc+"\n"); + else + bout.puts(",-1\n"); + Tcasel => + c = n.ty.cse; + bout.puts("\tword\t@mp+"+string offset+","+string c.nlab); + for(i = 0; i < c.nlab; i++){ + lab = c.labs[i]; + bout.puts(","+string(lab.start.c.val)+","+string(lab.stop.c.val+big 1)+","+string(lab.inst.pc)); + } + if(c.iwild != nil) + bout.puts(","+string c.iwild.pc+"\n"); + else + bout.puts(",-1\n"); + Tcasec => + c = n.ty.cse; + bout.puts("\tword\t@mp+"+string offset+","+string c.nlab+"\n"); + offset += IBY2WD; + for(i = 0; i < c.nlab; i++){ + lab = c.labs[i]; + asmstring(offset, lab.start.decl.sym); + offset += IBY2WD; + if(lab.stop != lab.start) + asmstring(offset, lab.stop.decl.sym); + offset += IBY2WD; + bout.puts("\tword\t@mp+"+string offset+","+string lab.inst.pc+"\n"); + offset += IBY2WD; + } + if(c.iwild != nil) + bout.puts("\tword\t@mp+"+string offset+","+string c.iwild.pc+"\n"); + else + bout.puts("\tword\t@mp+"+string offset+",-1\n"); + Tgoto => + c = n.ty.cse; + bout.puts("\tword\t@mp+"+string offset); + bout.puts(","+string(n.ty.size/IBY2WD-1)); + for(i = 0; i < c.nlab; i++) + bout.puts(","+string c.labs[i].inst.pc); + if(c.iwild != nil) + bout.puts(","+string c.iwild.pc); + bout.puts("\n"); + Tany => + break; + Tarray => + bout.puts("\tarray\t@mp+"+string offset+",$"+string n.ty.tof.decl.desc.id+","+string int n.left.c.val+"\n"); + if(n.right == nil) + break; + bout.puts("\tindir\t@mp+"+string offset+",0\n"); + c = n.right.ty.cse; + wild = nil; + if(c.wild != nil) + wild = c.wild.right; + last := 0; + esz := n.ty.tof.size; + for(i = 0; i < c.nlab; i++){ + e = int c.labs[i].start.c.val; + if(wild != nil){ + for(; last < e; last++) + asminitializer(esz * last, wild); + } + last = e; + e = int c.labs[i].stop.c.val; + elem := c.labs[i].node.right; + for(; last <= e; last++) + asminitializer(esz * last, elem); + } + if(wild != nil) + for(e = int n.left.c.val; last < e; last++) + asminitializer(esz * last, wild); + bout.puts("\tapop\n"); + Tiface => + if(LDT) + bout.puts("\tword\t@ldt+"+string offset+","+string int n.c.val+"\n"); + else + bout.puts("\tword\t@mp+"+string offset+","+string int n.c.val+"\n"); + offset += IBY2WD; + for(id = n.decl.ty.ids; id != nil; id = id.next){ + offset = align(offset, IBY2WD); + if(LDT) + bout.puts("\text\t@ldt+"+string offset+",0x"+string hex(sign(id), 0)+",\""); + else + bout.puts("\text\t@mp+"+string offset+",0x"+string hex(sign(id), 0)+",\""); + dotlen := 0; + idlen := len array of byte id.sym.name + 1; + if(id.dot.ty.kind == Tadt){ + dotlen = len array of byte id.dot.sym.name + 1; + bout.puts(id.dot.sym.name+"."); + } + bout.puts(id.sym.name+"\"\n"); + offset += idlen + dotlen + IBY2WD; + } + * => + fatal("can't asm global "+nodeconv(n)); + } +} + +asmexc(es: ref Except) +{ + e: ref Except; + + n := 0; + for(e = es; e != nil; e = e.next) + n++; + bout.puts("\texceptions\t" + string n + "\n"); + for(e = es; e != nil; e = e.next){ + if(!int e.p1.reach && !int e.p2.reach) + continue; + c := e.c; + o := e.d.offset; + if(e.desc != nil) + id := e.desc.id; + else + id = -1; + bout.puts("\texception\t" + string getpc(e.p1) + ", " + string getpc(e.p2) + ", " + string o + ", " + string id + ", " + string c.nlab + ", " + string e.ne + "\n"); + for(i := 0; i < c.nlab; i++){ + lab := c.labs[i]; + d := lab.start.decl; + if(lab.start.ty.kind == Texception) + d = d.init.decl; + bout.puts("\texctab\t\"" + d.sym.name + "\", " + string lab.inst.pc + "\n"); + } + if(c.iwild == nil) + bout.puts("\texctab\t" + "*" + ", " + string -1 + "\n"); + else + bout.puts("\texctab\t" + "*" + ", " + string c.iwild.pc + "\n"); + } +} + +asmstring(offset: int, sym: ref Sym) +{ + bout.puts("\tstring\t@mp+"+string offset+",\""); + s := sym.name; + for(i := 0; i < len s; i++){ + c := s[i]; + if(c == '\n') + bout.puts("\\n"); + else if(c == '\u0000') + bout.puts("\\z"); + else if(c == '"') + bout.puts("\\\""); + else if(c == '\\') + bout.puts("\\\\"); + else + bout.putc(c); + } + bout.puts("\"\n"); +} + +asminst(in: ref Inst) +{ + for(; in != nil; in = in.next){ + if(in.op == INOOP) + continue; + if(in.pc % 10 == 0){ + bout.putc('#'); + bout.puts(string in.pc); + bout.putc('\n'); + } + bout.puts(instconv(in)); + bout.putc('\n'); + } +} diff --git a/appl/cmd/limbo/com.b b/appl/cmd/limbo/com.b new file mode 100644 index 00000000..bc977d0a --- /dev/null +++ b/appl/cmd/limbo/com.b @@ -0,0 +1,1387 @@ +# back end + +breaks: array of ref Inst; +conts: array of ref Inst; +labels: array of ref Decl; +bcscps: array of ref Node; +labdep: int; +nocont: ref Inst; +nlabel: int; + +scp: int; +scps:= array[MaxScope] of ref Node; + +curfn: ref Decl; + +pushscp(n : ref Node) +{ + if (scp >= MaxScope) + fatal("scope too deep"); + scps[scp++] = n; +} + +popscp() +{ + scp--; +} + +curscp() : ref Node +{ + if (scp == 0) + return nil; + return scps[scp-1]; +} + +zeroscopes(stop : ref Node) +{ + i : int; + cs : ref Node; + + for (i = scp-1; i >= 0; i--) { + cs = scps[i]; + if (cs == stop) + break; + zcom(cs.left, nil); + } +} + +zeroallscopes(n: ref Node, nn: array of ref Node) +{ + if(n == nil) + return; + for(; n != nil; n = n.right){ + case(n.op){ + Oscope => + zeroallscopes(n.right, nn); + zcom(n.left, nn); + return; + Olabel or + Odo => + zeroallscopes(n.right, nn); + return; + Oif or + Ofor => + zeroallscopes(n.right.left, nn); + zeroallscopes(n.right.right, nn); + return; + Oalt or + Ocase or + Opick or + Oexcept => + for(n = n.right; n != nil; n = n.right) + zeroallscopes(n.left.right, nn); + return; + Oseq => + zeroallscopes(n.left, nn); + break; + Oexstmt => + zeroallscopes(n.left, nn); + zeroallscopes(n.right, nn); + return; + * => + return; + } + } +} + +excs: ref Except; + +installexc(en: ref Node, p1: ref Inst, p2: ref Inst, zn: ref Node) +{ + e := ref Except; + e.p1 = p1; + e.p2 = p2; + e.c = en.ty.cse; + e.d = en.left.decl; + e.zn = zn; + e.next = excs; + excs = e; + + ne := 0; + c := e.c; + for(i := 0; i < c.nlab; i++){ + lab := c.labs[i]; + if(lab.start.ty.kind == Texception) + ne++; + } + e.ne = ne; +} + +inlist(d: ref Decl, dd: ref Decl): int +{ + for( ; dd != nil; dd = dd.next) + if(d == dd) + return 1; + return 0; +} + +excdesc() +{ + dd, nd: ref Decl; + + for(e := excs; e != nil; e = e.next){ + if(e.zn != nil){ + dd = nil; + maxo := 0; + for(n := e.zn ; n != nil; n = n.right){ + d := n.decl; + d.locals = d.next; + if(!inlist(d, dd)){ + d.next = dd; + dd = d; + o := d.offset+d.ty.size; + if(o > maxo) + maxo = o; + } + } + e.desc = gendesc(e.d, align(maxo, MaxAlign), dd); + for(d := dd; d != nil; d = nd){ + nd = d.next; + d.next = d.locals; + d.locals = nil; + } + e.zn = nil; + } + } +} + +reve(e: ref Except): ref Except +{ + l, n: ref Except; + + l = nil; + for( ; e != nil; e = n){ + n = e.next; + e.next = l; + l = e; + } + return l; +} + +ckinline0(n: ref Node, d: ref Decl): int +{ + dd: ref Decl; + + if(n == nil) + return 1; + if(n.op == Oname){ + dd = n.decl; + if(d == dd) + return 0; + if(int dd.inline == 1) + return ckinline0(dd.init.right, d); + return 1; + } + return ckinline0(n.left, d) && ckinline0(n.right, d); +} + +ckinline(d: ref Decl) +{ + d.inline = byte ckinline0(d.init.right, d); +} + +modcom(entry: ref Decl) +{ + d, m: ref Decl; + + if(errors) + return; + + if(emitcode != "" || emitstub || emittab != "" || emitsbl != ""){ + emit(curscope()); + popscope(); + return; + } + + # + # scom introduces global variables for case statements + # and unaddressable constants, so it must be done before + # popping the global scope + # + gent = sys->millisec(); + nlabel = 0; + maxstack = MaxTemp; + nocont = ref Inst; + genstart(); + + for(i := 0; i < nfns; i++) + if(int fns[i].inline == 1) + ckinline(fns[i]); + + ok := 0; + for(i = 0; i < nfns; i++){ + d = fns[i]; + if(d.refs > 1 && !(int d.inline == 1 && local(d) && d.iface == nil)){ + fns[ok++] = d; + fncom(d); + } + } + fns = fns[:ok]; + nfns = ok; + if(blocks != -1) + fatal("blocks not nested correctly"); + firstinst = firstinst.next; + if(errors) + return; + + globals := popscope(); + checkrefs(globals); + if(errors) + return; + globals = vars(globals); + moddataref(); + + nils := popscope(); + m = nil; + for(d = nils; d != nil; d = d.next){ + if(debug['n']) + print("nil '%s' ref %d\n", d.sym.name, d.refs); + if(d.refs && m == nil) + m = dupdecl(d); + d.offset = 0; + } + globals = appdecls(m, globals); + globals = namesort(globals); + globals = modglobals(impdecls.d, globals); + vcom(globals); + narrowmods(); + ldts: ref Decl; + if(LDT) + (globals, ldts) = resolveldts(globals); + offset := idoffsets(globals, 0, IBY2WD); + if(LDT) + ldtoff := idindices(ldts); # idoffsets(ldts, 0, IBY2WD); + for(d = nils; d != nil; d = d.next){ + if(debug['n']) + print("nil '%s' ref %d\n", d.sym.name, d.refs); + if(d.refs) + d.offset = m.offset; + } + + if(debug['g']){ + print("globals:\n"); + printdecls(globals); + } + + ndata := 0; + for(d = globals; d != nil; d = d.next) + ndata++; + ndesc := resolvedesc(impdecls.d, offset, globals); + ninst := resolvepcs(firstinst); + modresolve(); + if(impdecls.next != nil) + for(dl := impdecls; dl != nil; dl = dl.next) + resolvemod(dl.d); + nlink := resolvemod(impdecl); + gent = sys->millisec() - gent; + + maxstack *= 10; + if(fixss != 0) + maxstack = fixss; + + if(debug['s']) + print("%d instructions\n%d data elements\n%d type descriptors\n%d functions exported\n%d stack size\n", + ninst, ndata, ndesc, nlink, maxstack); + + excs = reve(excs); + + writet = sys->millisec(); + if(gendis){ + discon(XMAGIC); + hints := 0; + if(mustcompile) + hints |= MUSTCOMPILE; + if(dontcompile) + hints |= DONTCOMPILE; + if(LDT) + hints |= HASLDT; + if(excs != nil) + hints |= HASEXCEPT; + discon(hints); # runtime hints + discon(maxstack); # minimum stack extent size + discon(ninst); + discon(offset); + discon(ndesc); + discon(nlink); + disentry(entry); + disinst(firstinst); + disdesc(descriptors); + disvar(offset, globals); + dismod(impdecl); + if(LDT) + disldt(ldtoff, ldts); + if(excs != nil) + disexc(excs); + dispath(); + }else{ + asminst(firstinst); + asmentry(entry); + asmdesc(descriptors); + asmvar(offset, globals); + asmmod(impdecl); + if(LDT) + asmldt(ldtoff, ldts); + if(excs != nil) + asmexc(excs); + asmpath(); + } + writet = sys->millisec() - writet; + + symt = sys->millisec(); + if(bsym != nil){ + sblmod(impdecl); + + sblfiles(); + sblinst(firstinst, ninst); + sblty(adts, nadts); + sblfn(fns, nfns); + sblvar(globals); + } + symt = sys->millisec() - symt; + + firstinst = nil; + lastinst = nil; + + excs = nil; +} + +fncom(decl: ref Decl) +{ + curfn = decl; + if(ispoly(decl)) + addfnptrs(decl, 1); + + # + # pick up the function body and compile it + # this code tries to clean up the parse nodes as fast as possible + # function is Ofunc(name, body) + # + decl.pc = nextinst(); + tinit(); + labdep = 0; + scp = 0; + breaks = array[maxlabdep] of ref Inst; + conts = array[maxlabdep] of ref Inst; + labels = array[maxlabdep] of ref Decl; + bcscps = array[maxlabdep] of ref Node; + + n := decl.init; + if(int decl.inline == 1) + decl.init = dupn(0, nosrc, n); + else + decl.init = n.left; + src := n.right.src; + src.start = src.stop - 1; + for(n = n.right; n != nil; n = n.right){ + if(n.op != Oseq){ + if(n.op == Ocall && trcom(n, nil, 1)) + break; + scom(n); + break; + } + if(n.left.op == Ocall && trcom(n.left, n.right, 1)){ + n = n.right; + if(n == nil || n.op != Oseq) + break; + } + else + scom(n.left); + } + pushblock(); + in := genrawop(src, IRET, nil, nil, nil); + popblock(); + reach(decl.pc); + if(in.reach != byte 0 && decl.ty.tof != tnone) + error(src.start, "no return at end of function " + dotconv(decl)); + # decl.endpc = lastinst; + if(labdep != 0) + fatal("unbalanced label stack"); + breaks = nil; + conts = nil; + labels = nil; + bcscps = nil; + + loc := declsort(appdecls(vars(decl.locals), tdecls())); + + decl.offset = idoffsets(loc, decl.offset, MaxAlign); + for(last := decl.ty.ids; last != nil && last.next != nil; last = last.next) + ; + if(last != nil) + last.next = loc; + else + decl.ty.ids = loc; + + if(debug['f']){ + print("fn: %s\n", decl.sym.name); + printdecls(decl.ty.ids); + } + + decl.desc = gendesc(decl, decl.offset, decl.ty.ids); + decl.locals = loc; + excdesc(); + if(decl.offset > maxstack) + maxstack = decl.offset; + if(optims) + optim(decl.pc, decl); + if(last != nil) + last.next = nil; + else + decl.ty.ids = nil; +} + +# +# statement compiler +# +scom(n: ref Node) +{ + b: int; + p, pp: ref Inst; + left: ref Node; + + for(; n != nil; n = n.right){ + case n.op{ + Ocondecl or + Otypedecl or + Ovardecl or + Oimport or + Oexdecl => + return; + Ovardecli => + break; + Oscope => + pushscp(n); + scom(n.right); + popscp(); + zcom(n.left, nil); + return; + Olabel => + scom(n.right); + return; + Oif => + pushblock(); + left = simplify(n.left); + if(left.op == Oconst && left.ty == tint){ + if(left.c.val != big 0) + scom(n.right.left); + else + scom(n.right.right); + popblock(); + return; + } + sumark(left); + pushblock(); + p = bcom(left, 1, nil); + tfreenow(); + popblock(); + scom(n.right.left); + if(n.right.right != nil){ + pp = p; + p = genrawop(lastinst.src, IJMP, nil, nil, nil); + patch(pp, nextinst()); + scom(n.right.right); + } + patch(p, nextinst()); + popblock(); + return; + Ofor => + n.left = left = simplify(n.left); + if(left.op == Oconst && left.ty == tint){ + if(left.c.val == big 0) + return; + left.op = Onothing; + left.ty = tnone; + left.decl = nil; + } + pp = nextinst(); + b = pushblock(); + sumark(left); + p = bcom(left, 1, nil); + tfreenow(); + popblock(); + + if(labdep >= maxlabdep) + fatal("label stack overflow"); + breaks[labdep] = nil; + conts[labdep] = nil; + labels[labdep] = n.decl; + bcscps[labdep] = curscp(); + labdep++; + scom(n.right.left); + labdep--; + + patch(conts[labdep], nextinst()); + if(n.right.right != nil){ + pushblock(); + scom(n.right.right); + popblock(); + } + repushblock(lastinst.block); # was b + patch(genrawop(lastinst.src, IJMP, nil, nil, nil), pp); # for cprof: was left.src + popblock(); + patch(p, nextinst()); + patch(breaks[labdep], nextinst()); + return; + Odo => + pp = nextinst(); + + if(labdep >= maxlabdep) + fatal("label stack overflow"); + breaks[labdep] = nil; + conts[labdep] = nil; + labels[labdep] = n.decl; + bcscps[labdep] = curscp(); + labdep++; + scom(n.right); + labdep--; + + patch(conts[labdep], nextinst()); + + left = simplify(n.left); + if(left.op == Onothing + || left.op == Oconst && left.ty == tint){ + if(left.op == Onothing || left.c.val != big 0){ + pushblock(); + p = genrawop(left.src, IJMP, nil, nil, nil); + popblock(); + }else + p = nil; + }else{ + pushblock(); + p = bcom(sumark(left), 0, nil); + tfreenow(); + popblock(); + } + patch(p, pp); + patch(breaks[labdep], nextinst()); + return; + Ocase or + Opick or + Oalt or + Oexcept => + pushblock(); + if(labdep >= maxlabdep) + fatal("label stack overflow"); + breaks[labdep] = nil; + conts[labdep] = nocont; + labels[labdep] = n.decl; + bcscps[labdep] = curscp(); + labdep++; + case n.op{ + Oalt => + altcom(n); + Ocase or + Opick => + casecom(n); + Oexcept => + excom(n); + } + labdep--; + patch(breaks[labdep], nextinst()); + popblock(); + return; + Obreak => + pushblock(); + bccom(n, breaks); + popblock(); + Ocont => + pushblock(); + bccom(n, conts); + popblock(); + Oseq => + if(n.left.op == Ocall && trcom(n.left, n.right, 0)){ + n = n.right; + if(n == nil || n.op != Oseq) + return; + } + else + scom(n.left); + Oret => + if(n.left != nil && n.left.op == Ocall && trcom(n.left, nil, 1)) + return; + pushblock(); + if(n.left != nil){ + n.left = simplify(n.left); + sumark(n.left); + ecom(n.left.src, retalloc(ref Node, n.left), n.left); + tfreenow(); + } + genrawop(n.src, IRET, nil, nil, nil); + popblock(); + return; + Oexit => + pushblock(); + genrawop(n.src, IEXIT, nil, nil, nil); + popblock(); + return; + Onothing => + return; + Ofunc => + fatal("Ofunc"); + return; + Oexstmt => + pushblock(); + pp = genrawop(n.right.src, IEXC0, nil, nil, nil); # marker + p1 := nextinst(); + scom(n.left); + p2 := nextinst(); + p3 := genrawop(n.right.src, IJMP, nil, nil, nil); + p = genrawop(n.right.src, IEXC, nil, nil, nil); # marker + p.d.decl = mkdecl(n.src, 0, n.right.ty); + zn := array[1] of ref Node; + zeroallscopes(n.left, zn); + scom(n.right); + patch(p3, nextinst()); + installexc(n.right, p1, p2, zn[0]); + patch(pp, p); + popblock(); + return; + * => + pushblock(); + n = simplify(n); + sumark(n); + ecom(n.src, nil, n); + tfreenow(); + popblock(); + return; + } + } +} + +# +# compile a break, continue +# +bccom(n: ref Node, bs: array of ref Inst) +{ + s: ref Sym; + + s = nil; + if(n.decl != nil) + s = n.decl.sym; + ok := -1; + for(i := 0; i < labdep; i++){ + if(bs[i] == nocont) + continue; + if(s == nil || labels[i] != nil && labels[i].sym == s) + ok = i; + } + if(ok < 0) + fatal("didn't find break or continue"); + zeroscopes(bcscps[ok]); + p := genrawop(n.src, IJMP, nil, nil, nil); + p.branch = bs[ok]; + bs[ok] = p; +} + +dogoto(c: ref Case): int +{ + i, j, k, n, r, q, v: int; + l, nl: array of Label; + src: Src; + + l = c.labs; + n = c.nlab; + if(n == 0) + return 0; + r = int l[n-1].stop.c.val - int l[0].start.c.val+1; + if(r >= 3 && r <= 3*n){ + if(r != n){ + # remove ranges, fill in gaps + c.nlab = r; + nl = c.labs = array[r] of Label; + k = 0; + v = int l[0].start.c.val-1; + for(i = 0; i < n; i++){ + # p = int l[i].start.c.val; + q = int l[i].stop.c.val; + src = l[i].start.src; + for(j = v+1; j <= q; j++){ + nl[k] = l[i]; + nl[k].start = nl[k].stop = mkconst(src, big j); + k++; + } + v = q; + } + if(k != r) + fatal("bad case expansion"); + } + l = c.labs; + for(i = 0; i < r; i++) + l[i].inst = nil; + return 1; + } + return 0; +} + +fillrange(c: ref Case, nn: ref Node, in: ref Inst) +{ + i, j, n, p, q: int; + l: array of Label; + + l = c.labs; + n = c.nlab; + p = int nn.left.c.val; + q = int nn.right.c.val; + for(i = 0; i < n; i++) + if(int l[i].start.c.val == p) + break; + if(i == n) + fatal("fillrange fails"); + for(j = p; j <= q; j++) + l[i++].inst = in; +} + +casecom(cn: ref Node) +{ + d: ref Decl; + left, p, tmp, tmpc: ref Node; + jmps, wild, j1, j2: ref Inst; + + c := cn.ty.cse; + + needwild := cn.op != Opick || c.nlab != cn.left.right.ty.tof.decl.tag; + igoto := cn.left.ty == tint && dogoto(c); + + # + # generate global which has case labels + # + if(igoto){ + d = mkids(cn.src, enter(".g"+string nlabel++, 0), cn.ty, nil); + cn.ty.kind = Tgoto; + } + else + d = mkids(cn.src, enter(".c"+string nlabel++, 0), cn.ty, nil); + d.init = mkdeclname(cn.src, d); + nto := ref znode; + nto.addable = Rmreg; + nto.left = nil; + nto.right = nil; + nto.op = Oname; + nto.ty = d.ty; + nto.decl = d; + + tmp = nil; + left = cn.left; + left = simplify(left); + cn.left = left; + sumark(left); + if(debug['c']) + print("case %s\n", nodeconv(left)); + ctype := cn.left.ty; + if(left.addable >= Rcant){ + if(cn.op == Opick){ + ecom(left.src, nil, left); + tfreenow(); + left = mkunary(Oind, dupn(1, left.src, left.left)); + left.ty = tint; + sumark(left); + ctype = tint; + }else{ + (left, tmp) = eacom(left, nil); + tfreenow(); + } + } + + labs := c.labs; + nlab := c.nlab; + + if(igoto){ + if(labs[0].start.c.val != big 0){ + tmpc = talloc(left.ty, nil); + if(left.addable == Radr || left.addable == Rmadr){ + genrawop(left.src, IMOVW, left, nil, tmpc); + left = tmpc; + } + genrawop(left.src, ISUBW, sumark(labs[0].start), left, tmpc); + left = tmpc; + } + if(needwild){ + j1 = genrawop(left.src, IBLTW, left, sumark(mkconst(left.src, big 0)), nil); + j2 = genrawop(left.src, IBGTW, left, sumark(mkconst(left.src, labs[nlab-1].start.c.val-labs[0].start.c.val)), nil); + } + j := nextinst(); + genrawop(left.src, IGOTO, left, nil, nto); + j.d.reg = IBY2WD; + } + else{ + op := ICASE; + if(ctype == tbig) + op = ICASEL; + else if(ctype == tstring) + op = ICASEC; + genrawop(left.src, op, left, nil, nto); + } + tfree(tmp); + tfree(tmpc); + + jmps = nil; + wild = nil; + for(n := cn.right; n != nil; n = n.right){ + j := nextinst(); + for(p = n.left.left; p != nil; p = p.right){ + if(debug['c']) + print("case qualifier %s\n", nodeconv(p.left)); + case p.left.op{ + Oconst => + labs[findlab(ctype, p.left, labs, nlab)].inst = j; + Orange => + labs[findlab(ctype, p.left.left, labs, nlab)].inst = j; + if(igoto) + fillrange(c, p.left, j); + Owild => + if(needwild) + wild = j; + # else + # nwarn(p.left, "default case redundant"); + } + } + + if(debug['c']) + print("case body for %s: %s\n", expconv(n.left.left), nodeconv(n.left.right)); + + k := nextinst(); + scom(n.left.right); + + src := lastinst.src; + # if(n.left.right == nil || n.left.right.op == Onothing) + if(k == nextinst()) + src = n.left.left.src; + j = genrawop(src, IJMP, nil, nil, nil); + j.branch = jmps; + jmps = j; + } + patch(jmps, nextinst()); + if(wild == nil && needwild) + wild = nextinst(); + + if(igoto){ + if(needwild){ + patch(j1, wild); + patch(j2, wild); + } + for(i := 0; i < nlab; i++) + if(labs[i].inst == nil) + labs[i].inst = wild; + } + + c.iwild = wild; + + d.ty.cse = c; + usetype(d.ty); + installids(Dglobal, d); +} + +altcom(nalt: ref Node) +{ + p, op, left: ref Node; + jmps, wild, j: ref Inst = nil; + + talt := nalt.ty; + c := talt.cse; + nlab := c.nlab; + nsnd := c.nsnd; + comm := array[nlab] of ref Node; + labs := array[nlab] of Label; + tmps := array[nlab] of ref Node; + c.labs = labs; + + # + # built the type of the alt channel table + # note that we lie to the garbage collector + # if we know that another reference exists for the channel + # + is := 0; + ir := nsnd; + i := 0; + for(n := nalt.left; n != nil; n = n.right){ + for(p = n.left.right.left; p != nil; p = p.right){ + left = simplify(p.left); + p.left = left; + if(left.op == Owild) + continue; + comm[i] = hascomm(left); + left = comm[i].left; + sumark(left); + isptr := left.addable >= Rcant; + if(comm[i].op == Osnd) + labs[is++].isptr = isptr; + else + labs[ir++].isptr = isptr; + i++; + } + } + + which := talloc(tint, nil); + tab := talloc(talt, nil); + + # + # build the node for the address of each channel, + # the values to send, and the storage fro values received + # + off := ref znode; + adr := ref znode; + add := ref znode; + slot := ref znode; + off.op = Oconst; + off.c = ref Const(big 0, 0.0); # jrf - added initialization + off.ty = tint; + off.addable = Rconst; + adr.op = Oadr; + adr.left = tab; + adr.ty = tint; + add.op = Oadd; + add.left = adr; + add.right = off; + add.ty = tint; + slot.op = Oind; + slot.left = add; + sumark(slot); + + # + # compile the sending and receiving channels and values + # + is = 2*IBY2WD; + ir = is + nsnd*2*IBY2WD; + i = 0; + for(n = nalt.left; n != nil; n = n.right){ + for(p = n.left.right.left; p != nil; p = p.right){ + if(p.left.op == Owild) + continue; + + # + # gen channel + # + op = comm[i]; + if(op.op == Osnd){ + off.c.val = big is; + is += 2*IBY2WD; + }else{ + off.c.val = big ir; + ir += 2*IBY2WD; + } + left = op.left; + + # + # this sleaze is lying to the garbage collector + # + if(left.addable < Rcant) + genmove(left.src, Mas, tint, left, slot); + else{ + slot.ty = left.ty; + ecom(left.src, slot, left); + tfreenow(); + slot.ty = nil; + } + + # + # gen value + # + off.c.val += big IBY2WD; + (p.left, tmps[i]) = rewritecomm(p.left, comm[i], slot); + + i++; + } + } + + # + # stuff the number of send & receive channels into the table + # + altsrc := nalt.src; + altsrc.stop = (altsrc.stop & ~PosMask) | ((altsrc.stop + 3) & PosMask); + off.c.val = big 0; + genmove(altsrc, Mas, tint, sumark(mkconst(altsrc, big nsnd)), slot); + off.c.val += big IBY2WD; + genmove(altsrc, Mas, tint, sumark(mkconst(altsrc, big(nlab-nsnd))), slot); + off.c.val += big IBY2WD; + + altop := IALT; + if(c.wild != nil) + altop = INBALT; + pp := genrawop(altsrc, altop, tab, nil, which); + pp.m.offset = talt.size; # for optimizer + + d := mkids(nalt.src, enter(".g"+string nlabel++, 0), mktype(nalt.src.start, nalt.src.stop, Tgoto, nil, nil), nil); + d.ty.cse = c; + d.init = mkdeclname(nalt.src, d); + + nto := ref znode; + nto.addable = Rmreg; + nto.left = nil; + nto.right = nil; + nto.op = Oname; + nto.decl = d; + nto.ty = d.ty; + + me := genrawop(altsrc, IGOTO, which, nil, nto); + me.d.reg = IBY2WD; # skip the number of cases field + tfree(tab); + tfree(which); + + # + # compile the guard expressions and bodies + # + i = 0; + is = 0; + ir = nsnd; + jmps = nil; + wild = nil; + for(n = nalt.left; n != nil; n = n.right){ + j = nil; + for(p = n.left.right.left; p != nil; p = p.right){ + tj := nextinst(); + if(p.left.op == Owild){ + wild = nextinst(); + }else{ + if(comm[i].op == Osnd) + labs[is++].inst = tj; + else{ + labs[ir++].inst = tj; + tacquire(tmps[i]); + } + sumark(p.left); + if(debug['a']) + print("alt guard %s\n", nodeconv(p.left)); + ecom(p.left.src, nil, p.left); + tfree(tmps[i]); + tfreenow(); + i++; + } + if(p.right != nil){ + tj = genrawop(lastinst.src, IJMP, nil, nil, nil); + tj.branch = j; + j = tj; + } + } + + patch(j, nextinst()); + if(debug['a']) + print("alt body %s\n", nodeconv(n.left.right)); + scom(n.left); + + j = genrawop(lastinst.src, IJMP, nil, nil, nil); + j.branch = jmps; + jmps = j; + } + patch(jmps, nextinst()); + comm = nil; + + c.iwild = wild; + + usetype(d.ty); + installids(Dglobal, d); +} + +excom(en: ref Node) +{ + ed: ref Decl; + p: ref Node; + jmps, wild: ref Inst; + + ed = en.left.decl; + ed.ty = rtexception; + c := en.ty.cse; + labs := c.labs; + nlab := c.nlab; + jmps = nil; + wild = nil; + for(n := en.right; n != nil; n = n.right){ + qt: ref Type = nil; + j := nextinst(); + for(p = n.left.left; p != nil; p = p.right){ + case p.left.op{ + Oconst => + labs[findlab(texception, p.left, labs, nlab)].inst = j; + Owild => + wild = j; + } + if(qt == nil) + qt = p.left.ty; + else if(!tequal(qt, p.left.ty)) + qt = texception; + } + if(qt != nil) + ed.ty = qt; + k := nextinst(); + scom(n.left.right); + src := lastinst.src; + if(k == nextinst()) + src = n.left.left.src; + j = genrawop(src, IJMP, nil, nil, nil); + j.branch = jmps; + jmps = j; + } + ed.ty = rtexception; + patch(jmps, nextinst()); + c.iwild = wild; +} + +# +# rewrite the communication operand +# allocate any temps needed for holding value to send or receive +# +rewritecomm(n, comm, slot: ref Node): (ref Node, ref Node) +{ + adr, tmp: ref Node; + + if(n == nil) + return (nil, nil); + adr = nil; + if(n == comm){ + if(comm.op == Osnd && sumark(n.right).addable < Rcant) + adr = n.right; + else{ + adr = tmp = talloc(n.ty, nil); + tmp.src = n.src; + if(comm.op == Osnd){ + ecom(n.right.src, tmp, n.right); + tfreenow(); + } + else + trelease(tmp); + } + } + if(n.right == comm && n.op == Oas && comm.op == Orcv + && sumark(n.left).addable < Rcant) + adr = n.left; + if(adr != nil){ + p := genrawop(comm.left.src, ILEA, adr, nil, slot); + p.m.offset = adr.ty.size; # for optimizer + if(comm.op == Osnd) + p.m.reg = 1; # for optimizer + return (adr, tmp); + } + (n.left, tmp) = rewritecomm(n.left, comm, slot); + if(tmp == nil) + (n.right, tmp) = rewritecomm(n.right, comm, slot); + return (n, tmp); +} + +# +# merge together two sorted lists, yielding a sorted list +# +declmerge(e, f: ref Decl): ref Decl +{ + d := rock := ref Decl; + while(e != nil && f != nil){ + fs := f.ty.size; + es := e.ty.size; + # v := 0; + v := (e.link == nil) - (f.link == nil); + if(v == 0 && (es <= IBY2WD || fs <= IBY2WD)) + v = fs - es; + if(v == 0) + v = e.refs - f.refs; + if(v == 0) + v = fs - es; + if(v == 0 && e.sym.name > f.sym.name) + v = -1; + if(v >= 0){ + d.next = e; + d = e; + e = e.next; + while(e != nil && e.nid == byte 0){ + d = e; + e = e.next; + } + }else{ + d.next = f; + d = f; + f = f.next; + while(f != nil && f.nid == byte 0){ + d = f; + f = f.next; + } + } + # d = d.next; + } + if(e != nil) + d.next = e; + else + d.next = f; + return rock.next; +} + +# +# recursively split lists and remerge them after they are sorted +# +recdeclsort(d: ref Decl, n: int): ref Decl +{ + if(n <= 1) + return d; + m := n / 2 - 1; + dd := d; + for(i := 0; i < m; i++){ + dd = dd.next; + while(dd.nid == byte 0) + dd = dd.next; + } + r := dd.next; + while(r.nid == byte 0){ + dd = r; + r = r.next; + } + dd.next = nil; + return declmerge(recdeclsort(d, n / 2), + recdeclsort(r, (n + 1) / 2)); +} + +# +# sort the ids by size and number of references +# +declsort(d: ref Decl): ref Decl +{ + n := 0; + for(dd := d; dd != nil; dd = dd.next) + if(dd.nid > byte 0) + n++; + return recdeclsort(d, n); +} + +nilsrc : Src; + +zcom1(n : ref Node, nn: array of ref Node) +{ + ty : ref Type; + d : ref Decl; + e : ref Node; + + ty = n.ty; + if (!tmustzero(ty)) + return; + if (n.op == Oname && n.decl.refs == 0) + return; + if (nn != nil) { + if(n.op != Oname) + error(n.src.start, "fatal: bad op in zcom1 map"); + n.right = nn[0]; + nn[0] = n; + return; + } + if (ty.kind == Tadtpick) + ty = ty.tof; + if (ty.kind == Ttuple || ty.kind == Tadt) { + for (d = ty.ids; d != nil; d = d.next) { + if (tmustzero(d.ty)) { + dn := n; + if (d.next != nil) + dn = dupn(0, nilsrc, n); + e = mkbin(Odot, dn, mkname(nilsrc, d.sym)); + e.right.decl = d; + e.ty = e.right.ty = d.ty; + zcom1(e, nn); + } + } + } + else { + src := n.src; + n.src = nilsrc; + e = mkbin(Oas, n, mknil(nilsrc)); + e.ty = e.right.ty = ty; + if (debug['Z']) + print("ecom %s\n", nodeconv(e)); + pushblock(); + e = simplify(e); + sumark(e); + ecom(e.src, nil, e); + popblock(); + n.src = src; + e = nil; + } +} + +zcom0(id : ref Decl, nn: array of ref Node) +{ + e := mkname(nilsrc, id.sym); + e.decl = id; + e.ty = id.ty; + zcom1(e, nn); +} + +zcom(n : ref Node, nn: array of ref Node) +{ + r : ref Node; + + for ( ; n != nil; n = r) { + r = n.right; + n.right = nil; + case (n.op) { + Ovardecl => + last := n.left.decl; + for (ids := n.decl; ids != last.next; ids = ids.next) + zcom0(ids, nn); + break; + Oname => + if (n.decl != nildecl) + zcom1(dupn(0, nilsrc, n), nn); + break; + Otuple => + for (nt := n.left; nt != nil; nt = nt.right) + zcom(nt.left, nn); + break; + * => + fatal("bad node in zcom()"); + break; + } + n.right = r; + } +} + +ret(n: ref Node, nilret: int): int +{ + if(n == nil) + return nilret; + if(n.op == Oseq) + n = n.left; + return n.op == Oret && n.left == nil; +} + +trcom(e: ref Node, ne: ref Node, nilret: int): int +{ + d, id: ref Decl; + as, a, f, n: ref Node; + p: ref Inst; + +return 0; # TBS + if(e.op != Ocall || e.left.op != Oname) + return 0; + d = e.left.decl; + if(d != curfn || int d.handler || ispoly(d)) + return 0; + if(!ret(ne, nilret)) + return 0; + pushblock(); + id = d.ty.ids; + # evaluate args in same order as normal calls + for(as = e.right; as != nil; as = as.right){ + a = as.left; + if(!(a.op == Oname && id == a.decl)){ + if(occurs(id, as.right)){ + f = talloc(id.ty, nil); + f.flags |= byte TEMP; + } + else + f = mkdeclname(as.src, id); + n = mkbin(Oas, f, a); + n.ty = id.ty; + scom(n); + if(int f.flags&TEMP) + as.left = f; + } + id = id.next; + } + id = d.ty.ids; + for(as = e.right; as != nil; as = as.right){ + a = as.left; + if(int a.flags&TEMP){ + f = mkdeclname(as.src, id); + n = mkbin(Oas, f, a); + n.ty = id.ty; + scom(n); + tfree(a); + } + id = id.next; + } + p = genrawop(e.src, IJMP, nil, nil, nil); + patch(p, d.pc); + popblock(); + return 1; +} diff --git a/appl/cmd/limbo/decls.b b/appl/cmd/limbo/decls.b new file mode 100644 index 00000000..53d9e822 --- /dev/null +++ b/appl/cmd/limbo/decls.b @@ -0,0 +1,1177 @@ + +storename := array[Dend] of +{ + Dtype => "type", + Dfn => "function", + Dglobal => "global", + Darg => "argument", + Dlocal => "local", + Dconst => "con", + Dfield => "field", + Dtag => "pick tag", + Dimport => "import", + Dunbound => "unbound", + Dundef => "undefined", + Dwundef => "undefined", +}; + +storeart := array[Dend] of +{ + Dtype => "a ", + Dfn => "a ", + Dglobal => "a ", + Darg => "an ", + Dlocal => "a ", + Dconst => "a ", + Dfield => "a ", + Dtag => "a ", + Dimport => "an ", + Dunbound => "", + Dundef => "", + Dwundef => "", +}; + +storespace := array[Dend] of +{ + Dtype => 0, + Dfn => 0, + Dglobal => 1, + Darg => 1, + Dlocal => 1, + Dconst => 0, + Dfield => 1, + Dtag => 0, + Dimport => 0, + Dunbound => 0, + Dundef => 0, + Dwundef => 0, +}; + +impdecl: ref Decl; +impdecls: ref Dlist; +scopes := array[MaxScope] of ref Decl; +tails := array[MaxScope] of ref Decl; +scopekind := array[MaxScope] of byte; +scopenode := array[MaxScope] of ref Node; +iota: ref Decl; +zdecl: Decl; + +popscopes() +{ + d: ref Decl; + + # + # clear out any decls left in syms + # + while(scope >= ScopeBuiltin){ + for(d = scopes[scope--]; d != nil; d = d.next){ + if(d.sym != nil){ + d.sym.decl = d.old; + d.old = nil; + } + } + } + + for(id := impdecls; id != nil; id = id.next){ + for(d = id.d.ty.ids; d != nil; d = d.next){ + d.sym.decl = nil; + d.old = nil; + } + } + impdecls = nil; + + scope = ScopeBuiltin; + scopes[ScopeBuiltin] = nil; + tails[ScopeBuiltin] = nil; +} + +declstart() +{ + iota = mkids(nosrc, enter("iota", 0), tint, nil); + iota.init = mkconst(nosrc, big 0); + + scope = ScopeNils; + scopes[ScopeNils] = nil; + tails[ScopeNils] = nil; + + nildecl = mkdecl(nosrc, Dglobal, tany); + nildecl.sym = enter("nil", 0); + installids(Dglobal, nildecl); + d := mkdecl(nosrc, Dglobal, tstring); + d.sym = enterstring(""); + installids(Dglobal, d); + + scope = ScopeGlobal; + scopes[ScopeGlobal] = nil; + tails[ScopeGlobal] = nil; +} + +redecl(d: ref Decl) +{ + old := d.sym.decl; + if(old.store == Dwundef) + return; + error(d.src.start, "redeclaration of "+declconv(d)+", previously declared as "+storeconv(old)+" on line "+ + lineconv(old.src.start)); +} + +checkrefs(d: ref Decl) +{ + id, m: ref Decl; + refs: int; + + for(; d != nil; d = d.next){ + if(d.das != byte 0) + d.refs--; + case d.store{ + Dtype => + refs = d.refs; + if(d.ty.kind == Tadt){ + for(id = d.ty.ids; id != nil; id = id.next){ + d.refs += id.refs; + if(id.store != Dfn) + continue; + if(id.init == nil && id.link == nil && d.importid == nil) + error(d.src.start, "function "+d.sym.name+"."+id.sym.name+" not defined"); + if(superwarn && !id.refs && d.importid == nil) + warn(d.src.start, "function "+d.sym.name+"."+id.sym.name+" not referenced"); + } + } + if(d.ty.kind == Tmodule){ + for(id = d.ty.ids; id != nil; id = id.next){ + refs += id.refs; + if(id.iface != nil) + id.iface.refs += id.refs; + if(id.store == Dtype){ + for(m = id.ty.ids; m != nil; m = m.next){ + refs += m.refs; + if(m.iface != nil) + m.iface.refs += m.refs; + } + } + } + d.refs = refs; + } + if(superwarn && !refs && d.importid == nil) + warn(d.src.start, declconv(d)+" not referenced"); + Dglobal => + if(superwarn && !d.refs && d.sym != nil && d.sym.name[0] != '.') + warn(d.src.start, declconv(d)+" not referenced"); + Dlocal or + Darg => + if(!d.refs && d.sym != nil && d.sym.name != nil && d.sym.name[0] != '.') + warn(d.src.start, declconv(d)+" not referenced"); + Dconst => + if(superwarn && !d.refs && d.sym != nil) + warn(d.src.start, declconv(d)+" not referenced"); + Dfn => + if(d.init == nil && d.importid == nil) + error(d.src.start, declconv(d)+" not defined"); + if(superwarn && !d.refs) + warn(d.src.start, declconv(d)+" not referenced"); + Dimport => + if(superwarn && !d.refs) + warn(d.src.start, declconv(d)+" not referenced"); + } + if(d.das != byte 0) + d.refs++; + } +} + +vardecl(ids: ref Decl, t: ref Type): ref Node +{ + n := mkn(Ovardecl, mkn(Oseq, nil, nil), nil); + n.decl = ids; + n.ty = t; + return n; +} + +vardecled(n: ref Node) +{ + store := Dlocal; + if(scope == ScopeGlobal) + store = Dglobal; + if(n.ty.kind == Texception && n.ty.cons == byte 1){ + store = Dconst; + fatal("Texception in vardecled"); + } + ids := n.decl; + installids(store, ids); + t := n.ty; + for(last := ids; ids != nil; ids = ids.next){ + ids.ty = t; + last = ids; + } + n.left.decl = last; +} + +condecl(ids: ref Decl, init: ref Node): ref Node +{ + n := mkn(Ocondecl, mkn(Oseq, nil, nil), init); + n.decl = ids; + return n; +} + +condecled(n: ref Node) +{ + ids := n.decl; + installids(Dconst, ids); + for(last := ids; ids != nil; ids = ids.next){ + ids.ty = tunknown; + last = ids; + } + n.left.decl = last; +} + +exdecl(ids: ref Decl, tids: ref Decl): ref Node +{ + n: ref Node; + t: ref Type; + + t = mktype(ids.src.start, ids.src.stop, Texception, nil, tids); + t.cons = byte 1; + n = mkn(Oexdecl, mkn(Oseq, nil, nil), nil); + n.decl = ids; + n.ty = t; + return n; +} + +exdecled(n: ref Node) +{ + ids, last: ref Decl; + t: ref Type; + + ids = n.decl; + installids(Dconst, ids); + t = n.ty; + for(last = ids; ids != nil; ids = ids.next){ + ids.ty = t; + last = ids; + } + n.left.decl = last; +} + +importdecl(m: ref Node, ids: ref Decl): ref Node +{ + n := mkn(Oimport, mkn(Oseq, nil, nil), m); + n.decl = ids; + return n; +} + +importdecled(n: ref Node) +{ + ids := n.decl; + installids(Dimport, ids); + for(last := ids; ids != nil; ids = ids.next){ + ids.ty = tunknown; + last = ids; + } + n.left.decl = last; +} + +mkscope(body: ref Node): ref Node +{ + n := mkn(Oscope, nil, body); + if(body != nil) + n.src = body.src; + return n; +} + +fndecl(n: ref Node, t: ref Type, body: ref Node): ref Node +{ + n = mkbin(Ofunc, n, body); + n.ty = t; + return n; +} + +fndecled(n: ref Node) +{ + left := n.left; + if(left.op == Oname){ + d := left.decl.sym.decl; + if(d == nil || d.store == Dimport){ + d = mkids(left.src, left.decl.sym, n.ty, nil); + installids(Dfn, d); + } + left.decl = d; + d.refs++; + } + if(left.op == Odot) + pushscope(nil, Sother); + if(n.ty.polys != nil){ + pushscope(nil, Sother); + installids(Dtype, n.ty.polys); + } + pushscope(nil, Sother); + installids(Darg, n.ty.ids); + n.ty.ids = popscope(); + if(n.ty.val != nil) + mergepolydecs(n.ty); + if(n.ty.polys != nil) + n.ty.polys = popscope(); + if(left.op == Odot) + popscope(); +} + +# +# check the function declaration only +# the body will be type checked later by fncheck +# +fnchk(n: ref Node): ref Decl +{ + bad := 0; + d := n.left.decl; + if(n.left.op == Odot) + d = n.left.right.decl; + if(d == nil) + fatal("decl() fnchk nil"); + n.left.decl = d; + if(d.store == Dglobal || d.store == Dfield) + d.store = Dfn; + if(d.store != Dfn || d.init != nil){ + nerror(n, "redeclaration of function "+dotconv(d)+", previously declared as " + +storeconv(d)+" on line "+lineconv(d.src.start)); + if(d.store == Dfn && d.init != nil) + bad = 1; + } + d.init = n; + + t := n.ty; + inadt := d.dot; + if(inadt != nil && (inadt.store != Dtype || inadt.ty.kind != Tadt)) + inadt = nil; + if(n.left.op == Odot){ + pushscope(nil, Sother); + adtp := outerpolys(n.left); + if(adtp != nil) + installids(Dtype, adtp); + if(!polyequal(adtp, n.decl)) + nerror(n, "adt polymorphic type mismatch"); + n.decl = nil; + } + t = validtype(t, inadt); + if(n.left.op == Odot) + popscope(); + if(debug['d']) + print("declare function %s ty %s newty %s\n", dotconv(d), typeconv(d.ty), typeconv(t)); + t = usetype(t); + + if(!polyequal(d.ty.polys, t.polys)) + nerror(n, "function polymorphic type mismatch"); + if(!tcompat(d.ty, t, 0)) + nerror(n, "type mismatch: "+dotconv(d)+" defined as " + +typeconv(t)+" declared as "+typeconv(d.ty)+" on line "+lineconv(d.src.start)); + else if(!raisescompat(d.ty.eraises, t.eraises)) + nerror(n, "raises mismatch: " + dotconv(d)); + if(t.varargs != byte 0) + nerror(n, "cannot define functions with a '*' argument, such as "+dotconv(d)); + + t.eraises = d.ty.eraises; + + d.ty = t; + d.offset = idoffsets(t.ids, MaxTemp, IBY2WD); + d.src = n.src; + + d.locals = nil; + + n.ty = t; + + if(bad) + return nil; + return d; +} + +globalas(dst: ref Node, v: ref Node, valok: int): ref Node +{ + if(v == nil) + return nil; + if(v.op == Oas || v.op == Odas){ + v = globalas(v.left, v.right, valok); + if(v == nil) + return nil; + }else if(valok && !initable(dst, v, 0)) + return nil; + case dst.op{ + Oname => + if(dst.decl.init != nil) + nerror(dst, "duplicate assignment to "+expconv(dst)+", previously assigned on line " + +lineconv(dst.decl.init.src.start)); + if(valok) + dst.decl.init = v; + return v; + Otuple => + if(valok && v.op != Otuple) + fatal("can't deal with "+nodeconv(v)+" in tuple case of globalas"); + tv := v.left; + for(dst = dst.left; dst != nil; dst = dst.right){ + globalas(dst.left, tv.left, valok); + if(valok) + tv = tv.right; + } + return v; + } + fatal("can't deal with "+nodeconv(dst)+" in globalas"); + return nil; +} + +needsstore(d: ref Decl): int +{ + if(!d.refs) + return 0; + if(d.importid != nil) + return 0; + if(storespace[d.store]) + return 1; + return 0; +} + +# +# return the list of all referenced storage variables +# +vars(d: ref Decl): ref Decl +{ + while(d != nil && !needsstore(d)) + d = d.next; + for(v := d; v != nil; v = v.next){ + while(v.next != nil){ + n := v.next; + if(needsstore(n)) + break; + v.next = n.next; + } + } + return d; +} + +# +# declare variables from the left side of a := statement +# +recdasdecl(n: ref Node, store: int, nid: int): (int, int) +{ + r: int; + + case n.op{ + Otuple => + ok := 1; + for(n = n.left; n != nil; n = n.right){ + (r, nid) = recdasdecl(n.left, store, nid); + ok &= r; + } + return (ok, nid); + Oname => + if(n.decl == nildecl) + return (1, -1); + d := mkids(n.src, n.decl.sym, nil, nil); + installids(store, d); + n.decl = d; + old := d.old; + if(old != nil + && old.store != Dfn + && old.store != Dwundef + && old.store != Dundef) + warn(d.src.start, "redeclaration of "+declconv(d)+", previously declared as " + +storeconv(old)+" on line "+lineconv(old.src.start)); + d.refs++; + d.das = byte 1; + if(nid >= 0) + nid++; + return (1, nid); + } + return (0, nid); +} + +recmark(n: ref Node, nid: int): int +{ + case(n.op){ + Otuple => + for(n = n.left; n != nil; n = n.right) + nid = recmark(n.left, nid); + Oname => + n.decl.nid = byte nid; + nid = 0; + } + return nid; +} + +dasdecl(n: ref Node): int +{ + ok: int; + + nid := 0; + store := Dlocal; + if(scope == ScopeGlobal) + store = Dglobal; + + (ok, nid) = recdasdecl(n, store, nid); + if(!ok) + nerror(n, "illegal declaration expression "+expconv(n)); + if(ok && store == Dlocal && nid > 1) + recmark(n, nid); + return ok; +} + +# +# declare global variables in nested := expressions +# +gdasdecl(n: ref Node) +{ + if(n == nil) + return; + + if(n.op == Odas){ + gdasdecl(n.right); + dasdecl(n.left); + }else{ + gdasdecl(n.left); + gdasdecl(n.right); + } +} + +undefed(src: Src, s: ref Sym): ref Decl +{ + d := mkids(src, s, tnone, nil); + error(src.start, s.name+" is not declared"); + installids(Dwundef, d); + return d; +} + +# inloop() : int +# { +# for (i := scope; i > 0; i--) +# if (int scopekind[i] == Sloop) +# return 1; +# return 0; +# } + +nested() : int +{ + for (i := scope; i > 0; i--) + if (int scopekind[i] == Sscope || int scopekind[i] == Sloop) + return 1; + return 0; +} + +decltozero(n : ref Node) +{ + if ((scop := scopenode[scope]) != nil) { + if (n.right != nil && errors == 0) + fatal("Ovardecl/Oname/Otuple has right field\n"); + n.right = scop.left; + scop.left = n; + } +} + +pushscope(scp : ref Node, kind : int) +{ + if(scope >= MaxScope) + fatal("scope too deep"); + scope++; + scopes[scope] = nil; + tails[scope] = nil; + scopenode[scope] = scp; + scopekind[scope] = byte kind; +} + +curscope(): ref Decl +{ + return scopes[scope]; +} + +# +# revert to old declarations for each symbol in the currect scope. +# remove the effects of any imported adt types +# whenever the adt is imported from a module, +# we record in the type's decl the module to use +# when calling members. the process is reversed here. +# +popscope(): ref Decl +{ + for(id := scopes[scope]; id != nil; id = id.next){ + if(id.sym != nil){ + id.sym.decl = id.old; + id.old = nil; + } + if(id.importid != nil) + id.importid.refs += id.refs; + t := id.ty; + if(id.store == Dtype + && t.decl != nil + && t.decl.timport == id) + t.decl.timport = id.timport; + if(id.store == Dlocal) + freeloc(id); + } + return scopes[scope--]; +} + +# +# make a new scope, +# preinstalled with some previously installed identifiers +# don't add the identifiers to the scope chain, +# so they remain separate from any newly installed ids +# +# these routines assume no ids are imports +# +repushids(ids: ref Decl) +{ + if(scope >= MaxScope) + fatal("scope too deep"); + scope++; + scopes[scope] = nil; + tails[scope] = nil; + scopenode[scope] = nil; + scopekind[scope] = byte Sother; + + for(; ids != nil; ids = ids.next){ + if(ids.scope != scope + && (ids.dot == nil || !isimpmod(ids.dot.sym) + || ids.scope != ScopeGlobal || scope != ScopeGlobal + 1)) + fatal("repushids scope mismatch"); + s := ids.sym; + if(s != nil && ids.store != Dtag){ + if(s.decl != nil && s.decl.scope >= scope) + ids.old = s.decl.old; + else + ids.old = s.decl; + s.decl = ids; + } + } +} + +# +# pop a scope which was started with repushids +# return any newly installed ids +# +popids(ids: ref Decl): ref Decl +{ + for(; ids != nil; ids = ids.next){ + if(ids.sym != nil && ids.store != Dtag){ + ids.sym.decl = ids.old; + ids.old = nil; + } + } + return popscope(); +} + +installids(store: int, ids: ref Decl) +{ + last : ref Decl = nil; + for(d := ids; d != nil; d = d.next){ + d.scope = scope; + if(d.store == Dundef) + d.store = store; + s := d.sym; + if(s != nil){ + if(s.decl != nil && s.decl.scope >= scope){ + redecl(d); + d.old = s.decl.old; + }else + d.old = s.decl; + s.decl = d; + } + last = d; + } + if(ids != nil){ + d = tails[scope]; + if(d == nil) + scopes[scope] = ids; + else + d.next = ids; + tails[scope] = last; + } +} + +lookup(sym: ref Sym): ref Decl +{ + s: int; + d: ref Decl; + + for(s = scope; s >= ScopeBuiltin; s--){ + for(d = scopes[s]; d != nil; d = d.next){ + if(d.sym == sym) + return d; + } + } + return nil; +} + +mkids(src: Src, s: ref Sym, t: ref Type, next: ref Decl): ref Decl +{ + d := ref zdecl; + d.src = src; + d.store = Dundef; + d.ty = t; + d.next = next; + d.sym = s; + d.nid = byte 1; + return d; +} + +mkdecl(src: Src, store: int, t: ref Type): ref Decl +{ + d := ref zdecl; + d.src = src; + d.store = store; + d.ty = t; + d.nid = byte 1; + return d; +} + +dupdecl(old: ref Decl): ref Decl +{ + d := ref *old; + d.next = nil; + return d; +} + +dupdecls(old: ref Decl): ref Decl +{ + d, nd, first, last: ref Decl; + + first = last = nil; + for(d = old; d != nil; d = d.next){ + nd = dupdecl(d); + if(first == nil) + first = nd; + else + last.next = nd; + last = nd; + } + return first; +} + +appdecls(d: ref Decl, dd: ref Decl): ref Decl +{ + if(d == nil) + return dd; + for(t := d; t.next != nil; t = t.next) + ; + t.next = dd; + return d; +} + +revids(id: ref Decl): ref Decl +{ + next : ref Decl; + d : ref Decl = nil; + for(; id != nil; id = next){ + next = id.next; + id.next = d; + d = id; + } + return d; +} + +idoffsets(id: ref Decl, offset: int, al: int): int +{ + algn := 1; + for(; id != nil; id = id.next){ + if(storespace[id.store]){ +usedty(id.ty); + if(id.store == Dlocal && id.link != nil){ + # id.nid always 1 + id.offset = id.link.offset; + continue; + } + a := id.ty.align; + if(id.nid > byte 1){ + for(d := id.next; d != nil && d.nid == byte 0; d = d.next) + if(d.ty.align > a) + a = d.ty.align; + algn = a; + } + offset = align(offset, a); + id.offset = offset; + offset += id.ty.size; + if(id.nid == byte 0 && (id.next == nil || id.next.nid != byte 0)) + offset = align(offset, algn); + } + } + return align(offset, al); +} + +idindices(id: ref Decl): int +{ + i := 0; + for(; id != nil; id = id.next){ + if(storespace[id.store]){ + usedty(id.ty); + id.offset = i++; + } + } + return i; +} + +declconv(d: ref Decl): string +{ + if(d.sym == nil) + return storename[d.store] + " " + "<???>"; + return storename[d.store] + " " + d.sym.name; +} + +storeconv(d: ref Decl): string +{ + return storeart[d.store] + storename[d.store]; +} + +dotconv(d: ref Decl): string +{ + s: string; + + if(d.dot != nil && !isimpmod(d.dot.sym)){ + s = dotconv(d.dot); + if(d.dot.ty != nil && d.dot.ty.kind == Tmodule) + s += "."; + else + s += "."; + } + s += d.sym.name; + return s; +} + +# +# merge together two sorted lists, yielding a sorted list +# +namemerge(e, f: ref Decl): ref Decl +{ + d := rock := ref Decl; + while(e != nil && f != nil){ + if(e.sym.name <= f.sym.name){ + d.next = e; + e = e.next; + }else{ + d.next = f; + f = f.next; + } + d = d.next; + } + if(e != nil) + d.next = e; + else + d.next = f; + return rock.next; +} + +# +# recursively split lists and remerge them after they are sorted +# +recnamesort(d: ref Decl, n: int): ref Decl +{ + if(n <= 1) + return d; + m := n / 2 - 1; + dd := d; + for(i := 0; i < m; i++) + dd = dd.next; + r := dd.next; + dd.next = nil; + return namemerge(recnamesort(d, n / 2), + recnamesort(r, (n + 1) / 2)); +} + +# +# sort the ids by name +# +namesort(d: ref Decl): ref Decl +{ + n := 0; + for(dd := d; dd != nil; dd = dd.next) + n++; + return recnamesort(d, n); +} + +printdecls(d: ref Decl) +{ + for(; d != nil; d = d.next) + print("%d: %s %s ref %d\n", d.offset, declconv(d), typeconv(d.ty), d.refs); +} + +mergepolydecs(t: ref Type) +{ + n, nn: ref Node; + id, ids, ids1: ref Decl; + + for(n = t.val; n != nil; n = n.right){ + nn = n.left; + for(ids = nn.decl; ids != nil; ids = ids.next){ + id = ids.sym.decl; + if(id == nil){ + undefed(ids.src, ids.sym); + break; + } + if(id.store != Dtype){ + error(ids.src.start, declconv(id) + " is not a type"); + break; + } + if(id.ty.kind != Tpoly){ + error(ids.src.start, declconv(id) + " is not a polymorphic type"); + break; + } + if(id.ty.ids != nil) + error(ids.src.start, declconv(id) + " redefined"); + pushscope(nil, Sother); + fielddecled(nn.left); + id.ty.ids = popscope(); + for(ids1 = id.ty.ids; ids1 != nil; ids1 = ids1.next){ + ids1.dot = id; + bindtypes(ids1.ty); + if(ids1.ty.kind != Tfn){ + error(ids1.src.start, "only function types expected"); + id.ty.ids = nil; + } + } + } + } + t.val = nil; +} + +adjfnptrs(d: ref Decl, polys1: ref Decl, polys2: ref Decl) +{ + n: int; + id, idt, idf, arg: ref Decl; + + n = 0; + for(id = d.ty.ids; id != nil; id = id.next) + n++; + for(idt = polys1; idt != nil; idt = idt.next) + for(idf = idt.ty.ids; idf != nil; idf = idf.next) + n -= 2; + for(idt = polys2; idt != nil; idt = idt.next) + for(idf = idt.ty.ids; idf != nil; idf = idf.next) + n -= 2; + for(arg = d.ty.ids; --n >= 0; arg = arg.next) + ; + for(idt = polys1; idt != nil; idt = idt.next){ + for(idf = idt.ty.ids; idf != nil; idf = idf.next){ + idf.link = arg; + arg = arg.next.next; + } + } + for(idt = polys2; idt != nil; idt = idt.next){ + for(idf = idt.ty.ids; idf != nil; idf = idf.next){ + idf.link = arg; + arg = arg.next.next; + } + } +} + +addptrs(polys: ref Decl, fps: ref Decl, last: ref Decl, link: int, src: Src): (ref Decl, ref Decl) +{ + for(idt := polys; idt != nil; idt = idt.next){ + for(idf := idt.ty.ids; idf != nil; idf = idf.next){ + fp := mkdecl(src, Darg, tany); + fp.sym = idf.sym; + if(link) + idf.link = fp; + if(fps == nil) + fps = fp; + else + last.next = fp; + last = fp; + fp = mkdecl(src, Darg, tint); + fp.sym = idf.sym; + last.next = fp; + last = fp; + } + } + return (fps, last); +} + +addfnptrs(d: ref Decl, link: int) +{ + fps, last, polys: ref Decl; + + polys = encpolys(d); + if(int(d.ty.flags&FULLARGS)){ + if(link) + adjfnptrs(d, d.ty.polys, polys); + return; + } + d.ty.flags |= FULLARGS; + fps = last = nil; + (fps, last) = addptrs(d.ty.polys, fps, last, link, d.src); + (fps, last) = addptrs(polys, fps, last, link, d.src); + for(last = d.ty.ids; last != nil && last.next != nil; last = last.next) + ; + if(last != nil) + last.next = fps; + else + d.ty.ids = fps; + d.offset = idoffsets(d.ty.ids, MaxTemp, IBY2WD); +} + +rmfnptrs(d: ref Decl) +{ + n: int; + id, idt, idf: ref Decl; + + if(int(d.ty.flags&FULLARGS)) + d.ty.flags &= ~FULLARGS; + else + return; + n = 0; + for(id = d.ty.ids; id != nil; id = id.next) + n++; + for(idt = d.ty.polys; idt != nil; idt = idt.next) + for(idf = idt.ty.ids; idf != nil; idf = idf.next) + n -= 2; + for(idt = encpolys(d); idt != nil; idt = idt.next) + for(idf = idt.ty.ids; idf != nil; idf = idf.next) + n -= 2; + if(n == 0){ + d.ty.ids = nil; + return; + } + for(id = d.ty.ids; --n > 0; id = id.next) + ; + id.next = nil; + d.offset = idoffsets(d.ty.ids, MaxTemp, IBY2WD); +} + +local(d: ref Decl): int +{ + for(d = d.dot; d != nil; d = d.dot) + if(d.store == Dtype && d.ty.kind == Tmodule) + return 0; + return 1; +} + +lmodule(d: ref Decl): ref Decl +{ + for(d = d.dot; d != nil; d = d.dot) + if(d.store == Dtype && d.ty.kind == Tmodule) + return d; + return nil; +} + +outerpolys(n: ref Node): ref Decl +{ + d: ref Decl; + + if(n.op == Odot){ + d = n.right.decl; + if(d == nil) + fatal("decl() outeradt nil"); + d = d.dot; + if(d != nil && d.store == Dtype && d.ty.kind == Tadt) + return d.ty.polys; + } + return nil; +} + +encpolys(d: ref Decl): ref Decl +{ + if((d = d.dot) == nil) + return nil; + return d.ty.polys; +} + +fnlookup(s: ref Sym, t: ref Type): (ref Decl, ref Node) +{ + id: ref Decl; + mod: ref Node; + + id = nil; + mod = nil; + if(t.kind == Tpoly || t.kind == Tmodule) + id = namedot(t.ids, s); + else if(t.kind == Tref){ + t = t.tof; + if(t.kind == Tadt){ + id = namedot(t.ids, s); + if(t.decl != nil && t.decl.timport != nil) + mod = t.decl.timport.eimport; + } + else if(t.kind == Tadtpick){ + id = namedot(t.ids, s); + if(t.decl != nil && t.decl.timport != nil) + mod = t.decl.timport.eimport; + t = t.decl.dot.ty; + if(id == nil) + id = namedot(t.ids, s); + if(t.decl != nil && t.decl.timport != nil) + mod = t.decl.timport.eimport; + } + } + if(id == nil){ + id = lookup(s); + if(id != nil) + mod = id.eimport; + } + return (id, mod); +} + +isimpmod(s: ref Sym): int +{ + d: ref Decl; + + for(d = impmods; d != nil; d = d.next) + if(d.sym == s) + return 1; + return 0; +} + +dequal(d1: ref Decl, d2: ref Decl, full: int): int +{ + return d1.sym == d2.sym && + d1.store == d2.store && + d1.implicit == d2.implicit && + d1.cyc == d2.cyc && + (!full || tequal(d1.ty, d2.ty)) && + (!full || d1.store == Dfn || sametree(d1.init, d2.init)); +} + +tzero(t: ref Type): int +{ + return t.kind == Texception || tmustzero(t); +} + +isptr(t: ref Type): int +{ + return t.kind == Texception || tattr[t.kind].isptr; +} + +# can d share the same stack location as another local ? +shareloc(d: ref Decl) +{ + z: int; + t, tt: ref Type; + dd, res: ref Decl; + + if(d.store != Dlocal || d.nid != byte 1) + return; + t = d.ty; + res = nil; + for(dd = fndecls; dd != nil; dd = dd.next){ + if(d == dd) + fatal("d==dd in shareloc"); + if(dd.store != Dlocal || dd.nid != byte 1 || dd.link != nil || dd.tref != 0) + continue; + tt = dd.ty; + if(t.size != tt.size || t.align != tt.align) + continue; + z = tzero(t)+tzero(tt); + if(z > 0) + continue; # for now + if(t == tt || tequal(t, tt)) + res = dd; + else{ + if(z == 1) + continue; + if(z == 0 || isptr(t) || isptr(tt) || mktdesc(t) == mktdesc(tt)) + res = dd; + } + if(res != nil){ + d.link = res; + res.tref = 1; + return; + } + } + return; +} + +freeloc(d: ref Decl) +{ + if(d.link != nil) + d.link.tref = 0; +} diff --git a/appl/cmd/limbo/dis.b b/appl/cmd/limbo/dis.b new file mode 100644 index 00000000..7d79553e --- /dev/null +++ b/appl/cmd/limbo/dis.b @@ -0,0 +1,560 @@ + +NAMELEN: con 28; + +cache: array of byte; +ncached: int; +ndatum: int; +startoff: int; +lastoff: int; +lastkind: int; + +discon(val: int) +{ + if(val >= -64 && val <= 63){ + bout.putb(byte (val & ~16r80)); + return; + } + if(val >= -8192 && val <= 8191){ + bout.putb(byte ((val>>8) & ~16rC0 | 16r80)); + bout.putb(byte val); + return; + } + if(val < 0 && ((val >> 29) & 7) != 7 + || val > 0 && (val >> 29) != 0) + fatal("overflow in constant 16r"+hex(val, 0)); + bout.putb(byte(val>>24 | 16rC0)); + bout.putb(byte(val>>16)); + bout.putb(byte(val>>8)); + bout.putb(byte val); +} + +disword(w: int) +{ + bout.putb(byte(w >> 24)); + bout.putb(byte(w >> 16)); + bout.putb(byte(w >> 8)); + bout.putb(byte w); +} + +disdata(kind, n: int) +{ + if(n < DMAX && n != 0) + bout.putb(byte((kind << DBYTE) | n)); + else{ + bout.putb(byte kind << DBYTE); + discon(n); + } +} + +dismod(m: ref Decl) +{ + fileoff := bout.seek(big 0, 1); + name := array of byte m.sym.name; + n := len name; + if(n > NAMELEN-1) + n = NAMELEN-1; + bout.write(name, n); + bout.putb(byte 0); + for(m = m.ty.tof.ids; m != nil; m = m.next){ + case m.store{ + Dglobal => + discon(-1); + discon(-1); + disword(sign(m)); + bout.puts(".mp"); + bout.putb(byte 0); + Dfn => + discon(m.pc.pc); + discon(m.desc.id); + disword(sign(m)); + if(m.dot.ty.kind == Tadt){ + bout.puts(m.dot.sym.name); + bout.putb(byte '.'); + } + bout.puts(m.sym.name); + bout.putb(byte 0); + * => + fatal("unknown kind in dismod: "+declconv(m)); + } + } + if(debug['s']) + print("%bd linkage bytes start %bd\n", bout.seek(big 0, 1) - fileoff, fileoff); +} + +dispath() +{ + sp := array of byte srcpath(); + bout.write(sp, len sp); + bout.putb(byte 0); +} + +disentry(e: ref Decl) +{ + if(e == nil){ + discon(-1); + discon(-1); + return; + } + discon(e.pc.pc); + discon(e.desc.id); +} + +disdesc(d: ref Desc) +{ + fileoff := bout.seek(big 0, 1); + for(; d != nil; d = d.next){ + discon(d.id); + discon(d.size); + discon(d.nmap); + bout.write(d.map, d.nmap); + } + if(debug['s']) + print("%bd type descriptor bytes start %bd\n", bout.seek(big 0, 1) - fileoff, fileoff); +} + +disvar(nil: int, ids: ref Decl) +{ + fileoff := bout.seek(big 0, 1); + lastkind = -1; + ncached = 0; + ndatum = 0; + + for(d := ids; d != nil; d = d.next) + if(d.store == Dglobal && d.init != nil) + disdatum(d.offset, d.init); + + disflush(-1, -1, 0); + + bout.putb(byte 0); + + if(debug['s']) + print("%bd data bytes start %bd\n", bout.seek(big 0, 1) - fileoff, fileoff); +} + +disldt(size: int, ds: ref Decl) +{ + if(0){ + discon(size); + disvar(size, ds); + return; + } + + m := 0; + for(d := ds; d != nil; d = d.next) + if(d.store == Dglobal && d.init != nil) + m++; + discon(m); + n: ref Node; + for(d = ds; d != nil; d = d.next){ + if(d.store == Dglobal && d.init != nil){ + n = d.init; + if(n.ty.kind != Tiface) + nerror(n, "disldt: not Tiface"); + discon(int n.c.val); + for(id := n.decl.ty.ids; id != nil; id = id.next){ + disword(sign(id)); + if(id.dot.ty.kind == Tadt){ + s := array of byte id.dot.sym.name; + bout.write(s, len s); + bout.putb(byte '.'); + } + s := array of byte id.sym.name; + bout.write(s, len s); + bout.putb(byte 0); + } + } + } + discon(0); +} + +disdatum(offset: int, n: ref Node) +{ + c: ref Case; + lab: Label; + id: ref Decl; + wild: ref Node; + i, e: int; + + case n.ty.kind{ + Tbyte => + disbyte(offset, byte n.c.val); + Tint or + Tfix => + disint(offset, int n.c.val); + Tbig => + disbig(offset, n.c.val); + Tstring => + disstring(offset, n.decl.sym); + Treal => + disreal(offset, n.c.rval); + Tadt or + Tadtpick or + Ttuple => + id = n.ty.ids; + for(n = n.left; n != nil; n = n.right){ + disdatum(offset + id.offset, n.left); + id = id.next; + } + Tany => + break; + Tcase => + c = n.ty.cse; + disint(offset, c.nlab); + offset += IBY2WD; + for(i = 0; i < c.nlab; i++){ + lab = c.labs[i]; + disint(offset, int lab.start.c.val); + offset += IBY2WD; + disint(offset, int lab.stop.c.val+1); + offset += IBY2WD; + disint(offset, lab.inst.pc); + offset += IBY2WD; + } + if(c.iwild != nil) + disint(offset, c.iwild.pc); + else + disint(offset, -1); + Tcasel => + c = n.ty.cse; + disint(offset, c.nlab); + offset += 2*IBY2WD; + for(i = 0; i < c.nlab; i++){ + lab = c.labs[i]; + disbig(offset, lab.start.c.val); + offset += IBY2LG; + disbig(offset, lab.stop.c.val+big 1); + offset += IBY2LG; + disint(offset, lab.inst.pc); + offset += 2*IBY2WD; + } + if(c.iwild != nil) + disint(offset, c.iwild.pc); + else + disint(offset, -1); + Tcasec => + c = n.ty.cse; + disint(offset, c.nlab); + offset += IBY2WD; + for(i = 0; i < c.nlab; i++){ + lab = c.labs[i]; + disstring(offset, lab.start.decl.sym); + offset += IBY2WD; + if(lab.stop != lab.start) + disstring(offset, lab.stop.decl.sym); + offset += IBY2WD; + disint(offset, lab.inst.pc); + offset += IBY2WD; + } + if(c.iwild != nil) + disint(offset, c.iwild.pc); + else + disint(offset, -1); + Tgoto => + c = n.ty.cse; + disint(offset, n.ty.size/IBY2WD-1); + offset += IBY2WD; + for(i = 0; i < c.nlab; i++){ + disint(offset, c.labs[i].inst.pc); + offset += IBY2WD; + } + if(c.iwild != nil) + disint(offset, c.iwild.pc); + Tarray => + disflush(-1, -1, 0); + disdata(DEFA, 1); # 1 is ignored + discon(offset); + disword(n.ty.tof.decl.desc.id); + disword(int n.left.c.val); + + if(n.right == nil) + break; + + disdata(DIND, 1); # 1 is ignored + discon(offset); + disword(0); + + c = n.right.ty.cse; + wild = nil; + if(c.wild != nil) + wild = c.wild.right; + last := 0; + esz := n.ty.tof.size; + for(i = 0; i < c.nlab; i++){ + e = int c.labs[i].start.c.val; + if(wild != nil){ + for(; last < e; last++) + disdatum(esz * last, wild); + } + last = e; + e = int c.labs[i].stop.c.val; + elem := c.labs[i].node.right; + for(; last <= e; last++) + disdatum(esz * last, elem); + } + if(wild != nil) + for(e = int n.left.c.val; last < e; last++) + disdatum(esz * last, wild); + + disflush(-1, -1, 0); + disdata(DAPOP, 1); # 1 is ignored + discon(0); + Tiface => + disint(offset, int n.c.val); + offset += IBY2WD; + for(id = n.decl.ty.ids; id != nil; id = id.next){ + offset = align(offset, IBY2WD); + disint(offset, sign(id)); + offset += IBY2WD; + + name: array of byte; + if(id.dot.ty.kind == Tadt){ + name = array of byte id.dot.sym.name; + disbytes(offset, name); + offset += len name; + disbyte(offset, byte '.'); + offset++; + } + name = array of byte id.sym.name; + disbytes(offset, name); + offset += len name; + disbyte(offset, byte 0); + offset++; + } + * => + fatal("can't gen global "+nodeconv(n)); + } +} + +disexc(es: ref Except) +{ + e: ref Except; + + n := 0; + for(e = es; e != nil; e = e.next) + if(int e.p1.reach || int e.p2.reach) + n++; + discon(n); + for(e = es; e != nil; e = e.next){ + if(!int e.p1.reach && !int e.p2.reach) + continue; + c := e.c; + discon(e.d.offset); + discon(getpc(e.p1)); + discon(getpc(e.p2)); + if(e.desc != nil) + discon(e.desc.id); + else + discon(-1); + discon(c.nlab|(e.ne<<16)); + for(i := 0; i < c.nlab; i++){ + lab := c.labs[i]; + d := lab.start.decl; + if(lab.start.ty.kind == Texception) + d = d.init.decl; + bout.puts(d.sym.name); + bout.putb(byte 0); + discon(lab.inst.pc); + } + if(c.iwild == nil) + discon(-1); + else + discon(c.iwild.pc); + } + discon(0); +} + +disbyte(off: int, v: byte) +{ + disflush(DEFB, off, 1); + cache[ncached++] = v; + ndatum++; +} + +disbytes(off: int, v: array of byte) +{ + n := len v; + disflush(DEFB, off, n); + cache[ncached:] = v; + ncached += n; + ndatum += n; +} + +disint(off, v: int) +{ + disflush(DEFW, off, IBY2WD); + cache[ncached++] = byte(v >> 24); + cache[ncached++] = byte(v >> 16); + cache[ncached++] = byte(v >> 8); + cache[ncached++] = byte(v); + ndatum++; +} + +disbig(off: int, v: big) +{ + disflush(DEFL, off, IBY2LG); + iv := int(v >> 32); + cache[ncached++] = byte(iv >> 24); + cache[ncached++] = byte(iv >> 16); + cache[ncached++] = byte(iv >> 8); + cache[ncached++] = byte(iv); + iv = int v; + cache[ncached++] = byte(iv >> 24); + cache[ncached++] = byte(iv >> 16); + cache[ncached++] = byte(iv >> 8); + cache[ncached++] = byte(iv); + ndatum++; +} + +disreal(off: int, v: real) +{ + disflush(DEFF, off, IBY2LG); + export_real(cache[ncached:ncached+8], array[] of {v}); + ncached += IBY2LG; + ndatum++; +} + +disstring(offset: int, sym: ref Sym) +{ + disflush(-1, -1, 0); + d := array of byte sym.name; + disdata(DEFS, len d); + discon(offset); + bout.write(d, len d); +} + +disflush(kind, off, size: int) +{ + if(kind != lastkind || off != lastoff){ + if(lastkind != -1 && ncached){ + disdata(lastkind, ndatum); + discon(startoff); + bout.write(cache, ncached); + } + startoff = off; + lastkind = kind; + ncached = 0; + ndatum = 0; + } + lastoff = off + size; + while(kind >= 0 && ncached + size >= len cache){ + c := array[ncached + 1024] of byte; + c[0:] = cache; + cache = c; + } +} + +dismode := array[int Aend] of +{ + int Aimm => byte AIMM, + int Amp => byte AMP, + int Ampind => byte(AMP|AIND), + int Afp => byte AFP, + int Afpind => byte(AFP|AIND), + int Apc => byte AIMM, + int Adesc => byte AIMM, + int Aoff => byte AIMM, + int Anoff => byte AIMM, + int Aerr => byte AXXX, + int Anone => byte AXXX, + int Aldt => byte AIMM, +}; + +disregmode := array[int Aend] of +{ + int Aimm => byte AXIMM, + int Amp => byte AXINM, + int Ampind => byte AXNON, + int Afp => byte AXINF, + int Afpind => byte AXNON, + int Apc => byte AXIMM, + int Adesc => byte AXIMM, + int Aoff => byte AXIMM, + int Anoff => byte AXIMM, + int Aerr => byte AXNON, + int Anone => byte AXNON, + int Aldt => byte AXIMM, +}; + +MAXCON: con 4; +MAXADDR: con 2*MAXCON; +MAXINST: con 3*MAXADDR+2; +NIBUF: con 1024; + +ibuf: array of byte; +nibuf: int; + +disinst(in: ref Inst) +{ + fileoff := bout.seek(big 0, 1); + ibuf = array[NIBUF] of byte; + nibuf = 0; + for(; in != nil; in = in.next){ + if(in.op == INOOP) + continue; + if(nibuf >= NIBUF-MAXINST){ + bout.write(ibuf, nibuf); + nibuf = 0; + } + ibuf[nibuf++] = byte in.op; + o := dismode[int in.sm] << SRC; + o |= dismode[int in.dm] << DST; + o |= disregmode[int in.mm]; + ibuf[nibuf++] = o; + if(in.mm != Anone) + disaddr(in.mm, in.m); + if(in.sm != Anone) + disaddr(in.sm, in.s); + if(in.dm != Anone) + disaddr(in.dm, in.d); + } + if(nibuf > 0) + bout.write(ibuf, nibuf); + ibuf = nil; + + if(debug['s']) + print("%bd instruction bytes start %bd\n", bout.seek(big 0, 1) - fileoff, fileoff); +} + +disaddr(m: byte, a: Addr) +{ + val := 0; + case int m{ + int Aimm or + int Apc or + int Adesc => + val = a.offset; + int Aoff => + val = a.decl.iface.offset; + int Anoff => + val = -(a.decl.iface.offset+1); + int Afp or + int Amp or + int Aldt => + val = a.reg; + int Afpind or + int Ampind => + disbcon(a.reg); + val = a.offset; + } + disbcon(val); +} + +disbcon(val: int) +{ + if(val >= -64 && val <= 63){ + ibuf[nibuf++] = byte(val & ~16r80); + return; + } + if(val >= -8192 && val <= 8191){ + ibuf[nibuf++] = byte(val>>8 & ~16rC0 | 16r80); + ibuf[nibuf++] = byte val; + return; + } + if(val < 0 && ((val >> 29) & 7) != 7 + || val > 0 && (val >> 29) != 0) + fatal("overflow in constant 16r"+hex(val, 0)); + ibuf[nibuf++] = byte(val>>24 | 16rC0); + ibuf[nibuf++] = byte(val>>16); + ibuf[nibuf++] = byte(val>>8); + ibuf[nibuf++] = byte val; +} diff --git a/appl/cmd/limbo/disoptab.m b/appl/cmd/limbo/disoptab.m new file mode 100644 index 00000000..a2e51a8b --- /dev/null +++ b/appl/cmd/limbo/disoptab.m @@ -0,0 +1,355 @@ +movetab:= array [Mend]of +{ + Mas => array[Tend] of + { + Tadt => IMOVM, + Tadtpick => IMOVM, + Tarray => IMOVP, + Tbig => IMOVL, + Tbyte => IMOVB, + Tchan => IMOVP, + Treal => IMOVF, + Tint => IMOVW, + Tlist => IMOVP, + Tmodule => IMOVP, + Tref => IMOVP, + Tstring => IMOVP, + Ttuple => IMOVM, + Texception => IMOVM, + Tfix => IMOVW, + Tpoly => IMOVP, + + Tany => IMOVP, + + * => 0 + }, + Mcons => array[Tend] of + { + Tadt => ICONSM, + Tadtpick => 0, + Tarray => ICONSP, + Tbig => ICONSL, + Tbyte => ICONSB, + Tchan => ICONSP, + Treal => ICONSF, + Tint => ICONSW, + Tlist => ICONSP, + Tmodule => ICONSP, + Tref => ICONSP, + Tstring => ICONSP, + Ttuple => ICONSM, + Texception => ICONSM, + Tfix => ICONSW, + Tpoly => ICONSP, + + Tany => ICONSP, + + * => 0 + }, + Mhd => array[Tend] of + { + Tadt => IHEADM, + Tadtpick => 0, + Tarray => IHEADP, + Tbig => IHEADL, + Tbyte => IHEADB, + Tchan => IHEADP, + Treal => IHEADF, + Tint => IHEADW, + Tlist => IHEADP, + Tmodule => IHEADP, + Tref => IHEADP, + Tstring => IHEADP, + Ttuple => IHEADM, + Texception => IHEADM, + Tfix => IHEADW, + Tpoly => IHEADP, + + Tany => IHEADP, + + * => 0 + }, + Mtl => array[Tend] of + { + Tlist => ITAIL, + + * => 0 + }, +}; + +chantab := array[Tend] of +{ + Tadt => INEWCM, + Tadtpick => 0, + Tarray => INEWCP, + Tbig => INEWCL, + Tbyte => INEWCB, + Tchan => INEWCP, + Treal => INEWCF, + Tint => INEWCW, + Tlist => INEWCP, + Tmodule => INEWCP, + Tref => INEWCP, + Tstring => INEWCP, + Ttuple => INEWCM, + Texception => INEWCM, + Tfix => INEWCW, + Tpoly => INEWCP, + + Tany => INEWCP, + + * => 0 +}; + +opind := array[Tend] of +{ + Tbyte => 1, + Tint => 2, + Tbig => 3, + Treal => 4, + Tstring => 5, + Tfix => 6, + + * => 0 +}; + +disoptab := array[Oend+1] of +{ + # opcode default byte word big real string fixed + Oadd => array[7] of {0, IADDB, IADDW, IADDL, IADDF, IADDC, IADDW,}, + Oaddas => array[7] of {0, IADDB, IADDW, IADDL, IADDF, IADDC, IADDW,}, + Oand => array[7] of {0, IANDB, IANDW, IANDL, 0, 0, 0,}, + Oandas => array[7] of {0, IANDB, IANDW, IANDL, 0, 0, 0,}, + Odec => array[7] of {0, ISUBB, ISUBW, ISUBL, ISUBF, 0, ISUBW,}, + Odiv => array[7] of {0, IDIVB, IDIVW, IDIVL, IDIVF, 0, IDIVX,}, + Odivas => array[7] of {0, IDIVB, IDIVW, IDIVL, IDIVF, 0, IDIVX,}, + Oeq => array[7] of {IBEQW, IBEQB, IBEQW, IBEQL, IBEQF, IBEQC, IBEQW,}, + Oexp => array[7] of {0, 0, IEXPW, IEXPL, IEXPF, 0, 0,}, + Oexpas => array[7] of {0, 0, IEXPW, IEXPL, IEXPF, 0, 0,}, + Ogeq => array[7] of {0, IBGEB, IBGEW, IBGEL, IBGEF, IBGEC, IBGEW,}, + Ogt => array[7] of {0, IBGTB, IBGTW, IBGTL, IBGTF, IBGTC, IBGTW,}, + Oinc => array[7] of {0, IADDB, IADDW, IADDL, IADDF, 0, IADDW,}, + Oinds => array[7] of {0, 0, IINDC, 0, 0, 0, 0,}, + Oindx => array[7] of {0, 0, IINDX, 0, 0, 0, 0,}, + Olen => array[7] of {ILENA, 0, 0, 0, 0, ILENC, 0,}, + Oleq => array[7] of {0, IBLEB, IBLEW, IBLEL, IBLEF, IBLEC, IBLEW,}, + Olsh => array[7] of {0, ISHLB, ISHLW, ISHLL, 0, 0, 0,}, + Olshas => array[7] of {0, ISHLB, ISHLW, ISHLL, 0, 0, 0,}, + Olt => array[7] of {0, IBLTB, IBLTW, IBLTL, IBLTF, IBLTC, IBLTW,}, + Omod => array[7] of {0, IMODB, IMODW, IMODL, 0, 0, 0,}, + Omodas => array[7] of {0, IMODB, IMODW, IMODL, 0, 0, 0,}, + Omul => array[7] of {0, IMULB, IMULW, IMULL, IMULF, 0, IMULX,}, + Omulas => array[7] of {0, IMULB, IMULW, IMULL, IMULF, 0, IMULX,}, + Oneg => array[7] of {0, 0, 0, 0, INEGF, 0, 0, }, + Oneq => array[7] of {IBNEW, IBNEB, IBNEW, IBNEL, IBNEF, IBNEC, IBNEW,}, + Oor => array[7] of {0, IORB, IORW, IORL, 0, 0, 0,}, + Ooras => array[7] of {0, IORB, IORW, IORL, 0, 0, 0,}, + Orsh => array[7] of {0, ISHRB, ISHRW, ISHRL, 0, 0, 0,}, + Orshas => array[7] of {0, ISHRB, ISHRW, ISHRL, 0, 0, 0,}, + Oslice => array[7] of {ISLICEA,0, 0, 0, 0, ISLICEC, 0,}, + Osub => array[7] of {0, ISUBB, ISUBW, ISUBL, ISUBF, 0, ISUBW,}, + Osubas => array[7] of {0, ISUBB, ISUBW, ISUBL, ISUBF, 0, ISUBW,}, + Oxor => array[7] of {0, IXORB, IXORW, IXORL, 0, 0, 0,}, + Oxoras => array[7] of {0, IXORB, IXORW, IXORL, 0, 0, 0,}, +}; + +isbyteinst := array [256] of +{ + IMULB => 1, + ISUBB => 1, + IADDB => 1, + IDIVB => 1, + IORB => 1, + IXORB => 1, + ISHLB => 1, + ISHRB => 1, + IMODB => 1, + IANDB => 1, + IBEQB => 1, + IBNEB => 1, + IBLTB => 1, + IBLEB => 1, + IBGTB => 1, + IBGEB => 1, + + * => 0, +}; + +instname := array[256] of +{ + "nop", + "alt", + "nbalt", + "goto", + "call", + "frame", + "spawn", + "runt", + "load", + "mcall", + "mspawn", + "mframe", + "ret", + "jmp", + "case", + "exit", + "new", + "newa", + "newcb", + "newcw", + "newcf", + "newcp", + "newcm", + "newcmp", + "send", + "recv", + "consb", + "consw", + "consp", + "consf", + "consm", + "consmp", + "headb", + "headw", + "headp", + "headf", + "headm", + "headmp", + "tail", + "lea", + "indx", + "movp", + "movm", + "movmp", + "movb", + "movw", + "movf", + "cvtbw", + "cvtwb", + "cvtfw", + "cvtwf", + "cvtca", + "cvtac", + "cvtwc", + "cvtcw", + "cvtfc", + "cvtcf", + "addb", + "addw", + "addf", + "subb", + "subw", + "subf", + "mulb", + "mulw", + "mulf", + "divb", + "divw", + "divf", + "modw", + "modb", + "andb", + "andw", + "orb", + "orw", + "xorb", + "xorw", + "shlb", + "shlw", + "shrb", + "shrw", + "insc", + "indc", + "addc", + "lenc", + "lena", + "lenl", + "beqb", + "bneb", + "bltb", + "bleb", + "bgtb", + "bgeb", + "beqw", + "bnew", + "bltw", + "blew", + "bgtw", + "bgew", + "beqf", + "bnef", + "bltf", + "blef", + "bgtf", + "bgef", + "beqc", + "bnec", + "bltc", + "blec", + "bgtc", + "bgec", + "slicea", + "slicela", + "slicec", + "indw", + "indf", + "indb", + "negf", + "movl", + "addl", + "subl", + "divl", + "modl", + "mull", + "andl", + "orl", + "xorl", + "shll", + "shrl", + "bnel", + "bltl", + "blel", + "bgtl", + "bgel", + "beql", + "cvtlf", + "cvtfl", + "cvtlw", + "cvtwl", + "cvtlc", + "cvtcl", + "headl", + "consl", + "newcl", + "casec", + "indl", + "movpc", + "tcmp", + "mnewz", + "cvtrf", + "cvtfr", + "cvtws", + "cvtsw", + "lsrw", + "lsrl", + "eclr", + "newz", + "newaz", + "raise", + "casel", + "mulx", + "divx", + "cvtxx", + "mulx0", + "divx0", + "cvtxx0", + "mulx1", + "divx1", + "cvtxx1", + "cvtfx", + "cvtxf", + "expw", + "expl", + "expf", + "self", +}; diff --git a/appl/cmd/limbo/ecom.b b/appl/cmd/limbo/ecom.b new file mode 100644 index 00000000..978882ab --- /dev/null +++ b/appl/cmd/limbo/ecom.b @@ -0,0 +1,2345 @@ +maxstack: int; # max size of a stack frame called + +precasttab := array[Tend] of array of ref Type; + +optabinit() +{ + ct := array[Tend] of ref Type; + for(i := 0; i < Tend; i++) + precasttab[i] = ct; + precasttab[Tstring] = array[Tend] of { Tbyte => tint, Tfix => treal, }; + precasttab[Tbig] = array[Tend] of { Tbyte => tint, Tfix => treal, }; + precasttab[Treal] = array[Tend] of { Tbyte => tint, }; + precasttab[Tfix] = array[Tend] of { Tbyte => tint, Tstring => treal, Tbig => treal, }; + precasttab[Tbyte] = array[Tend] of { Tstring => tint, Tbig => tint, Treal => tint, Tfix => tint, }; + + casttab = array[Tend] of { * => array[Tend] of {* => 0}}; + + casttab[Tint][Tint] = IMOVW; + casttab[Tbig][Tbig] = IMOVL; + casttab[Treal][Treal] = IMOVF; + casttab[Tbyte][Tbyte] = IMOVB; + casttab[Tstring][Tstring] = IMOVP; + casttab[Tfix][Tfix] = ICVTXX; # never same type + + casttab[Tint][Tbyte] = ICVTWB; + casttab[Tint][Treal] = ICVTWF; + casttab[Tint][Tstring] = ICVTWC; + casttab[Tint][Tfix] = ICVTXX; + casttab[Tbyte][Tint] = ICVTBW; + casttab[Treal][Tint] = ICVTFW; + casttab[Tstring][Tint] = ICVTCW; + casttab[Tfix][Tint] = ICVTXX; + + casttab[Tint][Tbig] = ICVTWL; + casttab[Treal][Tbig] = ICVTFL; + casttab[Tstring][Tbig] = ICVTCL; + casttab[Tbig][Tint] = ICVTLW; + casttab[Tbig][Treal] = ICVTLF; + casttab[Tbig][Tstring] = ICVTLC; + + casttab[Treal][Tstring] = ICVTFC; + casttab[Tstring][Treal] = ICVTCF; + + casttab[Treal][Tfix] = ICVTFX; + casttab[Tfix][Treal] = ICVTXF; + + casttab[Tstring][Tarray] = ICVTCA; + casttab[Tarray][Tstring] = ICVTAC; + + # + # placeholders; fixed in precasttab + # + casttab[Tbyte][Tstring] = 16rff; + casttab[Tstring][Tbyte] = 16rff; + casttab[Tbyte][Treal] = 16rff; + casttab[Treal][Tbyte] = 16rff; + casttab[Tbyte][Tbig] = 16rff; + casttab[Tbig][Tbyte] = 16rff; + casttab[Tfix][Tbyte] = 16rff; + casttab[Tbyte][Tfix] = 16rff; + casttab[Tfix][Tbig] = 16rff; + casttab[Tbig][Tfix] = 16rff; + casttab[Tfix][Tstring] = 16rff; + casttab[Tstring][Tfix] = 16rff; +} + +# +# global variable and constant initialization checking +# +vcom(ids: ref Decl): int +{ + ok := 1; + for(v := ids; v != nil; v = v.next) + ok &= varcom(v); + for(v = ids; v != nil; v = v.next) + v.init = simplify(v.init); + return ok; +} + +simplify(n: ref Node): ref Node +{ + if(n == nil) + return nil; + if(debug['F']) + print("simplify %s\n", nodeconv(n)); + n = efold(rewrite(n)); + if(debug['F']) + print("simplified %s\n", nodeconv(n)); + return n; +} + +isfix(n: ref Node): int +{ + if(n.ty.kind == Tint || n.ty.kind == Tfix){ + if(n.op == Ocast) + return n.left.ty.kind == Tint || n.left.ty.kind == Tfix; + return 1; + } + return 0; +} + +# +# rewrite an expression to make it easiser to compile, +# or give the correct results +# +rewrite(n: ref Node): ref Node +{ + v: big; + t: ref Type; + d: ref Decl; + nn: ref Node; + + if(n == nil) + return nil; + + left := n.left; + right := n.right; + + # + # rewrites + # + case n.op{ + Oname => + d = n.decl; + if(d.importid != nil){ + left = mkbin(Omdot, dupn(1, n.src, d.eimport), mkdeclname(n.src, d.importid)); + left.ty = n.ty; + return rewrite(left); + } + if((t = n.ty).kind == Texception){ + if(int t.cons) + fatal("cons in rewrite Oname"); + n = mkbin(Oadd, n, mkconst(n.src, big(2*IBY2WD))); + n = mkunary(Oind, n); + n.ty = t; + n.left.ty = n.left.left.ty = tint; + return rewrite(n); + } + Odas => + n.op = Oas; + return rewrite(n); + Oneg => + n.left = rewrite(left); + if(n.ty == treal) + break; + left = n.left; + n.right = left; + n.left = mkconst(n.src, big 0); + n.left.ty = n.ty; + n.op = Osub; + Ocomp => + v = big 0; + v = ~v; + n.right = mkconst(n.src, v); + n.right.ty = n.ty; + n.left = rewrite(left); + n.op = Oxor; + Oinc or + Odec or + Opreinc or + Opredec => + n.left = rewrite(left); + case n.ty.kind{ + Treal => + n.right = mkrconst(n.src, 1.0); + Tint or + Tbig or + Tbyte or + Tfix => + n.right = mkconst(n.src, big 1); + n.right.ty = n.ty; + * => + fatal("can't rewrite inc/dec "+nodeconv(n)); + } + if(n.op == Opreinc) + n.op = Oaddas; + else if(n.op == Opredec) + n.op = Osubas; + Oslice => + if(right.left.op == Onothing) + right.left = mkconst(right.left.src, big 0); + n.left = rewrite(left); + n.right = rewrite(right); + Oindex => + n.op = Oindx; + n.left = rewrite(left); + n.right = rewrite(right); + n = mkunary(Oind, n); + n.ty = n.left.ty; + n.left.ty = tint; + Oload => + n.right = mkn(Oname, nil, nil); + n.right.src = n.left.src; + n.right.decl = n.ty.tof.decl; + n.right.ty = n.ty; + n.left = rewrite(left); + Ocast => + if(left.ty.kind == Texception){ + n = rewrite(left); + break; + } + n.op = Ocast; + t = precasttab[left.ty.kind][n.ty.kind]; + if(t != nil){ + n.left = mkunary(Ocast, left); + n.left.ty = t; + return rewrite(n); + } + n.left = rewrite(left); + Oraise => + if(left.ty == tstring) + ; + else if(left.ty.cons == byte 0) + break; + else if(left.op != Ocall || left.left.ty.kind == Tfn){ + left = mkunary(Ocall, left); + left.ty = left.left.ty; + } + n.left = rewrite(left); + Ocall => + t = left.ty; + if(t.kind == Tref) + t = t.tof; + if(t.kind == Tfn){ + if(left.ty.kind == Tref){ # call by function reference + n.left = mkunary(Oind, left); + n.left.ty = t; + return rewrite(n); + } + d = nil; + if(left.op == Oname) + d = left.decl; + else if(left.op == Omdot && left.right.op == Odot) + d = left.right.right.decl; + else if(left.op == Omdot || left.op == Odot) + d = left.right.decl; + else if(left.op != Oind) + fatal("cannot deal with call " + nodeconv(n) + " in rewrite"); + if(ispoly(d)) + addfnptrs(d, 0); + n.left = rewrite(left); + if(right != nil) + n.right = rewrite(right); + if(d != nil && int d.inline == 1) + n = simplify(inline(n)); + break; + } + case n.ty.kind{ + Tref => + n = mkunary(Oref, n); + n.ty = n.left.ty; + n.left.ty = n.left.ty.tof; + n.left.left.ty = n.left.ty; + return rewrite(n); + Tadt => + n.op = Otuple; + n.right = nil; + if(n.ty.tags != nil){ + n.left = nn = mkunary(Oseq, mkconst(n.src, big left.right.decl.tag)); + if(right != nil){ + nn.right = right; + nn.src.stop = right.src.stop; + } + n.ty = left.right.decl.ty.tof; + }else + n.left = right; + return rewrite(n); + Tadtpick => + n.op = Otuple; + n.right = nil; + n.left = nn = mkunary(Oseq, mkconst(n.src, big left.right.decl.tag)); + if(right != nil){ + nn.right = right; + nn.src.stop = right.src.stop; + } + n.ty = left.right.decl.ty.tof; + return rewrite(n); + Texception => + if(n.ty.cons == byte 0) + return n.left; + if(left.op == Omdot){ + left.right.ty = left.ty; + left = left.right; + } + n.op = Otuple; + n.right = nil; + n.left = nn = mkunary(Oseq, left.decl.init); + nn.right = mkunary(Oseq, mkconst(n.src, big 0)); + nn.right.right = right; + n.ty = mkexbasetype(n.ty); + n = mkunary(Oref, n); + n.ty = internaltype(mktype(n.src.start, n.src.stop, Tref, t, nil)); + return rewrite(n); + * => + fatal("can't deal with "+nodeconv(n)+" in rewrite/Ocall"); + } + Omdot => + # + # what about side effects from left? + # + d = right.decl; + case d.store{ + Dfn => + n.left = rewrite(left); + if(right.op == Odot){ + n.right = dupn(1, left.src, right.right); + n.right.ty = d.ty; + } + Dconst or + Dtag or + Dtype => + # handled by fold + return n; + Dglobal => + right.op = Oconst; + right.c = ref Const(big d.offset, 0.); + right.ty = tint; + + n.left = left = mkunary(Oind, left); + left.ty = tint; + n.op = Oadd; + n = mkunary(Oind, n); + n.ty = n.left.ty; + n.left.ty = tint; + n.left = rewrite(n.left); + return n; + Darg => + return n; + * => + fatal("can't deal with "+nodeconv(n)+" in rewrite/Omdot"); + } + Odot => + # + # what about side effects from left? + # + d = right.decl; + case d.store{ + Dfn => + if(right.left != nil){ + n = mkbin(Omdot, dupn(1, left.src, right.left), right); + right.left = nil; + n.ty = d.ty; + return rewrite(n); + } + if(left.ty.kind == Tpoly){ + n = mkbin(Omdot, mkdeclname(left.src, d.link), mkdeclname(left.src, d.link.next)); + n.ty = d.ty; + return rewrite(n); + } + n.op = Oname; + n.decl = d; + n.right = nil; + n.left = nil; + return n; + Dconst or + Dtag or + Dtype => + # handled by fold + return n; + } + if(istuple(left)) + return n; # handled by fold + right.op = Oconst; + right.c = ref Const(big d.offset, 0.); + right.ty = tint; + + if(left.ty.kind != Tref){ + n.left = mkunary(Oadr, left); + n.left.ty = tint; + } + n.op = Oadd; + n = mkunary(Oind, n); + n.ty = n.left.ty; + n.left.ty = tint; + n.left = rewrite(n.left); + return n; + Oadr => + left = rewrite(left); + n.left = left; + if(left.op == Oind) + return left.left; + Otagof => + if(n.decl == nil){ + n.op = Oind; + return rewrite(n); + } + return n; + Omul or + Odiv => + left = n.left = rewrite(left); + right = n.right = rewrite(right); + if(n.ty.kind == Tfix && isfix(left) && isfix(right)){ + if(left.op == Ocast && tequal(left.ty, n.ty)) + n.left = left.left; + if(right.op == Ocast && tequal(right.ty, n.ty)) + n.right = right.left; + } + Oself => + if(newfnptr) + return n; + if(selfdecl == nil){ + d = selfdecl = mkids(n.src, enter(".self", 5), tany, nil); + installids(Dglobal, d); + d.refs++; + } + nn = mkn(Oload, nil, nil); + nn.src = n.src; + nn.left = mksconst(n.src, enterstring("$self")); + nn.ty = impdecl.ty; + usetype(nn.ty); + usetype(nn.ty.tof); + nn = rewrite(nn); + nn.op = Oself; + return nn; + Ofnptr => + if(n.flags == byte 0){ + # module + if(left == nil) + left = mkn(Oself, nil, nil); + return rewrite(left); + } + right.flags = n.flags; + n = right; + d = n.decl; + if(int n.flags == FNPTR2){ + if(left != nil && left.op != Oname) + fatal("not Oname for addiface"); + if(left == nil){ + addiface(nil, d); + if(newfnptr) + n.flags |= byte FNPTRN; + } + else + addiface(left.decl, d); + n.ty = tint; + return n; + } + if(int n.flags == FNPTRA){ + n = mkdeclname(n.src, d.link); + n.ty = tany; + return n; + } + if(int n.flags == (FNPTRA|FNPTR2)){ + n = mkdeclname(n.src, d.link.next); + n.ty = tint; + return n; + } + Ochan => + if(left == nil) + left = n.left = mkconst(n.src, big 0); + n.left = rewrite(left); + * => + n.left = rewrite(left); + n.right = rewrite(right); + } + + return n; +} + +# +# label a node with sethi-ullman numbers and addressablity +# genaddr interprets addable to generate operands, +# so a change here mandates a change there. +# +# addressable: +# const Rconst $value may also be Roff or Rdesc or Rnoff +# Asmall(local) Rreg value(FP) +# Asmall(global) Rmreg value(MP) +# ind(Rareg) Rreg value(FP) +# ind(Ramreg) Rmreg value(MP) +# ind(Rreg) Radr *value(FP) +# ind(Rmreg) Rmadr *value(MP) +# ind(Raadr) Radr value(value(FP)) +# ind(Ramadr) Rmadr value(value(MP)) +# +# almost addressable: +# adr(Rreg) Rareg +# adr(Rmreg) Ramreg +# add(const, Rareg) Rareg +# add(const, Ramreg) Ramreg +# add(const, Rreg) Raadr +# add(const, Rmreg) Ramadr +# add(const, Raadr) Raadr +# add(const, Ramadr) Ramadr +# adr(Radr) Raadr +# adr(Rmadr) Ramadr +# +# strangely addressable: +# fn Rpc +# mdot(module,exp) Rmpc +# +sumark(n: ref Node): ref Node +{ + if(n == nil) + return nil; + + n.temps = byte 0; + n.addable = Rcant; + + left := n.left; + right := n.right; + if(left != nil){ + sumark(left); + n.temps = left.temps; + } + if(right != nil){ + sumark(right); + if(right.temps == n.temps) + n.temps++; + else if(right.temps > n.temps) + n.temps = right.temps; + } + + case n.op{ + Oadr => + case int left.addable{ + int Rreg => + n.addable = Rareg; + int Rmreg => + n.addable = Ramreg; + int Radr => + n.addable = Raadr; + int Rmadr => + n.addable = Ramadr; + } + Oind => + case int left.addable{ + int Rreg => + n.addable = Radr; + int Rmreg => + n.addable = Rmadr; + int Rareg => + n.addable = Rreg; + int Ramreg => + n.addable = Rmreg; + int Raadr => + n.addable = Radr; + int Ramadr => + n.addable = Rmadr; + } + Oname => + case n.decl.store{ + Darg or + Dlocal => + n.addable = Rreg; + Dglobal => + n.addable = Rmreg; + if(LDT && n.decl.ty.kind == Tiface) + n.addable = Rldt; + Dtype => + # + # check for inferface to load + # + if(n.decl.ty.kind == Tmodule) + n.addable = Rmreg; + Dfn => + if(int n.flags & FNPTR){ + if(int n.flags == FNPTR2) + n.addable = Roff; + else if(int n.flags == FNPTR2|FNPTRN) + n.addable = Rnoff; + } + else + n.addable = Rpc; + * => + fatal("cannot deal with "+declconv(n.decl)+" in Oname in "+nodeconv(n)); + } + Omdot => + n.addable = Rmpc; + Oconst => + case n.ty.kind{ + Tint or + Tfix => + v := int n.c.val; + if(v < 0 && ((v >> 29) & 7) != 7 + || v > 0 && (v >> 29) != 0){ + n.decl = globalconst(n); + n.addable = Rmreg; + }else + n.addable = Rconst; + Tbig => + n.decl = globalBconst(n); + n.addable = Rmreg; + Tbyte => + n.decl = globalbconst(n); + n.addable = Rmreg; + Treal => + n.decl = globalfconst(n); + n.addable = Rmreg; + Tstring => + n.decl = globalsconst(n); + n.addable = Rmreg; + * => + fatal("cannot const in sumark "+typeconv(n.ty)); + } + Oadd => + if(right.addable == Rconst){ + case int left.addable{ + int Rareg => + n.addable = Rareg; + int Ramreg => + n.addable = Ramreg; + int Rreg or + int Raadr => + n.addable = Raadr; + int Rmreg or + int Ramadr => + n.addable = Ramadr; + } + } + } + if(n.addable < Rcant) + n.temps = byte 0; + else if(n.temps == byte 0) + n.temps = byte 1; + return n; +} + +mktn(t: ref Type): ref Node +{ + n := mkn(Oname, nil, nil); + usedesc(mktdesc(t)); + n.ty = t; + if(t.decl == nil) + fatal("mktn nil decl t "+typeconv(t)); + n.decl = t.decl; + n.addable = Rdesc; + return n; +} + +# does a tuple of the form (a, b, ...) form a contiguous block +# of memory on the stack when offsets are assigned later +# - only when (a, b, ...) := rhs and none of the names nil +# can we guarantee this +# +tupblk0(n: ref Node, d: ref Decl): (int, ref Decl) +{ + ok, nid: int; + + case(n.op){ + Otuple => + for(n = n.left; n != nil; n = n.right){ + (ok, d) = tupblk0(n.left, d); + if(!ok) + return (0, nil); + } + return (1, d); + Oname => + if(n.decl == nildecl) + return (0, nil); + if(d != nil && d.next != n.decl) + return (0, nil); + nid = int n.decl.nid; + if(d == nil && nid == 1) + return (0, nil); + if(d != nil && nid != 0) + return (0, nil); + return (1, n.decl); + } + return (0, nil); +} + +# could force locals to be next to each other +# - need to shuffle locals list +# - later +# +tupblk(n: ref Node): ref Node +{ + ok: int; + d: ref Decl; + + if(n.op != Otuple) + return nil; + d = nil; + (ok, d) = tupblk0(n, d); + if(!ok) + return nil; + while(n.op == Otuple) + n = n.left.left; + if(n.op != Oname || n.decl.nid == byte 1) + fatal("bad tupblk"); + return n; +} + +# for cprof +esrc(src: Src, osrc: Src, nto: ref Node): Src +{ + if(nto != nil && src.start != 0 && src.stop != 0) + return src; + return osrc; +} + +# +# compile an expression with an implicit assignment +# note: you are not allowed to use nto.src +# +# need to think carefully about the types used in moves +# +ecom(src: Src, nto, n: ref Node): ref Node +{ + tleft, tright, tto, ttn: ref Node; + t: ref Type; + p: ref Inst; + + if(debug['e']){ + print("ecom: %s\n", nodeconv(n)); + if(nto != nil) + print("ecom nto: %s\n", nodeconv(nto)); + } + + if(n.addable < Rcant){ + # + # think carefully about the type used here + # + if(nto != nil) + genmove(src, Mas, n.ty, n, nto); + return nto; + } + + left := n.left; + right := n.right; + op := n.op; + case op{ + * => + fatal("can't ecom "+nodeconv(n)); + return nto; + Oif => + p = bcom(left, 1, nil); + ecom(right.left.src, nto, right.left); + if(right.right != nil){ + pp := p; + p = genrawop(right.left.src, IJMP, nil, nil, nil); + patch(pp, nextinst()); + ecom(right.right.src, nto, right.right); + } + patch(p, nextinst()); + Ocomma => + ttn = left.left; + ecom(left.src, nil, left); + ecom(right.src, nto, right); + tfree(ttn); + Oname => + if(n.addable == Rpc){ + if(nto != nil) + genmove(src, Mas, n.ty, n, nto); + return nto; + } + fatal("can't ecom "+nodeconv(n)); + Onothing => + break; + Oused => + if(nto != nil) + fatal("superflous used "+nodeconv(left)+" nto "+nodeconv(nto)); + tto = talloc(left.ty, nil); + ecom(left.src, tto, left); + tfree(tto); + Oas => + if(right.ty == tany) + right.ty = n.ty; + if(left.op == Oname && left.decl.ty == tany){ + if(nto == nil) + nto = tto = talloc(right.ty, nil); + left = nto; + nto = nil; + } + if(left.op == Oinds){ + indsascom(src, nto, n); + tfree(tto); + break; + } + if(left.op == Oslice){ + slicelcom(src, nto, n); + tfree(tto); + break; + } + + if(left.op == Otuple){ + if(!tupsaliased(right, left)){ + if((tn := tupblk(left)) != nil){ + tn.ty = n.ty; + ecom(n.right.src, tn, right); + if(nto != nil) + genmove(src, Mas, n.ty, tn, nto); + tfree(tto); + break; + } + if((tn = tupblk(right)) != nil){ + tn.ty = n.ty; + tuplcom(tn, left); + if(nto != nil) + genmove(src, Mas, n.ty, tn, nto); + tfree(tto); + break; + } + if(nto == nil && right.op == Otuple && left.ty.kind != Tadtpick){ + tuplrcom(right, left); + tfree(tto); + break; + } + } + if(right.addable >= Ralways + || right.op != Oname + || tupaliased(right, left)){ + tright = talloc(n.ty, nil); + ecom(n.right.src, tright, right); + right = tright; + } + tuplcom(right, n.left); + if(nto != nil) + genmove(src, Mas, n.ty, right, nto); + tfree(tright); + tfree(tto); + break; + } + + # + # check for left/right aliasing and build right into temporary + # + if(right.op == Otuple){ + if(!tupsaliased(left, right) && (tn := tupblk(right)) != nil){ + tn.ty = n.ty; + right = tn; + } + else if(left.op != Oname || tupaliased(left, right)) + right = ecom(right.src, tright = talloc(right.ty, nil), right); + } + + # + # think carefully about types here + # + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + ecom(n.src, left, right); + if(nto != nil) + genmove(src, Mas, nto.ty, left, nto); + tfree(tleft); + tfree(tright); + tfree(tto); + Ochan => + if(left != nil && left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + genchan(src, left, n.ty.tof, nto); + tfree(tleft); + Oinds => + if(right.addable < Ralways){ + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + }else if(left.temps <= right.temps){ + right = ecom(right.src, tright = talloc(right.ty, nil), right); + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + }else{ + (left, tleft) = eacom(left, nil); + right = ecom(right.src, tright = talloc(right.ty, nil), right); + } + genop(n.src, op, left, right, nto); + tfree(tleft); + tfree(tright); + Osnd => + if(right.addable < Rcant){ + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + }else if(left.temps < right.temps){ + (right, tright) = eacom(right, nto); + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + }else{ + (left, tleft) = eacom(left, nto); + (right, tright) = eacom(right, nil); + } + p = genrawop(n.src, ISEND, right, nil, left); + p.m.offset = n.ty.size; # for optimizer + if(nto != nil) + genmove(src, Mas, right.ty, right, nto); + tfree(tleft); + tfree(tright); + Orcv => + if(nto == nil){ + ecom(n.src, tto = talloc(n.ty, nil), n); + tfree(tto); + return nil; + } + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + if(left.ty.kind == Tchan){ + p = genrawop(src, IRECV, left, nil, nto); + p.m.offset = n.ty.size; # for optimizer + }else{ + recvacom(src, nto, n); + } + tfree(tleft); + Ocons => + # + # another temp which can go with analysis + # + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + if(!sameaddr(right, nto)){ + ecom(right.src, tto = talloc(n.ty, nto), right); + genmove(src, Mcons, left.ty, left, tto); + if(!sameaddr(tto, nto)) + genmove(src, Mas, nto.ty, tto, nto); + }else + genmove(src, Mcons, left.ty, left, nto); + tfree(tleft); + tfree(tto); + Ohd => + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + genmove(src, Mhd, nto.ty, left, nto); + tfree(tleft); + Otl => + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + genmove(src, Mtl, left.ty, left, nto); + tfree(tleft); + Otuple => + if((tn := tupblk(n)) != nil){ + tn.ty = n.ty; + genmove(src, Mas, n.ty, tn, nto); + break; + } + tupcom(nto, n); + Oadd or + Osub or + Omul or + Odiv or + Omod or + Oand or + Oor or + Oxor or + Olsh or + Orsh or + Oexp => + # + # check for 2 operand forms + # + if(sameaddr(nto, left)){ + if(right.addable >= Rcant) + (right, tright) = eacom(right, nto); + genop(src, op, right, nil, nto); + tfree(tright); + break; + } + + if(opcommute[op] && sameaddr(nto, right) && n.ty != tstring){ + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + genop(src, opcommute[op], left, nil, nto); + tfree(tleft); + break; + } + + if(right.addable < left.addable + && opcommute[op] + && n.ty != tstring){ + op = opcommute[op]; + left = right; + right = n.left; + } + if(left.addable < Ralways){ + if(right.addable >= Rcant) + (right, tright) = eacom(right, nto); + }else if(right.temps <= left.temps){ + left = ecom(left.src, tleft = talloc(left.ty, nto), left); + if(right.addable >= Rcant) + (right, tright) = eacom(right, nil); + }else{ + (right, tright) = eacom(right, nto); + left = ecom(left.src, tleft = talloc(left.ty, nil), left); + } + + # + # check for 2 operand forms + # + if(sameaddr(nto, left)) + genop(src, op, right, nil, nto); + else if(opcommute[op] && sameaddr(nto, right) && n.ty != tstring) + genop(src, opcommute[op], left, nil, nto); + else + genop(src, op, right, left, nto); + tfree(tleft); + tfree(tright); + Oaddas or + Osubas or + Omulas or + Odivas or + Omodas or + Oexpas or + Oandas or + Ooras or + Oxoras or + Olshas or + Orshas => + if(left.op == Oinds){ + indsascom(src, nto, n); + break; + } + if(right.addable < Rcant){ + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + }else if(left.temps < right.temps){ + (right, tright) = eacom(right, nto); + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + }else{ + (left, tleft) = eacom(left, nto); + (right, tright) = eacom(right, nil); + } + genop(n.src, op, right, nil, left); + if(nto != nil) + genmove(src, Mas, left.ty, left, nto); + tfree(tleft); + tfree(tright); + Olen => + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + op = -1; + t = left.ty; + if(t == tstring) + op = ILENC; + else if(t.kind == Tarray) + op = ILENA; + else if(t.kind == Tlist) + op = ILENL; + else + fatal("can't len "+nodeconv(n)); + genrawop(src, op, left, nil, nto); + tfree(tleft); + Oneg => + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + genop(n.src, op, left, nil, nto); + tfree(tleft); + Oinc or + Odec => + if(left.op == Oinds){ + indsascom(src, nto, n); + break; + } + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + if(nto != nil) + genmove(src, Mas, left.ty, left, nto); + if(right.addable >= Rcant) + fatal("inc/dec amount not addressable: "+nodeconv(n)); + genop(n.src, op, right, nil, left); + tfree(tleft); + Ospawn => + if(left.left.op == Oind) + fpcall(n.src, op, left, nto); + else + callcom(n.src, op, left, nto); + Oraise => + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + genrawop(n.src, IRAISE, left, nil, nil); + tfree(tleft); + Ocall => + if(left.op == Oind) + fpcall(esrc(src, n.src, nto), op, n, nto); + else + callcom(esrc(src, n.src, nto), op, n, nto); + Oref => + t = left.ty; + if(left.op == Oname && left.decl.store == Dfn || left.op == Omdot && left.right.op == Oname && left.right.decl.store == Dfn){ # create a function reference + mod, ind: ref Node; + + d := left.decl; + if(left.op == Omdot){ + d = left.right.decl; + mod = left.left; + } + else if(d.eimport != nil) + mod = d.eimport; + else{ + mod = rewrite(mkn(Oself, nil, nil)); + addiface(nil, d); + } + sumark(mod); + tto = talloc(n.ty, nto); + genrawop(src, INEW, mktn(usetype(tfnptr)), nil, tto); + tright = ref znode; + tright.src = src; + tright.op = Oind; + tright.left = tto; + tright.right = nil; + tright.ty = tany; + sumark(tright); + ecom(src, tright, mod); + ind = mkunary(Oind, mkbin(Oadd, dupn(0, src, tto), mkconst(src, big IBY2WD))); + ind.ty = ind.left.ty = ind.left.right.ty = tint; + tright.op = Oas; + tright.left = ind; + tright.right = mkdeclname(src, d); + tright.ty = tright.right.ty = tint; + sumark(tright); + if(mod.op == Oself && newfnptr) + tright.right.addable = Rnoff; + else + tright.right.addable = Roff; + ecom(src, nil, tright); + if(!sameaddr(tto, nto)) + genmove(src, Mas, n.ty, tto, nto); + tfree(tto); + break; + } + if(left.op == Oname && left.decl.store == Dtype){ + genrawop(src, INEW, mktn(t), nil, nto); + break; + } + if(t.kind == Tadt && t.tags != nil){ + pickdupcom(src, nto, left); + break; + } + + tt := t; + if(left.op == Oconst && left.decl.store == Dtag) + t = left.decl.ty.tof; + + # + # could eliminate temp if nto does not occur + # in tuple initializer + # + tto = talloc(n.ty, nto); + genrawop(src, INEW, mktn(t), nil, tto); + tright = ref znode; + tright.op = Oind; + tright.left = tto; + tright.right = nil; + tright.ty = tt; + sumark(tright); + ecom(src, tright, left); + if(!sameaddr(tto, nto)) + genmove(src, Mas, n.ty, tto, nto); + tfree(tto); + Oload => + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + tright = talloc(tint, nil); + if(LDT) + genrawop(src, ILOAD, left, right, nto); + else{ + genrawop(src, ILEA, right, nil, tright); + genrawop(src, ILOAD, left, tright, nto); + } + tfree(tleft); + tfree(tright); + Ocast => + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + t = left.ty; + if(t.kind == Tfix || n.ty.kind == Tfix){ + op = casttab[t.kind][n.ty.kind]; + if(op == ICVTXX) + genfixcastop(src, op, left, nto); + else{ + ttn = sumark(mkrconst(src, scale2(t, n.ty))); + genrawop(src, op, left, ttn, nto); + } + } + else + genrawop(src, casttab[t.kind][n.ty.kind], left, nil, nto); + tfree(tleft); + Oarray => + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + if(arrayz) + genrawop(esrc(src, left.src, nto), INEWAZ, left, mktn(n.ty.tof), nto); + else + genrawop(esrc(src, left.src, nto), INEWA, left, mktn(n.ty.tof), nto); + if(right != nil) + arraycom(nto, right); + tfree(tleft); + Oslice => + tn := right.right; + right = right.left; + + # + # make the left node of the slice directly addressable + # therefore, if it's len is taken (via tn), + # left's tree won't be rewritten + # + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + + if(tn.op == Onothing){ + tn = mkn(Olen, left, nil); + tn.src = src; + tn.ty = tint; + sumark(tn); + } + if(tn.addable < Ralways){ + if(right.addable >= Rcant) + (right, tright) = eacom(right, nil); + }else if(right.temps <= tn.temps){ + tn = ecom(tn.src, ttn = talloc(tn.ty, nil), tn); + if(right.addable >= Rcant) + (right, tright) = eacom(right, nil); + }else{ + (right, tright) = eacom(right, nil); + tn = ecom(tn.src, ttn = talloc(tn.ty, nil), tn); + } + op = ISLICEA; + if(nto.ty == tstring) + op = ISLICEC; + + # + # overwrite the destination last, + # since it might be used in computing the slice bounds + # + if(!sameaddr(left, nto)) + ecom(left.src, nto, left); + + genrawop(src, op, right, tn, nto); + tfree(tleft); + tfree(tright); + tfree(ttn); + Oindx => + if(right.addable < Rcant){ + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + }else if(left.temps < right.temps){ + (right, tright) = eacom(right, nto); + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + }else{ + (left, tleft) = eacom(left, nto); + (right, tright) = eacom(right, nil); + } + if(nto.addable >= Ralways) + nto = ecom(src, tto = talloc(nto.ty, nil), nto); + op = IINDX; + case left.ty.tof.size{ + IBY2LG => + op = IINDL; + if(left.ty.tof == treal) + op = IINDF; + IBY2WD => + op = IINDW; + 1 => + op = IINDB; + } + genrawop(src, op, left, nto, right); + if(tleft != nil && tleft.decl != nil) + tfreelater(tleft); + else + tfree(tleft); + tfree(tright); + tfree(tto); + Oind => + (n, tleft) = eacom(n, nto); + genmove(src, Mas, n.ty, n, nto); + tfree(tleft); + Onot or + Oandand or + Ooror or + Oeq or + Oneq or + Olt or + Oleq or + Ogt or + Ogeq => + p = bcom(n, 1, nil); + genmove(src, Mas, tint, sumark(mkconst(src, big 1)), nto); + pp := genrawop(src, IJMP, nil, nil, nil); + patch(p, nextinst()); + genmove(src, Mas, tint, sumark(mkconst(src, big 0)), nto); + patch(pp, nextinst()); + Oself => + if(newfnptr){ + if(nto != nil) + genrawop(src, ISELF, nil, nil, nto); + break; + } + tn := sumark(mkdeclname(src, selfdecl)); + p = genbra(src, Oneq, tn, sumark(mkdeclname(src, nildecl))); + n.op = Oload; + ecom(src, tn, n); + patch(p, nextinst()); + genmove(src, Mas, n.ty, tn, nto); + } + return nto; +} + +# +# compile exp n to yield an addressable expression +# use reg to build a temporary; if t is a temp, it is usable +# +# note that 0adr's are strange as they are only used +# for calculating the addresses of fields within adt's. +# therefore an Oind is the parent or grandparent of the Oadr, +# and we pick off all of the cases where Oadr's argument is not +# addressable by looking from the Oind. +# +eacom(n, t: ref Node): (ref Node, ref Node) +{ + reg: ref Node; + + if(n.op == Ocomma){ + tn := n.left.left; + ecom(n.left.src, nil, n.left); + nn := eacom(n.right, t); + tfree(tn); + return nn; + } + + if(debug['e'] || debug['E']) + print("eacom: %s\n", nodeconv(n)); + + left := n.left; + if(n.op != Oind){ + ecom(n.src, reg = talloc(n.ty, t), n); + reg.src = n.src; + return (reg, reg); + } + + if(left.op == Oadd && left.right.op == Oconst){ + if(left.left.op == Oadr){ + (left.left.left, reg) = eacom(left.left.left, t); + sumark(n); + if(n.addable >= Rcant) + fatal("eacom can't make node addressable: "+nodeconv(n)); + return (n, reg); + } + reg = talloc(left.left.ty, t); + ecom(left.left.src, reg, left.left); + left.left.decl = reg.decl; + left.left.addable = Rreg; + left.left = reg; + left.addable = Raadr; + n.addable = Radr; + }else if(left.op == Oadr){ + reg = talloc(left.left.ty, t); + ecom(left.left.src, reg, left.left); + + # + # sleaze: treat the temp as the type of the field, not the enclosing structure + # + reg.ty = n.ty; + reg.src = n.src; + return (reg, reg); + }else{ + reg = talloc(left.ty, t); + ecom(left.src, reg, left); + n.left = reg; + n.addable = Radr; + } + return (n, reg); +} + +# +# compile an assignment to an array slice +# +slicelcom(src: Src, nto, n: ref Node): ref Node +{ + tleft, tright, tv: ref Node; + + left := n.left.left; + right := n.left.right.left; + v := n.right; + if(right.addable < Ralways){ + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + }else if(left.temps <= right.temps){ + right = ecom(right.src, tright = talloc(right.ty, nto), right); + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + }else{ + (left, tleft) = eacom(left, nil); # dangle on right and v + right = ecom(right.src, tright = talloc(right.ty, nil), right); + } + + case n.op{ + Oas => + if(v.addable >= Rcant) + (v, tv) = eacom(v, nil); + } + + genrawop(n.src, ISLICELA, v, right, left); + if(nto != nil) + genmove(src, Mas, n.ty, left, nto); + tfree(tleft); + tfree(tv); + tfree(tright); + return nto; +} + +# +# compile an assignment to a string location +# +indsascom(src: Src, nto, n: ref Node): ref Node +{ + tleft, tright, tv, tu, u: ref Node; + + left := n.left.left; + right := n.left.right; + v := n.right; + if(right.addable < Ralways){ + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nto); + }else if(left.temps <= right.temps){ + right = ecom(right.src, tright = talloc(right.ty, nto), right); + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + }else{ + (left, tleft) = eacom(left, nil); # dangle on right and v + right = ecom(right.src, tright = talloc(right.ty, nil), right); + } + + case n.op{ + Oas => + if(v.addable >= Rcant) + (v, tv) = eacom(v, nil); + Oinc or + Odec => + if(v.addable >= Rcant) + fatal("inc/dec amount not addable"); + u = tu = talloc(tint, nil); + genop(n.left.src, Oinds, left, right, u); + if(nto != nil) + genmove(src, Mas, n.ty, u, nto); + nto = nil; + genop(n.src, n.op, v, nil, u); + v = u; + Oaddas or + Osubas or + Omulas or + Odivas or + Omodas or + Oexpas or + Oandas or + Ooras or + Oxoras or + Olshas or + Orshas => + if(v.addable >= Rcant) + (v, tv) = eacom(v, nil); + u = tu = talloc(tint, nil); + genop(n.left.src, Oinds, left, right, u); + genop(n.src, n.op, v, nil, u); + v = u; + } + + genrawop(n.src, IINSC, v, right, left); + tfree(tleft); + tfree(tv); + tfree(tright); + tfree(tu); + if(nto != nil) + genmove(src, Mas, n.ty, v, nto); + return nto; +} + +callcom(src: Src, op: int, n, ret: ref Node) +{ + tmod, tind: ref Node; + callee: ref Decl; + + args := n.right; + nfn := n.left; + case(nfn.op){ + Odot => + callee = nfn.right.decl; + nfn.addable = Rpc; + Omdot => + callee = nfn.right.decl; + Oname => + callee = nfn.decl; + * => + callee = nil; + fatal("bad call op in callcom"); + } + if(nfn.addable != Rpc && nfn.addable != Rmpc) + fatal("can't gen call addresses"); + if(nfn.ty.tof != tnone && ret == nil){ + ecom(src, tmod = talloc(nfn.ty.tof, nil), n); + tfree(tmod); + return; + } + if(ispoly(callee)) + addfnptrs(callee, 0); + if(nfn.ty.varargs != byte 0){ + d := dupdecl(nfn.right.decl); + nfn.decl = d; + d.desc = gendesc(d, idoffsets(nfn.ty.ids, MaxTemp, MaxAlign), nfn.ty.ids); + } + + frame := talloc(tint, nil); + + mod := nfn.left; + ind := nfn.right; + if(nfn.addable == Rmpc){ + if(mod.addable >= Rcant) + (mod, tmod) = eacom(mod, nil); # dangle always + if(ind.op != Oname && ind.addable >= Ralways){ + tind = talloc(ind.ty, nil); + ecom(ind.src, tind, ind); + ind = tind; + } + else if(ind.decl != nil && ind.decl.store != Darg) + ind.addable = Roff; + } + + # + # stop nested uncalled frames + # otherwise exception handling very complicated + # + for(a := args; a != nil; a = a.right){ + if(hascall(a.left)){ + tn := talloc(a.left.ty, nil); + ecom(a.left.src, tn, a.left); + a.left = tn; + tn.flags |= byte TEMP; + } + } + + # + # allocate the frame + # + if(nfn.addable == Rmpc && nfn.ty.varargs == byte 0){ + genrawop(src, IMFRAME, mod, ind, frame); + }else if(nfn.op == Odot){ + genrawop(src, IFRAME, nfn.left, nil, frame); + }else{ + in := genrawop(src, IFRAME, nil, nil, frame); + in.sm = Adesc; + in.s.decl = nfn.decl; + } + + # + # build a fake node for the argument area + # + toff := ref znode; + tadd := ref znode; + pass := ref znode; + toff.op = Oconst; + toff.c = ref Const(big 0, 0.0); # jrf - added initialization + toff.addable = Rconst; + toff.ty = tint; + tadd.op = Oadd; + tadd.addable = Raadr; + tadd.left = frame; + tadd.right = toff; + tadd.ty = tint; + pass.op = Oind; + pass.addable = Radr; + pass.left = tadd; + + # + # compile all the args + # + d := nfn.ty.ids; + off := 0; + for(a = args; a != nil; a = a.right){ + off = d.offset; + toff.c.val = big off; + if(d.ty.kind == Tpoly) + pass.ty = a.left.ty; + else + pass.ty = d.ty; + ecom(a.left.src, pass, a.left); + d = d.next; + if(int a.left.flags & TEMP) + tfree(a.left); + } + if(off > maxstack) + maxstack = off; + + # + # pass return value + # + if(ret != nil){ + toff.c.val = big(REGRET*IBY2WD); + pass.ty = nfn.ty.tof; + p := genrawop(src, ILEA, ret, nil, pass); + p.m.offset = ret.ty.size; # for optimizer + } + + # + # call it + # + iop: int; + if(nfn.addable == Rmpc){ + iop = IMCALL; + if(op == Ospawn) + iop = IMSPAWN; + genrawop(src, iop, frame, ind, mod); + tfree(tmod); + tfree(tind); + }else if(nfn.op == Odot){ + iop = ICALL; + if(op == Ospawn) + iop = ISPAWN; + genrawop(src, iop, frame, nil, nfn.right); + }else{ + iop = ICALL; + if(op == Ospawn) + iop = ISPAWN; + in := genrawop(src, iop, frame, nil, nil); + in.d.decl = nfn.decl; + in.dm = Apc; + } + tfree(frame); +} + +# +# initialization code for arrays +# a must be addressable (< Rcant) +# +arraycom(a, elems: ref Node) +{ + top, out: ref Inst; + ri, n, wild: ref Node; + + if(debug['A']) + print("arraycom: %s %s\n", nodeconv(a), nodeconv(elems)); + + # c := elems.ty.cse; + # don't use c.wild in case we've been inlined + wild = nil; + for(e := elems; e != nil; e = e.right) + for(q := e.left.left; q != nil; q = q.right) + if(q.left.op == Owild) + wild = e.left; + if(wild != nil) + arraydefault(a, wild.right); + + tindex := ref znode; + fake := ref znode; + tmp := talloc(tint, nil); + tindex.op = Oindx; + tindex.addable = Rcant; + tindex.left = a; + tindex.right = nil; + tindex.ty = tint; + fake.op = Oind; + fake.addable = Radr; + fake.left = tmp; + fake.ty = a.ty.tof; + + for(e = elems; e != nil; e = e.right){ + # + # just duplicate the initializer for Oor + # + for(q = e.left.left; q != nil; q = q.right){ + if(q.left.op == Owild) + continue; + + body := e.left.right; + if(q.right != nil) + body = dupn(0, nosrc, body); + top = nil; + out = nil; + ri = nil; + if(q.left.op == Orange){ + # + # for(i := q.left.left; i <= q.left.right; i++) + # + ri = talloc(tint, nil); + ri.src = q.left.src; + ecom(q.left.src, ri, q.left.left); + + # i <= q.left.right; + n = mkn(Oleq, ri, q.left.right); + n.src = q.left.src; + n.ty = tint; + top = nextinst(); + out = bcom(n, 1, nil); + + tindex.right = ri; + }else{ + tindex.right = q.left; + } + + tindex.addable = Rcant; + tindex.src = q.left.src; + ecom(tindex.src, tmp, tindex); + + ecom(body.src, fake, body); + + if(q.left.op == Orange){ + # i++ + n = mkbin(Oinc, ri, sumark(mkconst(ri.src, big 1))); + n.ty = tint; + n.addable = Rcant; + ecom(n.src, nil, n); + + # jump to test + patch(genrawop(q.left.src, IJMP, nil, nil, nil), top); + patch(out, nextinst()); + tfree(ri); + } + } + } + tfree(tmp); +} + +# +# default initialization code for arrays. +# compiles to +# n = len a; +# while(n){ +# n--; +# a[n] = elem; +# } +# +arraydefault(a, elem: ref Node) +{ + e: ref Node; + + if(debug['A']) + print("arraydefault: %s %s\n", nodeconv(a), nodeconv(elem)); + + t := mkn(Olen, a, nil); + t.src = elem.src; + t.ty = tint; + t.addable = Rcant; + n := talloc(tint, nil); + n.src = elem.src; + ecom(t.src, n, t); + + top := nextinst(); + out := bcom(n, 1, nil); + + t = mkbin(Odec, n, sumark(mkconst(elem.src, big 1))); + t.ty = tint; + t.addable = Rcant; + ecom(t.src, nil, t); + + if(elem.addable >= Rcant) + (elem, e) = eacom(elem, nil); + + t = mkn(Oindx, a, n); + t.src = elem.src; + t = mkbin(Oas, mkunary(Oind, t), elem); + t.ty = elem.ty; + t.left.ty = elem.ty; + t.left.left.ty = tint; + sumark(t); + ecom(t.src, nil, t); + + patch(genrawop(t.src, IJMP, nil, nil, nil), top); + + tfree(n); + tfree(e); + patch(out, nextinst()); +} + +tupcom(nto, n: ref Node) +{ + if(debug['Y']) + print("tupcom %s\nto %s\n", nodeconv(n), nodeconv(nto)); + + # + # build a fake node for the tuple + # + toff := ref znode; + tadd := ref znode; + fake := ref znode; + tadr := ref znode; + toff.op = Oconst; + toff.c = ref Const(big 0, 0.0); # no val => may get fatal error below (jrf) + toff.ty = tint; + tadr.op = Oadr; + tadr.left = nto; + tadr.ty = tint; + tadd.op = Oadd; + tadd.left = tadr; + tadd.right = toff; + tadd.ty = tint; + fake.op = Oind; + fake.left = tadd; + sumark(fake); + if(fake.addable >= Rcant) + fatal("tupcom: bad value exp "+nodeconv(fake)); + + # + # compile all the exps + # + d := n.ty.ids; + for(e := n.left; e != nil; e = e.right){ + toff.c.val = big d.offset; + fake.ty = d.ty; + ecom(e.left.src, fake, e.left); + d = d.next; + } +} + +tuplcom(n, nto: ref Node) +{ + if(debug['Y']) + print("tuplcom %s\nto %s\n", nodeconv(n), nodeconv(nto)); + + # + # build a fake node for the tuple + # + toff := ref znode; + tadd := ref znode; + fake := ref znode; + tadr := ref znode; + toff.op = Oconst; + toff.c = ref Const(big 0, 0.0); # no val => may get fatal error below (jrf) + toff.ty = tint; + tadr.op = Oadr; + tadr.left = n; + tadr.ty = tint; + tadd.op = Oadd; + tadd.left = tadr; + tadd.right = toff; + tadd.ty = tint; + fake.op = Oind; + fake.left = tadd; + sumark(fake); + if(fake.addable >= Rcant) + fatal("tuplcom: bad value exp for "+nodeconv(fake)); + + # + # compile all the exps + # + tas := ref znode; + d := nto.ty.ids; + if(nto.ty.kind == Tadtpick) + d = nto.ty.tof.ids.next; + for(e := nto.left; e != nil; e = e.right){ + as := e.left; + if(as.op != Oname || as.decl != nildecl){ + toff.c.val = big d.offset; + fake.ty = d.ty; + fake.src = as.src; + if(as.addable < Rcant) + genmove(as.src, Mas, d.ty, fake, as); + else{ + tas.op = Oas; + tas.ty = d.ty; + tas.src = as.src; + tas.left = as; + tas.right = fake; + tas.addable = Rcant; + ecom(as.src, nil, tas); + } + } + d = d.next; + } +} + +tuplrcom(n: ref Node, nto: ref Node) +{ + s, d, tas: ref Node; + de: ref Decl; + + tas = ref znode; + de = nto.ty.ids; + for((s, d) = (n.left, nto.left); s != nil && d != nil; (s, d) = (s.right, d.right)){ + if(d.left.op != Oname || d.left.decl != nildecl){ + tas.op = Oas; + tas.ty = de.ty; + tas.src = s.left.src; + tas.left = d.left; + tas.right = s.left; + sumark(tas); + ecom(tas.src, nil, tas); + } + de = de.next; + } + if(s != nil || d != nil) + fatal("tuplrcom"); +} + +# +# boolean compiler +# fall through when condition == true +# +bcom(n: ref Node, true: int, b: ref Inst): ref Inst +{ + tleft, tright: ref Node; + + if(n.op == Ocomma){ + tn := n.left.left; + ecom(n.left.src, nil, n.left); + b = bcom(n.right, true, b); + tfree(tn); + return b; + } + + if(debug['b']) + print("bcom %s %d\n", nodeconv(n), true); + + left := n.left; + right := n.right; + op := n.op; + case op{ + Onothing => + return b; + Onot => + return bcom(n.left, !true, b); + Oandand => + if(!true) + return oror(n, true, b); + return andand(n, true, b); + Ooror => + if(!true) + return andand(n, true, b); + return oror(n, true, b); + Ogt or + Ogeq or + Oneq or + Oeq or + Olt or + Oleq => + break; + * => + if(n.ty.kind == Tint){ + right = mkconst(n.src, big 0); + right.addable = Rconst; + left = n; + op = Oneq; + break; + } + fatal("can't bcom "+nodeconv(n)); + return b; + } + + if(true) + op = oprelinvert[op]; + + if(left.addable < right.addable){ + t := left; + left = right; + right = t; + op = opcommute[op]; + } + + if(right.addable < Ralways){ + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + }else if(left.temps <= right.temps){ + right = ecom(right.src, tright = talloc(right.ty, nil), right); + if(left.addable >= Rcant) + (left, tleft) = eacom(left, nil); + }else{ + (left, tleft) = eacom(left, nil); + right = ecom(right.src, tright = talloc(right.ty, nil), right); + } + bb := genbra(n.src, op, left, right); + bb.branch = b; + tfree(tleft); + tfree(tright); + return bb; +} + +andand(n: ref Node, true: int, b: ref Inst): ref Inst +{ + if(debug['b']) + print("andand %s\n", nodeconv(n)); + b = bcom(n.left, true, b); + b = bcom(n.right, true, b); + return b; +} + +oror(n: ref Node, true: int, b: ref Inst): ref Inst +{ + if(debug['b']) + print("oror %s\n", nodeconv(n)); + bb := bcom(n.left, !true, nil); + b = bcom(n.right, true, b); + patch(bb, nextinst()); + return b; +} + +# +# generate code for a recva expression +# this is just a hacked up small alt +# +recvacom(src: Src, nto, n: ref Node) +{ + p: ref Inst; + + left := n.left; + + labs := array[1] of Label; + labs[0].isptr = left.addable >= Rcant; + c := ref Case; + c.nlab = 1; + c.nsnd = 0; + c.offset = 0; + c.labs = labs; + talt := mktalt(c); + + which := talloc(tint, nil); + tab := talloc(talt, nil); + + # + # build the node for the address of each channel, + # the values to send, and the storage for values received + # + off := ref znode; + adr := ref znode; + add := ref znode; + slot := ref znode; + off.op = Oconst; + off.c = ref Const(big 0, 0.0); # jrf - added initialization + off.ty = tint; + off.addable = Rconst; + adr.op = Oadr; + adr.left = tab; + adr.ty = tint; + add.op = Oadd; + add.left = adr; + add.right = off; + add.ty = tint; + slot.op = Oind; + slot.left = add; + sumark(slot); + + # + # gen the channel + # this sleaze is lying to the garbage collector + # + off.c.val = big(2*IBY2WD); + if(left.addable < Rcant) + genmove(src, Mas, tint, left, slot); + else{ + slot.ty = left.ty; + ecom(src, slot, left); + slot.ty = nil; + } + + # + # gen the value + # + off.c.val += big IBY2WD; + p = genrawop(left.src, ILEA, nto, nil, slot); + p.m.offset = nto.ty.size; # for optimizer + + # + # number of senders and receivers + # + off.c.val = big 0; + genmove(src, Mas, tint, sumark(mkconst(src, big 0)), slot); + off.c.val += big IBY2WD; + genmove(src, Mas, tint, sumark(mkconst(src, big 1)), slot); + off.c.val += big IBY2WD; + + p = genrawop(src, IALT, tab, nil, which); + p.m.offset = talt.size; # for optimizer + tfree(which); + tfree(tab); +} + +# +# generate code to duplicate an adt with pick fields +# this is just a hacked up small pick +# n is Oind(exp) +# +pickdupcom(src: Src, nto, n: ref Node) +{ + jmps: ref Inst; + + if(n.op != Oind) + fatal("pickdupcom not Oind: " + nodeconv(n)); + + t := n.ty; + nlab := t.decl.tag; + + # + # generate global which has case labels + # + d := mkids(src, enter(".c"+string nlabel++, 0), mktype(src.start, src.stop, Tcase, nil, nil), nil); + d.init = mkdeclname(src, d); + + clab := ref znode; + clab.addable = Rmreg; + clab.left = nil; + clab.right = nil; + clab.op = Oname; + clab.ty = d.ty; + clab.decl = d; + + # + # generate a temp to hold the real value + # then generate a case on the tag + # + orig := n.left; + tmp := talloc(orig.ty, nil); + ecom(src, tmp, orig); + orig = mkunary(Oind, tmp); + orig.ty = tint; + sumark(orig); + + dest := mkunary(Oind, nto); + dest.ty = nto.ty.tof; + sumark(dest); + + genrawop(src, ICASE, orig, nil, clab); + + labs := array[nlab] of Label; + + i := 0; + jmps = nil; + for(tg := t.tags; tg != nil; tg = tg.next){ + stg := tg; + for(; tg.next != nil; tg = tg.next) + if(stg.ty != tg.next.ty) + break; + start := sumark(simplify(mkdeclname(src, stg))); + stop := start; + node := start; + if(stg != tg){ + stop = sumark(simplify(mkdeclname(src, tg))); + node = mkbin(Orange, start, stop); + } + + labs[i].start = start; + labs[i].stop = stop; + labs[i].node = node; + labs[i++].inst = nextinst(); + + genrawop(src, INEW, mktn(tg.ty.tof), nil, nto); + genmove(src, Mas, tg.ty.tof, orig, dest); + + j := genrawop(src, IJMP, nil, nil, nil); + j.branch = jmps; + jmps = j; + } + + # + # this should really be a runtime error + # + wild := genrawop(src, IJMP, nil, nil, nil); + patch(wild, wild); + + patch(jmps, nextinst()); + tfree(tmp); + + if(i > nlab) + fatal("overflowed label tab for pickdupcom"); + + c := ref Case; + c.nlab = i; + c.nsnd = 0; + c.labs = labs; + c.iwild = wild; + + d.ty.cse = c; + usetype(d.ty); + installids(Dglobal, d); +} + +# +# see if name n occurs anywhere in e +# +tupaliased(n, e: ref Node): int +{ + for(;;){ + if(e == nil) + return 0; + if(e.op == Oname && e.decl == n.decl) + return 1; + if(tupaliased(n, e.left)) + return 1; + e = e.right; + } + return 0; +} + +# +# see if any name in n occurs anywere in e +# +tupsaliased(n, e: ref Node): int +{ + for(;;){ + if(n == nil) + return 0; + if(n.op == Oname && tupaliased(n, e)) + return 1; + if(tupsaliased(n.left, e)) + return 1; + n = n.right; + } + return 0; +} + +# +# put unaddressable constants in the global data area +# +globalconst(n: ref Node): ref Decl +{ + s := enter(".i." + hex(int n.c.val, 8), 0); + d := s.decl; + if(d == nil){ + d = mkids(n.src, s, tint, nil); + installids(Dglobal, d); + d.init = n; + d.refs++; + } + return d; +} + +globalBconst(n: ref Node): ref Decl +{ + s := enter(".B." + bhex(n.c.val, 16), 0); + d := s.decl; + if(d == nil){ + d = mkids(n.src, s, tbig, nil); + installids(Dglobal, d); + d.init = n; + d.refs++; + } + return d; +} + +globalbconst(n: ref Node): ref Decl +{ + s := enter(".b." + hex(int n.c.val & 16rff, 2), 0); + d := s.decl; + if(d == nil){ + d = mkids(n.src, s, tbyte, nil); + installids(Dglobal, d); + d.init = n; + d.refs++; + } + return d; +} + +globalfconst(n: ref Node): ref Decl +{ + ba := array[8] of byte; + export_real(ba, array[] of {n.c.rval}); + fs := ".f."; + for(i := 0; i < 8; i++) + fs += hex(int ba[i], 2); + if(fs != ".f." + bhex(math->realbits64(n.c.rval), 16)) + fatal("bad globalfconst number"); + s := enter(fs, 0); + d := s.decl; + if(d == nil){ + d = mkids(n.src, s, treal, nil); + installids(Dglobal, d); + d.init = n; + d.refs++; + } + return d; +} + +globalsconst(n: ref Node): ref Decl +{ + s := n.decl.sym; + n.decl = nil; + d := s.decl; + if(d == nil){ + d = mkids(n.src, s, tstring, nil); + installids(Dglobal, d); + d.init = n; + } + d.refs++; + n.decl = d; + return d; +} + +# +# make a global of type t +# used to make initialized data +# +globalztup(t: ref Type): ref Decl +{ + z := ".z." + string t.size + "."; + desc := t.decl.desc; + for(i := 0; i < desc.nmap; i++) + z += hex(int desc.map[i], 2); + s := enter(z, 0); + d := s.decl; + if(d == nil){ + d = mkids(t.src, s, t, nil); + installids(Dglobal, d); + d.init = nil; + } + d.refs++; + return d; +} + +subst(d: ref Decl, e: ref Node, n: ref Node): ref Node +{ + if(n == nil) + return nil; + if(n.op == Oname){ + if(d == n.decl){ + n = dupn(0, nosrc, e); + n.ty = d.ty; + } + return n; + } + n.left = subst(d, e, n.left); + n.right = subst(d, e, n.right); + return n; +} + +inline(n: ref Node): ref Node +{ + e, tn: ref Node; + t: ref Type; + d: ref Decl; + +if(debug['z']) sys->print("inline1: %s\n", nodeconv(n)); + if(n.left.op == Oname) + d = n.left.decl; + else + d = n.left.right.decl; + e = d.init; + t = e.ty; + e = dupn(1, n.src, e.right.left.left); + n = n.right; + for(d = t.ids; d != nil && n != nil; d = d.next){ + if(hasside(n.left, 0) && occurs(d, e) != 1){ + tn = talloc(d.ty, nil); + e = mkbin(Ocomma, mkbin(Oas, tn, n.left), subst(d, tn, e)); + e.ty = e.right.ty; + e.left.ty = d.ty; + } + else + e = subst(d, n.left, e); + n = n.right; + } + if(d != nil || n != nil) + fatal("bad arg match in inline()"); +if(debug['z']) sys->print("inline2: %s\n", nodeconv(e)); + return e; +} + +fpcall(src: Src, op: int, n: ref Node, ret: ref Node) +{ + tp, e, mod, ind: ref Node; + + e = n.left.left; + if(e.addable >= Rcant) + (e, tp) = eacom(e, nil); + mod = mkunary(Oind, e); + ind = mkunary(Oind, mkbin(Oadd, dupn(0, src, e), mkconst(src, big IBY2WD))); + n.left = mkbin(Omdot, mod, ind); + n.left.ty = e.ty.tof; + mod.ty = ind.ty = ind.left.ty = ind.left.right.ty = tint; + sumark(n); + callcom(src, op, n, ret); + tfree(tp); +} diff --git a/appl/cmd/limbo/gen.b b/appl/cmd/limbo/gen.b new file mode 100644 index 00000000..062980fb --- /dev/null +++ b/appl/cmd/limbo/gen.b @@ -0,0 +1,1012 @@ + blocks: int; # nesting of blocks while generating code + zinst: Inst; + firstinst: ref Inst; + lastinst: ref Inst; + +include "disoptab.m"; + +addrmode := array[int Rend] of +{ + int Rreg => Afp, + int Rmreg => Amp, + int Roff => Aoff, + int Rnoff => Anoff, + int Rdesc => Adesc, + int Rdescp => Adesc, + int Rconst => Aimm, + int Radr => Afpind, + int Rmadr => Ampind, + int Rpc => Apc, + int Rldt => Aldt, + * => Aerr, +}; + +wtemp: ref Decl; +bigtemp: ref Decl; +ntemp: int; +retnode: ref Node; +nilnode: ref Node; + +blockstack: array of int; +blockdep: int; +nblocks: int; +ntoz: ref Node; + +#znode: Node; + +genstart() +{ + d := mkdecl(nosrc, Dlocal, tint); + d.sym = enter(".ret", 0); + d.offset = IBY2WD * REGRET; + + retnode = ref znode; + retnode.op = Oname; + retnode.addable = Rreg; + retnode.decl = d; + retnode.ty = tint; + + zinst.op = INOP; + zinst.sm = Anone; + zinst.dm = Anone; + zinst.mm = Anone; + + firstinst = ref zinst; + lastinst = firstinst; + + nilnode = ref znode; + nilnode.op = Oname; + nilnode.addable = Rmreg; + nilnode.decl = nildecl; + nilnode.ty = nildecl.ty; + + blocks = -1; + blockdep = 0; + nblocks = 0; +} + +# +# manage nested control flow blocks +# +pushblock(): int +{ + if(blockdep >= len blockstack){ + bs := array[blockdep + 32] of int; + bs[0:] = blockstack; + blockstack = bs; + } + blockstack[blockdep++] = blocks; + return blocks = nblocks++; +} + +repushblock(b: int) +{ + blockstack[blockdep++] = blocks; + blocks = b; +} + +popblock() +{ + blocks = blockstack[blockdep -= 1]; +} + +tinit() +{ + wtemp = nil; + bigtemp = nil; +} + +tdecls(): ref Decl +{ + for(d := wtemp; d != nil; d = d.next){ + if(d.tref != 1) + fatal("temporary "+d.sym.name+" has "+string(d.tref-1)+" references"); + } + + for(d = bigtemp; d != nil; d = d.next){ + if(d.tref != 1) + fatal("temporary "+d.sym.name+" has "+string(d.tref-1)+" references"); + } + + return appdecls(wtemp, bigtemp); +} + +talloc(t: ref Type, nok: ref Node): ref Node +{ + ok, d: ref Decl; + + ok = nil; + if(nok != nil) + ok = nok.decl; + if(ok == nil || ok.tref == 0 || tattr[ok.ty.kind].isbig != tattr[t.kind].isbig || ok.ty.align != t.align) + ok = nil; + n := ref znode; + n.op = Oname; + n.addable = Rreg; + n.ty = t; + if(tattr[t.kind].isbig){ + desc := mktdesc(t); + if(ok != nil && ok.desc == desc){ + ok.tref++; + ok.refs++; + n.decl = ok; + return n; + } + for(d = bigtemp; d != nil; d = d.next){ + if(d.tref == 1 && d.desc == desc && d.ty.align == t.align){ + d.tref++; + d.refs++; + n.decl = d; + return n; + } + } + d = mkdecl(nosrc, Dlocal, t); + d.desc = desc; + d.tref = 2; + d.refs = 1; + d.sym = enter(".b"+string ntemp++, 0); + d.next = bigtemp; + bigtemp = d; + n.decl = d; + return n; + } + if(ok != nil + && tattr[ok.ty.kind].isptr == tattr[t.kind].isptr + && ok.ty.size == t.size){ + ok.tref++; + n.decl = ok; + return n; + } + for(d = wtemp; d != nil; d = d.next){ + if(d.tref == 1 + && tattr[d.ty.kind].isptr == tattr[t.kind].isptr + && d.ty.size == t.size + && d.ty.align == t.align){ + d.tref++; + n.decl = d; + return n; + } + } + d = mkdecl(nosrc, Dlocal, t); + d.tref = 2; + d.refs = 1; + d.sym = enter(".t"+string ntemp++, 0); + d.next = wtemp; + wtemp = d; + n.decl = d; + return n; +} + +tfree(n: ref Node) +{ + if(n == nil || n.decl == nil) + return; + d := n.decl; + if(d.tref == 0) + return; + + if(d.tref == 1) + fatal("double free of temporary " + d.sym.name); + if (--d.tref == 1) + zcom1(n, nil); + + # + # nil out any pointers so we don't + # hang onto references + # +# +# costs ~7% in instruction count +# if(d.tref != 1) +# return; +# if(!tattr[d.ty.kind].isbig){ +# if(tattr[d.ty.kind].isptr){ # or tmustzero() +# nilnode.decl.refs++; +# genmove(lastinst.src, Mas, d.ty, nilnode, n); +# } +# }else{ +# if(d.desc.nmap != 0){ # tmustzero() is better +# zn := ref znode; +# zn.op = Oname; +# zn.addable = Rmreg; +# zn.decl = globalztup(d.ty); +# zn.ty = d.ty; +# genmove(lastinst.src, Mas, d.ty, zn, n); +# } +# } +} + +tfreelater(n: ref Node) +{ + if(n == nil || n.decl == nil) + return; + d := n.decl; + if(d.tref == 0) + return; + + if(d.tref == 1) + fatal("double free of temporary " + d.sym.name); + if (--d.tref == 1){ + nn := mkn(Oname, nil, nil); + *nn = *n; + nn.left = ntoz; + ntoz = nn; + d.tref++; + } +} + +tfreenow() +{ + nn: ref Node; + + for(n := ntoz; n != nil; n = nn){ + nn = n.left; + n.left = nil; + if(n.decl.tref != 2) + fatal(sprint("bad free of temporary %s", n.decl.sym.name)); + --n.decl.tref; + zcom1(n, nil); + } + ntoz = nil; +} + +# +# realloc a temporary after it's been released +# +tacquire(n: ref Node): ref Node +{ + if(n == nil || n.decl == nil) + return n; + d := n.decl; + if(d.tref == 0) + return n; + # if(d.tref != 1) + # fatal("tacquire ref != 1: "+string d.tref); + d.tref++; + return n; +} + +trelease(n: ref Node) +{ + if(n == nil || n.decl == nil) + return; + d := n.decl; + if(d.tref == 0) + return; + if(d.tref == 1) + fatal("double release of temporary " + d.sym.name); + d.tref--; +} + +mkinst(): ref Inst +{ + in := lastinst.next; + if(in == nil){ + in = ref zinst; + lastinst.next = in; + } + lastinst = in; + in.block = blocks; + if(blocks < 0) + fatal("mkinst no block"); + return in; +} + +nextinst(): ref Inst +{ + in := lastinst.next; + if(in != nil) + return in; + in = ref zinst; + lastinst.next = in; + return in; +} + +# +# allocate a node for returning +# +retalloc(n, nn: ref Node): ref Node +{ + if(nn.ty == tnone) + return nil; + n = ref znode; + n.op = Oind; + n.addable = Radr; + n.left = dupn(1, n.src, retnode); + n.ty = nn.ty; + return n; +} + +genrawop(src: Src, op: int, s, m, d: ref Node): ref Inst +{ + in := mkinst(); + in.op = op; + in.src = src; + if(s != nil){ + in.s = genaddr(s); + in.sm = addrmode[int s.addable]; + } + if(m != nil){ + in.m = genaddr(m); + in.mm = addrmode[int m.addable]; + if(in.mm == Ampind || in.mm == Afpind) + fatal("illegal addressing mode in register "+nodeconv(m)); + } + if(d != nil){ + in.d = genaddr(d); + in.dm = addrmode[int d.addable]; + } + return in; +} + +genop(src: Src, op: int, s, m, d: ref Node): ref Inst +{ + iop := disoptab[op][opind[d.ty.kind]]; + if(iop == 0) + fatal("can't deal with op "+opconv(op)+" on "+nodeconv(s)+" "+nodeconv(m)+" "+nodeconv(d)+" in genop"); + if(iop == IMULX || iop == IDIVX) + return genfixop(src, iop, s, m, d); + in := mkinst(); + in.op = iop; + in.src = src; + if(s != nil){ + in.s = genaddr(s); + in.sm = addrmode[int s.addable]; + } + if(m != nil){ + in.m = genaddr(m); + in.mm = addrmode[int m.addable]; + if(in.mm == Ampind || in.mm == Afpind) + fatal("illegal addressing mode in register "+nodeconv(m)); + } + if(d != nil){ + in.d = genaddr(d); + in.dm = addrmode[int d.addable]; + } + return in; +} + +genbra(src: Src, op: int, s, m: ref Node): ref Inst +{ + t := s.ty; + if(t == tany) + t = m.ty; + iop := disoptab[op][opind[t.kind]]; + if(iop == 0) + fatal("can't deal with op "+opconv(op)+" on "+nodeconv(s)+" "+nodeconv(m)+" in genbra"); + in := mkinst(); + in.op = iop; + in.src = src; + if(s != nil){ + in.s = genaddr(s); + in.sm = addrmode[int s.addable]; + } + if(m != nil){ + in.m = genaddr(m); + in.mm = addrmode[int m.addable]; + if(in.mm == Ampind || in.mm == Afpind) + fatal("illegal addressing mode in register "+nodeconv(m)); + } + return in; +} + +genchan(src: Src, sz: ref Node, mt: ref Type, d: ref Node): ref Inst +{ + reg: Addr; + + regm := Anone; + reg.decl = nil; + reg.reg = 0; + reg.offset = 0; + op := chantab[mt.kind]; + if(op == 0) + fatal("can't deal with op "+string mt.kind+" in genchan"); + + case mt.kind{ + Tadt or + Tadtpick or + Ttuple => + td := mktdesc(mt); + if(td.nmap != 0){ + op++; # sleazy + usedesc(td); + regm = Adesc; + reg.decl = mt.decl; + }else{ + regm = Aimm; + reg.offset = mt.size; + } + } + in := mkinst(); + in.op = op; + in.src = src; + in.s = reg; + in.sm = regm; + if(sz != nil){ + in.m = genaddr(sz); + in.mm = addrmode[int sz.addable]; + } + if(d != nil){ + in.d = genaddr(d); + in.dm = addrmode[int d.addable]; + } + return in; +} + +genmove(src: Src, how: int, mt: ref Type, s, d: ref Node): ref Inst +{ + reg: Addr; + + regm := Anone; + reg.decl = nil; + reg.reg = 0; + reg.offset = 0; + op := movetab[how][mt.kind]; + if(op == 0) + fatal("can't deal with op "+string how+" on "+nodeconv(s)+" "+nodeconv(d)+" in genmove"); + + case mt.kind{ + Tadt or + Tadtpick or + Ttuple or + Texception => + if(mt.size == 0 && how == Mas) + return nil; + td := mktdesc(mt); + if(td.nmap != 0){ + op++; # sleazy + usedesc(td); + regm = Adesc; + reg.decl = mt.decl; + }else{ + regm = Aimm; + reg.offset = mt.size; + } + } + in := mkinst(); + in.op = op; + in.src = src; + if(s != nil){ + in.s = genaddr(s); + in.sm = addrmode[int s.addable]; + } + in.m = reg; + in.mm = regm; + if(d != nil){ + in.d = genaddr(d); + in.dm = addrmode[int d.addable]; + } + if(s.addable == Rpc) + in.op = IMOVPC; + return in; +} + +patch(b, dst: ref Inst) +{ + n: ref Inst; + + for(; b != nil; b = n){ + n = b.branch; + b.branch = dst; + } +} + +getpc(i: ref Inst): int +{ + if(i.pc == 0 && i != firstinst && (firstinst.op != INOOP || i != firstinst.next)){ + do + i = i.next; + while(i != nil && i.pc == 0); + if(i == nil || i.pc == 0) + fatal("bad instruction in getpc"); + } + return i.pc; +} + +# +# follow all possible paths from n, +# marking reached code, compressing branches, and reclaiming unreached insts +# +reach(in: ref Inst) +{ + foldbranch(in); + last := in; + for(in = in.next; in != nil; in = in.next){ + if(in.reach == byte 0) + last.next = in.next; + else + last = in; + } + lastinst = last; +} + +foldbranch(in: ref Inst) +{ + while(in != nil && in.reach != byte 1){ + in.reach = byte 1; + if(in.branch != nil) + while(in.branch.op == IJMP){ + if(in == in.branch || in.branch == in.branch.branch) + break; + in.branch = in.branch.branch; + } + case in.op{ + IGOTO or + ICASE or + ICASEL or + ICASEC or + IEXC => + foldbranch(in.d.decl.ty.cse.iwild); + lab := in.d.decl.ty.cse.labs; + n := in.d.decl.ty.cse.nlab; + for(i := 0; i < n; i++) + foldbranch(lab[i].inst); + if(in.op == IEXC) + in.op = INOOP; + return; + IEXC0 => + foldbranch(in.branch); + in.op = INOOP; + break; + IRET or + IEXIT or + IRAISE => + return; + IJMP => + b := in.branch; + case b.op{ + ICASE or + ICASEL or + ICASEC or + IRET or + IEXIT => + next := in.next; + *in = *b; + in.next = next; + # b.reach = byte 1; + continue; + } + foldbranch(in.branch); + return; + * => + if(in.branch != nil) + foldbranch(in.branch); + } + + in = in.next; + } +} + +# +# convert the addressable node into an operand +# see the comment for sumark +# +genaddr(n: ref Node): Addr +{ + a: Addr; + + a.reg = 0; + a.offset = 0; + a.decl = nil; + case int n.addable{ + int Rreg => + if(n.decl != nil) + a.decl = n.decl; + else + a = genaddr(n.left); + int Rmreg => + if(n.decl != nil) + a.decl = n.decl; + else + a = genaddr(n.left); + int Rdesc => + a.decl = n.ty.decl; + int Roff or + int Rnoff => + a.decl = n.decl; + int Rconst => + a.offset = int n.c.val; + int Radr => + a = genaddr(n.left); + int Rmadr => + a = genaddr(n.left); + int Rareg or + int Ramreg => + a = genaddr(n.left); + if(n.op == Oadd) + a.reg += int n.right.c.val; + int Raadr or + int Ramadr => + a = genaddr(n.left); + if(n.op == Oadd) + a.offset += int n.right.c.val; + int Rldt => + a.decl = n.decl; + int Rdescp or + int Rpc => + a.decl = n.decl; + * => + fatal("can't deal with "+nodeconv(n)+" in genaddr"); + } + return a; +} + +sameaddr(n, m: ref Node): int +{ + if(n.addable != m.addable) + return 0; + a := genaddr(n); + b := genaddr(m); + return a.offset == b.offset && a.reg == b.reg && a.decl == b.decl; +} + +resolvedesc(mod: ref Decl, length: int, id: ref Decl): int +{ + last: ref Desc; + + g := gendesc(mod, length, id); + g.used = 0; + last = nil; + for(d := descriptors; d != nil; d = d.next){ + if(!d.used){ + if(last != nil) + last.next = d.next; + else + descriptors = d.next; + continue; + } + last = d; + } + + g.next = descriptors; + descriptors = g; + + descid := 0; + for(d = descriptors; d != nil; d = d.next) + d.id = descid++; + if(g.id != 0) + fatal("bad global descriptor id"); + + return descid; +} + +resolvemod(m: ref Decl): int +{ + for(id := m.ty.ids; id != nil; id = id.next){ + case id.store{ + Dfn => + id.iface.pc = id.pc; + id.iface.desc = id.desc; + Dtype => + if(id.ty.kind != Tadt) + break; + for(d := id.ty.ids; d != nil; d = d.next){ + if(d.store == Dfn){ + d.iface.pc = d.pc; + d.iface.desc = d.desc; + } + } + } + } + # for addiface + for(id = m.ty.tof.ids; id != nil; id = id.next){ + if(id.store == Dfn){ + if(id.pc == nil) + id.pc = id.iface.pc; + if(id.desc == nil) + id.desc = id.iface.desc; + } + } + return int m.ty.tof.decl.init.c.val; +} + +# +# place the Tiface decs in another list +# +resolveldts(d: ref Decl): (ref Decl, ref Decl) +{ + d1, ld1, d2, ld2, n: ref Decl; + + d1 = d2 = nil; + ld1 = ld2 = nil; + for( ; d != nil; d = n){ + n = d.next; + d.next = nil; + if(d.ty.kind == Tiface){ + if(d2 == nil) + d2 = d; + else + ld2.next = d; + ld2 = d; + } + else{ + if(d1 == nil) + d1 = d; + else + ld1.next = d; + ld1 = d; + } + } + return (d1, d2); +} + +# +# fix up all pc's +# finalize all data offsets +# fix up instructions with offsets too large +# +resolvepcs(inst: ref Inst): int +{ + d: ref Decl; + + pc := 0; + for(in := inst; in != nil; in = in.next){ + if(in.reach == byte 0 || in.op == INOP) + fatal("unreachable pc: "+instconv(in)); + if(in.op == INOOP){ + in.pc = pc; + continue; + } + d = in.s.decl; + if(d != nil){ + if(in.sm == Adesc){ + if(d.desc != nil) + in.s.offset = d.desc.id; + }else + in.s.reg += d.offset; + } + r := in.s.reg; + off := in.s.offset; + if((in.sm == Afpind || in.sm == Ampind) + && (r >= MaxReg || off >= MaxReg)) + fatal("big offset in "+instconv(in)); + + d = in.m.decl; + if(d != nil){ + if(in.mm == Adesc){ + if(d.desc != nil) + in.m.offset = d.desc.id; + }else + in.m.reg += d.offset; + } + v := 0; + case int in.mm{ + int Anone => + break; + int Aimm or + int Apc or + int Adesc => + v = in.m.offset; + int Aoff or + int Anoff => + v = in.m.decl.iface.offset; + int Afp or + int Amp or + int Aldt => + v = in.m.reg; + if(v < 0) + v = 16r8000; + * => + fatal("can't deal with "+instconv(in)+"'s m mode"); + } + if(v > 16r7fff || v < -16r8000){ + case in.op{ + IALT or + IINDX => + rewritedestreg(in, IMOVW, RTemp); + * => + op := IMOVW; + if(isbyteinst[in.op]) + op = IMOVB; + in = rewritesrcreg(in, op, RTemp, pc++); + } + } + + d = in.d.decl; + if(d != nil){ + if(in.dm == Apc) + in.d.offset = d.pc.pc; + else + in.d.reg += d.offset; + } + r = in.d.reg; + off = in.d.offset; + if((in.dm == Afpind || in.dm == Ampind) + && (r >= MaxReg || off >= MaxReg)) + fatal("big offset in "+instconv(in)); + + in.pc = pc; + pc++; + } + for(in = inst; in != nil; in = in.next){ + d = in.s.decl; + if(d != nil && in.sm == Apc) + in.s.offset = d.pc.pc; + d = in.d.decl; + if(d != nil && in.dm == Apc) + in.d.offset = d.pc.pc; + if(in.branch != nil){ + in.dm = Apc; + in.d.offset = in.branch.pc; + } + } + return pc; +} + +# +# fixp up a big register constant uses as a source +# ugly: smashes the instruction +# +rewritesrcreg(in: ref Inst, op: int, treg: int, pc: int): ref Inst +{ + a := in.m; + am := in.mm; + in.mm = Afp; + in.m.reg = treg; + in.m.decl = nil; + + new := ref *in; + + *in = zinst; + in.src = new.src; + in.next = new; + in.op = op; + in.s = a; + in.sm = am; + in.dm = Afp; + in.d.reg = treg; + in.pc = pc; + in.reach = byte 1; + in.block = new.block; + return new; +} + +# +# fix up a big register constant by moving to the destination +# after the instruction completes +# +rewritedestreg(in: ref Inst, op: int, treg: int): ref Inst +{ + n := ref zinst; + n.next = in.next; + in.next = n; + n.src = in.src; + n.op = op; + n.sm = Afp; + n.s.reg = treg; + n.d = in.m; + n.dm = in.mm; + n.reach = byte 1; + n.block = in.block; + + in.mm = Afp; + in.m.reg = treg; + in.m.decl = nil; + + return n; +} + +instconv(in: ref Inst): string +{ + if(in.op == INOP) + return "nop"; + op := ""; + if(in.op >= 0 && in.op < 256) + op = instname[in.op]; + if(op == nil) + op = "?"+string in.op+"?"; + s := "\t" + op + "\t"; + comma := ""; + if(in.sm != Anone){ + s += addrconv(in.sm, in.s); + comma = ","; + } + if(in.mm != Anone){ + s += comma; + s += addrconv(in.mm, in.m); + comma = ","; + } + if(in.dm != Anone){ + s += comma; + s += addrconv(in.dm, in.d); + } + + if(!asmsym) + return s; + + if(in.s.decl != nil && in.sm == Adesc){ + s += "\t#"; + s += dotconv(in.s.decl); + } + if(0 && in.m.decl != nil){ + s += "\t#"; + s += dotconv(in.m.decl); + } + if(in.d.decl != nil && in.dm == Apc){ + s += "\t#"; + s += dotconv(in.d.decl); + } + s += "\t#"; + s += srcconv(in.src); + return s; +} + +addrconv(am: byte, a: Addr): string +{ + s := ""; + case int am{ + int Anone => + break; + int Aimm or + int Apc or + int Adesc => + s = "$" + string a.offset; + int Aoff => + s = "$" + string a.decl.iface.offset; + int Anoff => + s = "-$" + string a.decl.iface.offset; + int Afp => + s = string a.reg + "(fp)"; + int Afpind => + s = string a.offset + "(" + string a.reg + "(fp))"; + int Amp => + s = string a.reg + "(mp)"; + int Ampind => + s = string a.offset + "(" + string a.reg + "(mp))"; + int Aldt => + s = "$" + string a.reg; + * => + s = string a.offset + "(" + string a.reg + "(?" + string am + "?))"; + } + return s; +} + +genstore(src: Src, n: ref Node, offset: int) +{ + de := mkdecl(nosrc, Dlocal, tint); + de.sym = nil; + de.offset = offset; + + d := ref znode; + d.op = Oname; + d.addable = Rreg; + d.decl = de; + d.ty = tint; + genrawop(src, IMOVW, n, nil, d); +} + +genfixop(src: Src, op: int, s, m, d: ref Node): ref Inst +{ + p, a: int; + mm: ref Node; + + if(m == nil) + mm = d; + else + mm = m; + (op, p, a) = fixop(op, mm.ty, s.ty, d.ty); + if(op == IMOVW){ # just zero d + s = sumark(mkconst(src, big 0)); + return genrawop(src, op, s, nil, d); + } + if(op != IMULX && op != IDIVX) + genstore(src, sumark(mkconst(src, big a)), STemp); + genstore(src, sumark(mkconst(src, big p)), DTemp); + i := genrawop(src, op, s, m, d); + return i; +} + +genfixcastop(src: Src, op: int, s, d: ref Node): ref Inst +{ + p, a: int; + m: ref Node; + + (op, p, a) = fixop(op, s.ty, tint, d.ty); + if(op == IMOVW){ # just zero d + s = sumark(mkconst(src, big 0)); + return genrawop(src, op, s, nil, d); + } + m = sumark(mkconst(src, big p)); + if(op != ICVTXX) + genstore(src, sumark(mkconst(src, big a)), STemp); + return genrawop(src, op, s, m, d); +} diff --git a/appl/cmd/limbo/isa.m b/appl/cmd/limbo/isa.m new file mode 100644 index 00000000..9e9936d0 --- /dev/null +++ b/appl/cmd/limbo/isa.m @@ -0,0 +1,247 @@ +# +# VM instruction set +# + INOP, + IALT, + INBALT, + IGOTO, + ICALL, + IFRAME, + ISPAWN, + IRUNT, + ILOAD, + IMCALL, + IMSPAWN, + IMFRAME, + IRET, + IJMP, + ICASE, + IEXIT, + INEW, + INEWA, + INEWCB, + INEWCW, + INEWCF, + INEWCP, + INEWCM, + INEWCMP, + ISEND, + IRECV, + ICONSB, + ICONSW, + ICONSP, + ICONSF, + ICONSM, + ICONSMP, + IHEADB, + IHEADW, + IHEADP, + IHEADF, + IHEADM, + IHEADMP, + ITAIL, + ILEA, + IINDX, + IMOVP, + IMOVM, + IMOVMP, + IMOVB, + IMOVW, + IMOVF, + ICVTBW, + ICVTWB, + ICVTFW, + ICVTWF, + ICVTCA, + ICVTAC, + ICVTWC, + ICVTCW, + ICVTFC, + ICVTCF, + IADDB, + IADDW, + IADDF, + ISUBB, + ISUBW, + ISUBF, + IMULB, + IMULW, + IMULF, + IDIVB, + IDIVW, + IDIVF, + IMODW, + IMODB, + IANDB, + IANDW, + IORB, + IORW, + IXORB, + IXORW, + ISHLB, + ISHLW, + ISHRB, + ISHRW, + IINSC, + IINDC, + IADDC, + ILENC, + ILENA, + ILENL, + IBEQB, + IBNEB, + IBLTB, + IBLEB, + IBGTB, + IBGEB, + IBEQW, + IBNEW, + IBLTW, + IBLEW, + IBGTW, + IBGEW, + IBEQF, + IBNEF, + IBLTF, + IBLEF, + IBGTF, + IBGEF, + IBEQC, + IBNEC, + IBLTC, + IBLEC, + IBGTC, + IBGEC, + ISLICEA, + ISLICELA, + ISLICEC, + IINDW, + IINDF, + IINDB, + INEGF, + IMOVL, + IADDL, + ISUBL, + IDIVL, + IMODL, + IMULL, + IANDL, + IORL, + IXORL, + ISHLL, + ISHRL, + IBNEL, + IBLTL, + IBLEL, + IBGTL, + IBGEL, + IBEQL, + ICVTLF, + ICVTFL, + ICVTLW, + ICVTWL, + ICVTLC, + ICVTCL, + IHEADL, + ICONSL, + INEWCL, + ICASEC, + IINDL, + IMOVPC, + ITCMP, + IMNEWZ, + ICVTRF, + ICVTFR, + ICVTWS, + ICVTSW, + ILSRW, + ILSRL, + IECLR, + INEWZ, + INEWAZ, + IRAISE, + ICASEL, + IMULX, + IDIVX, + ICVTXX, + IMULX0, + IDIVX0, + ICVTXX0, + IMULX1, + IDIVX1, + ICVTXX1, + ICVTFX, + ICVTXF, + IEXPW, + IEXPL, + IEXPF, + ISELF, + # add new operators here + MAXDIS: con iota; + +XMAGIC: con 819248; # Normal magic +SMAGIC: con 923426; # Signed module + +AMP: con 16r00; # Src/Dst op addressing +AFP: con 16r01; +AIMM: con 16r2; +AXXX: con 16r03; +AIND: con 16r04; +AMASK: con 16r07; +AOFF: con 16r08; +AVAL: con 16r10; + +ARM: con 16rC0; # Middle op addressing +AXNON: con 16r00; +AXIMM: con 16r40; +AXINF: con 16r80; +AXINM: con 16rC0; + +DEFZ: con 0; +DEFB: con 1; # Byte +DEFW: con 2; # Word +DEFS: con 3; # Utf-string +DEFF: con 4; # Real value +DEFA: con 5; # Array +DIND: con 6; # Set index +DAPOP: con 7; # Restore address register +DEFL: con 8; # BIG + +DADEPTH: con 4; # Array address stack size + +REGLINK: con 0; +REGFRAME: con 1; +REGMOD: con 2; +REGTYP: con 3; +REGRET: con 4; +NREG: con 5; + +IBY2WD: con 4; +IBY2FT: con 8; +IBY2LG: con 8; + +MUSTCOMPILE: con 1<<0; +DONTCOMPILE: con 1<<1; +SHAREMP: con 1<<2; +DYNMOD: con 1<<3; +HASLDT0: con 1<<4; +HASEXCEPT: con 1<<5; +HASLDT: con 1<<6; + +DMAX: con 1 << 4; + +#define DTYPE(x) (x>>4) +#define DBYTE(x, l) ((x<<4)|l) +#define DMAX (1<<4) +#define DLEN(x) (x& (DMAX-1)) + +DBYTE: con 4; +SRC: con 3; +DST: con 0; + +#define SRC(x) ((x)<<3) +#define DST(x) ((x)<<0) +#define USRC(x) (((x)>>3)&AMASK) +#define UDST(x) ((x)&AMASK) +#define UXSRC(x) ((x)&(AMASK<<3)) +#define UXDST(x) ((x)&(AMASK<<0)) diff --git a/appl/cmd/limbo/lex.b b/appl/cmd/limbo/lex.b new file mode 100644 index 00000000..ae87b4a9 --- /dev/null +++ b/appl/cmd/limbo/lex.b @@ -0,0 +1,1146 @@ +Leof: con -1; +Linestart: con 0; + +Mlower, +Mupper, +Munder, +Mdigit, +Msign, +Mexp, +Mhex, +Mradix: con byte 1 << iota; +Malpha: con Mupper|Mlower|Munder; + +HashSize: con 1024; + +Keywd: adt +{ + name: string; + token: int; +}; + +# +# internals +# +savec: int; +files: array of ref File; # files making up the module, sorted by absolute line +nfiles: int; +lastfile := 0; # index of last file looked up +incpath := array[MaxIncPath] of string; +symbols := array[HashSize] of ref Sym; +strings := array[HashSize] of ref Sym; +map := array[256] of byte; +bins := array [MaxInclude] of ref Iobuf; +bin: ref Iobuf; +linestack := array[MaxInclude] of (int, int); +lineno: int; +linepos: int; +bstack: int; +lasttok: int; +lastyylval: YYSTYPE; +dowarn: int; +maxerr: int; +dosym: int; +toterrors: int; +fabort: int; +srcdir: string; +outfile: string; +stderr: ref Sys->FD; +dontinline: int; + +escmap := array[256] of +{ + '\'' => '\'', + '"' => '"', + '\\' => '\\', + 'a' => '\a', + 'b' => '\b', + 'f' => '\f', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + 'v' => '\v', + '0' => '\u0000', + + * => -1 +}; +unescmap := array[256] of +{ + '\'' => '\'', + '"' => '"', + '\\' => '\\', + '\a' => 'a', + '\b' => 'b', + '\f' => 'f', + '\n' => 'n', + '\r' => 'r', + '\t' => 't', + '\v' => 'v', + '\u0000' => '0', + + * => 0 +}; + +keywords := array [] of +{ + Keywd("adt", Ladt), + Keywd("alt", Lalt), + Keywd("array", Larray), + Keywd("big", Ltid), + Keywd("break", Lbreak), + Keywd("byte", Ltid), + Keywd("case", Lcase), + Keywd("chan", Lchan), + Keywd("con", Lcon), + Keywd("continue", Lcont), + Keywd("cyclic", Lcyclic), + Keywd("do", Ldo), + Keywd("else", Lelse), + Keywd("exception", Lexcept), + Keywd("exit", Lexit), + Keywd("fixed", Lfix), + Keywd("fn", Lfn), + Keywd("for", Lfor), + Keywd("hd", Lhd), + Keywd("if", Lif), + Keywd("implement", Limplement), + Keywd("import", Limport), + Keywd("include", Linclude), + Keywd("int", Ltid), + Keywd("len", Llen), + Keywd("list", Llist), + Keywd("load", Lload), + Keywd("module", Lmodule), + Keywd("nil", Lnil), + Keywd("of", Lof), + Keywd("or", Lor), + Keywd("pick", Lpick), + Keywd("raise", Lraise), + Keywd("raises", Lraises), + Keywd("real", Ltid), + Keywd("ref", Lref), + Keywd("return", Lreturn), + Keywd("self", Lself), + Keywd("spawn", Lspawn), + Keywd("string", Ltid), + Keywd("tagof", Ltagof), + Keywd("tl", Ltl), + Keywd("to", Lto), + Keywd("type", Ltype), + Keywd("while", Lwhile), +}; + +tokwords := array[] of +{ + Keywd("&=", Landeq), + Keywd("|=", Loreq), + Keywd("^=", Lxoreq), + Keywd("<<=", Llsheq), + Keywd(">>=", Lrsheq), + Keywd("+=", Laddeq), + Keywd("-=", Lsubeq), + Keywd("*=", Lmuleq), + Keywd("/=", Ldiveq), + Keywd("%=", Lmodeq), + Keywd("**=", Lexpeq), + Keywd(":=", Ldeclas), + Keywd("||", Loror), + Keywd("&&", Landand), + Keywd("::", Lcons), + Keywd("==", Leq), + Keywd("!=", Lneq), + Keywd("<=", Lleq), + Keywd(">=", Lgeq), + Keywd("<<", Llsh), + Keywd(">>", Lrsh), + Keywd("<-", Lcomm), + Keywd("++", Linc), + Keywd("--", Ldec), + Keywd("->", Lmdot), + Keywd("=>", Llabs), + Keywd("**", Lexp), + Keywd("EOF", Leof), +}; + +lexinit() +{ + for(i := 0; i < 256; i++){ + map[i] = byte 0; + if(i == '_' || i > 16ra0) + map[i] |= Munder; + if(i >= 'A' && i <= 'Z') + map[i] |= Mupper; + if(i >= 'a' && i <= 'z') + map[i] |= Mlower; + if(i >= 'A' && i <= 'F' || i >= 'a' && i <= 'f') + map[i] |= Mhex; + if(i == 'e' || i == 'E') + map[i] |= Mexp; + if(i == 'r' || i == 'R') + map[i] |= Mradix; + if(i == '-' || i == '+') + map[i] |= Msign; + if(i >= '0' && i <= '9') + map[i] |= Mdigit; + } + + for(i = 0; i < len keywords; i++) + enter(keywords[i].name, keywords[i].token); +} + +cmap(c: int): byte +{ + if(c<0) + return byte 0; + if(c<256) + return map[c]; + return Mlower; +} + +lexstart(in: string) +{ + savec = 0; + bstack = 0; + nfiles = 0; + addfile(ref File(in, 1, 0, -1, nil, 0, -1)); + bin = bins[bstack]; + lineno = 1; + linepos = Linestart; + + (srcdir, nil) = str->splitr(in, "/"); +} + +getc(): int +{ + if(c := savec){ + if(savec >= 0){ + linepos++; + savec = 0; + } + return c; + } + c = bin.getc(); + if(c < 0){ + savec = -1; + return savec; + } + linepos++; + return c; +} + +# +# dumps '\u0000' chararcters +# +ungetc(c: int) +{ + if(c > 0) + linepos--; + savec = c; +} + +addinclude(s: string) +{ + for(i := 0; i < MaxIncPath; i++){ + if(incpath[i] == nil){ + incpath[i] = s; + return; + } + } + fatal("out of include path space"); +} + +addfile(f: ref File): int +{ + if(lastfile >= nfiles) + lastfile = 0; + if(nfiles >= len files){ + nf := array[nfiles+32] of ref File; + nf[0:] = files; + files = nf; + } + files[nfiles] = f; + return nfiles++; +} + +# +# include a new file +# +includef(file: ref Sym) +{ + linestack[bstack] = (lineno, linepos); + bstack++; + if(bstack >= MaxInclude) + fatal(lineconv(lineno<<PosBits)+": include file depth too great"); + buf := file.name; + if(buf[0] != '/') + buf = srcdir+buf; + b := bufio->open(buf, Bufio->OREAD); + for(i := 0; b == nil && i < MaxIncPath && incpath[i] != nil && file.name[0] != '/'; i++){ + buf = incpath[i] + "/" + file.name; + b = bufio->open(buf, Bufio->OREAD); + } + bins[bstack] = b; + if(bins[bstack] == nil){ + yyerror("can't include "+file.name+": "+sprint("%r")); + bstack--; + }else{ + addfile(ref File(buf, lineno+1, -lineno, lineno, nil, 0, -1)); + lineno++; + linepos = Linestart; + } + bin = bins[bstack]; +} + +# +# we hit eof in the current file +# revert to the file which included it. +# +popinclude() +{ + savec = 0; + bstack--; + bin = bins[bstack]; + (oline, opos) := linestack[bstack]; + (f, ln) := fline(oline); + lineno++; + linepos = opos; + addfile(ref File(f.name, lineno, ln-lineno, f.in, f.act, f.actoff, -1)); +} + +# +# convert an absolute Line into a file and line within the file +# +fline(absline: int): (ref File, int) +{ + if(absline < files[lastfile].abs + || lastfile+1 < nfiles && absline >= files[lastfile+1].abs){ + lastfile = 0; + l := 0; + r := nfiles - 1; + while(l <= r){ + m := (r + l) / 2; + s := files[m].abs; + if(s <= absline){ + l = m + 1; + lastfile = m; + }else + r = m - 1; + } + } + return (files[lastfile], absline + files[lastfile].off); +} + +# +# read a comment; process #line file renamings +# +lexcom(): int +{ + i := 0; + buf := ""; + while((c := getc()) != '\n'){ + if(c == Bufio->EOF) + return -1; + buf[i++] = c; + } + + lineno++; + linepos = Linestart; + + if(len buf < 6 + || buf[len buf - 1] != '"' + || buf[:5] != "line " && buf[:5] != "line\t") + return 0; + for(s := 5; buf[s] == ' ' || buf[s] == '\t'; s++) + ; + if((cmap(buf[s]) & Mdigit) == byte 0) + return 0; + n := 0; + for(; (cmap(c = buf[s]) & Mdigit) != byte 0; s++) + n = n * 10 + c - '0'; + for(; buf[s] == ' ' || buf[s] == '\t'; s++) + ; + if(buf[s++] != '"') + return 0; + buf = buf[s:len buf - 1]; + f := files[nfiles - 1]; + if(n == f.off+lineno && buf == f.name) + return 1; + act := f.name; + actline := lineno + f.off; + if(f.act != nil){ + actline += f.actoff; + act = f.act; + } + addfile(ref File(buf, lineno, n-lineno, f.in, act, actline - n, -1)); + + return 1; +} + +curline(): Line +{ + return (lineno << PosBits) | (linepos & PosMask); +} + +lineconv(line: Line): string +{ + line >>= PosBits; + if(line < 0) + return "<noline>"; + (f, ln) := fline(line); + s := ""; + if(f.in >= 0){ + s = ": " + lineconv(f.in << PosBits); + } + if(f.act != nil) + s = " [ " + f.act + ":" + string(f.actoff+ln) + " ]" + s; + return f.name + ":" + string ln + s; +} + +posconv(s: Line): string +{ + if(s < 0) + return "nopos"; + spos := s & PosMask; + s >>= PosBits; + (f, ln) := fline(s); + return f.name + ":" + string ln + "." + string spos; +} + +srcconv(src: Src): string +{ + s := posconv(src.start); + s[len s] = ','; + s += posconv(src.stop); + return s; +} + +lexid(c: int): int +{ + id := ""; + i := 0; + for(;;){ + if(i < StrSize) + id[i++] = c; + c = getc(); + if(c == Bufio->EOF + || (cmap(c) & (Malpha|Mdigit)) == byte 0){ + ungetc(c); + break; + } + } + sym := enter(id, Lid); + t := sym.token; + if(t == Lid || t == Ltid) + yyctxt.lval.tok.v.idval = sym; + return t; +} + +maxfast := array[37] of +{ + 2 => 31, + 4 => 15, + 8 => 10, + 10 => 9, + 16 => 7, + 32 => 6, + * => 0, +}; + +strtoi(t: string, bbase: big): big +{ + # + # do the first part in ints + # + v := 0; + bv: big; + base := int bbase; + n := maxfast[base]; + + neg := 0; + i := 0; + if(i < len t && t[i] == '-'){ + neg = 1; + i++; + }else if(i < len t && t[i] == '+') + i++; + + for(; i < len t; i++){ + c := t[i]; + if(c >= '0' && c <= '9') + c -= '0'; + else if(c >= 'a' && c <= 'z') + c -= 'a' - 10; + else + c -= 'A' - 10; + if(c >= base){ + yyerror("digit '"+t[i:i+1]+"' is not radix "+string base); + return big -1; + } + if(i < n) + v = v * base + c; + else{ + if(i == n) + bv = big v; + bv = bv * bbase + big c; + } + } + if(i <= n) + bv = big v; + if(neg) + return -bv; + return bv; +} + +digit(c: int, base: int): int +{ + ck: byte; + cc: int; + + cc = c; + ck = cmap(c); + if((ck & Mdigit) != byte 0) + c -= '0'; + else if((ck & Mlower) != byte 0) + c = c - 'a' + 10; + else if((ck & Mupper) != byte 0) + c = c - 'A' + 10; + else if((ck & Munder) != byte 0) + ; + else + return -1; + if(c >= base){ + s := "z"; + s[0] = cc; + yyerror("digit '" + s + "' not radix " + string base); + } + return c; +} + +strtodb(t: string, base: int): real +{ + num, dem, rbase: real; + neg, eneg, dig, exp, c, d: int; + + t[len t] = 0; + + num = 0.0; + rbase = real base; + neg = 0; + dig = 0; + exp = 0; + eneg = 0; + + i := 0; + c = t[i++]; + if(c == '-' || c == '+'){ + if(c == '-') + neg = 1; + c = t[i++]; + } + while((d = digit(c, base)) >= 0){ + num = num*rbase + real d; + c = t[i++]; + } + if(c == '.') + c = t[i++]; + while((d = digit(c, base)) >= 0){ + num = num*rbase + real d; + dig++; + c = t[i++]; + } + if(c == 'e' || c == 'E'){ + c = t[i++]; + if(c == '-' || c == '+'){ + if(c == '-'){ + dig = -dig; + eneg = 1; + } + c = t[i++]; + } + while((d = digit(c, base)) >= 0){ + exp = exp*base + d; + c = t[i++]; + } + } + exp -= dig; + if(exp < 0){ + exp = -exp; + eneg = !eneg; + } + dem = rpow(rbase, exp); + if(eneg) + num /= dem; + else + num *= dem; + if(neg) + return -num; + return num; +} + +# +# parse a numeric identifier +# format [0-9]+(r[0-9A-Za-z]+)? +# or ([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)? +# +lexnum(c: int): int +{ + Int, Radix, RadixSeen, Frac, ExpSeen, ExpSignSeen, Exp, FracB: con iota; + + i := 0; + buf := ""; + buf[i++] = c; + state := Int; + if(c == '.') + state = Frac; + radix := ""; + +done: for(;;){ + c = getc(); + if(c == Bufio->EOF){ + yyerror("end of file in numeric constant"); + return Leof; + } + + ck := cmap(c); + case state{ + Int => + if((ck & Mdigit) != byte 0) + break; + if((ck & Mexp) != byte 0){ + state = ExpSeen; + break; + } + if((ck & Mradix) != byte 0){ + radix = buf; + buf = ""; + i = 0; + state = RadixSeen; + break; + } + if(c == '.'){ + state = Frac; + break; + } + break done; + RadixSeen or + Radix => + if((ck & (Mdigit|Malpha)) != byte 0){ + state = Radix; + break; + } + if(c == '.'){ + state = FracB; + break; + } + break done; + Frac => + if((ck & Mdigit) != byte 0) + break; + if((ck & Mexp) != byte 0) + state = ExpSeen; + else + break done; + FracB => + if((ck & (Mdigit|Malpha)) != byte 0) + break; + break done; + ExpSeen => + if((ck & Msign) != byte 0){ + state = ExpSignSeen; + break; + } + if((ck & Mdigit) != byte 0){ + state = Exp; + break; + } + break done; + ExpSignSeen or + Exp => + if((ck & Mdigit) != byte 0){ + state = Exp; + break; + } + break done; + } + buf[i++] = c; + } + + ungetc(c); + v: big; + case state{ + * => + yyerror("malformed numerical constant '"+radix+buf+"'"); + yyctxt.lval.tok.v.ival = big 0; + return Lconst; + Radix => + v = strtoi(radix, big 10); + if(v < big 2 || v > big 36){ + yyerror("radix '"+radix+"' is not between 2 and 36"); + break; + } + v = strtoi(buf[1:], v); + Int => + v = strtoi(buf, big 10); + Frac or + Exp => + yyctxt.lval.tok.v.rval = real buf; + return Lrconst; + FracB => + v = strtoi(radix, big 10); + if(v < big 2 || v > big 36){ + yyerror("radix '"+radix+"' is not between 2 and 36"); + break; + } + yyctxt.lval.tok.v.rval = strtodb(buf[1:], int v); + return Lrconst; + } + yyctxt.lval.tok.v.ival = v; + return Lconst; +} + +escchar(): int +{ + c := getc(); + if(c == Bufio->EOF) + return Bufio->EOF; + if(c == 'u'){ + v := 0; + for(i := 0; i < 4; i++){ + c = getc(); + ck := cmap(c); + if(c == Bufio->EOF || (ck & (Mdigit|Mhex)) == byte 0){ + yyerror("malformed \\u escape sequence"); + ungetc(c); + break; + } + if((ck & Mdigit) != byte 0) + c -= '0'; + else if((ck & Mlower) != byte 0) + c = c - 'a' + 10; + else if((ck & Mupper) != byte 0) + c = c - 'A' + 10; + v = v * 16 + c; + } + return v; + } + if(c < len escmap && (v := escmap[c]) >= 0) + return v; + s := ""; + s[0] = c; + yyerror("unrecognized escape \\"+s); + return c; +} + +lexstring() +{ + s := ""; + i := 0; +loop: for(;;){ + case c := getc(){ + '\\' => + c = escchar(); + if(c != Bufio->EOF) + s[i++] = c; + Bufio->EOF => + yyerror("end of file in string constant"); + break loop; + '\n' => + yyerror("newline in string constant"); + lineno++; + linepos = Linestart; + break loop; + '"' => + break loop; + * => + s[i++] = c; + } + } + yyctxt.lval.tok.v.idval = enterstring(s); +} + +lex(): int +{ + for(;;){ + yyctxt.lval.tok.src.start = (lineno << PosBits) | (linepos & PosMask); + case c := getc(){ + Bufio->EOF => + bin.close(); + if(bstack == 0) + return Leof; + popinclude(); + '#' => + if(lexcom() < 0){ + bin.close(); + if(bstack == 0) + return Leof; + popinclude(); + } + '\n' => + lineno++; + linepos = Linestart; + ' ' or + '\t' or + '\r' or + '\v' => + ; + '"' => + lexstring(); + return Lsconst; + '\'' => + c = getc(); + if(c == '\\') + c = escchar(); + if(c == Bufio->EOF){ + yyerror("end of file in character constant"); + return Bufio->EOF; + }else + yyctxt.lval.tok.v.ival = big c; + c = getc(); + if(c != '\''){ + yyerror("missing closing '"); + ungetc(c); + } + return Lconst; + '(' or + ')' or + '[' or + ']' or + '{' or + '}' or + ',' or + ';' or + '~' => + return c; + ':' => + c = getc(); + if(c == ':') + return Lcons; + if(c == '=') + return Ldeclas; + ungetc(c); + return ':'; + '.' => + c = getc(); + ungetc(c); + if(c != Bufio->EOF && (cmap(c) & Mdigit) != byte 0) + return lexnum('.'); + return '.'; + '|' => + c = getc(); + if(c == '=') + return Loreq; + if(c == '|') + return Loror; + ungetc(c); + return '|'; + '&' => + c = getc(); + if(c == '=') + return Landeq; + if(c == '&') + return Landand; + ungetc(c); + return '&'; + '^' => + c = getc(); + if(c == '=') + return Lxoreq; + ungetc(c); + return '^'; + '*' => + c = getc(); + if(c == '=') + return Lmuleq; + if(c == '*'){ + c = getc(); + if(c == '=') + return Lexpeq; + ungetc(c); + return Lexp; + } + ungetc(c); + return '*'; + '/' => + c = getc(); + if(c == '=') + return Ldiveq; + ungetc(c); + return '/'; + '%' => + c = getc(); + if(c == '=') + return Lmodeq; + ungetc(c); + return '%'; + '=' => + c = getc(); + if(c == '=') + return Leq; + if(c == '>') + return Llabs; + ungetc(c); + return '='; + '!' => + c = getc(); + if(c == '=') + return Lneq; + ungetc(c); + return '!'; + '>' => + c = getc(); + if(c == '=') + return Lgeq; + if(c == '>'){ + c = getc(); + if(c == '=') + return Lrsheq; + ungetc(c); + return Lrsh; + } + ungetc(c); + return '>'; + '<' => + c = getc(); + if(c == '=') + return Lleq; + if(c == '-') + return Lcomm; + if(c == '<'){ + c = getc(); + if(c == '=') + return Llsheq; + ungetc(c); + return Llsh; + } + ungetc(c); + return '<'; + '+' => + c = getc(); + if(c == '=') + return Laddeq; + if(c == '+') + return Linc; + ungetc(c); + return '+'; + '-' => + c = getc(); + if(c == '=') + return Lsubeq; + if(c == '-') + return Ldec; + if(c == '>') + return Lmdot; + ungetc(c); + return '-'; + '0' to '9' => + return lexnum(c); + * => + if((cmap(c) & Malpha) != byte 0) + return lexid(c); + s := ""; + s[0] = c; + yyerror("unknown character '"+s+"'"); + } + } +} + +YYLEX.lex(nil: self ref YYLEX): int +{ + t := lex(); + yyctxt.lval.tok.src.stop = (lineno << PosBits) | (linepos & PosMask); + lasttok = t; + lastyylval = yyctxt.lval; + return t; +} + +toksp(t: int): string +{ + case(t){ + Lconst => + return sprint("%bd", lastyylval.tok.v.ival); + Lrconst => + return sprint("%f", lastyylval.tok.v.rval); + Lsconst => + return sprint("\"%s\"", lastyylval.tok.v.idval.name); + Ltid or Lid => + return lastyylval.tok.v.idval.name; + } + for(i := 0; i < len keywords; i++) + if(t == keywords[i].token) + return keywords[i].name; + for(i = 0; i < len tokwords; i++) + if(t == tokwords[i].token) + return tokwords[i].name; + if(t < 0 || t > 255) + fatal(sprint("bad token %d in toksp()", t)); + buf := "Z"; + buf[0] = t; + return buf; +} + +enterstring(name: string): ref Sym +{ + h := 0; + n := len name; + for(i := 0; i < n; i++){ + c := d := name[i]; + c ^= c << 6; + h += (c << 11) ^ (c >> 1); + h ^= (d << 14) + (d << 7) + (d << 4) + d; + } + + h &= HashSize-1; + for(s := strings[h]; s != nil; s = s.next){ + sn := s.name; + if(len sn == n && sn == name) + return s; + } + + + s = ref Sym; + s.token = -1; + s.name = name; + s.hash = h; + s.next = strings[h]; + strings[h] = s; + return s; +} + +stringcat(s, t: ref Sym): ref Sym +{ + return enterstring(s.name+t.name); +} + +enter(name: string, token: int): ref Sym +{ + h := 0; + n := len name; + for(i := 0; i < n; i++){ + c := d := name[i]; + c ^= c << 6; + h += (c << 11) ^ (c >> 1); + h ^= (d << 14) + (d << 7) + (d << 4) + d; + } + + h &= HashSize-1; + for(s := symbols[h]; s != nil; s = s.next){ + sn := s.name; + if(len sn == n && sn == name) + return s; + } + + if(token == 0) + token = Lid; + s = ref Sym; + s.token = token; + s.name = name; + s.hash = h; + s.next = symbols[h]; + symbols[h] = s; + return s; +} + +stringpr(sym: ref Sym): string +{ + s := sym.name; + n := len s; + if(n > 10) + n = 10; + sb := "\""; + for(i := 0; i < n; i++){ + case c := s[i]{ + '\\' or + '"' or + '\n' or + '\r' or + '\t' or + '\b' or + '\a' or + '\v' or + '\u0000' => + sb[len sb] = '\\'; + sb[len sb] = unescmap[c]; + * => + sb[len sb] = c; + } + } + if(n != len s) + sb += "..."; + sb[len sb] = '"'; + return sb; +} + +warn(line: Line, msg: string) +{ + if(errors || !dowarn) + return; + fprint(stderr, "%s: warning: %s\n", lineconv(line), msg); +} + +nwarn(n: ref Node, msg: string) +{ + if(errors || !dowarn) + return; + fprint(stderr, "%s: warning: %s\n", lineconv(n.src.start), msg); +} + +error(line: Line, msg: string) +{ + errors++; + if(errors > maxerr) + return; + fprint(stderr, "%s: %s\n", lineconv(line), msg); + if(errors == maxerr) + fprint(stderr, "too many errors, stopping\n"); +} + +nerror(n: ref Node, msg: string) +{ + errors++; + if(errors > maxerr) + return; + fprint(stderr, "%s: %s\n", lineconv(n.src.start), msg); + if(errors == maxerr) + fprint(stderr, "too many errors, stopping\n"); +} + +YYLEX.error(nil: self ref YYLEX, msg: string) +{ + errors++; + if(errors > maxerr) + return; + if(lasttok != 0) + fprint(stderr, "%s: near ` %s ` : %s\n", lineconv(lineno<<PosBits), toksp(lasttok), msg); + else + fprint(stderr, "%s: %s\n", lineconv(lineno<<PosBits), msg); + if(errors == maxerr) + fprint(stderr, "too many errors, stopping\n"); +} + +yyerror(msg: string) +{ + yyctxt.error(msg); +} + +fatal(msg: string) +{ + if(errors == 0 || fabort) + fprint(stderr, "fatal limbo compiler error: %s\n", msg); + if(bout != nil) + sys->remove(outfile); + if(fabort){ + n: ref Node; + if(n.ty == nil); # abort + } + raise "fail:error"; +} + +hex(v, n: int): string +{ + return sprint("%.*ux", n, v); +} + +bhex(v: big, n: int): string +{ + return sprint("%.*bux", n, v); +} diff --git a/appl/cmd/limbo/limbo.b b/appl/cmd/limbo/limbo.b new file mode 100644 index 00000000..c8f51779 --- /dev/null +++ b/appl/cmd/limbo/limbo.b @@ -0,0 +1,3099 @@ +implement Limbo; + +#line 2 "limbo.y" +include "limbo.m"; +include "draw.m"; + +Limbo: module { + + init: fn(ctxt: ref Draw->Context, argv: list of string); + + YYSTYPE: adt{ + tok: Tok; + ids: ref Decl; + node: ref Node; + ty: ref Type; + types: ref Typelist; + }; + + YYLEX: adt { + lval: YYSTYPE; + lex: fn(nil: self ref YYLEX): int; + error: fn(nil: self ref YYLEX, err: string); + }; +Landeq: con 57346; +Loreq: con 57347; +Lxoreq: con 57348; +Llsheq: con 57349; +Lrsheq: con 57350; +Laddeq: con 57351; +Lsubeq: con 57352; +Lmuleq: con 57353; +Ldiveq: con 57354; +Lmodeq: con 57355; +Lexpeq: con 57356; +Ldeclas: con 57357; +Lload: con 57358; +Loror: con 57359; +Landand: con 57360; +Lcons: con 57361; +Leq: con 57362; +Lneq: con 57363; +Lleq: con 57364; +Lgeq: con 57365; +Llsh: con 57366; +Lrsh: con 57367; +Lexp: con 57368; +Lcomm: con 57369; +Linc: con 57370; +Ldec: con 57371; +Lof: con 57372; +Lref: con 57373; +Lif: con 57374; +Lelse: con 57375; +Lfn: con 57376; +Lexcept: con 57377; +Lraises: con 57378; +Lmdot: con 57379; +Lto: con 57380; +Lor: con 57381; +Lrconst: con 57382; +Lconst: con 57383; +Lid: con 57384; +Ltid: con 57385; +Lsconst: con 57386; +Llabs: con 57387; +Lnil: con 57388; +Llen: con 57389; +Lhd: con 57390; +Ltl: con 57391; +Ltagof: con 57392; +Limplement: con 57393; +Limport: con 57394; +Linclude: con 57395; +Lcon: con 57396; +Ltype: con 57397; +Lmodule: con 57398; +Lcyclic: con 57399; +Ladt: con 57400; +Larray: con 57401; +Llist: con 57402; +Lchan: con 57403; +Lself: con 57404; +Ldo: con 57405; +Lwhile: con 57406; +Lfor: con 57407; +Lbreak: con 57408; +Lalt: con 57409; +Lcase: con 57410; +Lpick: con 57411; +Lcont: con 57412; +Lreturn: con 57413; +Lexit: con 57414; +Lspawn: con 57415; +Lraise: con 57416; +Lfix: con 57417; + +}; + +#line 27 "limbo.y" + # + # lex.b + # + signdump: string; # name of function for sig debugging + superwarn: int; + debug: array of int; + noline: Line; + nosrc: Src; + arrayz: int; + emitcode: string; # emit stub routines for system module functions + emitdyn: int; # emit as above but for dynamic modules + emitsbl: string; # emit symbol file for sysm modules + emitstub: int; # emit type and call frames for system modules + emittab: string; # emit table of runtime functions for this module + errors: int; + mustcompile: int; + dontcompile: int; + asmsym: int; # generate symbols in assembly language? + bout: ref Bufio->Iobuf; # output file + bsym: ref Bufio->Iobuf; # symbol output file; nil => no sym out + gendis: int; # generate dis or asm? + fixss: int; + newfnptr: int; # ISELF and -ve indices + optims: int; + + # + # decls.b + # + scope: int; + # impmod: ref Sym; # name of implementation module + impmods: ref Decl; # name of implementation module(s) + nildecl: ref Decl; # declaration for limbo's nil + selfdecl: ref Decl; # declaration for limbo's self + + # + # types.b + # + tany: ref Type; + tbig: ref Type; + tbyte: ref Type; + terror: ref Type; + tint: ref Type; + tnone: ref Type; + treal: ref Type; + tstring: ref Type; + texception: ref Type; + tunknown: ref Type; + tfnptr: ref Type; + rtexception: ref Type; + descriptors: ref Desc; # list of all possible descriptors + tattr: array of Tattr; + + # + # nodes.b + # + opcommute: array of int; + oprelinvert: array of int; + isused: array of int; + casttab: array of array of int; # instruction to cast from [1] to [2] + + nfns: int; # functions defined + nfnexp: int; + fns: array of ref Decl; # decls for fns defined + tree: ref Node; # root of parse tree + + parset: int; # time to parse + checkt: int; # time to typecheck + gent: int; # time to generate code + writet: int; # time to write out code + symt: int; # time to write out symbols +YYEOFCODE: con 1; +YYERRCODE: con 2; +YYMAXDEPTH: con 200; + +#line 1630 "limbo.y" + + +include "keyring.m"; + +sys: Sys; + print, fprint, sprint: import sys; + +bufio: Bufio; + Iobuf: import bufio; + +str: String; + +keyring:Keyring; + md5: import keyring; + +math: Math; + import_real, export_real, isnan: import math; + +yyctxt: ref YYLEX; + +canonnan: real; + +debug = array[256] of {* => 0}; + +noline = -1; +nosrc = Src(-1, -1); + +infile: string; + +# front end +include "arg.m"; +include "lex.b"; +include "types.b"; +include "nodes.b"; +include "decls.b"; + +include "typecheck.b"; + +# back end +include "gen.b"; +include "ecom.b"; +include "asm.b"; +include "dis.b"; +include "sbl.b"; +include "stubs.b"; +include "com.b"; +include "optim.b"; + +init(nil: ref Draw->Context, argv: list of string) +{ + s: string; + + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + math = load Math Math->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil){ + sys->print("can't load %s: %r\n", Bufio->PATH); + raise("fail:bad module"); + } + str = load String String->PATH; + if(str == nil){ + sys->print("can't load %s: %r\n", String->PATH); + raise("fail:bad module"); + } + + stderr = sys->fildes(2); + yyctxt = ref YYLEX; + + math->FPcontrol(0, Math->INVAL|Math->ZDIV|Math->OVFL|Math->UNFL|Math->INEX); + na := array[1] of {0.}; + import_real(array[8] of {byte 16r7f, * => byte 16rff}, na); + canonnan = na[0]; + if(!isnan(canonnan)) + fatal("bad canonical NaN"); + + lexinit(); + typeinit(); + optabinit(); + + gendis = 1; + asmsym = 0; + maxerr = 20; + ofile := ""; + ext := ""; + + arg := Arg.init(argv); + while(c := arg.opt()){ + case c{ + 'Y' => + emitsbl = arg.arg(); + if(emitsbl == nil) + usage(); + 'C' => + dontcompile = 1; + 'D' => + # + # debug flags: + # + # a alt compilation + # A array constructor compilation + # b boolean and branch compilation + # c case compilation + # d function declaration + # D descriptor generation + # e expression compilation + # E addressable expression compilation + # f print arguments for compiled functions + # F constant folding + # g print out globals + # m module declaration and type checking + # n nil references + # s print sizes of output file sections + # S type signing + # t type checking function bodies + # T timing + # v global var and constant compilation + # x adt verification + # Y tuple compilation + # z Z bug fixes + # + s = arg.arg(); + for(i := 0; i < len s; i++){ + c = s[i]; + if(c < len debug) + debug[c] = 1; + } + 'I' => + s = arg.arg(); + if(s == "") + usage(); + addinclude(s); + 'G' => + asmsym = 1; + 'S' => + gendis = 0; + 'a' => + emitstub = 1; + 'A' => + emitstub = emitdyn = 1; + 'c' => + mustcompile = 1; + 'e' => + maxerr = 1000; + 'f' => + fabort = 1; + 'F' => + newfnptr = 1; + 'g' => + dosym = 1; + 'i' => + dontinline = 1; + 'o' => + ofile = arg.arg(); + 'O' => + optims = 1; + 's' => + s = arg.arg(); + if(s != nil) + fixss = int s; + 't' => + emittab = arg.arg(); + if(emittab == nil) + usage(); + 'T' => + emitcode = arg.arg(); + if(emitcode == nil) + usage(); + 'd' => + emitcode = arg.arg(); + if(emitcode == nil) + usage(); + emitdyn = 1; + 'w' => + superwarn = dowarn; + dowarn = 1; + 'x' => + ext = arg.arg(); + 'X' => + signdump = arg.arg(); + 'z' => + arrayz = 1; + * => + usage(); + } + } + + addinclude("/module"); + + argv = arg.argv; + arg = nil; + + if(argv == nil){ + usage(); + }else if(ofile != nil){ + if(len argv != 1) + usage(); + translate(hd argv, ofile, mkfileext(ofile, ".dis", ".sbl")); + }else{ + pr := len argv != 1; + if(ext == ""){ + ext = ".s"; + if(gendis) + ext = ".dis"; + } + for(; argv != nil; argv = tl argv){ + file := hd argv; + (nil, s) = str->splitr(file, "/"); + if(pr) + print("%s:\n", s); + out := mkfileext(s, ".b", ext); + translate(file, out, mkfileext(out, ext, ".sbl")); + } + } + if (toterrors > 0) + raise("fail:errors"); +} + +usage() +{ + fprint(stderr, "usage: limbo [-GSagwe] [-I incdir] [-o outfile] [-{T|t|d} module] [-D debug] file ...\n"); + raise("fail:usage"); +} + +mkfileext(file, oldext, ext: string): string +{ + n := len file; + n2 := len oldext; + if(n >= n2 && file[n-n2:] == oldext) + file = file[:n-n2]; + return file + ext; +} + +translate(in, out, dbg: string) +{ + infile = in; + outfile = out; + errors = 0; + bins[0] = bufio->open(in, Bufio->OREAD); + if(bins[0] == nil){ + fprint(stderr, "can't open %s: %r\n", in); + toterrors++; + return; + } + doemit := emitcode != "" || emitstub || emittab != "" || emitsbl != ""; + if(!doemit){ + bout = bufio->create(out, Bufio->OWRITE, 8r666); + if(bout == nil){ + fprint(stderr, "can't open %s: %r\n", out); + toterrors++; + bins[0].close(); + return; + } + if(dosym){ + bsym = bufio->create(dbg, Bufio->OWRITE, 8r666); + if(bsym == nil) + fprint(stderr, "can't open %s: %r\n", dbg); + } + } + + lexstart(in); + + popscopes(); + typestart(); + declstart(); + nfnexp = 0; + + parset = sys->millisec(); + yyparse(yyctxt); + parset = sys->millisec() - parset; + + checkt = sys->millisec(); + entry := typecheck(!doemit); + checkt = sys->millisec() - checkt; + + modcom(entry); + + fns = nil; + nfns = 0; + descriptors = nil; + + if(debug['T']) + print("times: parse=%d type=%d: gen=%d write=%d symbols=%d\n", + parset, checkt, gent, writet, symt); + + if(bout != nil) + bout.close(); + if(bsym != nil) + bsym.close(); + toterrors += errors; + if(errors && bout != nil) + sys->remove(out); + if(errors && bsym != nil) + sys->remove(dbg); +} + +pwd(): string +{ + workdir := load Workdir Workdir->PATH; + if(workdir == nil) + cd := "/"; + else + cd = workdir->init(); + # sys->print("pwd: %s\n", cd); + return cd; +} + +cleanname(s: string): string +{ + ls, path: list of string; + + if(s == nil) + return nil; + if(s[0] != '/' && s[0] != '\\') + (nil, ls) = sys->tokenize(pwd(), "/\\"); + for( ; ls != nil; ls = tl ls) + path = hd ls :: path; + (nil, ls) = sys->tokenize(s, "/\\"); + for( ; ls != nil; ls = tl ls){ + n := hd ls; + if(n == ".") + ; + else if (n == ".."){ + if(path != nil) + path = tl path; + } + else + path = n :: path; + } + p := ""; + for( ; path != nil; path = tl path) + p = "/" + hd path + p; + if(p == nil) + p = "/"; + # sys->print("cleanname: %s\n", p); + return p; +} + +srcpath(): string +{ + srcp := cleanname(infile); + # sys->print("srcpath: %s\n", srcp); + return srcp; +} +yyexca := array[] of {-1, 1, + 1, -1, + -2, 0, +-1, 3, + 1, 3, + -2, 0, +-1, 17, + 39, 88, + 50, 62, + 54, 88, + 98, 62, + -2, 252, +-1, 211, + 59, 29, + 71, 29, + -2, 0, +-1, 230, + 1, 2, + -2, 0, +-1, 273, + 50, 176, + -2, 257, +-1, 308, + 59, 41, + 71, 41, + 91, 41, + -2, 0, +-1, 310, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 380, + 50, 62, + 98, 62, + -2, 252, +-1, 381, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 387, + 53, 71, + 54, 71, + -2, 110, +-1, 389, + 53, 72, + 54, 72, + -2, 112, +-1, 421, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 428, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 443, + 53, 71, + 54, 71, + -2, 111, +-1, 444, + 53, 72, + 54, 72, + -2, 113, +-1, 452, + 71, 279, + 98, 279, + -2, 163, +-1, 469, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 486, + 50, 126, + 98, 126, + -2, 239, +-1, 491, + 71, 276, + -2, 0, +-1, 503, + 59, 47, + 71, 47, + -2, 0, +-1, 508, + 59, 41, + 71, 41, + 91, 41, + -2, 0, +-1, 514, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 548, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 554, + 71, 154, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 562, + 56, 59, + 62, 59, + -2, 62, +-1, 568, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 573, + 71, 157, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 577, + 72, 176, + -2, 163, +-1, 596, + 71, 160, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 602, + 71, 168, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 606, + 72, 175, + 85, 150, + 86, 150, + 87, 150, + 89, 150, + 90, 150, + 91, 150, + -2, 0, +-1, 609, + 50, 62, + 56, 171, + 62, 171, + 98, 62, + -2, 252, +}; +YYNPROD: con 284; +YYPRIVATE: con 57344; +yytoknames: array of string; +yystates: array of string; +yydebug: con 0; +YYLAST: con 2727; +yyact := array[] of { + 379, 591, 453, 364, 505, 384, 412, 310, 369, 314, + 359, 451, 449, 185, 84, 83, 432, 298, 270, 15, + 8, 49, 213, 102, 320, 12, 42, 110, 48, 78, + 79, 80, 4, 35, 198, 51, 23, 459, 363, 6, + 458, 3, 6, 544, 486, 491, 365, 14, 382, 21, + 14, 353, 400, 293, 350, 423, 225, 285, 118, 330, + 286, 226, 223, 46, 31, 112, 465, 11, 105, 517, + 566, 599, 308, 186, 164, 165, 166, 167, 168, 169, + 170, 171, 172, 173, 174, 175, 176, 43, 117, 309, + 182, 183, 184, 349, 71, 10, 349, 205, 10, 208, + 93, 286, 286, 422, 32, 37, 119, 114, 40, 294, + 349, 294, 32, 585, 44, 286, 119, 428, 427, 426, + 547, 430, 429, 431, 231, 232, 233, 234, 235, 236, + 237, 238, 239, 240, 241, 242, 485, 244, 245, 246, + 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, + 257, 258, 259, 260, 261, 262, 263, 264, 265, 186, + 6, 483, 273, 230, 482, 22, 481, 438, 14, 22, + 271, 424, 267, 210, 5, 409, 407, 565, 279, 187, + 513, 410, 284, 87, 420, 419, 418, 228, 94, 288, + 85, 312, 311, 90, 289, 99, 269, 415, 217, 202, + 5, 415, 47, 92, 82, 22, 209, 26, 303, 25, + 212, 19, 24, 218, 229, 508, 10, 354, 96, 601, + 98, 95, 100, 595, 101, 88, 89, 86, 572, 194, + 195, 17, 87, 557, 553, 18, 297, 19, 536, 85, + 525, 77, 90, 313, 326, 305, 490, 13, 512, 112, + 323, 318, 92, 82, 468, 207, 399, 17, 87, 383, + 498, 18, 215, 23, 479, 85, 316, 467, 90, 6, + 398, 2, 500, 13, 88, 89, 86, 14, 92, 82, + 194, 195, 361, 186, 43, 282, 219, 340, 194, 195, + 77, 114, 193, 211, 487, 499, 338, 182, 500, 559, + 88, 89, 86, 336, 194, 195, 488, 535, 87, 324, + 341, 44, 87, 325, 580, 85, 77, 579, 90, 85, + 381, 348, 90, 206, 19, 10, 358, 357, 92, 82, + 214, 393, 92, 82, 604, 33, 389, 387, 391, 448, + 614, 194, 195, 402, 45, 539, 194, 195, 18, 392, + 88, 89, 86, 356, 88, 89, 86, 321, 194, 195, + 192, 194, 195, 403, 404, 530, 77, 281, 317, 108, + 77, 416, 493, 19, 19, 421, 436, 495, 612, 186, + 301, 385, 604, 435, 564, 437, 507, 216, 603, 493, + 434, 441, 439, 115, 115, 600, 562, 116, 116, 452, + 543, 340, 183, 444, 443, 504, 414, 45, 316, 493, + 22, 18, 493, 480, 493, 597, 336, 493, 588, 70, + 574, 493, 63, 555, 540, 73, 473, 494, 469, 433, + 478, 442, 476, 76, 75, 69, 68, 74, 291, 18, + 54, 55, 62, 60, 61, 64, 87, 290, 268, 452, + 157, 91, 120, 85, 91, 104, 90, 65, 66, 67, + 159, 489, 507, 39, 497, 103, 92, 82, 194, 195, + 594, 510, 186, 77, 568, 477, 168, 487, 36, 518, + 523, 466, 522, 515, 516, 511, 406, 417, 88, 89, + 86, 87, 452, 527, 523, 529, 528, 408, 85, 329, + 533, 90, 593, 526, 77, 91, 224, 91, 532, 537, + 106, 92, 82, 34, 545, 91, 401, 177, 546, 541, + 523, 331, 552, 397, 335, 556, 91, 592, 299, 554, + 332, 300, 201, 88, 89, 86, 158, 200, 161, 197, + 162, 163, 560, 563, 441, 316, 179, 446, 445, 77, + 160, 159, 570, 328, 227, 577, 569, 575, 571, 573, + 81, 477, 181, 97, 177, 346, 180, 523, 178, 583, + 345, 41, 584, 203, 577, 606, 587, 138, 139, 140, + 137, 135, 586, 72, 561, 548, 386, 327, 414, 222, + 596, 221, 549, 73, 598, 477, 475, 577, 602, 605, + 91, 76, 75, 45, 607, 74, 611, 18, 474, 471, + 613, 425, 137, 135, 196, 477, 199, 91, 39, 188, + 91, 91, 19, 91, 204, 524, 243, 360, 538, 307, + 91, 183, 168, 287, 29, 220, 141, 142, 138, 139, + 140, 137, 135, 368, 91, 91, 30, 121, 1, 464, + 272, 274, 315, 477, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 136, 542, 156, 155, + 154, 153, 152, 151, 149, 150, 145, 146, 147, 148, + 144, 143, 141, 142, 138, 139, 140, 137, 135, 582, + 343, 581, 413, 503, 502, 590, 27, 589, 91, 144, + 143, 141, 142, 138, 139, 140, 137, 135, 28, 283, + 16, 411, 306, 355, 91, 9, 551, 550, 521, 520, + 91, 7, 450, 337, 266, 506, 292, 371, 109, 295, + 296, 107, 113, 111, 20, 87, 38, 0, 0, 199, + 0, 91, 85, 0, 0, 90, 0, 99, 342, 0, + 0, 91, 91, 319, 322, 92, 82, 0, 0, 0, + 0, 87, 0, 0, 0, 91, 91, 0, 85, 91, + 96, 90, 98, 95, 0, 0, 0, 88, 89, 86, + 0, 92, 82, 0, 0, 0, 0, 0, 0, 0, + 0, 87, 282, 77, 0, 0, 0, 0, 85, 0, + 0, 90, 0, 88, 89, 86, 0, 333, 91, 0, + 455, 92, 82, 0, 0, 0, 0, 91, 0, 77, + 0, 91, 0, 347, 0, 50, 91, 0, 91, 351, + 0, 0, 0, 88, 89, 86, 0, 91, 0, 0, + 52, 53, 454, 91, 0, 0, 59, 72, 0, 77, + 390, 57, 58, 0, 63, 0, 0, 73, 0, 0, + 395, 396, 0, 0, 0, 76, 75, 69, 68, 74, + 0, 18, 54, 55, 62, 60, 61, 64, 405, 0, + 0, 0, 91, 0, 0, 0, 91, 0, 0, 65, + 66, 67, 145, 146, 147, 148, 144, 143, 141, 142, + 138, 139, 140, 137, 135, 77, 0, 91, 0, 0, + 0, 0, 0, 366, 0, 0, 0, 196, 0, 0, + 91, 0, 0, 0, 0, 0, 447, 0, 50, 0, + 456, 0, 0, 0, 0, 460, 0, 461, 0, 0, + 0, 0, 0, 52, 53, 56, 97, 0, 0, 59, + 378, 0, 472, 0, 57, 58, 0, 63, 370, 0, + 73, 0, 0, 0, 0, 0, 0, 0, 76, 75, + 380, 68, 74, 0, 18, 54, 55, 62, 60, 61, + 64, 367, 509, 366, 0, 0, 13, 0, 0, 0, + 0, 496, 65, 66, 67, 501, 0, 0, 50, 372, + 0, 0, 0, 373, 374, 377, 375, 376, 77, 0, + 0, 0, 0, 52, 53, 56, 501, 0, 0, 59, + 378, 0, 0, 0, 57, 58, 0, 63, 370, 534, + 73, 0, 0, 0, 0, 0, 0, 0, 76, 75, + 380, 68, 74, 0, 18, 54, 55, 62, 60, 61, + 64, 367, 470, 366, 0, 0, 13, 0, 0, 0, + 0, 0, 65, 66, 67, 0, 0, 0, 50, 372, + 0, 0, 0, 373, 374, 377, 375, 376, 77, 0, + 0, 0, 0, 52, 53, 56, 0, 0, 0, 59, + 378, 0, 0, 0, 57, 58, 0, 63, 370, 0, + 73, 0, 0, 0, 0, 0, 0, 0, 76, 75, + 380, 68, 74, 0, 18, 54, 55, 62, 60, 61, + 64, 367, 440, 366, 0, 0, 13, 0, 0, 0, + 0, 0, 65, 66, 67, 0, 0, 0, 50, 372, + 0, 0, 0, 373, 374, 377, 375, 376, 77, 0, + 0, 0, 0, 52, 53, 56, 0, 0, 0, 59, + 378, 0, 0, 0, 57, 58, 0, 63, 370, 0, + 73, 0, 0, 0, 0, 0, 0, 0, 76, 75, + 380, 68, 74, 0, 18, 54, 55, 62, 60, 61, + 64, 367, 362, 608, 0, 0, 13, 0, 0, 0, + 0, 0, 65, 66, 67, 0, 0, 0, 50, 372, + 0, 0, 0, 373, 374, 377, 375, 376, 77, 0, + 0, 0, 0, 52, 53, 610, 0, 0, 0, 59, + 378, 0, 0, 0, 57, 58, 0, 63, 370, 0, + 73, 0, 0, 0, 0, 0, 0, 0, 76, 75, + 609, 68, 74, 0, 18, 54, 55, 62, 60, 61, + 64, 367, 576, 0, 0, 0, 13, 0, 0, 0, + 0, 0, 65, 66, 67, 0, 0, 50, 0, 372, + 0, 0, 0, 373, 374, 377, 375, 376, 77, 0, + 0, 0, 52, 53, 454, 0, 0, 0, 59, 378, + 0, 0, 0, 57, 58, 0, 63, 370, 0, 73, + 0, 0, 0, 0, 0, 0, 0, 76, 75, 380, + 68, 74, 0, 18, 54, 55, 62, 60, 61, 64, + 367, 366, 0, 0, 0, 13, 0, 0, 0, 0, + 0, 65, 66, 67, 0, 0, 50, 0, 372, 0, + 0, 0, 373, 374, 377, 375, 376, 77, 0, 0, + 0, 52, 53, 56, 0, 0, 0, 59, 378, 0, + 0, 0, 57, 58, 0, 63, 370, 0, 73, 0, + 0, 0, 0, 0, 0, 0, 76, 75, 380, 68, + 74, 0, 18, 54, 55, 62, 60, 61, 64, 367, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 65, 66, 67, 50, 0, 0, 0, 372, 0, 0, + 0, 373, 374, 377, 375, 376, 77, 0, 52, 53, + 56, 0, 0, 0, 59, 72, 0, 0, 0, 57, + 58, 0, 63, 0, 0, 73, 0, 0, 0, 0, + 0, 0, 0, 76, 75, 69, 275, 74, 0, 18, + 54, 55, 62, 60, 61, 64, 0, 0, 0, 50, + 0, 0, 0, 0, 0, 278, 0, 276, 277, 67, + 0, 0, 0, 0, 52, 53, 56, 0, 0, 0, + 59, 72, 0, 77, 280, 57, 58, 0, 63, 0, + 0, 73, 0, 0, 0, 0, 0, 0, 0, 76, + 75, 69, 68, 74, 0, 18, 54, 55, 62, 60, + 61, 64, 0, 0, 50, 0, 0, 0, 0, 0, + 0, 0, 0, 65, 66, 67, 0, 0, 0, 52, + 53, 56, 0, 0, 0, 59, 72, 0, 0, 77, + 57, 58, 0, 63, 0, 0, 73, 0, 0, 0, + 0, 0, 0, 0, 76, 75, 69, 68, 74, 0, + 18, 54, 55, 62, 60, 61, 64, 0, 0, 0, + 52, 53, 56, 0, 0, 0, 59, 72, 65, 66, + 67, 57, 58, 0, 63, 0, 0, 73, 0, 0, + 0, 0, 0, 0, 77, 76, 75, 69, 68, 74, + 0, 18, 54, 55, 62, 60, 61, 64, 0, 0, + 0, 0, 0, 0, 0, 0, 87, 0, 0, 65, + 66, 67, 0, 85, 0, 0, 90, 0, 99, 0, + 0, 0, 0, 0, 0, 77, 92, 82, 149, 150, + 145, 146, 147, 148, 144, 143, 141, 142, 138, 139, + 140, 137, 135, 463, 462, 0, 0, 101, 88, 89, + 86, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 136, 77, 156, 155, 154, 153, 152, + 151, 149, 150, 145, 146, 147, 148, 144, 143, 141, + 142, 138, 139, 140, 137, 135, 155, 154, 153, 152, + 151, 149, 150, 145, 146, 147, 148, 144, 143, 141, + 142, 138, 139, 140, 137, 135, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 136, 567, + 156, 155, 154, 153, 152, 151, 149, 150, 145, 146, + 147, 148, 144, 143, 141, 142, 138, 139, 140, 137, + 135, 154, 153, 152, 151, 149, 150, 145, 146, 147, + 148, 144, 143, 141, 142, 138, 139, 140, 137, 135, + 0, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 136, 558, 156, 155, 154, 153, 152, + 151, 149, 150, 145, 146, 147, 148, 144, 143, 141, + 142, 138, 139, 140, 137, 135, 152, 151, 149, 150, + 145, 146, 147, 148, 144, 143, 141, 142, 138, 139, + 140, 137, 135, 0, 0, 0, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 136, 531, + 156, 155, 154, 153, 152, 151, 149, 150, 145, 146, + 147, 148, 144, 143, 141, 142, 138, 139, 140, 137, + 135, 151, 149, 150, 145, 146, 147, 148, 144, 143, + 141, 142, 138, 139, 140, 137, 135, 0, 0, 0, + 0, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 136, 484, 156, 155, 154, 153, 152, + 151, 149, 150, 145, 146, 147, 148, 144, 143, 141, + 142, 138, 139, 140, 137, 135, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 136, 352, + 156, 155, 154, 153, 152, 151, 149, 150, 145, 146, + 147, 148, 144, 143, 141, 142, 138, 139, 140, 137, + 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 136, 344, 156, 155, 154, 153, 152, + 151, 149, 150, 145, 146, 147, 148, 144, 143, 141, + 142, 138, 139, 140, 137, 135, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 136, 304, + 156, 155, 154, 153, 152, 151, 149, 150, 145, 146, + 147, 148, 144, 143, 141, 142, 138, 139, 140, 137, + 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 136, 302, 156, 155, 154, 153, 152, + 151, 149, 150, 145, 146, 147, 148, 144, 143, 141, + 142, 138, 139, 140, 137, 135, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 136, 191, + 156, 155, 154, 153, 152, 151, 149, 150, 145, 146, + 147, 148, 144, 143, 141, 142, 138, 139, 140, 137, + 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 136, 190, 156, 155, 154, 153, 152, + 151, 149, 150, 145, 146, 147, 148, 144, 143, 141, + 142, 138, 139, 140, 137, 135, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 136, 189, + 156, 155, 154, 153, 152, 151, 149, 150, 145, 146, + 147, 148, 144, 143, 141, 142, 138, 139, 140, 137, + 135, 0, 87, 0, 0, 0, 87, 0, 0, 85, + 0, 0, 90, 388, 0, 0, 90, 0, 0, 0, + 0, 0, 92, 394, 0, 0, 92, 82, 0, 0, + 0, 0, 0, 0, 122, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 88, 89, 86, 0, 88, 89, + 86, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 77, 0, 0, 0, 77, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 136, 0, 156, + 155, 154, 153, 152, 151, 149, 150, 145, 146, 147, + 148, 144, 143, 141, 142, 138, 139, 140, 137, 135, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 123, 124, + 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 136, 578, 156, 155, 154, 153, 152, 151, 149, 150, + 145, 146, 147, 148, 144, 143, 141, 142, 138, 139, + 140, 137, 135, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 136, 519, 156, 155, 154, 153, 152, + 151, 149, 150, 145, 146, 147, 148, 144, 143, 141, + 142, 138, 139, 140, 137, 135, 0, 0, 0, 123, + 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, + 134, 136, 492, 156, 155, 154, 153, 152, 151, 149, + 150, 145, 146, 147, 148, 144, 143, 141, 142, 138, + 139, 140, 137, 135, 0, 0, 0, 339, 123, 124, + 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 136, 0, 156, 155, 154, 153, 152, 151, 149, 150, + 145, 146, 147, 148, 144, 143, 141, 142, 138, 139, + 140, 137, 135, 0, 0, 0, 334, 123, 124, 125, + 126, 127, 128, 129, 130, 131, 132, 133, 134, 136, + 0, 156, 155, 154, 153, 152, 151, 149, 150, 145, + 146, 147, 148, 144, 143, 141, 142, 138, 139, 140, + 137, 135, 0, 514, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 136, 0, 156, 155, + 154, 153, 152, 151, 149, 150, 145, 146, 147, 148, + 144, 143, 141, 142, 138, 139, 140, 137, 135, 0, + 457, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 136, 0, 156, 155, 154, 153, 152, + 151, 149, 150, 145, 146, 147, 148, 144, 143, 141, + 142, 138, 139, 140, 137, 135, 156, 155, 154, 153, + 152, 151, 149, 150, 145, 146, 147, 148, 144, 143, + 141, 142, 138, 139, 140, 137, 135, +}; +yypact := array[] of { + 198,-1000, 351, 172,-1000, 140,-1000,-1000, 137, 135, + 692, 630, 14, 274, 463,-1000, 424, 530,-1000, 285, + -35, 130,-1000,-1000,-1000,-1000,-1000,1507,1507,1507, +1507, 752, 583, 116, 144, 413, 396, -19, 460, 335, +-1000, 351, 18,-1000,-1000,-1000, 393,-1000,2272,-1000, + 391, 497,1548,1548,1548,1548,1548,1548,1548,1548, +1548,1548,1548,1548,1548, 523, 501, 521,1548, 376, +1548,-1000,1507, 579,-1000,-1000,-1000, 580,2217,2162, +2107, 288,-1000,-1000,-1000, 752, 494, 752, 492, 487, + 530,-1000, 532,-1000,-1000, 752,1507, 251,1507, 134, + 223, 530, 260, 348, 530, 216, 752, 551, 549, -36, +-1000, 456, 6, -37,-1000,-1000,-1000, 512,-1000, 285, +-1000, 172,-1000,1507,1507,1507,1507,1507,1507,1507, +1507,1507,1507,1507,1507, 622,1507,1507,1507,1507, +1507,1507,1507,1507,1507,1507,1507,1507,1507,1507, +1507,1507,1507,1507,1507,1507,1507,1507,1507, 389, + 544,1396,-1000,-1000,-1000,-1000,-1000,-1000,-1000,-1000, +-1000,-1000,-1000,-1000,-1000,-1000,-1000,1452, 307, 215, + 752,1507,-1000,-1000,-1000, 17,2667,-1000,1507,-1000, +-1000,-1000,-1000,1507, 388, 379, 415, 752, 13, 415, + 752, 752, 579, 452, 308,2052,-1000,1507,1997,-1000, + 752, 627, 2,-1000,-1000, 121, 285,-1000,-1000, 351, + 415,-1000,-1000, 334, 273, 273, 250,-1000,-1000,-1000, + 172,2667,2667,2667,2667,2667,2667,2667,2667,2667, +2667,2667,2667,1507,2667, 575, 575, 575, 575, 543, + 543, 604, 604, 669, 669, 669, 669, 866, 866,1624, +1848,1794,1741,1741,1687,2688, 547, -38,-1000, 406, + 511, 449, -39,2667,-1000,1548, 476, 485, 752,2554, + 479,1548,1507, 415,2515,-1000,1507, 260, 650,1942, + 529, 524, 415,-1000, 752, 415, 415, 413, 12, 415, + 752,-1000,-1000,1887,-1000, 11, 146,-1000, 625, 212, +1121,-1000,-1000, 8, 188,-1000, 319, 546,-1000, 415, +-1000,2277, 415,-1000,-1000,-1000,2667,-1000,-1000,1507, +1396,2273, 722, 415, 478, 200,-1000, 185, -46, 471, +2667,-1000,1507,-1000,-1000, 452, 452, 415,-1000, 407, +-1000, 415,-1000, 104,-1000,-1000, 447, 103,-1000, 110, +-1000, 351,-1000,-1000,-1000, 437, 114,-1000, 5, 99, + 572, 32, 370, 370,1507,1507,1507, 95,1507,2667, + 376,1051,-1000,-1000, 351,-1000,-1000,-1000, 752,-1000, + 415, 506, 505,2667,1548, 415, 415, 269, 808,-1000, +1507, 752,2630, -2, -5, 415, 752,-1000,1587,-1000, + -21,-1000,-1000,-1000, 431, 197, 183, 696,-1000,-1000, +-1000, 981, 570, 752,-1000,1507, 569, 557,1329,1507, + 194, 354, 94,-1000, 92, 89,1832, 64,-1000, 4, +-1000,-1000, 244,-1000,-1000,-1000,-1000, 415, 808, 175, + -53,-1000,2477, 365,1548,-1000, 415,-1000,-1000,-1000, + 415, 305, 752,1507,-1000, 190, 219, 403, 145, 911, + 420,1507, 176,2593,1507,1507, -17, 429,2424, 808, + 609,-1000,-1000,-1000,-1000,-1000,-1000, 193,-1000, 169, +-1000, 808,1507, 808,1507,-1000, 293,1777, 351,1507, + 752, 235, 167, 626,-1000, 283, 368,-1000, 625,-1000, + 341, 3,-1000,1507,1329, 48, 545, 553,-1000, 808, + 163,-1000, 361,2477,1507,-1000,-1000,2667,-1000,2667, +-1000,-1000, 162,1722, 227,-1000,-1000, 337, 327,-1000, + 325, 106, 0,-1000,-1000,1667, 426,1507,1329,1507, + 157,-1000, 358,-1000,1260,-1000,2371,-1000,-1000,-1000, + 255, 427,-1000, 252,-1000,-1000, 808,-1000,1329, 41, +-1000, 542,-1000,1260,-1000, 356, 114,2477, 468,-1000, +-1000, 152,-1000, 353,-1000,1507, -1, 333,-1000, 148, +-1000, 326,-1000,-1000,-1000,-1000,1260,-1000, 535,-1000, +-1000,-1000,1191,-1000, 468, 316,1329, 278, 114, 376, +1548,-1000,-1000,-1000,-1000, +}; +yypgo := array[] of { + 0, 528, 736, 105, 33, 24, 419, 15, 14, 46, + 734, 733, 732, 34, 731, 728, 27, 727, 16, 4, + 725, 108, 8, 0, 21, 35, 13, 724, 723, 94, + 25, 67, 26, 12, 722, 11, 2, 38, 41, 32, + 721, 22, 3, 7, 719, 718, 717, 716, 715, 20, + 713, 712, 711, 10, 710, 697, 695, 1, 694, 693, + 692, 6, 5, 691, 689, 667, 19, 23, 652, 9, + 651, 18, 650, 649, 17, 648, 647, 643, 633, +}; +yyr1 := array[] of { + 0, 76, 75, 75, 38, 38, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 30, 30, 37, + 37, 37, 37, 37, 37, 37, 66, 66, 48, 51, + 51, 51, 50, 50, 50, 50, 50, 49, 49, 73, + 73, 53, 53, 53, 52, 52, 52, 62, 62, 61, + 61, 60, 58, 58, 58, 59, 59, 59, 19, 20, + 20, 9, 10, 10, 6, 6, 74, 74, 74, 74, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 7, 7, 8, 8, 13, 13, 21, 21, + 2, 2, 2, 3, 3, 4, 4, 14, 14, 15, + 15, 16, 16, 16, 16, 11, 12, 12, 12, 12, + 5, 5, 5, 5, 40, 67, 67, 67, 41, 41, + 41, 54, 54, 43, 43, 43, 77, 77, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, + 17, 17, 18, 18, 44, 45, 45, 46, 47, 47, + 63, 64, 64, 36, 36, 36, 36, 36, 55, 56, + 56, 57, 57, 57, 57, 22, 22, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, + 78, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 29, 29, 31, 72, 72, 71, 71, 70, + 70, 70, 70, 65, 65, 32, 32, 32, 32, 27, + 27, 28, 28, 26, 26, 33, 33, 34, 34, 35, + 35, 69, 68, 68, +}; +yyr2 := array[] of { + 0, 0, 5, 1, 1, 2, 2, 1, 1, 2, + 2, 4, 4, 4, 4, 4, 6, 1, 3, 3, + 5, 5, 4, 6, 5, 1, 4, 7, 6, 0, + 2, 1, 4, 2, 5, 5, 1, 8, 11, 0, + 4, 0, 2, 1, 1, 1, 5, 0, 2, 5, + 4, 4, 2, 2, 1, 2, 4, 4, 1, 1, + 3, 1, 1, 3, 6, 4, 1, 2, 3, 4, + 1, 1, 1, 3, 6, 2, 3, 3, 3, 3, + 4, 1, 1, 4, 3, 6, 1, 3, 0, 3, + 3, 3, 5, 1, 3, 1, 5, 0, 1, 1, + 3, 3, 3, 3, 3, 1, 1, 1, 3, 3, + 2, 3, 2, 3, 4, 4, 2, 0, 3, 2, + 4, 2, 4, 0, 2, 2, 3, 5, 2, 2, + 4, 3, 4, 6, 2, 5, 7, 10, 6, 8, + 3, 3, 3, 3, 3, 6, 5, 8, 2, 8, + 0, 2, 0, 1, 2, 2, 4, 2, 2, 4, + 2, 2, 4, 1, 3, 1, 3, 1, 2, 2, + 4, 1, 1, 3, 1, 0, 1, 1, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 6, 8, 7, + 5, 3, 6, 4, 2, 2, 2, 1, 4, 3, + 0, 4, 3, 3, 4, 6, 2, 2, 1, 1, + 1, 6, 1, 1, 3, 1, 3, 1, 1, 1, + 3, 3, 2, 1, 0, 1, 1, 3, 3, 0, + 1, 1, 2, 1, 3, 1, 2, 1, 3, 1, + 3, 2, 2, 4, +}; +yychk := array[] of { +-1000, -75, 73, -38, -39, 2, -37, -40, -49, -48, + -29, -31, -30, 75, -9, -66, -54, 59, 63, 39, + -10, -9, 59, -39, 72, 72, 72, 4, 16, 4, + 16, 50, 98, 61, 50, -4, 54, -3, -2, 39, + -21, 41, -32, -31, -29, 59, 98, 72, -23, -24, + 17, -25, 32, 33, 64, 65, 34, 43, 44, 38, + 67, 68, 66, 46, 69, 81, 82, 83, 60, 59, + -6, -29, 39, 49, 61, 58, 57, 97, -23, -23, + -23, -1, 60, -7, -8, 46, 83, 39, 81, 82, + 49, -6, 59, -31, 72, 77, 74, -1, 76, 51, + 78, 80, -67, 52, 59, 87, 50, -14, 34, -15, + -16, -11, -30, -12, -31, 59, 63, -9, 40, 98, + 59, -76, 72, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 38, 16, 37, 34, 35, + 36, 32, 33, 31, 30, 26, 27, 28, 29, 24, + 25, 23, 22, 21, 20, 19, 18, 59, 39, 54, + 53, 41, 43, 44, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24, 41, 45, 45, + 45, 41, -24, -24, -24, -26, -23, -3, 39, 72, + 72, 72, 72, 4, 53, 54, -1, 45, -13, -1, + 45, 45, -21, 41, -1, -23, 72, 4, -23, 72, + 39, 70, -21, -41, 70, 2, 39, -29, -21, 70, + -1, 40, 40, 98, 50, 50, 98, 42, -31, -29, + -38, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, 4, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -27, -26, 59, -25, + -71, -22, -72, -23, -70, 60, 81, 82, 79, -23, + 42, 60, 70, -1, -23, 40, 98, -78, -23, -23, + 59, 59, -1, 40, 98, -1, -1, -4, -74, -1, + 79, 72, 72, -23, 72, -13, -51, 2, 70, 87, + -43, 71, 70, -32, -69, -68, -9, 34, -16, -1, + -5, 84, -1, -5, 59, 63, -23, 40, 42, 50, + 98, 45, 45, -1, 42, 45, -24, -28, -26, 42, + -23, -41, 98, 40, 72, 41, 41, -1, -67, 98, + 42, -1, 72, 40, 71, -50, -9, -49, -66, -53, + 2, 70, 71, -37, -42, -9, 2, 70, -77, -22, + 47, -17, 88, 92, 93, 95, 96, 94, 39, -23, + 59, -43, 40, 71, -62, 62, 40, -7, 46, -8, + -1, -22, -71, -23, 60, -1, -1, 45, 70, 71, + 98, 45, -23, -74, -74, -1, 79, 72, 50, 72, + 71, -52, -61, -60, -9, 91, -69, 50, 72, 71, + 70, -43, 98, 50, 72, 39, 87, 86, 85, 90, + 89, 91, -18, 59, -18, -22, -23, -22, 72, -26, + 71, -61, -9, -7, -8, 42, 42, -1, 70, -33, + -34, -35, -23, -36, 34, 2, -1, 40, 42, 42, + -1, -1, 77, 76, -73, 87, 50, 70, 71, -43, + 71, 39, -1, -23, 39, 39, -42, -9, -23, 70, + 59, 72, 72, 72, 72, 72, 40, 50, 62, -33, + 71, 98, 55, 56, 62, 72, -1, -23, 70, 76, + 79, -1, -58, -59, 2, -19, -20, 59, 70, 71, + 51, -26, 72, 4, 40, -22, -22, 86, 50, 70, + -44, -45, -36, -23, 16, 71, -35, -23, -36, -23, + 72, 72, -69, -23, -1, 72, 71, -62, 2, 62, + 56, -53, -65, 59, 40, -23, -42, 72, 40, 39, + -46, -47, -36, 71, -43, 62, -23, 71, 72, 72, + -19, -9, 59, -19, 59, 71, 70, 72, 48, -22, + -42, -22, 71, -43, 62, -36, 2, -23, 70, 62, + 62, -63, -64, -36, -42, 72, 40, -36, 62, -55, + -56, -57, 59, 34, 2, 71, -43, 62, -22, 72, + 62, 71, -43, 62, 56, -36, 40, -57, 2, 59, + 34, -57, 62, -42, 62, +}; +yydef := array[] of { + 0, -2, 0, -2, 4, 0, 7, 8, 0, 0, + 0, 17, 0, 0, 0, 25, 0, -2, 253, 0, + 61, 0, 62, 5, 6, 9, 10, 0, 0, 0, + 0, 0, 0, 0, 0, 117, 0, 95, 93, 97, + 121, 0, 0, 265, 266, 252, 0, 1, 0, 177, + 0, 213, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 252, + 0, 237, 0, 0, 248, 249, 250, 0, 0, 0, + 0, 0, 70, 71, 72, 0, 0, 0, 0, 0, + 88, 81, 82, 18, 19, 0, 0, 0, 0, 0, + 0, 88, 0, 0, 88, 0, 0, 0, 0, 98, + 99, 0, 0, 105, 17, 106, 107, 0, 254, 0, + 63, 0, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 269, 0, + 0, 175, 246, 247, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 0, 0, 0, + 0, 0, 234, 235, 236, 0, 273, 240, 0, 13, + 12, 14, 15, 0, 0, 0, 75, 0, 0, 86, + 0, 0, 0, 0, 0, 0, 22, 0, 0, 26, + 0, -2, 0, 114, 123, 0, 0, 116, 122, 0, + 94, 90, 91, 0, 0, 0, 0, 89, 267, 268, + -2, 178, 179, 180, 181, 182, 183, 184, 185, 186, + 187, 188, 189, 0, 191, 193, 194, 195, 196, 197, + 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, + 208, 209, 210, 211, 212, 192, 0, 270, 242, 243, + 255, 0, 0, -2, 258, 259, 0, 0, 0, 0, + 0, 0, 0, 231, 0, 239, 0, 0, 0, 0, + 73, 84, 76, 77, 0, 78, 79, 117, 0, 66, + 0, 20, 21, 0, 24, 0, 0, 31, -2, 0, + -2, 119, 123, 0, 0, 47, 0, 0, 100, 101, + 102, 0, 103, 104, 108, 109, 190, 238, 244, 175, + 0, 0, 0, 262, 0, 0, 233, 0, 271, 0, + 274, 241, 0, 65, 16, 0, 0, 87, 80, 0, + 83, 67, 23, 0, 28, 30, 0, 0, 36, 0, + 43, 0, 118, 124, 125, 0, 0, 123, 0, 0, + 0, 0, 152, 152, 175, 0, 175, 0, 0, 176, + -2, -2, 115, 96, 281, 282, 92, -2, 0, -2, + 0, 0, 256, 257, 70, 260, 261, 0, 0, 230, + 272, 0, 0, 0, 0, 68, 0, 27, 0, 33, + 39, 42, 44, 45, 0, 0, 0, 151, 128, 129, + 123, -2, 0, 0, 134, 0, 0, 0, -2, 0, + 0, 0, 0, 153, 0, 0, 0, 0, 148, 0, + 120, 48, 0, -2, -2, 245, 251, 227, 0, 0, + 275, 277, -2, 0, 165, 167, 232, 64, 74, 85, + 69, 0, 0, 0, 37, 0, 0, 0, 0, -2, + 131, 0, 0, 0, 175, 175, 0, 0, 0, 0, + 0, 140, 141, 142, 143, 144, -2, 0, 283, 0, + 229, -2, 0, 0, 0, 32, 0, 0, 0, 0, + 0, 0, 0, -2, 54, 0, 58, 59, -2, 130, + 264, 0, 132, 0, -2, 0, 0, 0, 151, 0, + 0, 123, 0, 163, 0, 228, 278, 164, 166, 280, + 34, 35, 0, 0, 0, 50, 51, 52, 53, 55, + 0, 0, 0, 263, 127, 0, 135, 175, -2, 175, + 0, 123, 0, 146, -2, 155, 0, 40, 46, 49, + 0, 0, -2, 0, 60, 38, 0, 133, -2, 0, + 138, 0, 145, -2, 158, 0, 167, -2, 0, 56, + 57, 0, 123, 0, 136, 175, 0, 0, 156, 0, + 123, 0, 171, 172, 174, 149, -2, 161, 0, 139, + 159, 147, -2, 169, 0, 0, -2, 0, 174, -2, + 172, 173, 162, 137, 170, +}; +yytok1 := array[] of { + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 64, 3, 3, 3, 36, 23, 3, + 39, 40, 34, 32, 98, 33, 54, 35, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 50, 72, + 26, 4, 27, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 41, 3, 42, 22, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 70, 21, 71, 65, +}; +yytok2 := array[] of { + 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16, 17, 18, 19, 20, 24, 25, + 28, 29, 30, 31, 37, 38, 43, 44, 45, 46, + 47, 48, 49, 51, 52, 53, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 66, 67, 68, 69, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, + 94, 95, 96, 97, +}; +yytok3 := array[] of { + 0 +}; + +YYSys: module +{ + FD: adt + { + fd: int; + }; + fildes: fn(fd: int): ref FD; + fprint: fn(fd: ref FD, s: string, *): int; +}; + +yysys: YYSys; +yystderr: ref YYSys->FD; + +YYFLAG: con -1000; + +# parser for yacc output + +yytokname(yyc: int): string +{ + if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil) + return yytoknames[yyc-1]; + return "<"+string yyc+">"; +} + +yystatname(yys: int): string +{ + if(yys >= 0 && yys < len yystates && yystates[yys] != nil) + return yystates[yys]; + return "<"+string yys+">\n"; +} + +yylex1(yylex: ref YYLEX): int +{ + c : int; + yychar := yylex.lex(); + if(yychar <= 0) + c = yytok1[0]; + else if(yychar < len yytok1) + c = yytok1[yychar]; + else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2) + c = yytok2[yychar-YYPRIVATE]; + else{ + n := len yytok3; + c = 0; + for(i := 0; i < n; i+=2) { + if(yytok3[i+0] == yychar) { + c = yytok3[i+1]; + break; + } + } + if(c == 0) + c = yytok2[1]; # unknown char + } + if(yydebug >= 3) + yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c)); + return c; +} + +YYS: adt +{ + yyv: YYSTYPE; + yys: int; +}; + +yyparse(yylex: ref YYLEX): int +{ + if(yydebug >= 1 && yysys == nil) { + yysys = load YYSys "$Sys"; + yystderr = yysys->fildes(2); + } + + yys := array[YYMAXDEPTH] of YYS; + + yyval: YYSTYPE; + yystate := 0; + yychar := -1; + yynerrs := 0; # number of errors + yyerrflag := 0; # error recovery flag + yyp := -1; + yyn := 0; + +yystack: + for(;;){ + # put a state and value onto the stack + if(yydebug >= 4) + yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + + yyp++; + if(yyp >= len yys) + yys = (array[len yys * 2] of YYS)[0:] = yys; + yys[yyp].yys = yystate; + yys[yyp].yyv = yyval; + + for(;;){ + yyn = yypact[yystate]; + if(yyn > YYFLAG) { # simple state + if(yychar < 0) + yychar = yylex1(yylex); + yyn += yychar; + if(yyn >= 0 && yyn < YYLAST) { + yyn = yyact[yyn]; + if(yychk[yyn] == yychar) { # valid shift + yychar = -1; + yyp++; + if(yyp >= len yys) + yys = (array[len yys * 2] of YYS)[0:] = yys; + yystate = yyn; + yys[yyp].yys = yystate; + yys[yyp].yyv = yylex.lval; + if(yyerrflag > 0) + yyerrflag--; + if(yydebug >= 4) + yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + continue; + } + } + } + + # default state action + yyn = yydef[yystate]; + if(yyn == -2) { + if(yychar < 0) + yychar = yylex1(yylex); + + # look through exception table + for(yyxi:=0;; yyxi+=2) + if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate) + break; + for(yyxi += 2;; yyxi += 2) { + yyn = yyexca[yyxi]; + if(yyn < 0 || yyn == yychar) + break; + } + yyn = yyexca[yyxi+1]; + if(yyn < 0){ + yyn = 0; + break yystack; + } + } + + if(yyn != 0) + break; + + # error ... attempt to resume parsing + if(yyerrflag == 0) { # brand new error + yylex.error("syntax error"); + yynerrs++; + if(yydebug >= 1) { + yysys->fprint(yystderr, "%s", yystatname(yystate)); + yysys->fprint(yystderr, "saw %s\n", yytokname(yychar)); + } + } + + if(yyerrflag != 3) { # incompletely recovered error ... try again + yyerrflag = 3; + + # find a state where "error" is a legal shift action + while(yyp >= 0) { + yyn = yypact[yys[yyp].yys] + YYERRCODE; + if(yyn >= 0 && yyn < YYLAST) { + yystate = yyact[yyn]; # simulate a shift of "error" + if(yychk[yystate] == YYERRCODE) + continue yystack; + } + + # the current yyp has no shift onn "error", pop stack + if(yydebug >= 2) + yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n", + yys[yyp].yys, yys[yyp-1].yys ); + yyp--; + } + # there is no state on the stack with an error shift ... abort + yyn = 1; + break yystack; + } + + # no shift yet; clobber input char + if(yydebug >= 2) + yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar)); + if(yychar == YYEOFCODE) { + yyn = 1; + break yystack; + } + yychar = -1; + # try again in the same state + } + + # reduction by production yyn + if(yydebug >= 2) + yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); + + yypt := yyp; + yyp -= yyr2[yyn]; +# yyval = yys[yyp+1].yyv; + yym := yyn; + + # consult goto table to find next state + yyn = yyr1[yyn]; + yyg := yypgo[yyn]; + yyj := yyg + yys[yyp].yys + 1; + + if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) + yystate = yyact[yyg]; + case yym { + +1=> +#line 151 "limbo.y" +{ + impmods = yys[yypt-1].yyv.ids; + } +2=> +#line 154 "limbo.y" +{ + tree = rotater(yys[yypt-0].yyv.node); + } +3=> +#line 158 "limbo.y" +{ + impmods = nil; + tree = rotater(yys[yypt-0].yyv.node); + } +4=> +yyval.node = yys[yyp+1].yyv.node; +5=> +#line 166 "limbo.y" +{ + if(yys[yypt-1].yyv.node == nil) + yyval.node = yys[yypt-0].yyv.node; + else if(yys[yypt-0].yyv.node == nil) + yyval.node = yys[yypt-1].yyv.node; + else + yyval.node = mkbin(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); + } +6=> +#line 177 "limbo.y" +{ + yyval.node = nil; + } +7=> +yyval.node = yys[yyp+1].yyv.node; +8=> +yyval.node = yys[yyp+1].yyv.node; +9=> +yyval.node = yys[yyp+1].yyv.node; +10=> +yyval.node = yys[yyp+1].yyv.node; +11=> +#line 185 "limbo.y" +{ + yyval.node = mkbin(Oas, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node); + } +12=> +#line 189 "limbo.y" +{ + yyval.node = mkbin(Oas, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node); + } +13=> +#line 193 "limbo.y" +{ + yyval.node = mkbin(Odas, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node); + } +14=> +#line 197 "limbo.y" +{ + yyval.node = mkbin(Odas, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node); + } +15=> +#line 201 "limbo.y" +{ + yyerror("illegal declaration"); + yyval.node = nil; + } +16=> +#line 206 "limbo.y" +{ + yyerror("illegal declaration"); + yyval.node = nil; + } +17=> +yyval.node = yys[yyp+1].yyv.node; +18=> +#line 214 "limbo.y" +{ + yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +19=> +#line 220 "limbo.y" +{ + includef(yys[yypt-1].yyv.tok.v.idval); + yyval.node = nil; + } +20=> +#line 225 "limbo.y" +{ + yyval.node = typedecl(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.ty); + } +21=> +#line 229 "limbo.y" +{ + yyval.node = importdecl(yys[yypt-1].yyv.node, yys[yypt-4].yyv.ids); + yyval.node.src.start = yys[yypt-4].yyv.ids.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +22=> +#line 235 "limbo.y" +{ + yyval.node = vardecl(yys[yypt-3].yyv.ids, yys[yypt-1].yyv.ty); + } +23=> +#line 239 "limbo.y" +{ + yyval.node = mkbin(Ovardecli, vardecl(yys[yypt-5].yyv.ids, yys[yypt-3].yyv.ty), varinit(yys[yypt-5].yyv.ids, yys[yypt-1].yyv.node)); + } +24=> +#line 243 "limbo.y" +{ + yyval.node = condecl(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.node); + } +25=> +yyval.node = yys[yyp+1].yyv.node; +26=> +#line 250 "limbo.y" +{ + yyval.node = exdecl(yys[yypt-3].yyv.ids, nil); + } +27=> +#line 254 "limbo.y" +{ + yyval.node = exdecl(yys[yypt-6].yyv.ids, revids(yys[yypt-2].yyv.ids)); + } +28=> +#line 260 "limbo.y" +{ + yys[yypt-5].yyv.ids.src.stop = yys[yypt-0].yyv.tok.src.stop; + yyval.node = moddecl(yys[yypt-5].yyv.ids, rotater(yys[yypt-1].yyv.node)); + } +29=> +#line 267 "limbo.y" +{ + yyval.node = nil; + } +30=> +#line 271 "limbo.y" +{ + if(yys[yypt-1].yyv.node == nil) + yyval.node = yys[yypt-0].yyv.node; + else if(yys[yypt-0].yyv.node == nil) + yyval.node = yys[yypt-1].yyv.node; + else + yyval.node = mkn(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); + } +31=> +#line 280 "limbo.y" +{ + yyval.node = nil; + } +32=> +#line 286 "limbo.y" +{ + yyval.node = fielddecl(Dglobal, typeids(yys[yypt-3].yyv.ids, yys[yypt-1].yyv.ty)); + } +33=> +yyval.node = yys[yyp+1].yyv.node; +34=> +#line 291 "limbo.y" +{ + yyval.node = typedecl(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.ty); + } +35=> +#line 295 "limbo.y" +{ + yyval.node = condecl(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.node); + } +36=> +yyval.node = yys[yyp+1].yyv.node; +37=> +#line 302 "limbo.y" +{ + yys[yypt-7].yyv.ids.src.stop = yys[yypt-1].yyv.tok.src.stop; + yyval.node = adtdecl(yys[yypt-7].yyv.ids, rotater(yys[yypt-2].yyv.node)); + yyval.node.ty.polys = yys[yypt-4].yyv.ids; + yyval.node.ty.val = rotater(yys[yypt-0].yyv.node); + } +38=> +#line 309 "limbo.y" +{ + yys[yypt-10].yyv.ids.src.stop = yys[yypt-0].yyv.tok.src.stop; + yyval.node = adtdecl(yys[yypt-10].yyv.ids, rotater(yys[yypt-1].yyv.node)); + yyval.node.ty.polys = yys[yypt-7].yyv.ids; + yyval.node.ty.val = rotater(yys[yypt-4].yyv.node); + } +39=> +#line 318 "limbo.y" +{ + yyval.node = nil; + } +40=> +#line 322 "limbo.y" +{ + yyval.node = yys[yypt-1].yyv.node; + } +41=> +#line 328 "limbo.y" +{ + yyval.node = nil; + } +42=> +#line 332 "limbo.y" +{ + if(yys[yypt-1].yyv.node == nil) + yyval.node = yys[yypt-0].yyv.node; + else if(yys[yypt-0].yyv.node == nil) + yyval.node = yys[yypt-1].yyv.node; + else + yyval.node = mkn(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); + } +43=> +#line 341 "limbo.y" +{ + yyval.node = nil; + } +44=> +yyval.node = yys[yyp+1].yyv.node; +45=> +yyval.node = yys[yyp+1].yyv.node; +46=> +#line 349 "limbo.y" +{ + yyval.node = condecl(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.node); + } +47=> +#line 355 "limbo.y" +{ + yyval.node = nil; + } +48=> +#line 359 "limbo.y" +{ + if(yys[yypt-1].yyv.node == nil) + yyval.node = yys[yypt-0].yyv.node; + else if(yys[yypt-0].yyv.node == nil) + yyval.node = yys[yypt-1].yyv.node; + else + yyval.node = mkn(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); + } +49=> +#line 370 "limbo.y" +{ + for(d := yys[yypt-4].yyv.ids; d != nil; d = d.next) + d.cyc = byte 1; + yyval.node = fielddecl(Dfield, typeids(yys[yypt-4].yyv.ids, yys[yypt-1].yyv.ty)); + } +50=> +#line 376 "limbo.y" +{ + yyval.node = fielddecl(Dfield, typeids(yys[yypt-3].yyv.ids, yys[yypt-1].yyv.ty)); + } +51=> +#line 382 "limbo.y" +{ + yyval.node = yys[yypt-1].yyv.node; + } +52=> +#line 388 "limbo.y" +{ + yys[yypt-1].yyv.node.right.right = yys[yypt-0].yyv.node; + yyval.node = yys[yypt-1].yyv.node; + } +53=> +#line 393 "limbo.y" +{ + yyval.node = nil; + } +54=> +#line 397 "limbo.y" +{ + yyval.node = nil; + } +55=> +#line 403 "limbo.y" +{ + yyval.node = mkn(Opickdecl, nil, mkn(Oseq, fielddecl(Dtag, yys[yypt-1].yyv.ids), nil)); + typeids(yys[yypt-1].yyv.ids, mktype(yys[yypt-1].yyv.ids.src.start, yys[yypt-1].yyv.ids.src.stop, Tadtpick, nil, nil)); + } +56=> +#line 408 "limbo.y" +{ + yys[yypt-3].yyv.node.right.right = yys[yypt-2].yyv.node; + yyval.node = mkn(Opickdecl, yys[yypt-3].yyv.node, mkn(Oseq, fielddecl(Dtag, yys[yypt-1].yyv.ids), nil)); + typeids(yys[yypt-1].yyv.ids, mktype(yys[yypt-1].yyv.ids.src.start, yys[yypt-1].yyv.ids.src.stop, Tadtpick, nil, nil)); + } +57=> +#line 414 "limbo.y" +{ + yyval.node = mkn(Opickdecl, nil, mkn(Oseq, fielddecl(Dtag, yys[yypt-1].yyv.ids), nil)); + typeids(yys[yypt-1].yyv.ids, mktype(yys[yypt-1].yyv.ids.src.start, yys[yypt-1].yyv.ids.src.stop, Tadtpick, nil, nil)); + } +58=> +#line 421 "limbo.y" +{ + yyval.ids = revids(yys[yypt-0].yyv.ids); + } +59=> +#line 427 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, nil); + } +60=> +#line 431 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, yys[yypt-2].yyv.ids); + } +61=> +#line 437 "limbo.y" +{ + yyval.ids = revids(yys[yypt-0].yyv.ids); + } +62=> +#line 443 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, nil); + } +63=> +#line 447 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, yys[yypt-2].yyv.ids); + } +64=> +#line 453 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-5].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tfix, nil, nil); + yyval.ty.val = mkbin(Oseq, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node); + } +65=> +#line 458 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-3].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tfix, nil, nil); + yyval.ty.val = yys[yypt-1].yyv.node; + } +66=> +#line 465 "limbo.y" +{ + yyval.types = addtype(yys[yypt-0].yyv.ty, nil); + } +67=> +#line 469 "limbo.y" +{ + yyval.types = addtype(yys[yypt-0].yyv.ty, nil); + yys[yypt-0].yyv.ty.flags |= CYCLIC; + } +68=> +#line 474 "limbo.y" +{ + yyval.types = addtype(yys[yypt-0].yyv.ty, yys[yypt-2].yyv.types); + } +69=> +#line 478 "limbo.y" +{ + yyval.types = addtype(yys[yypt-0].yyv.ty, yys[yypt-3].yyv.types); + yys[yypt-0].yyv.ty.flags |= CYCLIC; + } +70=> +#line 485 "limbo.y" +{ + yyval.ty = mkidtype(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval); + } +71=> +#line 489 "limbo.y" +{ + yyval.ty = yys[yypt-0].yyv.ty; + } +72=> +#line 493 "limbo.y" +{ + yyval.ty = yys[yypt-0].yyv.ty; + } +73=> +#line 497 "limbo.y" +{ + yyval.ty = mkarrowtype(yys[yypt-2].yyv.ty.src.start, yys[yypt-0].yyv.tok.src.stop, yys[yypt-2].yyv.ty, yys[yypt-0].yyv.tok.v.idval); + } +74=> +#line 501 "limbo.y" +{ + yyval.ty = mkarrowtype(yys[yypt-5].yyv.ty.src.start, yys[yypt-3].yyv.tok.src.stop, yys[yypt-5].yyv.ty, yys[yypt-3].yyv.tok.v.idval); + yyval.ty = mkinsttype(yys[yypt-5].yyv.ty.src, yyval.ty, yys[yypt-1].yyv.types); + } +75=> +#line 506 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-1].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tref, yys[yypt-0].yyv.ty, nil); + } +76=> +#line 510 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tchan, yys[yypt-0].yyv.ty, nil); + } +77=> +#line 514 "limbo.y" +{ + if(yys[yypt-1].yyv.ids.next == nil) + yyval.ty = yys[yypt-1].yyv.ids.ty; + else + yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Ttuple, nil, revids(yys[yypt-1].yyv.ids)); + } +78=> +#line 521 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tarray, yys[yypt-0].yyv.ty, nil); + } +79=> +#line 525 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tlist, yys[yypt-0].yyv.ty, nil); + } +80=> +#line 529 "limbo.y" +{ + yys[yypt-1].yyv.ty.src.start = yys[yypt-3].yyv.tok.src.start; + yys[yypt-1].yyv.ty.polys = yys[yypt-2].yyv.ids; + yys[yypt-1].yyv.ty.eraises = yys[yypt-0].yyv.node; + yyval.ty = yys[yypt-1].yyv.ty; + } +81=> +yyval.ty = yys[yyp+1].yyv.ty; +82=> +#line 549 "limbo.y" +{ + yyval.ty = mkidtype(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval); + } +83=> +#line 553 "limbo.y" +{ + yyval.ty = mkinsttype(yys[yypt-3].yyv.tok.src, mkidtype(yys[yypt-3].yyv.tok.src, yys[yypt-3].yyv.tok.v.idval), yys[yypt-1].yyv.types); + } +84=> +#line 559 "limbo.y" +{ + yyval.ty = mkdottype(yys[yypt-2].yyv.ty.src.start, yys[yypt-0].yyv.tok.src.stop, yys[yypt-2].yyv.ty, yys[yypt-0].yyv.tok.v.idval); + } +85=> +#line 563 "limbo.y" +{ + yyval.ty = mkdottype(yys[yypt-5].yyv.ty.src.start, yys[yypt-3].yyv.tok.src.stop, yys[yypt-5].yyv.ty, yys[yypt-3].yyv.tok.v.idval); + yyval.ty = mkinsttype(yys[yypt-5].yyv.ty.src, yyval.ty, yys[yypt-1].yyv.types); + } +86=> +#line 570 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-0].yyv.ty.src, nil, yys[yypt-0].yyv.ty, nil); + } +87=> +#line 574 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-2].yyv.ids.src, nil, yys[yypt-0].yyv.ty, yys[yypt-2].yyv.ids); + } +88=> +#line 580 "limbo.y" +{ + yyval.ids = nil; + } +89=> +#line 584 "limbo.y" +{ + yyval.ids = polydecl(yys[yypt-1].yyv.ids); + } +90=> +#line 590 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tfn, tnone, yys[yypt-1].yyv.ids); + } +91=> +#line 594 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tfn, tnone, nil); + yyval.ty.varargs = byte 1; + } +92=> +#line 599 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-4].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tfn, tnone, yys[yypt-3].yyv.ids); + yyval.ty.varargs = byte 1; + } +93=> +#line 606 "limbo.y" +{ + yyval.ty = yys[yypt-0].yyv.ty; + } +94=> +#line 610 "limbo.y" +{ + yys[yypt-2].yyv.ty.tof = yys[yypt-0].yyv.ty; + yys[yypt-2].yyv.ty.src.stop = yys[yypt-0].yyv.ty.src.stop; + yyval.ty = yys[yypt-2].yyv.ty; + } +95=> +#line 618 "limbo.y" +{ + yyval.ty = yys[yypt-0].yyv.ty; + } +96=> +#line 622 "limbo.y" +{ + yyval.ty = yys[yypt-4].yyv.ty; + yyval.ty.val = rotater(yys[yypt-1].yyv.node); + } +97=> +#line 629 "limbo.y" +{ + yyval.ids = nil; + } +98=> +yyval.ids = yys[yyp+1].yyv.ids; +99=> +yyval.ids = yys[yyp+1].yyv.ids; +100=> +#line 637 "limbo.y" +{ + yyval.ids = appdecls(yys[yypt-2].yyv.ids, yys[yypt-0].yyv.ids); + } +101=> +#line 643 "limbo.y" +{ + yyval.ids = typeids(yys[yypt-2].yyv.ids, yys[yypt-0].yyv.ty); + } +102=> +#line 647 "limbo.y" +{ + yyval.ids = typeids(yys[yypt-2].yyv.ids, yys[yypt-0].yyv.ty); + for(d := yyval.ids; d != nil; d = d.next) + d.implicit = byte 1; + } +103=> +#line 653 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-2].yyv.node.src, enter("junk", 0), yys[yypt-0].yyv.ty, nil); + yyval.ids.store = Darg; + yyerror("illegal argument declaraion"); + } +104=> +#line 659 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-2].yyv.node.src, enter("junk", 0), yys[yypt-0].yyv.ty, nil); + yyval.ids.store = Darg; + yyerror("illegal argument declaraion"); + } +105=> +#line 667 "limbo.y" +{ + yyval.ids = revids(yys[yypt-0].yyv.ids); + } +106=> +#line 673 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, nil); + yyval.ids.store = Darg; + } +107=> +#line 678 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-0].yyv.tok.src, nil, nil, nil); + yyval.ids.store = Darg; + } +108=> +#line 683 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, yys[yypt-2].yyv.ids); + yyval.ids.store = Darg; + } +109=> +#line 688 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-0].yyv.tok.src, nil, nil, yys[yypt-2].yyv.ids); + yyval.ids.store = Darg; + } +110=> +#line 695 "limbo.y" +{ + yyval.ty = yys[yypt-0].yyv.ty; + } +111=> +#line 699 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-1].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tref, yys[yypt-0].yyv.ty, nil); + } +112=> +#line 703 "limbo.y" +{ + yyval.ty = yys[yypt-0].yyv.ty; + } +113=> +#line 707 "limbo.y" +{ + yyval.ty = mktype(yys[yypt-1].yyv.tok.src.start, yys[yypt-0].yyv.tok.src.stop, Tref, yys[yypt-0].yyv.ty, nil); + } +114=> +#line 713 "limbo.y" +{ + yyval.node = fndecl(yys[yypt-3].yyv.node, yys[yypt-2].yyv.ty, yys[yypt-0].yyv.node); + nfns++; + # patch up polydecs + if(yys[yypt-3].yyv.node.op == Odot){ + if(yys[yypt-3].yyv.node.right.left != nil){ + yys[yypt-2].yyv.ty.polys = yys[yypt-3].yyv.node.right.left.decl; + yys[yypt-3].yyv.node.right.left = nil; + } + if(yys[yypt-3].yyv.node.left.op == Oname && yys[yypt-3].yyv.node.left.left != nil){ + yyval.node.decl = yys[yypt-3].yyv.node.left.left.decl; + yys[yypt-3].yyv.node.left.left = nil; + } + } + else{ + if(yys[yypt-3].yyv.node.left != nil){ + yys[yypt-2].yyv.ty.polys = yys[yypt-3].yyv.node.left.decl; + yys[yypt-3].yyv.node.left = nil; + } + } + yys[yypt-2].yyv.ty.eraises = yys[yypt-1].yyv.node; + yyval.node.src = yys[yypt-3].yyv.node.src; + } +115=> +#line 739 "limbo.y" +{ + yyval.node = mkn(Otuple, rotater(yys[yypt-1].yyv.node), nil); + yyval.node.src.start = yys[yypt-3].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +116=> +#line 745 "limbo.y" +{ + yyval.node = mkn(Otuple, mkunary(Oseq, yys[yypt-0].yyv.node), nil); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.node.src.stop; + } +117=> +#line 751 "limbo.y" +{ + yyval.node = nil; + } +118=> +#line 757 "limbo.y" +{ + if(yys[yypt-1].yyv.node == nil){ + yys[yypt-1].yyv.node = mkn(Onothing, nil, nil); + yys[yypt-1].yyv.node.src.start = curline(); + yys[yypt-1].yyv.node.src.stop = yys[yypt-1].yyv.node.src.start; + } + yyval.node = rotater(yys[yypt-1].yyv.node); + yyval.node.src.start = yys[yypt-2].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +119=> +#line 768 "limbo.y" +{ + yyval.node = mkn(Onothing, nil, nil); + } +120=> +#line 772 "limbo.y" +{ + yyval.node = mkn(Onothing, nil, nil); + } +121=> +#line 778 "limbo.y" +{ + yyval.node = mkname(yys[yypt-1].yyv.tok.src, yys[yypt-1].yyv.tok.v.idval); + if(yys[yypt-0].yyv.ids != nil){ + yyval.node.left = mkn(Onothing, nil ,nil); + yyval.node.left.decl = yys[yypt-0].yyv.ids; + } + } +122=> +#line 786 "limbo.y" +{ + yyval.node = mkbin(Odot, yys[yypt-3].yyv.node, mkname(yys[yypt-1].yyv.tok.src, yys[yypt-1].yyv.tok.v.idval)); + if(yys[yypt-0].yyv.ids != nil){ + yyval.node.right.left = mkn(Onothing, nil ,nil); + yyval.node.right.left.decl = yys[yypt-0].yyv.ids; + } + } +123=> +#line 796 "limbo.y" +{ + yyval.node = nil; + } +124=> +#line 800 "limbo.y" +{ + if(yys[yypt-1].yyv.node == nil) + yyval.node = yys[yypt-0].yyv.node; + else if(yys[yypt-0].yyv.node == nil) + yyval.node = yys[yypt-1].yyv.node; + else + yyval.node = mkbin(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); + } +125=> +#line 809 "limbo.y" +{ + if(yys[yypt-1].yyv.node == nil) + yyval.node = yys[yypt-0].yyv.node; + else + yyval.node = mkbin(Oseq, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); + } +128=> +#line 822 "limbo.y" +{ + yyval.node = mkn(Onothing, nil, nil); + yyval.node.src.start = curline(); + yyval.node.src.stop = yyval.node.src.start; + } +129=> +#line 828 "limbo.y" +{ + yyval.node = mkn(Onothing, nil, nil); + yyval.node.src.start = curline(); + yyval.node.src.stop = yyval.node.src.start; + } +130=> +#line 834 "limbo.y" +{ + yyval.node = mkn(Onothing, nil, nil); + yyval.node.src.start = curline(); + yyval.node.src.stop = yyval.node.src.start; + } +131=> +#line 840 "limbo.y" +{ + if(yys[yypt-1].yyv.node == nil){ + yys[yypt-1].yyv.node = mkn(Onothing, nil, nil); + yys[yypt-1].yyv.node.src.start = curline(); + yys[yypt-1].yyv.node.src.stop = yys[yypt-1].yyv.node.src.start; + } + yyval.node = mkscope(rotater(yys[yypt-1].yyv.node)); + } +132=> +#line 849 "limbo.y" +{ + yyerror("illegal declaration"); + yyval.node = mkn(Onothing, nil, nil); + yyval.node.src.start = curline(); + yyval.node.src.stop = yyval.node.src.start; + } +133=> +#line 856 "limbo.y" +{ + yyerror("illegal declaration"); + yyval.node = mkn(Onothing, nil, nil); + yyval.node.src.start = curline(); + yyval.node.src.stop = yyval.node.src.start; + } +134=> +#line 863 "limbo.y" +{ + yyval.node = yys[yypt-1].yyv.node; + } +135=> +#line 867 "limbo.y" +{ + yyval.node = mkn(Oif, yys[yypt-2].yyv.node, mkunary(Oseq, yys[yypt-0].yyv.node)); + yyval.node.src.start = yys[yypt-4].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.node.src.stop; + } +136=> +#line 873 "limbo.y" +{ + yyval.node = mkn(Oif, yys[yypt-4].yyv.node, mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node)); + yyval.node.src.start = yys[yypt-6].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.node.src.stop; + } +137=> +#line 879 "limbo.y" +{ + yyval.node = mkunary(Oseq, yys[yypt-0].yyv.node); + if(yys[yypt-2].yyv.node.op != Onothing) + yyval.node.right = yys[yypt-2].yyv.node; + yyval.node = mkbin(Ofor, yys[yypt-4].yyv.node, yyval.node); + yyval.node.decl = yys[yypt-9].yyv.ids; + if(yys[yypt-6].yyv.node.op != Onothing) + yyval.node = mkbin(Oseq, yys[yypt-6].yyv.node, yyval.node); + } +138=> +#line 889 "limbo.y" +{ + yyval.node = mkn(Ofor, yys[yypt-2].yyv.node, mkunary(Oseq, yys[yypt-0].yyv.node)); + yyval.node.src.start = yys[yypt-4].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.node.src.stop; + yyval.node.decl = yys[yypt-5].yyv.ids; + } +139=> +#line 896 "limbo.y" +{ + yyval.node = mkn(Odo, yys[yypt-2].yyv.node, yys[yypt-5].yyv.node); + yyval.node.src.start = yys[yypt-6].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-1].yyv.tok.src.stop; + yyval.node.decl = yys[yypt-7].yyv.ids; + } +140=> +#line 903 "limbo.y" +{ + yyval.node = mkn(Obreak, nil, nil); + yyval.node.decl = yys[yypt-1].yyv.ids; + yyval.node.src = yys[yypt-2].yyv.tok.src; + } +141=> +#line 909 "limbo.y" +{ + yyval.node = mkn(Ocont, nil, nil); + yyval.node.decl = yys[yypt-1].yyv.ids; + yyval.node.src = yys[yypt-2].yyv.tok.src; + } +142=> +#line 915 "limbo.y" +{ + yyval.node = mkn(Oret, yys[yypt-1].yyv.node, nil); + yyval.node.src = yys[yypt-2].yyv.tok.src; + if(yys[yypt-1].yyv.node.op == Onothing) + yyval.node.left = nil; + else + yyval.node.src.stop = yys[yypt-1].yyv.node.src.stop; + } +143=> +#line 924 "limbo.y" +{ + yyval.node = mkn(Ospawn, yys[yypt-1].yyv.node, nil); + yyval.node.src.start = yys[yypt-2].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-1].yyv.node.src.stop; + } +144=> +#line 930 "limbo.y" +{ + yyval.node = mkn(Oraise, yys[yypt-1].yyv.node, nil); + yyval.node.src.start = yys[yypt-2].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-1].yyv.node.src.stop; + } +145=> +#line 936 "limbo.y" +{ + yyval.node = mkn(Ocase, yys[yypt-3].yyv.node, caselist(yys[yypt-1].yyv.node, nil)); + yyval.node.src = yys[yypt-3].yyv.node.src; + yyval.node.decl = yys[yypt-5].yyv.ids; + } +146=> +#line 942 "limbo.y" +{ + yyval.node = mkn(Oalt, caselist(yys[yypt-1].yyv.node, nil), nil); + yyval.node.src = yys[yypt-3].yyv.tok.src; + yyval.node.decl = yys[yypt-4].yyv.ids; + } +147=> +#line 948 "limbo.y" +{ + yyval.node = mkn(Opick, mkbin(Odas, mkname(yys[yypt-5].yyv.tok.src, yys[yypt-5].yyv.tok.v.idval), yys[yypt-3].yyv.node), caselist(yys[yypt-1].yyv.node, nil)); + yyval.node.src.start = yys[yypt-5].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-3].yyv.node.src.stop; + yyval.node.decl = yys[yypt-7].yyv.ids; + } +148=> +#line 955 "limbo.y" +{ + yyval.node = mkn(Oexit, nil, nil); + yyval.node.src = yys[yypt-1].yyv.tok.src; + } +149=> +#line 960 "limbo.y" +{ + if(yys[yypt-6].yyv.node == nil){ + yys[yypt-6].yyv.node = mkn(Onothing, nil, nil); + yys[yypt-6].yyv.node.src.start = yys[yypt-6].yyv.node.src.stop = curline(); + } + yys[yypt-6].yyv.node = mkscope(rotater(yys[yypt-6].yyv.node)); + yyval.node = mkbin(Oexstmt, yys[yypt-6].yyv.node, mkn(Oexcept, yys[yypt-3].yyv.node, caselist(yys[yypt-1].yyv.node, nil))); + } +150=> +#line 975 "limbo.y" +{ + yyval.ids = nil; + } +151=> +#line 979 "limbo.y" +{ + if(yys[yypt-1].yyv.ids.next != nil) + yyerror("only one identifier allowed in a label"); + yyval.ids = yys[yypt-1].yyv.ids; + } +152=> +#line 987 "limbo.y" +{ + yyval.ids = nil; + } +153=> +#line 991 "limbo.y" +{ + yyval.ids = mkids(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval, nil, nil); + } +154=> +#line 997 "limbo.y" +{ + yys[yypt-1].yyv.node.left.right.right = yys[yypt-0].yyv.node; + yyval.node = yys[yypt-1].yyv.node; + } +155=> +#line 1004 "limbo.y" +{ + yyval.node = mkunary(Oseq, mkscope(mkunary(Olabel, rotater(yys[yypt-1].yyv.node)))); + } +156=> +#line 1008 "limbo.y" +{ + yys[yypt-3].yyv.node.left.right.right = yys[yypt-2].yyv.node; + yyval.node = mkbin(Oseq, mkscope(mkunary(Olabel, rotater(yys[yypt-1].yyv.node))), yys[yypt-3].yyv.node); + } +157=> +#line 1015 "limbo.y" +{ + yys[yypt-1].yyv.node.left.right = mkscope(yys[yypt-0].yyv.node); + yyval.node = yys[yypt-1].yyv.node; + } +158=> +#line 1022 "limbo.y" +{ + yyval.node = mkunary(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node))); + } +159=> +#line 1026 "limbo.y" +{ + yys[yypt-3].yyv.node.left.right = mkscope(yys[yypt-2].yyv.node); + yyval.node = mkbin(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node)), yys[yypt-3].yyv.node); + } +160=> +#line 1033 "limbo.y" +{ + yys[yypt-1].yyv.node.left.right = mkscope(yys[yypt-0].yyv.node); + yyval.node = yys[yypt-1].yyv.node; + } +161=> +#line 1040 "limbo.y" +{ + yyval.node = mkunary(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node))); + } +162=> +#line 1044 "limbo.y" +{ + yys[yypt-3].yyv.node.left.right = mkscope(yys[yypt-2].yyv.node); + yyval.node = mkbin(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node)), yys[yypt-3].yyv.node); + } +163=> +yyval.node = yys[yyp+1].yyv.node; +164=> +#line 1052 "limbo.y" +{ + yyval.node = mkbin(Orange, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +165=> +#line 1056 "limbo.y" +{ + yyval.node = mkn(Owild, nil, nil); + yyval.node.src = yys[yypt-0].yyv.tok.src; + } +166=> +#line 1061 "limbo.y" +{ + yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +167=> +#line 1065 "limbo.y" +{ + yyval.node = mkn(Onothing, nil, nil); + yyval.node.src.start = curline(); + yyval.node.src.stop = yyval.node.src.start; + } +168=> +#line 1073 "limbo.y" +{ + yys[yypt-1].yyv.node.left.right = mkscope(yys[yypt-0].yyv.node); + yyval.node = yys[yypt-1].yyv.node; + } +169=> +#line 1080 "limbo.y" +{ + yyval.node = mkunary(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node))); + } +170=> +#line 1084 "limbo.y" +{ + yys[yypt-3].yyv.node.left.right = mkscope(yys[yypt-2].yyv.node); + yyval.node = mkbin(Oseq, mkunary(Olabel, rotater(yys[yypt-1].yyv.node)), yys[yypt-3].yyv.node); + } +171=> +#line 1091 "limbo.y" +{ + yyval.node = mkname(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval); + } +172=> +#line 1095 "limbo.y" +{ + yyval.node = mkn(Owild, nil, nil); + yyval.node.src = yys[yypt-0].yyv.tok.src; + } +173=> +#line 1100 "limbo.y" +{ + yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +174=> +#line 1104 "limbo.y" +{ + yyval.node = mkn(Onothing, nil, nil); + yyval.node.src.start = curline(); + yyval.node.src.stop = yyval.node.src.start; + } +175=> +#line 1112 "limbo.y" +{ + yyval.node = mkn(Onothing, nil, nil); + yyval.node.src.start = curline(); + yyval.node.src.stop = yyval.node.src.start; + } +176=> +yyval.node = yys[yyp+1].yyv.node; +177=> +yyval.node = yys[yyp+1].yyv.node; +178=> +#line 1122 "limbo.y" +{ + yyval.node = mkbin(Oas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +179=> +#line 1126 "limbo.y" +{ + yyval.node = mkbin(Oandas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +180=> +#line 1130 "limbo.y" +{ + yyval.node = mkbin(Ooras, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +181=> +#line 1134 "limbo.y" +{ + yyval.node = mkbin(Oxoras, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +182=> +#line 1138 "limbo.y" +{ + yyval.node = mkbin(Olshas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +183=> +#line 1142 "limbo.y" +{ + yyval.node = mkbin(Orshas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +184=> +#line 1146 "limbo.y" +{ + yyval.node = mkbin(Oaddas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +185=> +#line 1150 "limbo.y" +{ + yyval.node = mkbin(Osubas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +186=> +#line 1154 "limbo.y" +{ + yyval.node = mkbin(Omulas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +187=> +#line 1158 "limbo.y" +{ + yyval.node = mkbin(Odivas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +188=> +#line 1162 "limbo.y" +{ + yyval.node = mkbin(Omodas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +189=> +#line 1166 "limbo.y" +{ + yyval.node = mkbin(Oexpas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +190=> +#line 1170 "limbo.y" +{ + yyval.node = mkbin(Osnd, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node); + } +191=> +#line 1174 "limbo.y" +{ + yyval.node = mkbin(Odas, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +192=> +#line 1178 "limbo.y" +{ + yyval.node = mkn(Oload, yys[yypt-0].yyv.node, nil); + yyval.node.src.start = yys[yypt-2].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.node.src.stop; + yyval.node.ty = mkidtype(yys[yypt-1].yyv.tok.src, yys[yypt-1].yyv.tok.v.idval); + } +193=> +#line 1185 "limbo.y" +{ + yyval.node = yyval.node = mkbin(Oexp, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +194=> +#line 1189 "limbo.y" +{ + yyval.node = mkbin(Omul, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +195=> +#line 1193 "limbo.y" +{ + yyval.node = mkbin(Odiv, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +196=> +#line 1197 "limbo.y" +{ + yyval.node = mkbin(Omod, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +197=> +#line 1201 "limbo.y" +{ + yyval.node = mkbin(Oadd, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +198=> +#line 1205 "limbo.y" +{ + yyval.node = mkbin(Osub, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +199=> +#line 1209 "limbo.y" +{ + yyval.node = mkbin(Orsh, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +200=> +#line 1213 "limbo.y" +{ + yyval.node = mkbin(Olsh, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +201=> +#line 1217 "limbo.y" +{ + yyval.node = mkbin(Olt, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +202=> +#line 1221 "limbo.y" +{ + yyval.node = mkbin(Ogt, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +203=> +#line 1225 "limbo.y" +{ + yyval.node = mkbin(Oleq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +204=> +#line 1229 "limbo.y" +{ + yyval.node = mkbin(Ogeq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +205=> +#line 1233 "limbo.y" +{ + yyval.node = mkbin(Oeq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +206=> +#line 1237 "limbo.y" +{ + yyval.node = mkbin(Oneq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +207=> +#line 1241 "limbo.y" +{ + yyval.node = mkbin(Oand, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +208=> +#line 1245 "limbo.y" +{ + yyval.node = mkbin(Oxor, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +209=> +#line 1249 "limbo.y" +{ + yyval.node = mkbin(Oor, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +210=> +#line 1253 "limbo.y" +{ + yyval.node = mkbin(Ocons, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +211=> +#line 1257 "limbo.y" +{ + yyval.node = mkbin(Oandand, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +212=> +#line 1261 "limbo.y" +{ + yyval.node = mkbin(Ooror, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +213=> +yyval.node = yys[yyp+1].yyv.node; +214=> +#line 1268 "limbo.y" +{ + yys[yypt-0].yyv.node.src.start = yys[yypt-1].yyv.tok.src.start; + yyval.node = yys[yypt-0].yyv.node; + } +215=> +#line 1273 "limbo.y" +{ + yyval.node = mkunary(Oneg, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +216=> +#line 1278 "limbo.y" +{ + yyval.node = mkunary(Onot, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +217=> +#line 1283 "limbo.y" +{ + yyval.node = mkunary(Ocomp, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +218=> +#line 1288 "limbo.y" +{ + yyval.node = mkunary(Oind, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +219=> +#line 1293 "limbo.y" +{ + yyval.node = mkunary(Opreinc, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +220=> +#line 1298 "limbo.y" +{ + yyval.node = mkunary(Opredec, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +221=> +#line 1303 "limbo.y" +{ + yyval.node = mkunary(Orcv, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +222=> +#line 1308 "limbo.y" +{ + yyval.node = mkunary(Ohd, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +223=> +#line 1313 "limbo.y" +{ + yyval.node = mkunary(Otl, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +224=> +#line 1318 "limbo.y" +{ + yyval.node = mkunary(Olen, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +225=> +#line 1323 "limbo.y" +{ + yyval.node = mkunary(Oref, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +226=> +#line 1328 "limbo.y" +{ + yyval.node = mkunary(Otagof, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + } +227=> +#line 1333 "limbo.y" +{ + yyval.node = mkn(Oarray, yys[yypt-3].yyv.node, nil); + yyval.node.ty = mktype(yys[yypt-5].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tarray, yys[yypt-0].yyv.ty, nil); + yyval.node.src = yyval.node.ty.src; + } +228=> +#line 1339 "limbo.y" +{ + yyval.node = mkn(Oarray, yys[yypt-5].yyv.node, yys[yypt-1].yyv.node); + yyval.node.src.start = yys[yypt-7].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +229=> +#line 1345 "limbo.y" +{ + yyval.node = mkn(Onothing, nil, nil); + yyval.node.src.start = yys[yypt-5].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-4].yyv.tok.src.stop; + yyval.node = mkn(Oarray, yyval.node, yys[yypt-1].yyv.node); + yyval.node.src.start = yys[yypt-6].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +230=> +#line 1354 "limbo.y" +{ + yyval.node = etolist(yys[yypt-1].yyv.node); + yyval.node.src.start = yys[yypt-4].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +231=> +#line 1360 "limbo.y" +{ + yyval.node = mkn(Ochan, nil, nil); + yyval.node.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tchan, yys[yypt-0].yyv.ty, nil); + yyval.node.src = yyval.node.ty.src; + } +232=> +#line 1366 "limbo.y" +{ + yyval.node = mkn(Ochan, yys[yypt-3].yyv.node, nil); + yyval.node.ty = mktype(yys[yypt-5].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tchan, yys[yypt-0].yyv.ty, nil); + yyval.node.src = yyval.node.ty.src; + } +233=> +#line 1372 "limbo.y" +{ + yyval.node = mkunary(Ocast, yys[yypt-0].yyv.node); + yyval.node.ty = mktype(yys[yypt-3].yyv.tok.src.start, yys[yypt-0].yyv.node.src.stop, Tarray, mkidtype(yys[yypt-1].yyv.tok.src, yys[yypt-1].yyv.tok.v.idval), nil); + yyval.node.src = yyval.node.ty.src; + } +234=> +#line 1378 "limbo.y" +{ + yyval.node = mkunary(Ocast, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + yyval.node.ty = mkidtype(yyval.node.src, yys[yypt-1].yyv.tok.v.idval); + } +235=> +#line 1384 "limbo.y" +{ + yyval.node = mkunary(Ocast, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + yyval.node.ty = mkidtype(yyval.node.src, yys[yypt-1].yyv.tok.v.idval); + } +236=> +#line 1390 "limbo.y" +{ + yyval.node = mkunary(Ocast, yys[yypt-0].yyv.node); + yyval.node.src.start = yys[yypt-1].yyv.tok.src.start; + yyval.node.ty = yys[yypt-1].yyv.ty; + } +237=> +yyval.node = yys[yyp+1].yyv.node; +238=> +#line 1399 "limbo.y" +{ + yyval.node = mkn(Ocall, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node); + yyval.node.src.start = yys[yypt-3].yyv.node.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +239=> +#line 1405 "limbo.y" +{ + yyval.node = yys[yypt-1].yyv.node; + if(yys[yypt-1].yyv.node.op == Oseq) + yyval.node = mkn(Otuple, rotater(yys[yypt-1].yyv.node), nil); + else + yyval.node.flags |= byte PARENS; + yyval.node.src.start = yys[yypt-2].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +240=> +#line 1415 "limbo.y" +{ +# n := mkdeclname($1, mkids($1, enter(".fn"+string nfnexp++, 0), nil, nil)); +# $<node>$ = fndef(n, $2); +# nfns++; + } +241=> +#line 1420 "limbo.y" +{ +# $$ = fnfinishdef($<node>3, $4); +# $$ = mkdeclname($1, $$.left.decl); + yyerror("urt unk"); + yyval.node = nil; + } +242=> +#line 1427 "limbo.y" +{ + yyval.node = mkbin(Odot, yys[yypt-2].yyv.node, mkname(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval)); + } +243=> +#line 1431 "limbo.y" +{ + yyval.node = mkbin(Omdot, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +244=> +#line 1435 "limbo.y" +{ + yyval.node = mkbin(Oindex, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node); + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +245=> +#line 1440 "limbo.y" +{ + if(yys[yypt-3].yyv.node.op == Onothing) + yys[yypt-3].yyv.node.src = yys[yypt-2].yyv.tok.src; + if(yys[yypt-1].yyv.node.op == Onothing) + yys[yypt-1].yyv.node.src = yys[yypt-2].yyv.tok.src; + yyval.node = mkbin(Oslice, yys[yypt-5].yyv.node, mkbin(Oseq, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node)); + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +246=> +#line 1449 "limbo.y" +{ + yyval.node = mkunary(Oinc, yys[yypt-1].yyv.node); + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +247=> +#line 1454 "limbo.y" +{ + yyval.node = mkunary(Odec, yys[yypt-1].yyv.node); + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +248=> +#line 1459 "limbo.y" +{ + yyval.node = mksconst(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval); + } +249=> +#line 1463 "limbo.y" +{ + yyval.node = mkconst(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.ival); + if(yys[yypt-0].yyv.tok.v.ival > big 16r7fffffff || yys[yypt-0].yyv.tok.v.ival < big -16r7fffffff) + yyval.node.ty = tbig; + } +250=> +#line 1469 "limbo.y" +{ + yyval.node = mkrconst(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.rval); + } +251=> +#line 1473 "limbo.y" +{ + yyval.node = mkbin(Oindex, yys[yypt-5].yyv.node, rotater(mkbin(Oseq, yys[yypt-3].yyv.node, yys[yypt-1].yyv.node))); + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +252=> +#line 1480 "limbo.y" +{ + yyval.node = mkname(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval); + } +253=> +#line 1484 "limbo.y" +{ + yyval.node = mknil(yys[yypt-0].yyv.tok.src); + } +254=> +#line 1490 "limbo.y" +{ + yyval.node = mkn(Otuple, rotater(yys[yypt-1].yyv.node), nil); + yyval.node.src.start = yys[yypt-2].yyv.tok.src.start; + yyval.node.src.stop = yys[yypt-0].yyv.tok.src.stop; + } +255=> +yyval.node = yys[yyp+1].yyv.node; +256=> +#line 1499 "limbo.y" +{ + yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +257=> +yyval.node = yys[yyp+1].yyv.node; +258=> +yyval.node = yys[yyp+1].yyv.node; +259=> +#line 1509 "limbo.y" +{ + yyval.node = mkn(Otype, nil, nil); + yyval.node.ty = mkidtype(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval); + yyval.node.src = yyval.node.ty.src; + } +260=> +#line 1515 "limbo.y" +{ + yyval.node = mkn(Otype, nil, nil); + yyval.node.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tarray, yys[yypt-0].yyv.ty, nil); + yyval.node.src = yyval.node.ty.src; + } +261=> +#line 1521 "limbo.y" +{ + yyval.node = mkn(Otype, nil, nil); + yyval.node.ty = mktype(yys[yypt-2].yyv.tok.src.start, yys[yypt-0].yyv.ty.src.stop, Tlist, yys[yypt-0].yyv.ty, nil); + yyval.node.src = yyval.node.ty.src; + } +262=> +#line 1527 "limbo.y" +{ + yyval.node = mkn(Otype, nil ,nil); + yyval.node.ty = yys[yypt-0].yyv.ty; + yyval.node.ty.flags |= CYCLIC; + yyval.node.src = yyval.node.ty.src; + } +263=> +#line 1536 "limbo.y" +{ + yyval.node = mkname(yys[yypt-0].yyv.tok.src, yys[yypt-0].yyv.tok.v.idval); + } +264=> +#line 1540 "limbo.y" +{ + yyval.node = nil; + } +265=> +yyval.node = yys[yyp+1].yyv.node; +266=> +yyval.node = yys[yyp+1].yyv.node; +267=> +#line 1548 "limbo.y" +{ + yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +268=> +#line 1552 "limbo.y" +{ + yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +269=> +#line 1558 "limbo.y" +{ + yyval.node = nil; + } +270=> +#line 1562 "limbo.y" +{ + yyval.node = rotater(yys[yypt-0].yyv.node); + } +271=> +yyval.node = yys[yyp+1].yyv.node; +272=> +yyval.node = yys[yyp+1].yyv.node; +273=> +yyval.node = yys[yyp+1].yyv.node; +274=> +#line 1573 "limbo.y" +{ + yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +275=> +#line 1579 "limbo.y" +{ + yyval.node = rotater(yys[yypt-0].yyv.node); + } +276=> +#line 1583 "limbo.y" +{ + yyval.node = rotater(yys[yypt-1].yyv.node); + } +277=> +yyval.node = yys[yyp+1].yyv.node; +278=> +#line 1590 "limbo.y" +{ + yyval.node = mkbin(Oseq, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); + } +279=> +#line 1596 "limbo.y" +{ + yyval.node = mkn(Oelem, nil, yys[yypt-0].yyv.node); + yyval.node.src = yys[yypt-0].yyv.node.src; + } +280=> +#line 1601 "limbo.y" +{ + yyval.node = mkbin(Oelem, rotater(yys[yypt-2].yyv.node), yys[yypt-0].yyv.node); + } +281=> +#line 1607 "limbo.y" +{ + if(yys[yypt-1].yyv.node.op == Oseq) + yys[yypt-1].yyv.node.right.left = rotater(yys[yypt-0].yyv.node); + else + yys[yypt-1].yyv.node.left = rotater(yys[yypt-0].yyv.node); + yyval.node = yys[yypt-1].yyv.node; + } +282=> +#line 1617 "limbo.y" +{ + yyval.node = typedecl(yys[yypt-1].yyv.ids, mktype(yys[yypt-1].yyv.ids.src.start, yys[yypt-0].yyv.tok.src.stop, Tpoly, nil, nil)); + } +283=> +#line 1621 "limbo.y" +{ + if(yys[yypt-3].yyv.node.op == Oseq) + yys[yypt-3].yyv.node.right.left = rotater(yys[yypt-2].yyv.node); + else + yys[yypt-3].yyv.node.left = rotater(yys[yypt-2].yyv.node); + yyval.node = mkbin(Oseq, yys[yypt-3].yyv.node, typedecl(yys[yypt-1].yyv.ids, mktype(yys[yypt-1].yyv.ids.src.start, yys[yypt-0].yyv.tok.src.stop, Tpoly, nil, nil))); + } + } + } + + return yyn; +} diff --git a/appl/cmd/limbo/limbo.m b/appl/cmd/limbo/limbo.m new file mode 100644 index 00000000..8c2efce0 --- /dev/null +++ b/appl/cmd/limbo/limbo.m @@ -0,0 +1,527 @@ +include "sys.m"; +include "math.m"; +include "string.m"; +include "bufio.m"; +include "isa.m"; +include "workdir.m"; + +# internal dis ops +IEXC: con MAXDIS; +IEXC0: con (MAXDIS+1); +INOOP: con (MAXDIS+2); + +# temporary +LDT: con 1; + +STemp: con NREG * IBY2WD; +RTemp: con STemp + IBY2WD; +DTemp: con RTemp + IBY2WD; +MaxTemp: con DTemp + IBY2WD; +MaxReg: con 1 << 16; +MaxAlign: con IBY2LG; +StrSize: con 256; +MaxIncPath: con 32; # max directories in include path +MaxScope: con 64; # max nested {} +MaxInclude: con 32; # max nested include "" +ScopeBuiltin, +ScopeNils, +ScopeGlobal: con iota; + +Line: type int; +PosBits: con 10; +PosMask: con (1 << PosBits) - 1; + +Src: adt +{ + start: Line; + stop: Line; +}; + +File: adt +{ + name: string; + abs: int; # absolute line of start of the part of file + off: int; # offset to line in the file + in: int; # absolute line where included + act: string; # name of real file with #line fake file + actoff: int; # offset from fake line to real line + sbl: int; # symbol file number +}; + +Val: adt +{ + idval: ref Sym; + ival: big; + rval: real; +}; + +Tok: adt +{ + src: Src; + v: Val; +}; + +# +# addressing modes +# + Aimm, # immediate + Amp, # global + Ampind, # global indirect + Afp, # activation frame + Afpind, # frame indirect + Apc, # branch + Adesc, # type descriptor immediate + Aoff, # offset in module description table + Anoff, # above encoded as a -ve + Aerr, # error + Anone, # no operand + Aldt, # linkage descriptor table immediate + Aend: con byte iota; + +Addr: adt +{ + reg: int; + offset: int; + decl: cyclic ref Decl; +}; + +Inst: adt +{ + src: Src; + op: int; # could be a byte + pc: int; + reach: byte; # could a control path reach this instruction? + sm: byte; # operand addressing modes + mm: byte; + dm: byte; + s: cyclic Addr; # operands + m: cyclic Addr; + d: cyclic Addr; + branch: cyclic ref Inst; # branch destination + next: cyclic ref Inst; + block: int; # blocks nested inside +}; + +Case: adt +{ + nlab: int; + nsnd: int; + offset: int; # offset in mp + labs: cyclic array of Label; + wild: cyclic ref Node; # if nothing matches + iwild: cyclic ref Inst; +}; + +Label: adt +{ + node: cyclic ref Node; + isptr: int; # true if the labelled alt channel is a pointer + start: cyclic ref Node; # value in range [start, stop) => code + stop: cyclic ref Node; + inst: cyclic ref Inst; +}; + +# +# storage classes +# + Dtype, + Dfn, + Dglobal, + Darg, + Dlocal, + Dconst, + Dfield, + Dtag, # pick tags + Dimport, # imported identifier + Dunbound, # unbound identified + Dundef, + Dwundef, # undefined, but don't whine + + Dend: con iota; + +Decl: adt +{ + src: Src; # where declaration + sym: cyclic ref Sym; # name + store: int; # storage class + nid: byte; # block grouping for locals + inline: byte; # inline function + handler: byte; # fn has exception handler(s) + das: byte; # declared with := + dot: cyclic ref Decl; # parent adt or module + ty: cyclic ref Type; + refs: int; # number of references + offset: int; + tag: int; # union tag + + scope: int; # in which it was declared + next: cyclic ref Decl; # list in same scope, field or argument list, etc. + old: cyclic ref Decl; # declaration of the symbol in enclosing scope + + eimport: cyclic ref Node; # expr from which imported + importid: cyclic ref Decl; # identifier imported + timport: cyclic ref Decl; # stack of identifiers importing a type + + init: cyclic ref Node; # data initialization + tref: int; # 1 => is a tmp; >=2 => tmp in use + cycle: byte; # can create a cycle + cyc: byte; # so labelled in source + cycerr: byte; # delivered an error message for cycle? + implicit: byte; # implicit first argument in an adt? + + iface: cyclic ref Decl; # used external declarations in a module + + locals: cyclic ref Decl; # locals for a function + link: cyclic ref Decl; # pointer to parent function or function argument or local share or parent type dec + pc: cyclic ref Inst; # start of function + # endpc: cyclic ref Inst; # limit of function - unused + +# should be able to move this to Type + desc: ref Desc; # heap descriptor +}; + +Desc: adt +{ + id: int; # dis type identifier + used: int; # actually used in output? + map: array of byte; # byte map of pointers + size: int; # length of the object + nmap: int; # length of good bytes in map + next: cyclic ref Desc; +}; + +Dlist: adt +{ + d: ref Decl; + next: cyclic ref Dlist; +}; + +Except: adt +{ + p1: ref Inst; # first pc covered + p2: ref Inst; # last pc not covered + c: ref Case; # exception case instructions + d: ref Decl; # exception definition if any + zn: ref Node; # list of nodes to zero in handler + desc: ref Desc; # descriptor map for above + ne: int; # number of exceptions (ie not strings) in case + next: cyclic ref Except; +}; + +Sym: adt +{ + token: int; + name: string; + hash: int; + next: cyclic ref Sym; + decl: cyclic ref Decl; + unbound: cyclic ref Decl; # place holder for unbound symbols +}; + +# +# ops for nodes +# + Oadd, + Oaddas, + Oadr, + Oadtdecl, + Oalt, + Oand, + Oandand, + Oandas, + Oarray, + Oas, + Obreak, + Ocall, + Ocase, + Ocast, + Ochan, + Ocomma, + Ocomp, + Ocondecl, + Ocons, + Oconst, + Ocont, + Odas, + Odec, + Odiv, + Odivas, + Odo, + Odot, + Oelem, + Oeq, + Oexcept, + Oexdecl, + Oexit, + Oexp, + Oexpas, + Oexstmt, + Ofielddecl, + Ofnptr, + Ofor, + Ofunc, + Ogeq, + Ogt, + Ohd, + Oif, + Oimport, + Oinc, + Oind, + Oindex, + Oinds, + Oindx, + Oinv, + Ojmp, + Olabel, + Olen, + Oleq, + Oload, + Olsh, + Olshas, + Olt, + Omdot, + Omod, + Omodas, + Omoddecl, + Omul, + Omulas, + Oname, + Oneg, + Oneq, + Onot, + Onothing, + Oor, + Ooras, + Ooror, + Opick, + Opickdecl, + Opredec, + Opreinc, + Oraise, + Orange, + Orcv, + Oref, + Oret, + Orsh, + Orshas, + Oscope, + Oself, + Oseq, + Oslice, + Osnd, + Ospawn, + Osub, + Osubas, + Otagof, + Otl, + Otuple, + Otype, + Otypedecl, + Oused, + Ovardecl, + Ovardecli, + Owild, + Oxor, + Oxoras, + + Oend: con iota + 1; + +# +# moves +# + Mas, + Mcons, + Mhd, + Mtl, + + Mend: con iota; + +# +# addressability +# + Rreg, # v(fp) + Rmreg, # v(mp) + Roff, # $v + Rnoff, # $v encoded as -ve + Rdesc, # $v + Rdescp, # $v + Rconst, # $v + Ralways, # preceeding are always addressable + Radr, # v(v(fp)) + Rmadr, # v(v(mp)) + Rcant, # following are not quite addressable + Rpc, # branch address + Rmpc, # cross module branch address + Rareg, # $v(fp) + Ramreg, # $v(mp) + Raadr, # $v(v(fp)) + Ramadr, # $v(v(mp)) + Rldt, # $v + + Rend: con byte iota; + + +Const: adt +{ + val: big; + rval: real; +}; + +PARENS: con 1; +TEMP: con 2; +FNPTRA: con 4; # argument +FNPTR2: con 8; # 2nd parameter +FNPTRN: con 16; # use -ve offset +FNPTR: con FNPTRA|FNPTR2|FNPTRN; + +Node: adt +{ + src: Src; + op: int; + addable: byte; + flags: byte; + temps: byte; + left: cyclic ref Node; + right: cyclic ref Node; + ty: cyclic ref Type; + decl: cyclic ref Decl; + c: ref Const; # for Oconst +}; + + # + # types visible to limbo + # + Tnone, + Tadt, + Tadtpick, # pick case of an adt + Tarray, + Tbig, # 64 bit int + Tbyte, # 8 bit unsigned int + Tchan, + Treal, + Tfn, + Tint, # 32 bit int + Tlist, + Tmodule, + Tref, + Tstring, + Ttuple, + Texception, + Tfix, + Tpoly, + + # + # internal use types + # + Tainit, # array initializers + Talt, # alt channels + Tany, # type of nil + Tarrow, # unresolved ty->ty types + Tcase, # case labels + Tcasel, # case big labels + Tcasec, # case string labels + Tdot, # unresolved ty.id types + Terror, + Tgoto, # goto labels + Tid, # id with unknown type + Tiface, # module interface + Texcept, # exception handler tables + Tinst, # instantiated adt + + Tend: con iota; + + # + # marks for various phases of verifing types + # + OKbind, # type decls are bound + OKverify, # type looks ok + OKsized, # started figuring size + OKref, # recorded use of type + OKclass, # equivalence class found + OKcyc, # checked for cycles + OKcycsize, # checked for cycles and size + OKmodref: # started checking for a module handle + + con byte 1 << iota; + OKmask: con byte 16rff; + + # + # recursive marks + # + TReq, + TRcom, + TRcyc, + TRvis: + con byte 1 << iota; + +# type flags +FULLARGS: con byte 1; # all hidden args added +INST: con byte 2; # instantiated adt +CYCLIC: con byte 4; # cyclic type +POLY: con byte 8; # polymorphic types inside +NOPOLY: con byte 16; # no polymorphic types inside + +# must put some picks in here +Type: adt +{ + src: Src; + kind: int; + ok: byte; # set when type is verified + varargs: byte; # if a function, ends with vargs? + linkall: byte; # put all iface fns in external linkage? + rec: byte; # in the middle of recursive type + pr: byte; # in the middle of printing a recursive type + cons: byte; # exception constant + flags: byte; + sbl: int; # slot in .sbl adt table + sig: int; # signature for dynamic type check + size: int; # storage required, in bytes + align: int; # alignment in bytes + decl: cyclic ref Decl; + tof: cyclic ref Type; + ids: cyclic ref Decl; + tags: cyclic ref Decl;# tagged fields in an adt + polys: cyclic ref Decl;# polymorphic fields in fn or adt + cse: cyclic ref Case;# case or goto labels + teq: cyclic ref Type;# temporary equiv class for equiv checking + tcom: cyclic ref Type;# temporary equiv class for compat checking + eq: cyclic ref Teq; # real equiv class + eraises: cyclic ref Node; # for Tfn only + val: cyclic ref Node; # for Tfix, Tfn, Tadt only + tlist: cyclic ref Typelist; # for Tinst only + tmap: cyclic ref Tpair; # for Tadt only +}; + +# +# type equivalence classes +# +Teq: adt +{ + id: int; # for signing + ty: cyclic ref Type;# an instance of the class + eq: cyclic ref Teq; # used to link eq sets +}; + +Tattr: adt +{ + isptr: int; + refable: int; + conable: int; + isbig: int; + vis: int; # type visible to users +}; + +Tpair: adt +{ + t1: cyclic ref Type; + t2: cyclic ref Type; + nxt: cyclic ref Tpair; +}; + +Typelist: adt +{ + t: cyclic ref Type; + nxt: cyclic ref Typelist; +}; + +Sother, Sloop, Sscope : con iota; diff --git a/appl/cmd/limbo/limbo.y b/appl/cmd/limbo/limbo.y new file mode 100644 index 00000000..0c56bd1b --- /dev/null +++ b/appl/cmd/limbo/limbo.y @@ -0,0 +1,1973 @@ +%{ +include "limbo.m"; +include "draw.m"; + +%} + +%module Limbo +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); + + YYSTYPE: adt{ + tok: Tok; + ids: ref Decl; + node: ref Node; + ty: ref Type; + types: ref Typelist; + }; + + YYLEX: adt { + lval: YYSTYPE; + lex: fn(nil: self ref YYLEX): int; + error: fn(nil: self ref YYLEX, err: string); + }; +} + +%{ + # + # lex.b + # + signdump: string; # name of function for sig debugging + superwarn: int; + debug: array of int; + noline: Line; + nosrc: Src; + arrayz: int; + emitcode: string; # emit stub routines for system module functions + emitdyn: int; # emit as above but for dynamic modules + emitsbl: string; # emit symbol file for sysm modules + emitstub: int; # emit type and call frames for system modules + emittab: string; # emit table of runtime functions for this module + errors: int; + mustcompile: int; + dontcompile: int; + asmsym: int; # generate symbols in assembly language? + bout: ref Bufio->Iobuf; # output file + bsym: ref Bufio->Iobuf; # symbol output file; nil => no sym out + gendis: int; # generate dis or asm? + fixss: int; + newfnptr: int; # ISELF and -ve indices + optims: int; + + # + # decls.b + # + scope: int; + # impmod: ref Sym; # name of implementation module + impmods: ref Decl; # name of implementation module(s) + nildecl: ref Decl; # declaration for limbo's nil + selfdecl: ref Decl; # declaration for limbo's self + + # + # types.b + # + tany: ref Type; + tbig: ref Type; + tbyte: ref Type; + terror: ref Type; + tint: ref Type; + tnone: ref Type; + treal: ref Type; + tstring: ref Type; + texception: ref Type; + tunknown: ref Type; + tfnptr: ref Type; + rtexception: ref Type; + descriptors: ref Desc; # list of all possible descriptors + tattr: array of Tattr; + + # + # nodes.b + # + opcommute: array of int; + oprelinvert: array of int; + isused: array of int; + casttab: array of array of int; # instruction to cast from [1] to [2] + + nfns: int; # functions defined + nfnexp: int; + fns: array of ref Decl; # decls for fns defined + tree: ref Node; # root of parse tree + + parset: int; # time to parse + checkt: int; # time to typecheck + gent: int; # time to generate code + writet: int; # time to write out code + symt: int; # time to write out symbols +%} + +%type <ty> type fnarg fnargret fnargretp adtk fixtype iditype dotiditype +%type <ids> ids rids nids nrids tuplist forms ftypes ftype + bclab bctarg ptags rptags polydec +%type <node> zexp exp monexp term elist zelist celist + idatom idterms idterm idlist + initlist elemlist elem qual + decl topdecls topdecl fndef fbody stmt stmts qstmts qbodies cqstmts cqbodies + mdecl adtdecl mfield mfields field fields fnname + pstmts pbodies pqual pfields pfbody pdecl dfield dfields + eqstmts eqbodies idexc edecl raises tpoly tpolys texp export exportlist forpoly +%type <types> types + +%right <tok.src> '=' Landeq Loreq Lxoreq Llsheq Lrsheq + Laddeq Lsubeq Lmuleq Ldiveq Lmodeq Lexpeq Ldeclas +%left <tok.src> Lload +%left <tok.src> Loror +%left <tok.src> Landand +%right <tok.src> Lcons +%left <tok.src> '|' +%left <tok.src> '^' +%left <tok.src> '&' +%left <tok.src> Leq Lneq +%left <tok.src> '<' '>' Lleq Lgeq +%left <tok.src> Llsh Lrsh +%left <tok.src> '+' '-' +%left <tok.src> '*' '/' '%' +%right <tok.src> Lexp +%right <tok.src> Lcomm + +%left <tok.src> '(' ')' '[' ']' Linc Ldec Lof Lref +%right <tok.src> Lif Lelse Lfn ':' Lexcept Lraises +%left <tok.src> Lmdot +%left <tok.src> '.' + +%left <tok.src> Lto +%left <tok.src> Lor + + +%nonassoc <tok.v.rval> Lrconst +%nonassoc <tok.v.ival> Lconst +%nonassoc <tok.v.idval> Lid Ltid Lsconst +%nonassoc <tok.src> Llabs Lnil + '!' '~' Llen Lhd Ltl Ltagof + '{' '}' ';' + Limplement Limport Linclude + Lcon Ltype Lmodule Lcyclic + Ladt Larray Llist Lchan Lself + Ldo Lwhile Lfor Lbreak + Lalt Lcase Lpick Lcont + Lreturn Lexit Lspawn Lraise Lfix +%% +prog : Limplement ids ';' + { + impmods = $2; + } topdecls + { + tree = rotater($5); + } + | topdecls + { + impmods = nil; + tree = rotater($1); + } + ; + +topdecls: topdecl + | topdecls topdecl + { + if($1 == nil) + $$ = $2; + else if($2 == nil) + $$ = $1; + else + $$ = mkbin(Oseq, $1, $2); + } + ; + +topdecl : error ';' + { + $$ = nil; + } + | decl + | fndef + | adtdecl ';' + | mdecl ';' + | idatom '=' exp ';' + { + $$ = mkbin(Oas, $1, $3); + } + | idterm '=' exp ';' + { + $$ = mkbin(Oas, $1, $3); + } + | idatom Ldeclas exp ';' + { + $$ = mkbin(Odas, $1, $3); + } + | idterm Ldeclas exp ';' + { + $$ = mkbin(Odas, $1, $3); + } + | idterms ':' type ';' + { + yyerror("illegal declaration"); + $$ = nil; + } + | idterms ':' type '=' exp ';' + { + yyerror("illegal declaration"); + $$ = nil; + } + ; + +idterms : idterm + | idterms ',' idterm + { + $$ = mkbin(Oseq, $1, $3); + } + ; + +decl : Linclude Lsconst ';' + { + includef($2); + $$ = nil; + } + | ids ':' Ltype type ';' + { + $$ = typedecl($1, $4); + } + | ids ':' Limport exp ';' + { + $$ = importdecl($4, $1); + $$.src.start = $1.src.start; + $$.src.stop = $5.stop; + } + | ids ':' type ';' + { + $$ = vardecl($1, $3); + } + | ids ':' type '=' exp ';' + { + $$ = mkbin(Ovardecli, vardecl($1, $3), varinit($1, $5)); + } + | ids ':' Lcon exp ';' + { + $$ = condecl($1, $4); + } + | edecl + ; + +edecl : ids ':' Lexcept ';' + { + $$ = exdecl($1, nil); + } + | ids ':' Lexcept '(' tuplist ')' ';' + { + $$ = exdecl($1, revids($5)); + } + ; + +mdecl : ids ':' Lmodule '{' mfields '}' + { + $1.src.stop = $6.stop; + $$ = moddecl($1, rotater($5)); + } + ; + +mfields : + { + $$ = nil; + } + | mfields mfield + { + if($1 == nil) + $$ = $2; + else if($2 == nil) + $$ = $1; + else + $$ = mkn(Oseq, $1, $2); + } + | error + { + $$ = nil; + } + ; + +mfield : ids ':' type ';' + { + $$ = fielddecl(Dglobal, typeids($1, $3)); + } + | adtdecl ';' + | ids ':' Ltype type ';' + { + $$ = typedecl($1, $4); + } + | ids ':' Lcon exp ';' + { + $$ = condecl($1, $4); + } + | edecl + ; + +adtdecl : ids ':' Ladt polydec '{' fields '}' forpoly + { + $1.src.stop = $7.stop; + $$ = adtdecl($1, rotater($6)); + $$.ty.polys = $4; + $$.ty.val = rotater($8); + } + | ids ':' Ladt polydec Lfor '{' tpolys '}' '{' fields '}' + { + $1.src.stop = $11.stop; + $$ = adtdecl($1, rotater($10)); + $$.ty.polys = $4; + $$.ty.val = rotater($7); + } + ; + +forpoly : + { + $$ = nil; + } + | Lfor '{' tpolys '}' + { + $$ = $3; + } + ; + +fields : + { + $$ = nil; + } + | fields field + { + if($1 == nil) + $$ = $2; + else if($2 == nil) + $$ = $1; + else + $$ = mkn(Oseq, $1, $2); + } + | error + { + $$ = nil; + } + ; + +field : dfield + | pdecl + | ids ':' Lcon exp ';' + { + $$ = condecl($1, $4); + } + ; + +dfields : + { + $$ = nil; + } + | dfields dfield + { + if($1 == nil) + $$ = $2; + else if($2 == nil) + $$ = $1; + else + $$ = mkn(Oseq, $1, $2); + } + ; + +dfield : ids ':' Lcyclic type ';' + { + for(d := $1; d != nil; d = d.next) + d.cyc = byte 1; + $$ = fielddecl(Dfield, typeids($1, $4)); + } + | ids ':' type ';' + { + $$ = fielddecl(Dfield, typeids($1, $3)); + } + ; + +pdecl : Lpick '{' pfields '}' + { + $$ = $3; + } + ; + +pfields : pfbody dfields + { + $1.right.right = $2; + $$ = $1; + } + | pfbody error + { + $$ = nil; + } + | error + { + $$ = nil; + } + ; + +pfbody : ptags Llabs + { + $$ = mkn(Opickdecl, nil, mkn(Oseq, fielddecl(Dtag, $1), nil)); + typeids($1, mktype($1.src.start, $1.src.stop, Tadtpick, nil, nil)); + } + | pfbody dfields ptags Llabs + { + $1.right.right = $2; + $$ = mkn(Opickdecl, $1, mkn(Oseq, fielddecl(Dtag, $3), nil)); + typeids($3, mktype($3.src.start, $3.src.stop, Tadtpick, nil, nil)); + } + | pfbody error ptags Llabs + { + $$ = mkn(Opickdecl, nil, mkn(Oseq, fielddecl(Dtag, $3), nil)); + typeids($3, mktype($3.src.start, $3.src.stop, Tadtpick, nil, nil)); + } + ; + +ptags : rptags + { + $$ = revids($1); + } + ; + +rptags : Lid + { + $$ = mkids($<tok.src>1, $1, nil, nil); + } + | rptags Lor Lid + { + $$ = mkids($<tok.src>3, $3, nil, $1); + } + ; + +ids : rids + { + $$ = revids($1); + } + ; + +rids : Lid + { + $$ = mkids($<tok.src>1, $1, nil, nil); + } + | rids ',' Lid + { + $$ = mkids($<tok.src>3, $3, nil, $1); + } + ; + +fixtype : Lfix '(' exp ',' exp ')' + { + $$ = mktype($1.start, $6.stop, Tfix, nil, nil); + $$.val = mkbin(Oseq, $3, $5); + } + | Lfix '(' exp ')' + { + $$ = mktype($1.start, $4.stop, Tfix, nil, nil); + $$.val = $3; + } + ; + +types : type + { + $$ = addtype($1, nil); + } + | Lcyclic type + { + $$ = addtype($2, nil); + $2.flags |= CYCLIC; + } + | types ',' type + { + $$ = addtype($3, $1); + } + | types ',' Lcyclic type + { + $$ = addtype($4, $1); + $4.flags |= CYCLIC; + } + ; + +type : Ltid + { + $$ = mkidtype($<tok.src>1, $1); + } + | iditype + { + $$ = $1; + } + | dotiditype + { + $$ = $1; + } + | type Lmdot Lid + { + $$ = mkarrowtype($1.src.start, $<tok.src>3.stop, $1, $3); + } + | type Lmdot Lid '[' types ']' + { + $$ = mkarrowtype($1.src.start, $<tok.src>3.stop, $1, $3); + $$ = mkinsttype($1.src, $$, $5); + } + | Lref type + { + $$ = mktype($1.start, $2.src.stop, Tref, $2, nil); + } + | Lchan Lof type + { + $$ = mktype($1.start, $3.src.stop, Tchan, $3, nil); + } + | '(' tuplist ')' + { + if($2.next == nil) + $$ = $2.ty; + else + $$ = mktype($1.start, $3.stop, Ttuple, nil, revids($2)); + } + | Larray Lof type + { + $$ = mktype($1.start, $3.src.stop, Tarray, $3, nil); + } + | Llist Lof type + { + $$ = mktype($1.start, $3.src.stop, Tlist, $3, nil); + } + | Lfn polydec fnargretp raises + { + $3.src.start = $1.start; + $3.polys = $2; + $3.eraises = $4; + $$ = $3; + } + | fixtype +# | Lexcept +# { +# $$ = mktype($1.start, $1.stop, Texception, nil, nil); +# $$.cons = byte 1; +# } +# | Lexcept '(' tuplist ')' +# { +# $$ = mktype($1.start, $4.stop, Texception, nil, revids($3)); +# $$.cons = byte 1; +# } + ; + +iditype : Lid + { + $$ = mkidtype($<tok.src>1, $1); + } + | Lid '[' types ']' + { + $$ = mkinsttype($<tok.src>1, mkidtype($<tok.src>1, $1), $3); + } + ; + +dotiditype : type '.' Lid + { + $$ = mkdottype($1.src.start, $<tok.src>3.stop, $1, $3); + } + | type '.' Lid '[' types ']' + { + $$ = mkdottype($1.src.start, $<tok.src>3.stop, $1, $3); + $$ = mkinsttype($1.src, $$, $5); + } + ; + +tuplist : type + { + $$ = mkids($1.src, nil, $1, nil); + } + | tuplist ',' type + { + $$ = mkids($1.src, nil, $3, $1); + } + ; + +polydec : + { + $$ = nil; + } + | '[' ids ']' + { + $$ = polydecl($2); + } + ; + +fnarg : '(' forms ')' + { + $$ = mktype($1.start, $3.stop, Tfn, tnone, $2); + } + | '(' '*' ')' + { + $$ = mktype($1.start, $3.stop, Tfn, tnone, nil); + $$.varargs = byte 1; + } + | '(' ftypes ',' '*' ')' + { + $$ = mktype($1.start, $5.stop, Tfn, tnone, $2); + $$.varargs = byte 1; + } + ; + +fnargret: fnarg %prec ':' + { + $$ = $1; + } + | fnarg ':' type + { + $1.tof = $3; + $1.src.stop = $3.src.stop; + $$ = $1; + } + ; + +fnargretp: fnargret %prec '=' + { + $$ = $1; + } + | fnargret Lfor '{' tpolys '}' + { + $$ = $1; + $$.val = rotater($4); + } + ; + +forms : + { + $$ = nil; + } + | ftypes + ; + +ftypes : ftype + | ftypes ',' ftype + { + $$ = appdecls($1, $3); + } + ; + +ftype : nids ':' type + { + $$ = typeids($1, $3); + } + | nids ':' adtk + { + $$ = typeids($1, $3); + for(d := $$; d != nil; d = d.next) + d.implicit = byte 1; + } + | idterms ':' type + { + $$ = mkids($1.src, enter("junk", 0), $3, nil); + $$.store = Darg; + yyerror("illegal argument declaraion"); + } + | idterms ':' adtk + { + $$ = mkids($1.src, enter("junk", 0), $3, nil); + $$.store = Darg; + yyerror("illegal argument declaraion"); + } + ; + +nids : nrids + { + $$ = revids($1); + } + ; + +nrids : Lid + { + $$ = mkids($<tok.src>1, $1, nil, nil); + $$.store = Darg; + } + | Lnil + { + $$ = mkids($1, nil, nil, nil); + $$.store = Darg; + } + | nrids ',' Lid + { + $$ = mkids($<tok.src>3, $3, nil, $1); + $$.store = Darg; + } + | nrids ',' Lnil + { + $$ = mkids($3, nil, nil, $1); + $$.store = Darg; + } + ; + +adtk : Lself iditype + { + $$ = $2; + } + | Lself Lref iditype + { + $$ = mktype($<tok.src>2.start, $<tok.src>3.stop, Tref, $3, nil); + } + | Lself dotiditype + { + $$ = $2; + } + | Lself Lref dotiditype + { + $$ = mktype($<tok.src>2.start, $<tok.src>3.stop, Tref, $3, nil); + } + ; + +fndef : fnname fnargretp raises fbody + { + $$ = fndecl($1, $2, $4); + nfns++; + # patch up polydecs + if($1.op == Odot){ + if($1.right.left != nil){ + $2.polys = $1.right.left.decl; + $1.right.left = nil; + } + if($1.left.op == Oname && $1.left.left != nil){ + $$.decl = $1.left.left.decl; + $1.left.left = nil; + } + } + else{ + if($1.left != nil){ + $2.polys = $1.left.decl; + $1.left = nil; + } + } + $2.eraises = $3; + $$.src = $1.src; + } + ; + +raises : Lraises '(' idlist ')' + { + $$ = mkn(Otuple, rotater($3), nil); + $$.src.start = $1.start; + $$.src.stop = $4.stop; + } + | Lraises idatom + { + $$ = mkn(Otuple, mkunary(Oseq, $2), nil); + $$.src.start = $1.start; + $$.src.stop = $2.src.stop; + } + | %prec Lraises + { + $$ = nil; + } + ; + +fbody : '{' stmts '}' + { + if($2 == nil){ + $2 = mkn(Onothing, nil, nil); + $2.src.start = curline(); + $2.src.stop = $2.src.start; + } + $$ = rotater($2); + $$.src.start = $1.start; + $$.src.stop = $3.stop; + } + | error '}' + { + $$ = mkn(Onothing, nil, nil); + } + | error '{' stmts '}' + { + $$ = mkn(Onothing, nil, nil); + } + ; + +fnname : Lid polydec + { + $$ = mkname($<tok.src>1, $1); + if($2 != nil){ + $$.left = mkn(Onothing, nil ,nil); + $$.left.decl = $2; + } + } + | fnname '.' Lid polydec + { + $$ = mkbin(Odot, $1, mkname($<tok.src>3, $3)); + if($4 != nil){ + $$.right.left = mkn(Onothing, nil ,nil); + $$.right.left.decl = $4; + } + } + ; + +stmts : + { + $$ = nil; + } + | stmts decl + { + if($1 == nil) + $$ = $2; + else if($2 == nil) + $$ = $1; + else + $$ = mkbin(Oseq, $1, $2); + } + | stmts stmt + { + if($1 == nil) + $$ = $2; + else + $$ = mkbin(Oseq, $1, $2); + } + ; + +elists : '(' elist ')' + | elists ',' '(' elist ')' + ; + +stmt : error ';' + { + $$ = mkn(Onothing, nil, nil); + $$.src.start = curline(); + $$.src.stop = $$.src.start; + } + | error '}' + { + $$ = mkn(Onothing, nil, nil); + $$.src.start = curline(); + $$.src.stop = $$.src.start; + } + | error '{' stmts '}' + { + $$ = mkn(Onothing, nil, nil); + $$.src.start = curline(); + $$.src.stop = $$.src.start; + } + | '{' stmts '}' + { + if($2 == nil){ + $2 = mkn(Onothing, nil, nil); + $2.src.start = curline(); + $2.src.stop = $2.src.start; + } + $$ = mkscope(rotater($2)); + } + | elists ':' type ';' + { + yyerror("illegal declaration"); + $$ = mkn(Onothing, nil, nil); + $$.src.start = curline(); + $$.src.stop = $$.src.start; + } + | elists ':' type '=' exp';' + { + yyerror("illegal declaration"); + $$ = mkn(Onothing, nil, nil); + $$.src.start = curline(); + $$.src.stop = $$.src.start; + } + | zexp ';' + { + $$ = $1; + } + | Lif '(' exp ')' stmt + { + $$ = mkn(Oif, $3, mkunary(Oseq, $5)); + $$.src.start = $1.start; + $$.src.stop = $5.src.stop; + } + | Lif '(' exp ')' stmt Lelse stmt + { + $$ = mkn(Oif, $3, mkbin(Oseq, $5, $7)); + $$.src.start = $1.start; + $$.src.stop = $7.src.stop; + } + | bclab Lfor '(' zexp ';' zexp ';' zexp ')' stmt + { + $$ = mkunary(Oseq, $10); + if($8.op != Onothing) + $$.right = $8; + $$ = mkbin(Ofor, $6, $$); + $$.decl = $1; + if($4.op != Onothing) + $$ = mkbin(Oseq, $4, $$); + } + | bclab Lwhile '(' zexp ')' stmt + { + $$ = mkn(Ofor, $4, mkunary(Oseq, $6)); + $$.src.start = $2.start; + $$.src.stop = $6.src.stop; + $$.decl = $1; + } + | bclab Ldo stmt Lwhile '(' zexp ')' ';' + { + $$ = mkn(Odo, $6, $3); + $$.src.start = $2.start; + $$.src.stop = $7.stop; + $$.decl = $1; + } + | Lbreak bctarg ';' + { + $$ = mkn(Obreak, nil, nil); + $$.decl = $2; + $$.src = $1; + } + | Lcont bctarg ';' + { + $$ = mkn(Ocont, nil, nil); + $$.decl = $2; + $$.src = $1; + } + | Lreturn zexp ';' + { + $$ = mkn(Oret, $2, nil); + $$.src = $1; + if($2.op == Onothing) + $$.left = nil; + else + $$.src.stop = $2.src.stop; + } + | Lspawn exp ';' + { + $$ = mkn(Ospawn, $2, nil); + $$.src.start = $1.start; + $$.src.stop = $2.src.stop; + } + | Lraise zexp ';' + { + $$ = mkn(Oraise, $2, nil); + $$.src.start = $1.start; + $$.src.stop = $2.src.stop; + } + | bclab Lcase exp '{' cqstmts '}' + { + $$ = mkn(Ocase, $3, caselist($5, nil)); + $$.src = $3.src; + $$.decl = $1; + } + | bclab Lalt '{' qstmts '}' + { + $$ = mkn(Oalt, caselist($4, nil), nil); + $$.src = $2; + $$.decl = $1; + } + | bclab Lpick Lid Ldeclas exp '{' pstmts '}' + { + $$ = mkn(Opick, mkbin(Odas, mkname($<tok.src>3, $3), $5), caselist($7, nil)); + $$.src.start = $<tok.src>3.start; + $$.src.stop = $5.src.stop; + $$.decl = $1; + } + | Lexit ';' + { + $$ = mkn(Oexit, nil, nil); + $$.src = $1; + } + | '{' stmts '}' Lexcept idexc '{' eqstmts '}' + { + if($2 == nil){ + $2 = mkn(Onothing, nil, nil); + $2.src.start = $2.src.stop = curline(); + } + $2 = mkscope(rotater($2)); + $$ = mkbin(Oexstmt, $2, mkn(Oexcept, $5, caselist($7, nil))); + } +# | stmt Lexcept idexc '{' eqstmts '}' +# { +# $$ = mkbin(Oexstmt, $1, mkn(Oexcept, $3, caselist($5, nil))); +# } + ; + +bclab : + { + $$ = nil; + } + | ids ':' + { + if($1.next != nil) + yyerror("only one identifier allowed in a label"); + $$ = $1; + } + ; + +bctarg : + { + $$ = nil; + } + | Lid + { + $$ = mkids($<tok.src>1, $1, nil, nil); + } + ; + +qstmts : qbodies stmts + { + $1.left.right.right = $2; + $$ = $1; + } + ; + +qbodies : qual Llabs + { + $$ = mkunary(Oseq, mkscope(mkunary(Olabel, rotater($1)))); + } + | qbodies stmts qual Llabs + { + $1.left.right.right = $2; + $$ = mkbin(Oseq, mkscope(mkunary(Olabel, rotater($3))), $1); + } + ; + +cqstmts : cqbodies stmts + { + $1.left.right = mkscope($2); + $$ = $1; + } + ; + +cqbodies : qual Llabs + { + $$ = mkunary(Oseq, mkunary(Olabel, rotater($1))); + } + | cqbodies stmts qual Llabs + { + $1.left.right = mkscope($2); + $$ = mkbin(Oseq, mkunary(Olabel, rotater($3)), $1); + } + ; + +eqstmts : eqbodies stmts + { + $1.left.right = mkscope($2); + $$ = $1; + } + ; + +eqbodies : qual Llabs + { + $$ = mkunary(Oseq, mkunary(Olabel, rotater($1))); + } + | eqbodies stmts qual Llabs + { + $1.left.right = mkscope($2); + $$ = mkbin(Oseq, mkunary(Olabel, rotater($3)), $1); + } + ; + +qual : exp + | exp Lto exp + { + $$ = mkbin(Orange, $1, $3); + } + | '*' + { + $$ = mkn(Owild, nil, nil); + $$.src = $1; + } + | qual Lor qual + { + $$ = mkbin(Oseq, $1, $3); + } + | error + { + $$ = mkn(Onothing, nil, nil); + $$.src.start = curline(); + $$.src.stop = $$.src.start; + } + ; + +pstmts : pbodies stmts + { + $1.left.right = mkscope($2); + $$ = $1; + } + ; + +pbodies : pqual Llabs + { + $$ = mkunary(Oseq, mkunary(Olabel, rotater($1))); + } + | pbodies stmts pqual Llabs + { + $1.left.right = mkscope($2); + $$ = mkbin(Oseq, mkunary(Olabel, rotater($3)), $1); + } + ; + +pqual : Lid + { + $$ = mkname($<tok>1.src, $1); + } + | '*' + { + $$ = mkn(Owild, nil, nil); + $$.src = $1; + } + | pqual Lor pqual + { + $$ = mkbin(Oseq, $1, $3); + } + | error + { + $$ = mkn(Onothing, nil, nil); + $$.src.start = curline(); + $$.src.stop = $$.src.start; + } + ; + +zexp : + { + $$ = mkn(Onothing, nil, nil); + $$.src.start = curline(); + $$.src.stop = $$.src.start; + } + | exp + ; + +exp : monexp + | exp '=' exp + { + $$ = mkbin(Oas, $1, $3); + } + | exp Landeq exp + { + $$ = mkbin(Oandas, $1, $3); + } + | exp Loreq exp + { + $$ = mkbin(Ooras, $1, $3); + } + | exp Lxoreq exp + { + $$ = mkbin(Oxoras, $1, $3); + } + | exp Llsheq exp + { + $$ = mkbin(Olshas, $1, $3); + } + | exp Lrsheq exp + { + $$ = mkbin(Orshas, $1, $3); + } + | exp Laddeq exp + { + $$ = mkbin(Oaddas, $1, $3); + } + | exp Lsubeq exp + { + $$ = mkbin(Osubas, $1, $3); + } + | exp Lmuleq exp + { + $$ = mkbin(Omulas, $1, $3); + } + | exp Ldiveq exp + { + $$ = mkbin(Odivas, $1, $3); + } + | exp Lmodeq exp + { + $$ = mkbin(Omodas, $1, $3); + } + | exp Lexpeq exp + { + $$ = mkbin(Oexpas, $1, $3); + } + | exp Lcomm '=' exp + { + $$ = mkbin(Osnd, $1, $4); + } + | exp Ldeclas exp + { + $$ = mkbin(Odas, $1, $3); + } + | Lload Lid exp %prec Lload + { + $$ = mkn(Oload, $3, nil); + $$.src.start = $<tok.src.start>1; + $$.src.stop = $3.src.stop; + $$.ty = mkidtype($<tok.src>2, $2); + } + | exp Lexp exp + { + $$ = $$ = mkbin(Oexp, $1, $3); + } + | exp '*' exp + { + $$ = mkbin(Omul, $1, $3); + } + | exp '/' exp + { + $$ = mkbin(Odiv, $1, $3); + } + | exp '%' exp + { + $$ = mkbin(Omod, $1, $3); + } + | exp '+' exp + { + $$ = mkbin(Oadd, $1, $3); + } + | exp '-' exp + { + $$ = mkbin(Osub, $1, $3); + } + | exp Lrsh exp + { + $$ = mkbin(Orsh, $1, $3); + } + | exp Llsh exp + { + $$ = mkbin(Olsh, $1, $3); + } + | exp '<' exp + { + $$ = mkbin(Olt, $1, $3); + } + | exp '>' exp + { + $$ = mkbin(Ogt, $1, $3); + } + | exp Lleq exp + { + $$ = mkbin(Oleq, $1, $3); + } + | exp Lgeq exp + { + $$ = mkbin(Ogeq, $1, $3); + } + | exp Leq exp + { + $$ = mkbin(Oeq, $1, $3); + } + | exp Lneq exp + { + $$ = mkbin(Oneq, $1, $3); + } + | exp '&' exp + { + $$ = mkbin(Oand, $1, $3); + } + | exp '^' exp + { + $$ = mkbin(Oxor, $1, $3); + } + | exp '|' exp + { + $$ = mkbin(Oor, $1, $3); + } + | exp Lcons exp + { + $$ = mkbin(Ocons, $1, $3); + } + | exp Landand exp + { + $$ = mkbin(Oandand, $1, $3); + } + | exp Loror exp + { + $$ = mkbin(Ooror, $1, $3); + } + ; + +monexp : term + | '+' monexp + { + $2.src.start = $1.start; + $$ = $2; + } + | '-' monexp + { + $$ = mkunary(Oneg, $2); + $$.src.start = $1.start; + } + | '!' monexp + { + $$ = mkunary(Onot, $2); + $$.src.start = $1.start; + } + | '~' monexp + { + $$ = mkunary(Ocomp, $2); + $$.src.start = $1.start; + } + | '*' monexp + { + $$ = mkunary(Oind, $2); + $$.src.start = $1.start; + } + | Linc monexp + { + $$ = mkunary(Opreinc, $2); + $$.src.start = $1.start; + } + | Ldec monexp + { + $$ = mkunary(Opredec, $2); + $$.src.start = $1.start; + } + | Lcomm monexp + { + $$ = mkunary(Orcv, $2); + $$.src.start = $1.start; + } + | Lhd monexp + { + $$ = mkunary(Ohd, $2); + $$.src.start = $1.start; + } + | Ltl monexp + { + $$ = mkunary(Otl, $2); + $$.src.start = $1.start; + } + | Llen monexp + { + $$ = mkunary(Olen, $2); + $$.src.start = $1.start; + } + | Lref monexp + { + $$ = mkunary(Oref, $2); + $$.src.start = $1.start; + } + | Ltagof monexp + { + $$ = mkunary(Otagof, $2); + $$.src.start = $1.start; + } + | Larray '[' exp ']' Lof type + { + $$ = mkn(Oarray, $3, nil); + $$.ty = mktype($1.start, $6.src.stop, Tarray, $6, nil); + $$.src = $$.ty.src; + } + | Larray '[' exp ']' Lof '{' initlist '}' + { + $$ = mkn(Oarray, $3, $7); + $$.src.start = $1.start; + $$.src.stop = $8.stop; + } + | Larray '[' ']' Lof '{' initlist '}' + { + $$ = mkn(Onothing, nil, nil); + $$.src.start = $2.start; + $$.src.stop = $3.stop; + $$ = mkn(Oarray, $$, $6); + $$.src.start = $1.start; + $$.src.stop = $7.stop; + } + | Llist Lof '{' celist '}' + { + $$ = etolist($4); + $$.src.start = $1.start; + $$.src.stop = $5.stop; + } + | Lchan Lof type + { + $$ = mkn(Ochan, nil, nil); + $$.ty = mktype($1.start, $3.src.stop, Tchan, $3, nil); + $$.src = $$.ty.src; + } + | Lchan '[' exp ']' Lof type + { + $$ = mkn(Ochan, $3, nil); + $$.ty = mktype($1.start, $6.src.stop, Tchan, $6, nil); + $$.src = $$.ty.src; + } + | Larray Lof Ltid monexp + { + $$ = mkunary(Ocast, $4); + $$.ty = mktype($1.start, $4.src.stop, Tarray, mkidtype($<tok.src>3, $3), nil); + $$.src = $$.ty.src; + } + | Ltid monexp + { + $$ = mkunary(Ocast, $2); + $$.src.start = $<tok.src>1.start; + $$.ty = mkidtype($$.src, $1); + } + | Lid monexp + { + $$ = mkunary(Ocast, $2); + $$.src.start = $<tok.src>1.start; + $$.ty = mkidtype($$.src, $1); + } + | fixtype monexp + { + $$ = mkunary(Ocast, $2); + $$.src.start = $<tok.src>1.start; + $$.ty = $1; + } + ; + +term : idatom + | term '(' zelist ')' + { + $$ = mkn(Ocall, $1, $3); + $$.src.start = $1.src.start; + $$.src.stop = $4.stop; + } + | '(' elist ')' + { + $$ = $2; + if($2.op == Oseq) + $$ = mkn(Otuple, rotater($2), nil); + else + $$.flags |= byte PARENS; + $$.src.start = $1.start; + $$.src.stop = $3.stop; + } + | Lfn fnargret + { +# n := mkdeclname($1, mkids($1, enter(".fn"+string nfnexp++, 0), nil, nil)); +# $<node>$ = fndef(n, $2); +# nfns++; + } fbody + { +# $$ = fnfinishdef($<node>3, $4); +# $$ = mkdeclname($1, $$.left.decl); + yyerror("urt unk"); + $$ = nil; + } + | term '.' Lid + { + $$ = mkbin(Odot, $1, mkname($<tok.src>3, $3)); + } + | term Lmdot term + { + $$ = mkbin(Omdot, $1, $3); + } + | term '[' export ']' + { + $$ = mkbin(Oindex, $1, $3); + $$.src.stop = $4.stop; + } + | term '[' zexp ':' zexp ']' + { + if($3.op == Onothing) + $3.src = $4; + if($5.op == Onothing) + $5.src = $4; + $$ = mkbin(Oslice, $1, mkbin(Oseq, $3, $5)); + $$.src.stop = $6.stop; + } + | term Linc + { + $$ = mkunary(Oinc, $1); + $$.src.stop = $2.stop; + } + | term Ldec + { + $$ = mkunary(Odec, $1); + $$.src.stop = $2.stop; + } + | Lsconst + { + $$ = mksconst($<tok.src>1, $1); + } + | Lconst + { + $$ = mkconst($<tok.src>1, $1); + if($1 > big 16r7fffffff || $1 < big -16r7fffffff) + $$.ty = tbig; + } + | Lrconst + { + $$ = mkrconst($<tok.src>1, $1); + } + | term '[' exportlist ',' export ']' + { + $$ = mkbin(Oindex, $1, rotater(mkbin(Oseq, $3, $5))); + $$.src.stop = $6.stop; + } + ; + +idatom : Lid + { + $$ = mkname($<tok.src>1, $1); + } + | Lnil + { + $$ = mknil($<tok.src>1); + } + ; + +idterm : '(' idlist ')' + { + $$ = mkn(Otuple, rotater($2), nil); + $$.src.start = $1.start; + $$.src.stop = $3.stop; + } + ; + +exportlist : export + | exportlist ',' export + { + $$ = mkbin(Oseq, $1, $3); + } + ; + +export : exp + | texp + ; + +texp : Ltid + { + $$ = mkn(Otype, nil, nil); + $$.ty = mkidtype($<tok.src>1, $1); + $$.src = $$.ty.src; + } + | Larray Lof type + { + $$ = mkn(Otype, nil, nil); + $$.ty = mktype($1.start, $3.src.stop, Tarray, $3, nil); + $$.src = $$.ty.src; + } + | Llist Lof type + { + $$ = mkn(Otype, nil, nil); + $$.ty = mktype($1.start, $3.src.stop, Tlist, $3, nil); + $$.src = $$.ty.src; + } + | Lcyclic type + { + $$ = mkn(Otype, nil ,nil); + $$.ty = $2; + $$.ty.flags |= CYCLIC; + $$.src = $$.ty.src; + } + ; + +idexc : Lid + { + $$ = mkname($<tok.src>1, $1); + } + | # empty + { + $$ = nil; + } + ; + +idlist : idterm + | idatom + | idlist ',' idterm + { + $$ = mkbin(Oseq, $1, $3); + } + | idlist ',' idatom + { + $$ = mkbin(Oseq, $1, $3); + } + ; + +zelist : + { + $$ = nil; + } + | elist + { + $$ = rotater($1); + } + ; + +celist : elist + | elist ',' + ; + +elist : exp + | elist ',' exp + { + $$ = mkbin(Oseq, $1, $3); + } + ; + +initlist : elemlist + { + $$ = rotater($1); + } + | elemlist ',' + { + $$ = rotater($1); + } + ; + +elemlist : elem + | elemlist ',' elem + { + $$ = mkbin(Oseq, $1, $3); + } + ; + +elem : exp + { + $$ = mkn(Oelem, nil, $1); + $$.src = $1.src; + } + | qual Llabs exp + { + $$ = mkbin(Oelem, rotater($1), $3); + } + ; + +tpolys : tpoly dfields + { + if($1.op == Oseq) + $1.right.left = rotater($2); + else + $1.left = rotater($2); + $$ = $1; + } + ; + +tpoly : ids Llabs + { + $$ = typedecl($1, mktype($1.src.start, $2.stop, Tpoly, nil, nil)); + } + | tpoly dfields ids Llabs + { + if($1.op == Oseq) + $1.right.left = rotater($2); + else + $1.left = rotater($2); + $$ = mkbin(Oseq, $1, typedecl($3, mktype($3.src.start, $4.stop, Tpoly, nil, nil))); + } + ; + +%% + +include "keyring.m"; + +sys: Sys; + print, fprint, sprint: import sys; + +bufio: Bufio; + Iobuf: import bufio; + +str: String; + +keyring:Keyring; + md5: import keyring; + +math: Math; + import_real, export_real, isnan: import math; + +yyctxt: ref YYLEX; + +canonnan: real; + +debug = array[256] of {* => 0}; + +noline = -1; +nosrc = Src(-1, -1); + +infile: string; + +# front end +include "arg.m"; +include "lex.b"; +include "types.b"; +include "nodes.b"; +include "decls.b"; + +include "typecheck.b"; + +# back end +include "gen.b"; +include "ecom.b"; +include "asm.b"; +include "dis.b"; +include "sbl.b"; +include "stubs.b"; +include "com.b"; +include "optim.b"; + +init(nil: ref Draw->Context, argv: list of string) +{ + s: string; + + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + math = load Math Math->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil){ + sys->print("can't load %s: %r\n", Bufio->PATH); + raise("fail:bad module"); + } + str = load String String->PATH; + if(str == nil){ + sys->print("can't load %s: %r\n", String->PATH); + raise("fail:bad module"); + } + + stderr = sys->fildes(2); + yyctxt = ref YYLEX; + + math->FPcontrol(0, Math->INVAL|Math->ZDIV|Math->OVFL|Math->UNFL|Math->INEX); + na := array[1] of {0.}; + import_real(array[8] of {byte 16r7f, * => byte 16rff}, na); + canonnan = na[0]; + if(!isnan(canonnan)) + fatal("bad canonical NaN"); + + lexinit(); + typeinit(); + optabinit(); + + gendis = 1; + asmsym = 0; + maxerr = 20; + ofile := ""; + ext := ""; + + arg := Arg.init(argv); + while(c := arg.opt()){ + case c{ + 'Y' => + emitsbl = arg.arg(); + if(emitsbl == nil) + usage(); + 'C' => + dontcompile = 1; + 'D' => + # + # debug flags: + # + # a alt compilation + # A array constructor compilation + # b boolean and branch compilation + # c case compilation + # d function declaration + # D descriptor generation + # e expression compilation + # E addressable expression compilation + # f print arguments for compiled functions + # F constant folding + # g print out globals + # m module declaration and type checking + # n nil references + # s print sizes of output file sections + # S type signing + # t type checking function bodies + # T timing + # v global var and constant compilation + # x adt verification + # Y tuple compilation + # z Z bug fixes + # + s = arg.arg(); + for(i := 0; i < len s; i++){ + c = s[i]; + if(c < len debug) + debug[c] = 1; + } + 'I' => + s = arg.arg(); + if(s == "") + usage(); + addinclude(s); + 'G' => + asmsym = 1; + 'S' => + gendis = 0; + 'a' => + emitstub = 1; + 'A' => + emitstub = emitdyn = 1; + 'c' => + mustcompile = 1; + 'e' => + maxerr = 1000; + 'f' => + fabort = 1; + 'F' => + newfnptr = 1; + 'g' => + dosym = 1; + 'i' => + dontinline = 1; + 'o' => + ofile = arg.arg(); + 'O' => + optims = 1; + 's' => + s = arg.arg(); + if(s != nil) + fixss = int s; + 't' => + emittab = arg.arg(); + if(emittab == nil) + usage(); + 'T' => + emitcode = arg.arg(); + if(emitcode == nil) + usage(); + 'd' => + emitcode = arg.arg(); + if(emitcode == nil) + usage(); + emitdyn = 1; + 'w' => + superwarn = dowarn; + dowarn = 1; + 'x' => + ext = arg.arg(); + 'X' => + signdump = arg.arg(); + 'z' => + arrayz = 1; + * => + usage(); + } + } + + addinclude("/module"); + + argv = arg.argv; + arg = nil; + + if(argv == nil){ + usage(); + }else if(ofile != nil){ + if(len argv != 1) + usage(); + translate(hd argv, ofile, mkfileext(ofile, ".dis", ".sbl")); + }else{ + pr := len argv != 1; + if(ext == ""){ + ext = ".s"; + if(gendis) + ext = ".dis"; + } + for(; argv != nil; argv = tl argv){ + file := hd argv; + (nil, s) = str->splitr(file, "/"); + if(pr) + print("%s:\n", s); + out := mkfileext(s, ".b", ext); + translate(file, out, mkfileext(out, ext, ".sbl")); + } + } + if (toterrors > 0) + raise("fail:errors"); +} + +usage() +{ + fprint(stderr, "usage: limbo [-GSagwe] [-I incdir] [-o outfile] [-{T|t|d} module] [-D debug] file ...\n"); + raise("fail:usage"); +} + +mkfileext(file, oldext, ext: string): string +{ + n := len file; + n2 := len oldext; + if(n >= n2 && file[n-n2:] == oldext) + file = file[:n-n2]; + return file + ext; +} + +translate(in, out, dbg: string) +{ + infile = in; + outfile = out; + errors = 0; + bins[0] = bufio->open(in, Bufio->OREAD); + if(bins[0] == nil){ + fprint(stderr, "can't open %s: %r\n", in); + toterrors++; + return; + } + doemit := emitcode != "" || emitstub || emittab != "" || emitsbl != ""; + if(!doemit){ + bout = bufio->create(out, Bufio->OWRITE, 8r666); + if(bout == nil){ + fprint(stderr, "can't open %s: %r\n", out); + toterrors++; + bins[0].close(); + return; + } + if(dosym){ + bsym = bufio->create(dbg, Bufio->OWRITE, 8r666); + if(bsym == nil) + fprint(stderr, "can't open %s: %r\n", dbg); + } + } + + lexstart(in); + + popscopes(); + typestart(); + declstart(); + nfnexp = 0; + + parset = sys->millisec(); + yyparse(yyctxt); + parset = sys->millisec() - parset; + + checkt = sys->millisec(); + entry := typecheck(!doemit); + checkt = sys->millisec() - checkt; + + modcom(entry); + + fns = nil; + nfns = 0; + descriptors = nil; + + if(debug['T']) + print("times: parse=%d type=%d: gen=%d write=%d symbols=%d\n", + parset, checkt, gent, writet, symt); + + if(bout != nil) + bout.close(); + if(bsym != nil) + bsym.close(); + toterrors += errors; + if(errors && bout != nil) + sys->remove(out); + if(errors && bsym != nil) + sys->remove(dbg); +} + +pwd(): string +{ + workdir := load Workdir Workdir->PATH; + if(workdir == nil) + cd := "/"; + else + cd = workdir->init(); + # sys->print("pwd: %s\n", cd); + return cd; +} + +cleanname(s: string): string +{ + ls, path: list of string; + + if(s == nil) + return nil; + if(s[0] != '/' && s[0] != '\\') + (nil, ls) = sys->tokenize(pwd(), "/\\"); + for( ; ls != nil; ls = tl ls) + path = hd ls :: path; + (nil, ls) = sys->tokenize(s, "/\\"); + for( ; ls != nil; ls = tl ls){ + n := hd ls; + if(n == ".") + ; + else if (n == ".."){ + if(path != nil) + path = tl path; + } + else + path = n :: path; + } + p := ""; + for( ; path != nil; path = tl path) + p = "/" + hd path + p; + if(p == nil) + p = "/"; + # sys->print("cleanname: %s\n", p); + return p; +} + +srcpath(): string +{ + srcp := cleanname(infile); + # sys->print("srcpath: %s\n", srcp); + return srcp; +} diff --git a/appl/cmd/limbo/mkfile b/appl/cmd/limbo/mkfile new file mode 100644 index 00000000..2a555510 --- /dev/null +++ b/appl/cmd/limbo/mkfile @@ -0,0 +1,35 @@ +<../../../mkconfig + +TARG= limbo.dis\ + +MODULES=\ + arg.m\ + disoptab.m\ + isa.m\ + limbo.m\ + opname.m\ + asm.b\ + com.b\ + decls.b\ + dis.b\ + ecom.b\ + gen.b\ + lex.b\ + nodes.b\ + optim.b\ + sbl.b\ + stubs.b\ + typecheck.b\ + types.b\ + +SYSMODULES= \ + bufio.m\ + draw.m\ + keyring.m\ + math.m\ + string.m\ + sys.m\ + +DISBIN=$ROOT/dis + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/limbo/nodes.b b/appl/cmd/limbo/nodes.b new file mode 100644 index 00000000..61e97dc0 --- /dev/null +++ b/appl/cmd/limbo/nodes.b @@ -0,0 +1,1402 @@ +include "opname.m"; + +znode: Node; + +isused = array[Oend] of +{ + Oas => 1, + Odas => 1, + Oaddas => 1, + Osubas => 1, + Omulas => 1, + Odivas => 1, + Omodas => 1, + Oexpas => 1, + Oandas => 1, + Ooras => 1, + Oxoras => 1, + Olshas => 1, + Onothing => 1, + Orshas => 1, + Oinc => 1, + Odec => 1, + Opreinc => 1, + Opredec => 1, + Ocall => 1, + Oraise => 1, + Ospawn => 1, + Osnd => 1, + Orcv => 1, + + * => 0 +}; + +sideeffect := array[Oend] of +{ + Oas => 1, + Odas => 1, + Oaddas => 1, + Osubas => 1, + Omulas => 1, + Odivas => 1, + Omodas => 1, + Oexpas => 1, + Oandas => 1, + Ooras => 1, + Oxoras => 1, + Olshas => 1, + Orshas => 1, + Oinc => 1, + Odec => 1, + Opreinc => 1, + Opredec => 1, + Ocall => 1, + Oraise => 1, + Ospawn => 1, + Osnd => 1, + Orcv => 1, + + Oadr => 1, + Oarray => 1, + Ocast => 1, + Ochan => 1, + Ocons => 1, + Odiv => 1, + Odot => 1, + Oind => 1, + Oindex => 1, + Oinds => 1, + Oindx => 1, + Olen => 1, + Oload => 1, + Omod => 1, + Oref => 1, + + * => 0 +}; + +opcommute = array[Oend] of +{ + Oeq => Oeq, + Oneq => Oneq, + Olt => Ogt, + Ogt => Olt, + Ogeq => Oleq, + Oleq => Ogeq, + Oadd => Oadd, + Omul => Omul, + Oxor => Oxor, + Oor => Oor, + Oand => Oand, + + * => 0 +}; + +oprelinvert = array[Oend] of +{ + + Oeq => Oneq, + Oneq => Oeq, + Olt => Ogeq, + Ogt => Oleq, + Ogeq => Olt, + Oleq => Ogt, + + * => 0 +}; + +isrelop := array[Oend] of +{ + + Oeq => 1, + Oneq => 1, + Olt => 1, + Oleq => 1, + Ogt => 1, + Ogeq => 1, + Oandand => 1, + Ooror => 1, + Onot => 1, + + * => 0 +}; + +ipow(x: big, n: int): big +{ + inv: int; + r: big; + + inv = 0; + if(n < 0){ + n = -n; + inv = 1; + } + r = big 1; + for(;;){ + if(n&1) + r *= x; + if((n >>= 1) == 0) + break; + x *= x; + } + if(inv) + r = big 1/r; + return r; +} + +rpow(x: real, n: int): real +{ + inv: int; + r: real; + + inv = 0; + if(n < 0){ + n = -n; + inv = 1; + } + r = 1.0; + for(;;){ + if(n&1) + r *= x; + if((n >>= 1) == 0) + break; + x *= x; + } + if(inv) + r = 1.0/r; + return r; +} + +real2fix(v: real, t: ref Type): big +{ + return big(v/scale(t)); +} + +fix2fix(v: big, f: ref Type, t: ref Type): big +{ + return big(real v * (scale(f)/scale(t))); +} + +fix2real(v: big, f: ref Type): real +{ + return real v * scale(f); +} + +istuple(n: ref Node): int +{ + d: ref Decl; + + case(n.op){ + Otuple => + return 1; + Oname => + d = n.decl; + if(d.importid != nil) + d = d.importid; + return d.store == Dconst && (n.ty.kind == Ttuple || n.ty.kind == Tadt); + Odot => + return 0; # istuple(n.left); + } + return 0; +} + +tuplemem(n: ref Node, d: ref Decl): ref Node +{ + ty: ref Type; + ids: ref Decl; + + ty = n.ty; + n = n.left; + for(ids = ty.ids; ids != nil; ids = ids.next){ + if(ids.sym == d.sym) + break; + else + n = n.right; + } + if(n == nil) + fatal("tuplemem cannot cope !\n"); + return n.left; +} + +varcom(v: ref Decl): int +{ + n := v.init; + n = fold(n); + v.init = n; + if(debug['v']) + print("variable '%s' val %s\n", v.sym.name, expconv(n)); + if(n == nil) + return 1; + + tn := ref znode; + tn.op = Oname; + tn.decl = v; + tn.src = v.src; + tn.ty = v.ty; + return initable(tn, n, 0); +} + +initable(v, n: ref Node, allocdep: int): int +{ + case n.ty.kind{ + Tiface or + Tgoto or + Tcase or + Tcasel or + Tcasec or + Talt or + Texcept => + return 1; + Tint or + Tbig or + Tbyte or + Treal or + Tstring or + Tfix => + if(n.op != Oconst) + break; + return 1; + Tadt or + Tadtpick or + Ttuple => + if(n.op == Otuple) + n = n.left; + else if(n.op == Ocall) + n = n.right; + else + break; + for(; n != nil; n = n.right) + if(!initable(v, n.left, allocdep)) + return 0; + return 1; + Tarray => + if(n.op != Oarray) + break; + if(allocdep >= DADEPTH){ + nerror(v, expconv(v)+"s initializer has arrays nested more than "+string allocdep+" deep"); + return 0; + } + allocdep++; + usedesc(mktdesc(n.ty.tof)); + if(n.left.op != Oconst){ + nerror(v, expconv(v)+"s size is not a constant"); + return 0; + } + for(e := n.right; e != nil; e = e.right) + if(!initable(v, e.left.right, allocdep)) + return 0; + return 1; + Tany => + return 1; + Tref or + Tlist or + Tpoly or + * => + nerror(v, "can't initialize "+etconv(v)); + return 0; + } + nerror(v, expconv(v)+"s initializer, "+expconv(n)+", is not a constant expression"); + return 0; +} + +# +# merge together two sorted lists, yielding a sorted list +# +elemmerge(e, f: ref Node): ref Node +{ + r := rock := ref Node; + while(e != nil && f != nil){ + if(e.left.left.c.val <= f.left.left.c.val){ + r.right = e; + e = e.right; + }else{ + r.right = f; + f = f.right; + } + r = r.right; + } + if(e != nil) + r.right = e; + else + r.right = f; + return rock.right; +} + +# +# recursively split lists and remerge them after they are sorted +# +recelemsort(e: ref Node, n: int): ref Node +{ + if(n <= 1) + return e; + m := n / 2 - 1; + ee := e; + for(i := 0; i < m; i++) + ee = ee.right; + r := ee.right; + ee.right = nil; + return elemmerge(recelemsort(e, n / 2), + recelemsort(r, (n + 1) / 2)); +} + +# +# sort the elems by index; wild card is first +# +elemsort(e: ref Node): ref Node +{ + n := 0; + for(ee := e; ee != nil; ee = ee.right){ + if(ee.left.left.op == Owild) + ee.left.left.c = ref Const(big -1, 0.); + n++; + } + return recelemsort(e, n); +} + +sametree(n1: ref Node, n2: ref Node): int +{ + if(n1 == n2) + return 1; + if(n1 == nil || n2 == nil) + return 0; + if(n1.op != n2.op || n1.ty != n2.ty) + return 0; + if(n1.op == Oconst){ + case(n1.ty.kind){ + Tbig or + Tbyte or + Tint => + return n1.c.val == n2.c.val; + Treal => + return n1.c.rval == n2.c.rval; + Tfix => + return n1.c.val == n2.c.val && tequal(n1.ty, n2.ty); + Tstring => + return n1.decl.sym == n2.decl.sym; + } + return 0; + } + return n1.decl == n2.decl && sametree(n1.left, n2.left) && sametree(n1.right, n2.right); +} + +occurs(d: ref Decl, n: ref Node): int +{ + if(n == nil) + return 0; + if(n.op == Oname){ + if(d == n.decl) + return 1; + return 0; + } + return occurs(d, n.left) + occurs(d, n.right); +} + +# +# left and right subtrees the same +# +folds(n: ref Node): ref Node +{ + if(hasside(n, 1)) + return n; + case(n.op){ + Oeq or + Oleq or + Ogeq => + n.c = ref Const(big 1, 0.0); + Osub => + n.c = ref Const(big 0, 0.0); + Oxor or + Oneq or + Olt or + Ogt => + n.c = ref Const(big 0, 0.0); + Oand or + Oor or + Oandand or + Ooror => + return n.left; + * => + return n; + } + n.op = Oconst; + n.left = n.right = nil; + n.decl = nil; + return n; +} + +# +# constant folding for typechecked expressions +# +fold(n: ref Node): ref Node +{ + if(n == nil) + return nil; + if(debug['F']) + print("fold %s\n", nodeconv(n)); + n = efold(n); + if(debug['F']) + print("folded %s\n", nodeconv(n)); + return n; +} + +efold(n: ref Node): ref Node +{ + d: ref Decl; + + if(n == nil) + return nil; + + left := n.left; + right := n.right; + case n.op{ + Oname => + d = n.decl; + if(d.importid != nil) + d = d.importid; + if(d.store != Dconst){ + if(d.store == Dtag){ + n.op = Oconst; + n.ty = tint; + n.c = ref Const(big d.tag, 0.); + } + break; + } + case n.ty.kind{ + Tbig => + n.op = Oconst; + n.c = ref Const(d.init.c.val, 0.); + Tbyte => + n.op = Oconst; + n.c = ref Const(big byte d.init.c.val, 0.); + Tint or + Tfix => + n.op = Oconst; + n.c = ref Const(big int d.init.c.val, 0.); + Treal => + n.op = Oconst; + n.c = ref Const(big 0, d.init.c.rval); + Tstring => + n.op = Oconst; + n.decl = d.init.decl; + Ttuple => + *n = *d.init; + Tadt => + *n = *d.init; + n = rewrite(n); # was call + Texception => + if(n.ty.cons == byte 0) + fatal("non-const exception type in efold"); + n.op = Oconst; + * => + fatal("unknown const type "+typeconv(n.ty)+" in efold"); + } + Oadd => + left = efold(left); + right = efold(right); + n.left = left; + n.right = right; + if(n.ty == tstring && right.op == Oconst){ + if(left.op == Oconst) + n = mksconst(n.src, stringcat(left.decl.sym, right.decl.sym)); + else if(left.op == Oadd && left.ty == tstring && left.right.op == Oconst){ + left.right = mksconst(n.src, stringcat(left.right.decl.sym, right.decl.sym)); + n = left; + } + } + Olen => + left = efold(left); + n.left = left; + if(left.ty == tstring && left.op == Oconst) + n = mkconst(n.src, big len left.decl.sym.name); + Oslice => + if(right.left.op == Onothing) + right.left = mkconst(right.left.src, big 0); + n.left = efold(left); + n.right = efold(right); + Oinds => + n.left = left = efold(left); + n.right = right = efold(right); + if(right.op == Oconst && left.op == Oconst){ + ; + } + Ocast => + n.op = Ocast; + left = efold(left); + n.left = left; + if(n.ty == left.ty || n.ty.kind == Tfix && tequal(n.ty, left.ty)) + return left; + if(left.op == Oconst) + return foldcast(n, left); + Odot or + Omdot => + # + # what about side effects from left? + # + d = right.decl; + case d.store{ + Dconst or + Dtag or + Dtype => + # + # set it up as a name and let that case do the hard work + # + n.op = Oname; + n.decl = d; + n.left = nil; + n.right = nil; + return efold(n); + } + n.left = efold(left); + if(n.left.op == Otuple) + n = tuplemem(n.left, d); + else + n.right = efold(right); + Otagof => + if(n.decl != nil){ + n.op = Oconst; + n.left = nil; + n.right = nil; + n.c = ref Const(big n.decl.tag, 0.); + return efold(n); + } + n.left = efold(left); + Oif => + n.left = left = efold(left); + n.right = right = efold(right); + if(left.op == Oconst){ + if(left.c.val != big 0) + return right.left; + else + return right.right; + } + * => + n.left = efold(left); + n.right = efold(right); + } + + left = n.left; + right = n.right; + if(left == nil) + return n; + + if(right == nil){ + if(left.op == Oconst){ + if(left.ty == tint || left.ty == tbyte || left.ty == tbig) + return foldc(n); + if(left.ty == treal) + return foldr(n); + } + return n; + } + + if(left.op == Oconst){ + case n.op{ + Olsh or + Orsh => + if(left.c.val == big 0 && !hasside(right, 1)) + return left; + Ooror => + if(left.ty == tint || left.ty == tbyte || left.ty == tbig){ + if(left.c.val == big 0){ + n = mkbin(Oneq, right, mkconst(right.src, big 0)); + n.ty = right.ty; + n.left.ty = right.ty; + return efold(n); + } + left.c.val = big 1; + return left; + } + Oandand => + if(left.ty == tint || left.ty == tbyte || left.ty == tbig){ + if(left.c.val == big 0) + return left; + n = mkbin(Oneq, right, mkconst(right.src, big 0)); + n.ty = right.ty; + n.left.ty = right.ty; + return efold(n); + } + } + } + if(left.op == Oconst && right.op != Oconst + && opcommute[n.op] + && n.ty != tstring){ + n.op = opcommute[n.op]; + n.left = right; + n.right = left; + left = right; + right = n.right; + } + if(right.op == Oconst && left.op == n.op && left.right.op == Oconst + && (n.op == Oadd || n.op == Omul || n.op == Oor || n.op == Oxor || n.op == Oand) + && n.ty != tstring){ + n.left = left.left; + left.left = right; + right = efold(left); + n.right = right; + left = n.left; + } + if(right.op == Oconst){ + if(n.op == Oexp && left.ty == treal){ + if(left.op == Oconst) + return foldr(n); + return n; + } + if(right.ty == tint || right.ty == tbyte || left.ty == tbig){ + if(left.op == Oconst) + return foldc(n); + return foldvc(n); + } + if(right.ty == treal && left.op == Oconst) + return foldr(n); + } + if(sametree(left, right)) + return folds(n); + return n; +} + +# +# does evaluating the node have any side effects? +# +hasside(n: ref Node, strict: int): int +{ + for(; n != nil; n = n.right){ + if(sideeffect[n.op] && (strict || n.op != Oadr && n.op != Oind)) + return 1; + if(hasside(n.left, strict)) + return 1; + } + return 0; +} + +hascall(n: ref Node): int +{ + for(; n != nil; n = n.right){ + if(n.op == Ocall || n.op == Ospawn) + return 1; + if(hascall(n.left)) + return 1; + } + return 0; +} + +hasasgns(n: ref Node): int +{ + if(n == nil) + return 0; + if(n.op != Ocall && isused[n.op] && n.op != Onothing) + return 1; + return hasasgns(n.left) || hasasgns(n.right); +} + +nodes(n: ref Node): int +{ + if(n == nil) + return 0; + return 1+nodes(n.left)+nodes(n.right); +} + +foldcast(n, left: ref Node): ref Node +{ + case left.ty.kind{ + Tint => + left.c.val = big int left.c.val; + return foldcasti(n, left); + Tbyte => + left.c.val = big byte left.c.val; + return foldcasti(n, left); + Tbig => + return foldcasti(n, left); + Treal => + case n.ty.kind{ + Tint or + Tbyte or + Tbig => + left.c.val = big left.c.rval; + Tfix => + left.c.val = real2fix(left.c.rval, n.ty); + Tstring => + return mksconst(n.src, enterstring(string left.c.rval)); + * => + return n; + } + Tfix => + case n.ty.kind{ + Tint or + Tbyte or + Tbig => + left.c.val = big fix2real(left.c.val, left.ty); + Treal => + left.c.rval = fix2real(left.c.val, left.ty); + Tfix => + if(tequal(left.ty, n.ty)) + return left; + left.c.val = fix2fix(left.c.val, left.ty, n.ty); + Tstring => + return mksconst(n.src, enterstring(string fix2real(left.c.val, left.ty))); + * => + return n; + } + break; + Tstring => + case n.ty.kind{ + Tint or + Tbyte or + Tbig => + left.c = ref Const(big left.decl.sym.name, 0.); + Treal => + left.c = ref Const(big 0, real left.decl.sym.name); + Tfix => + left.c = ref Const(real2fix(real left.decl.sym.name, n.ty), 0.); + * => + return n; + } + * => + return n; + } + left.ty = n.ty; + left.src = n.src; + return left; +} + +# +# left is some kind of int type +# +foldcasti(n, left: ref Node): ref Node +{ + case n.ty.kind{ + Tint => + left.c.val = big int left.c.val; + Tbyte => + left.c.val = big byte left.c.val; + Tbig => + ; + Treal => + left.c.rval = real left.c.val; + Tfix => + left.c.val = real2fix(real left.c.val, n.ty); + Tstring => + return mksconst(n.src, enterstring(string left.c.val)); + * => + return n; + } + left.ty = n.ty; + left.src = n.src; + return left; +} + +# +# right is a const int +# +foldvc(n: ref Node): ref Node +{ + left := n.left; + right := n.right; + case n.op{ + Oadd or + Osub or + Oor or + Oxor or + Olsh or + Orsh or + Ooror => + if(right.c.val == big 0) + return left; + if(n.op == Ooror && !hasside(left, 1)) + return right; + Oand => + if(right.c.val == big 0 && !hasside(left, 1)) + return right; + Omul => + if(right.c.val == big 1) + return left; + if(right.c.val == big 0 && !hasside(left, 1)) + return right; + Odiv => + if(right.c.val == big 1) + return left; + Omod => + if(right.c.val == big 1 && !hasside(left, 1)){ + right.c.val = big 0; + return right; + } + Oexp => + if(right.c.val == big 0){ + right.c.val = big 1; + return right; + } + if(right.c.val == big 1) + return left; + Oandand => + if(right.c.val != big 0) + return left; + if(!hasside(left, 1)) + return right; + Oneq => + if(!isrelop[left.op]) + return n; + if(right.c.val == big 0) + return left; + n.op = Onot; + n.right = nil; + Oeq => + if(!isrelop[left.op]) + return n; + if(right.c.val != big 0) + return left; + n.op = Onot; + n.right = nil; + } + return n; +} + +# +# left and right are const ints +# +foldc(n: ref Node): ref Node +{ + v: big; + rv, nb: int; + + left := n.left; + right := n.right; + case n.op{ + Oadd => + v = left.c.val + right.c.val; + Osub => + v = left.c.val - right.c.val; + Omul => + v = left.c.val * right.c.val; + Odiv => + if(right.c.val == big 0){ + nerror(n, "divide by 0 in constant expression"); + return n; + } + v = left.c.val / right.c.val; + Omod => + if(right.c.val == big 0){ + nerror(n, "mod by 0 in constant expression"); + return n; + } + v = left.c.val % right.c.val; + Oexp => + if(left.c.val == big 0 && right.c.val < big 0){ + nerror(n, "0 to negative power in constant expression"); + return n; + } + v = ipow(left.c.val, int right.c.val); + Oand => + v = left.c.val & right.c.val; + Oor => + v = left.c.val | right.c.val; + Oxor => + v = left.c.val ^ right.c.val; + Olsh => + v = left.c.val; + rv = int right.c.val; + if(rv < 0 || rv >= n.ty.size * 8){ + nwarn(n, "shift amount "+string rv+" out of range"); + rv = 0; + } + if(rv == 0) + break; + v <<= rv; + Orsh => + v = left.c.val; + rv = int right.c.val; + nb = n.ty.size * 8; + if(rv < 0 || rv >= nb){ + nwarn(n, "shift amount "+string rv+" out of range"); + rv = 0; + } + if(rv == 0) + break; + v >>= rv; + Oneg => + v = -left.c.val; + Ocomp => + v = ~left.c.val; + Oeq => + v = big(left.c.val == right.c.val); + Oneq => + v = big(left.c.val != right.c.val); + Ogt => + v = big(left.c.val > right.c.val); + Ogeq => + v = big(left.c.val >= right.c.val); + Olt => + v = big(left.c.val < right.c.val); + Oleq => + v = big(left.c.val <= right.c.val); + Oandand => + v = big(int left.c.val && int right.c.val); + Ooror => + v = big(int left.c.val || int right.c.val); + Onot => + v = big(left.c.val == big 0); + * => + return n; + } + if(n.ty == tint) + v = big int v; + else if(n.ty == tbyte) + v = big byte v; + n.left = nil; + n.right = nil; + n.decl = nil; + n.op = Oconst; + n.c = ref Const(v, 0.); + return n; +} + +# +# left and right are const reals +# +foldr(n: ref Node): ref Node +{ + rv := 0.; + v := big 0; + + left := n.left; + right := n.right; + case n.op{ + Ocast => + return n; + Oadd => + rv = left.c.rval + right.c.rval; + Osub => + rv = left.c.rval - right.c.rval; + Omul => + rv = left.c.rval * right.c.rval; + Odiv => + rv = left.c.rval / right.c.rval; + Oexp => + rv = rpow(left.c.rval, int right.c.val); + Oneg => + rv = -left.c.rval; + Oinv => + if(left.c.rval == 0.0){ + error(n.src.start, "divide by 0 in fixed point type"); + return n; + } + rv = 1.0/left.c.rval; + Oeq => + v = big(left.c.rval == right.c.rval); + Oneq => + v = big(left.c.rval != right.c.rval); + Ogt => + v = big(left.c.rval > right.c.rval); + Ogeq => + v = big(left.c.rval >= right.c.rval); + Olt => + v = big(left.c.rval < right.c.rval); + Oleq => + v = big(left.c.rval <= right.c.rval); + * => + return n; + } + n.left = nil; + n.right = nil; + n.op = Oconst; + + if(isnan(rv)) + rv = canonnan; + + n.c = ref Const(v, rv); + return n; +} + +varinit(d: ref Decl, e: ref Node): ref Node +{ + n := mkdeclname(e.src, d); + if(d.next == nil) + return mkbin(Oas, n, e); + return mkbin(Oas, n, varinit(d.next, e)); +} + +# +# given: an Oseq list with left == next or the last child +# make a list with the right == next +# ie: Oseq(Oseq(a, b),c) ==> Oseq(a, Oseq(b, Oseq(c, nil)))) +# +rotater(e: ref Node): ref Node +{ + if(e == nil) + return e; + if(e.op != Oseq) + return mkunary(Oseq, e); + e.right = mkunary(Oseq, e.right); + while(e.left.op == Oseq){ + left := e.left; + e.left = left.right; + left.right = e; + e = left; + } + return e; +} + +# +# reverse the case labels list +# +caselist(s, nr: ref Node): ref Node +{ + r := s.right; + s.right = nr; + if(r == nil) + return s; + return caselist(r, s); +} + +# +# e is a seq of expressions; make into cons's to build a list +# +etolist(e: ref Node): ref Node +{ + if(e == nil) + return nil; + n := mknil(e.src); + n.src.start = n.src.stop; + if(e.op != Oseq) + return mkbin(Ocons, e, n); + e.right = mkbin(Ocons, e.right, n); + while(e.left.op == Oseq){ + e.op = Ocons; + left := e.left; + e.left = left.right; + left.right = e; + e = left; + } + e.op = Ocons; + return e; +} + +dupn(resrc: int, src: Src, n: ref Node): ref Node +{ + nn := ref *n; + if(resrc) + nn.src = src; + if(nn.left != nil) + nn.left = dupn(resrc, src, nn.left); + if(nn.right != nil) + nn.right = dupn(resrc, src, nn.right); + return nn; +} + +mkn(op: int, left, right: ref Node): ref Node +{ + n := ref Node; + n.op = op; + n.flags = byte 0; + n.left = left; + n.right = right; + return n; +} + +mkunary(op: int, left: ref Node): ref Node +{ + n := ref Node; + n.src = left.src; + n.op = op; + n.flags = byte 0; + n.left = left; + return n; +} + +mkbin(op: int, left, right: ref Node): ref Node +{ + n := ref Node; + n.src.start = left.src.start; + n.src.stop = right.src.stop; + n.op = op; + n.flags = byte 0; + n.left = left; + n.right = right; + return n; +} + +mkdeclname(src: Src, d: ref Decl): ref Node +{ + n := ref Node; + n.src = src; + n.op = Oname; + n.flags = byte 0; + n.decl = d; + n.ty = d.ty; + d.refs++; + return n; +} + +mknil(src: Src): ref Node +{ + return mkdeclname(src, nildecl); +} + +mkname(src: Src, s: ref Sym): ref Node +{ + n := ref Node; + n.src = src; + n.op = Oname; + n.flags = byte 0; + if(s.unbound == nil){ + s.unbound = mkdecl(src, Dunbound, nil); + s.unbound.sym = s; + } + n.decl = s.unbound; + return n; +} + +mkconst(src: Src, v: big): ref Node +{ + n := ref Node; + n.src = src; + n.op = Oconst; + n.flags = byte 0; + n.ty = tint; + n.c = ref Const(v, 0.); + return n; +} + +mkrconst(src: Src, v: real): ref Node +{ + n := ref Node; + n.src = src; + n.op = Oconst; + n.flags = byte 0; + n.ty = treal; + n.c = ref Const(big 0, v); + return n; +} + +mksconst(src: Src, s: ref Sym): ref Node +{ + n := ref Node; + n.src = src; + n.op = Oconst; + n.flags = byte 0; + n.ty = tstring; + n.decl = mkdecl(src, Dconst, tstring); + n.decl.sym = s; + return n; +} + +opconv(op: int): string +{ + if(op < 0 || op > Oend) + return "op "+string op; + return opname[op]; +} + +etconv(n: ref Node): string +{ + s := expconv(n); + if(n.ty == tany || n.ty == tnone || n.ty == terror) + return s; + s += " of type "; + s += typeconv(n.ty); + return s; +} + +expconv(n: ref Node): string +{ + return "'" + subexpconv(n) + "'"; +} + +subexpconv(n: ref Node): string +{ + if(n == nil) + return ""; + s := ""; + if(int n.flags & PARENS) + s[len s] = '('; + case n.op{ + Obreak or + Ocont => + s += opname[n.op]; + if(n.decl != nil) + s += " "+n.decl.sym.name; + Oexit or + Owild => + s += opname[n.op]; + Onothing => + ; + Oadr or + Oused => + s += subexpconv(n.left); + Oseq => + s += eprintlist(n, ", "); + Oname => + if(n.decl == nil) + s += "<nil>"; + else + s += n.decl.sym.name; + Oconst => + if(n.ty.kind == Tstring){ + s += stringpr(n.decl.sym); + break; + } + if(n.decl != nil && n.decl.sym != nil){ + s += n.decl.sym.name; + break; + } + case n.ty.kind{ + Tbig or + Tint or + Tbyte => + s += string n.c.val; + Treal => + s += string n.c.rval; + Tfix => + s += string n.c.val + "(" + string n.ty.val.c.rval + ")"; + * => + s += opname[n.op]; + } + Ocast => + s += typeconv(n.ty); + s[len s] = ' '; + s += subexpconv(n.left); + Otuple => + if(n.ty != nil && n.ty.kind == Tadt) + s += n.ty.decl.sym.name; + s[len s] = '('; + s += eprintlist(n.left, ", "); + s[len s] = ')'; + Ochan => + if(n.left != nil){ + s += "chan ["; + s += subexpconv(n.left); + s += "] of "; + s += typeconv(n.ty.tof); + } + else + s += "chan of "+typeconv(n.ty.tof); + Oarray => + s += "array ["; + if(n.left != nil) + s += subexpconv(n.left); + s += "] of "; + if(n.right != nil){ + s += "{"; + s += eprintlist(n.right, ", "); + s += "}"; + }else{ + s += typeconv(n.ty.tof); + } + Oelem or + Olabel => + if(n.left != nil){ + s += eprintlist(n.left, " or "); + s += " =>"; + } + s += subexpconv(n.right); + Orange => + s += subexpconv(n.left); + s += " to "; + s += subexpconv(n.right); + Ospawn => + s += "spawn "; + s += subexpconv(n.left); + Oraise => + s += "raise "; + s += subexpconv(n.left); + Ocall => + s += subexpconv(n.left); + s += "("; + s += eprintlist(n.right, ", "); + s += ")"; + Oinc or + Odec => + s += subexpconv(n.left); + s += opname[n.op]; + Oindex or + Oindx or + Oinds => + s += subexpconv(n.left); + s += "["; + s += subexpconv(n.right); + s += "]"; + Oslice => + s += subexpconv(n.left); + s += "["; + s += subexpconv(n.right.left); + s += ":"; + s += subexpconv(n.right.right); + s += "]"; + Oload => + s += "load "; + s += typeconv(n.ty); + s += " "; + s += subexpconv(n.left); + Oref or + Olen or + Ohd or + Otl or + Otagof => + s += opname[n.op]; + s[len s] = ' '; + s += subexpconv(n.left); + * => + if(n.right == nil){ + s += opname[n.op]; + s += subexpconv(n.left); + }else{ + s += subexpconv(n.left); + s += opname[n.op]; + s += subexpconv(n.right); + } + } + if(int n.flags & PARENS) + s[len s] = ')'; + return s; +} + +eprintlist(elist: ref Node, sep: string): string +{ + if(elist == nil) + return ""; + s := ""; + for(; elist.right != nil; elist = elist.right){ + if(elist.op == Onothing) + continue; + if(elist.left.op == Ofnptr) + return s; + s += subexpconv(elist.left); + if(elist.right.left.op != Ofnptr) + s += sep; + } + s += subexpconv(elist.left); + return s; +} + +nodeconv(n: ref Node): string +{ + return nprint(n, 0); +} + +nprint(n: ref Node, indent: int): string +{ + if(n == nil) + return ""; + s := "\n"; + for(i := 0; i < indent; i++) + s[len s] = ' '; + case n.op{ + Oname => + if(n.decl == nil) + s += "<nil>"; + else + s += n.decl.sym.name; + Oconst => + if(n.decl != nil && n.decl.sym != nil) + s += n.decl.sym.name; + else + s += opconv(n.op); + if(n.ty == tint || n.ty == tbyte || n.ty == tbig) + s += " (" + string n.c.val + ")"; + * => + s += opconv(n.op); + } + s += " " + typeconv(n.ty) + " " + string n.addable + " " + string n.temps; + indent += 2; + s += nprint(n.left, indent); + s += nprint(n.right, indent); + return s; +} diff --git a/appl/cmd/limbo/opname.m b/appl/cmd/limbo/opname.m new file mode 100644 index 00000000..50da6ec9 --- /dev/null +++ b/appl/cmd/limbo/opname.m @@ -0,0 +1,109 @@ +opname := array[Oend+1] of +{ + "unknown", + + Oadd => "+", + Oaddas => "+=", + Oadr => "adr", + Oadtdecl => "adtdecl", + Oalt => "alt", + Oand => "&", + Oandand => "&&", + Oandas => "&=", + Oarray => "array", + Oas => "=", + Obreak => "break", + Ocall => "call", + Ocase => "case", + Ocast => "cast", + Ochan => "chan", + Ocomma => ",", + Ocomp => "~", + Ocondecl => "condecl", + Ocons => "::", + Oconst => "const", + Ocont => "continue", + Odas => ":=", + Odec => "--", + Odiv => "/", + Odivas => "/=", + Odo => "do", + Odot => ".", + Oelem => "elem", + Oeq => "==", + Oexcept => "except", + Oexdecl => "exdecl", + Oexit => "exit", + Oexp => "**", + Oexpas => "**=", + Oexstmt => "exstat", + Ofielddecl => "fielddecl", + Ofnptr => "fnptr", + Ofor => "for", + Ofunc => "fn(){}", + Ogeq => ">=", + Ogt => ">", + Ohd => "hd", + Oif => "if", + Oimport => "import", + Oinc => "++", + Oind => "*", + Oindex => "index", + Oinds => "inds", + Oindx => "indx", + Oinv => "inv", + Ojmp => "jmp", + Olabel => "label", + Olen => "len", + Oleq => "<=", + Oload => "load", + Olsh => "<<", + Olshas => "<<=", + Olt => "<", + Omdot => "->", + Omod => "%", + Omodas => "%=", + Omoddecl => "moddecl", + Omul => "*", + Omulas => "*=", + Oname => "name", + Oneg => "-", + Oneq => "!=", + Onot => "!", + Onothing => "nothing", + Oor => "|", + Ooras => "|=", + Ooror => "||", + Opick => "pick", + Opickdecl => "pickdec", + Opredec => "--", + Opreinc => "++", + Oraise => "raise", + Orange => "range", + Orcv => "<-", + Oref => "ref", + Oret => "return", + Orsh => ">>", + Orshas => ">>=", + Oscope => "scope", + Oself => "self", + Oseq => "seq", + Oslice => "slice", + Osnd => "<-=", + Ospawn => "spawn", + Osub => "-", + Osubas => "-=", + Otagof => "tagof", + Otl => "tl", + Otuple => "tuple", + Otype => "type", + Otypedecl => "typedecl", + Oused => "used", + Ovardecl => "vardecl", + Ovardecli => "vardecli", + Owild => "*", + Oxor => "^", + Oxoras => "^=", + + Oend => "unknown" +}; diff --git a/appl/cmd/limbo/optim.b b/appl/cmd/limbo/optim.b new file mode 100644 index 00000000..ac437fab --- /dev/null +++ b/appl/cmd/limbo/optim.b @@ -0,0 +1,3 @@ +optim(nil: ref Inst, nil: ref Decl) +{ +} diff --git a/appl/cmd/limbo/sbl.b b/appl/cmd/limbo/sbl.b new file mode 100644 index 00000000..0ae69d5f --- /dev/null +++ b/appl/cmd/limbo/sbl.b @@ -0,0 +1,397 @@ + +sbltname := array[Tend] of +{ + Tnone => byte 'n', + Tadt => byte 'a', + Tadtpick => byte 'a', + Tarray => byte 'A', + Tbig => byte 'B', + Tbyte => byte 'b', + Tchan => byte 'C', + Treal => byte 'f', + Tfn => byte 'F', + Tint => byte 'i', + Tlist => byte 'L', + Tmodule => byte 'm', + Tref => byte 'R', + Tstring => byte 's', + Ttuple => byte 't', + Texception => byte 't', + Tfix => byte 'i', + Tpoly => byte 'P', + + Tainit => byte '?', + Talt => byte '?', + Tany => byte 'N', + Tarrow => byte '?', + Tcase => byte '?', + Tcasel => byte '?', + Tcasec => byte '?', + Tdot => byte '?', + Terror => byte '?', + Tgoto => byte '?', + Tid => byte '?', + Tiface => byte '?', + Texcept => byte '?', + Tinst => byte '?', +}; +sbltadtpick: con byte 'p'; + +sfiles: ref Sym; +ftail: ref Sym; +nsfiles: int; +blockid: int; +lastf: int; +lastline: int; + +MAXSBLINT: con 12; +MAXSBLSRC: con 6*(MAXSBLINT+1); + +sblmod(m: ref Decl) +{ + bsym.puts("limbo .sbl 2.1\n"); + bsym.puts(m.sym.name); + bsym.putb(byte '\n'); + + blockid = 0; + nsfiles = 0; + sfiles = ftail = nil; + lastf = 0; + lastline = 0; +} + +sblfile(name: string): int +{ + i := 0; + for(s := sfiles; s != nil; s = s.next){ + if(s.name == name) + return i; + i++; + } + s = ref Sym; + s.name = name; + s.next = nil; + if(sfiles == nil) + sfiles = s; + else + ftail.next = s; + ftail = s; + nsfiles = i + 1; + return i; +} + +filename(s: string): string +{ + (nil, file) := str->splitr(s, "/ \\"); + return file; +} + +sblfiles() +{ + for(i := 0; i < nfiles; i++) + files[i].sbl = sblfile(files[i].name); + bsym.puts(string nsfiles); + bsym.putb(byte '\n'); + for(s := sfiles; s != nil; s = s.next){ + bsym.puts(filename(s.name)); + bsym.putb(byte '\n'); + } +} + +sblint(buf: array of byte, off, v: int): int +{ + if(v == 0){ + buf[off++] = byte '0'; + return off; + } + stop := off + MAXSBLINT; + if(v < 0){ + buf[off++] = byte '-'; + v = -v; + } + n := stop; + while(v > 0){ + buf[n -= 1] = byte(v % 10 + '0'); + v = v / 10; + } + while(n < stop) + buf[off++] = buf[n++]; + return off; +} + +sblsrcconvb(buf: array of byte, off: int, src: Src): int +{ + (startf, startl) := fline(src.start >> PosBits); + (stopf, stopl) := fline(src.stop >> PosBits); + if(lastf != startf.sbl){ + off = sblint(buf, off, startf.sbl); + buf[off++] = byte ':'; + } + if(lastline != startl){ + off = sblint(buf, off, startl); + buf[off++] = byte '.'; + } + off = sblint(buf, off, (src.start & PosMask)); + buf[off++] = byte ','; + if(startf.sbl != stopf.sbl){ + off = sblint(buf, off, stopf.sbl); + buf[off++] = byte ':'; + } + if(startl != stopl){ + off = sblint(buf, off, stopl); + buf[off++] = byte '.'; + } + off = sblint(buf, off, (src.stop & PosMask)); + buf[off++] = byte ' '; + lastf = stopf.sbl; + lastline = stopl; + return off; +} + +sblsrcconv(src: Src): string +{ + s := ""; + (startf, startl) := fline(src.start >> PosBits); + (stopf, stopl) := fline(src.stop >> PosBits); + if(lastf != startf.sbl){ + s += string startf.sbl; + s[len s] = ':'; + } + if(lastline != startl){ + s += string startl; + s[len s] = '.'; + } + s += string (src.start & PosMask); + s[len s] = ','; + if(startf.sbl != stopf.sbl){ + s += string stopf.sbl; + s[len s] = ':'; + } + if(startl != stopl){ + s += string stopl; + s[len s] = '.'; + } + s += string (src.stop & PosMask); + s[len s] = ' '; + lastf = stopf.sbl; + lastline = stopl; + return s; +} + +isnilsrc(s: Src): int +{ + return s.start == 0 && s.stop == 0; +} + +isnilstopsrc(s: Src): int +{ + return s.stop == 0; +} + +sblinst(in: ref Inst, ninst: int) +{ + src: Src; + + MAXSBL: con 8*1024; + buf := array[MAXSBL] of byte; + n := 0; + bsym.puts(string ninst); + bsym.putb(byte '\n'); + sblblocks := array[nblocks] of {* => -1}; + for(; in != nil; in = in.next){ + if(in.op == INOOP) + continue; + if(in.src.start < 0) + fatal("no file specified for "+instconv(in)); + if(n >= (MAXSBL - MAXSBLSRC - MAXSBLINT - 1)){ + bsym.write(buf, n); + n = 0; + } + if(isnilsrc(in.src)) + in.src = src; + else if(isnilstopsrc(in.src)){ # how does this happen ? + in.src.stop = in.src.start; + in.src.stop++; + } + n = sblsrcconvb(buf, n, in.src); + src = in.src; + b := sblblocks[in.block]; + if(b < 0) + sblblocks[in.block] = b = blockid++; + n = sblint(buf, n, b); + buf[n++] = byte '\n'; + } + if(n > 0) + bsym.write(buf, n); +} + +sblty(tys: array of ref Decl, ntys: int) +{ + bsym.puts(string ntys); + bsym.putb(byte '\n'); + for(i := 0; i < ntys; i++){ + d := tys[i]; + d.ty.sbl = i; + } + for(i = 0; i < ntys; i++){ + d := tys[i]; + sbltype(d.ty, 1); + } +} + +sblfn(fns: array of ref Decl, nfns: int) +{ + bsym.puts(string nfns); + bsym.putb(byte '\n'); + for(i := 0; i < nfns; i++){ + f := fns[i]; + if(ispoly(f)) + rmfnptrs(f); + bsym.puts(string f.pc.pc); + bsym.putb(byte ':'); + if(f.dot != nil && f.dot.ty.kind == Tadt){ + bsym.puts(f.dot.sym.name); + bsym.putb(byte '.'); + } + bsym.puts(f.sym.name); + bsym.putb(byte '\n'); + sbldecl(f.ty.ids, Darg); + sbldecl(f.locals, Dlocal); + sbltype(f.ty.tof, 0); + } +} + +sblvar(vars: ref Decl) +{ + sbldecl(vars, Dglobal); +} + +isvis(id: ref Decl): int +{ + if(!tattr[id.ty.kind].vis + || id.sym == nil + || id.sym.name == "" + || id.sym.name[0] == '.') + return 0; + if(id.ty == tstring && id.init != nil && id.init.op == Oconst) + return 0; + if(id.src.start < 0 || id.src.stop < 0) + return 0; + return 1; +} + +sbldecl(ids: ref Decl, store: int) +{ + n := 0; + for(id := ids; id != nil; id = id.next){ + if(id.store != store || !isvis(id)) + continue; + n++; + } + bsym.puts(string n); + bsym.putb(byte '\n'); + for(id = ids; id != nil; id = id.next){ + if(id.store != store || !isvis(id)) + continue; + bsym.puts(string id.offset); + bsym.putb(byte ':'); + bsym.puts(id.sym.name); + bsym.putb(byte ':'); + bsym.puts(sblsrcconv(id.src)); + sbltype(id.ty, 0); + bsym.putb(byte '\n'); + } +} + +sbltype(t: ref Type, force: int) +{ + if(t.kind == Tadtpick) + t = t.decl.dot.ty; + + d := t.decl; + if(!force && d != nil && d.ty.sbl >= 0){ + bsym.putb(byte '@'); + bsym.puts(string d.ty.sbl); + bsym.putb(byte '\n'); + return; + } + + if(t.rec != byte 0) + fatal("recursive sbl type: "+typeconv(t)); + + t.rec = byte 1; + case t.kind{ + * => + fatal("bad type in sbltype: "+typeconv(t)); + Tnone or + Tany or + Tint or + Tbig or + Tbyte or + Treal or + Tstring or + Tfix or + Tpoly => + bsym.putb(sbltname[t.kind]); + Tfn => + bsym.putb(sbltname[t.kind]); + sbldecl(t.ids, Darg); + sbltype(t.tof, 0); + Tarray or + Tlist or + Tchan or + Tref => + bsym.putb(sbltname[t.kind]); + if(t.kind == Tref && t.tof.kind == Tfn){ + tattr[Tany].vis = 1; + sbltype(tfnptr, 0); + tattr[Tany].vis = 0; + } + else + sbltype(t.tof, 0); + Ttuple or + Texception => + bsym.putb(sbltname[t.kind]); + bsym.puts(string t.size); + bsym.putb(byte '.'); + sbldecl(t.ids, Dfield); + Tadt => + if(t.tags != nil) + bsym.putb(sbltadtpick); + else + bsym.putb(sbltname[t.kind]); + if(d.dot != nil && !isimpmod(d.dot.sym)) + bsym.puts(d.dot.sym.name + "->"); + bsym.puts(d.sym.name); + bsym.putb(byte ' '); + bsym.puts(sblsrcconv(d.src)); + bsym.puts(string d.ty.size); + bsym.putb(byte '\n'); + sbldecl(t.ids, Dfield); + if(t.tags != nil){ + bsym.puts(string t.decl.tag); + bsym.putb(byte '\n'); + lastt : ref Type = nil; + for(tg := t.tags; tg != nil; tg = tg.next){ + bsym.puts(tg.sym.name); + bsym.putb(byte ':'); + bsym.puts(sblsrcconv(tg.src)); + if(lastt == tg.ty){ + bsym.putb(byte '\n'); + }else{ + bsym.puts(string tg.ty.size); + bsym.putb(byte '\n'); + sbldecl(tg.ty.ids, Dfield); + } + lastt = tg.ty; + } + } + Tmodule => + bsym.putb(sbltname[t.kind]); + bsym.puts(d.sym.name); + bsym.putb(byte '\n'); + bsym.puts(sblsrcconv(d.src)); + sbldecl(t.ids, Dglobal); + } + t.rec = byte 0; +} diff --git a/appl/cmd/limbo/stubs.b b/appl/cmd/limbo/stubs.b new file mode 100644 index 00000000..acb24b81 --- /dev/null +++ b/appl/cmd/limbo/stubs.b @@ -0,0 +1,575 @@ +# +# write out some stub C code for limbo modules +# +emit(globals: ref Decl) +{ + for(m := globals; m != nil; m = m.next){ + if(m.store != Dtype || m.ty.kind != Tmodule) + continue; + m.ty = usetype(m.ty); + for(d := m.ty.ids; d != nil; d = d.next){ + d.ty = usetype(d.ty); + if(d.store == Dglobal || d.store == Dfn) + modrefable(d.ty); + if(d.store == Dtype && d.ty.kind == Tadt){ + for(id := d.ty.ids; id != nil; id = id.next){ + id.ty = usetype(id.ty); + modrefable(d.ty); + } + } + } + } + if(emitstub){ + print("#pragma hjdicks x4\n"); + print("#pragma pack x4\n"); + adtstub(globals); + modstub(globals); + print("#pragma pack off\n"); + print("#pragma hjdicks off\n"); + } + if(emittab != nil) + modtab(globals); + if(emitcode != nil) + modcode(globals); + if(emitsbl != nil) + modsbl(globals); +} + +modsbl(globals: ref Decl) +{ + for(d := globals; d != nil; d = d.next) + if(d.store == Dtype && d.ty.kind == Tmodule && d.sym.name == emitsbl) + break; + + if(d == nil) + return; + bsym = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + + sblmod(d); + sblfiles(); + n := 0; + genstart(); + for(id := d.ty.tof.ids; id != nil; id = id.next){ + if(id.sym.name == ".mp") + continue; + pushblock(); + id.pc = genrawop(id.src, INOP, nil, nil, nil); + id.pc.pc = n++; + popblock(); + } + firstinst = firstinst.next; + sblinst(firstinst, n); +# (adts, nadts) := findadts(globals); + sblty(adts, nadts); + fs := array[n] of ref Decl; + n = 0; + for(id = d.ty.tof.ids; id != nil; id = id.next){ + if(id.sym.name == ".mp") + continue; + fs[n] = id; + n++; + } + sblfn(fs, n); + sblvar(nil); +} + +lowercase(f: string): string +{ + for(i := 0; i < len f; i++) + if(f[i] >= 'A' && f[i] <= 'Z') + f[i] += 'a' - 'A'; + return f; +} + +modcode(globals: ref Decl) +{ + buf: string; + + if(emitdyn){ + buf = lowercase(emitcode); + print("#include \"%s.h\"\n", buf); + } + else{ + print("#include <lib9.h>\n"); + print("#include <isa.h>\n"); + print("#include <interp.h>\n"); + print("#include \"%smod.h\"\n", emitcode); + } + print("\n"); + + for(d := globals; d != nil; d = d.next) + if(d.store == Dtype && d.ty.kind == Tmodule && d.sym.name == emitcode) + break; + + if(d == nil) + return; + + # + # stub types + # + for(id := d.ty.ids; id != nil; id = id.next){ + if(id.store == Dtype && id.ty.kind == Tadt){ + id.ty = usetype(id.ty); + print("Type*\tT_%s;\n", id.sym.name); + } + } + + # + # type maps + # + if(emitdyn){ + for(id = d.ty.ids; id != nil; id = id.next) + if(id.store == Dtype && id.ty.kind == Tadt) + print("uchar %s_map[] = %s_%s_map;\n", + id.sym.name, emitcode, id.sym.name); + } + + # + # heap allocation and garbage collection for a type + # + if(emitdyn){ + for(id = d.ty.ids; id != nil; id = id.next) + if(id.store == Dtype && id.ty.kind == Tadt){ + print("\n%s_%s*\n%salloc%s(void)\n{\n\tHeap *h;\n\n\th = heap(T_%s);\n\treturn H2D(%s_%s*, h);\n}\n", emitcode, id.sym.name, emitcode, id.sym.name, id.sym.name, emitcode, id.sym.name); + print("\nvoid\n%sfree%s(Heap *h, int swept)\n{\n\t%s_%s *d;\n\n\td = H2D(%s_%s*, h);\n\tfreeheap(h, swept);\n}\n", emitcode, id.sym.name, emitcode, id.sym.name, emitcode, id.sym.name); + } + } + + # + # initialization function + # + if(emitdyn) + print("\nvoid\n%sinit(void)\n{\n", emitcode); + else{ + print("\nvoid\n%smodinit(void)\n{\n", emitcode); + print("\tbuiltinmod(\"$%s\", %smodtab);\n", emitcode, emitcode); + } + for(id = d.ty.ids; id != nil; id = id.next) + if(id.store == Dtype && id.ty.kind == Tadt){ + if(emitdyn) + print("\tT_%s = dtype(%sfree%s, %s_%s_size, %s_map, sizeof(%s_map));\n", + id.sym.name, emitcode, id.sym.name, emitcode, id.sym.name, id.sym.name, id.sym.name); + else + print("\tT_%s = dtype(freeheap, sizeof(%s), %smap, sizeof(%smap));\n", + id.sym.name, id.sym.name, id.sym.name, id.sym.name); + } + print("}\n"); + + # + # end function + # + if(emitdyn){ + print("\nvoid\n%send(void)\n{\n", emitcode); + for(id = d.ty.ids; id != nil; id = id.next) + if(id.store == Dtype && id.ty.kind == Tadt) + print("\tfreetype(T_%s);\n", id.sym.name); + print("}\n"); + } + + # + # stub functions + # + for(id = d.ty.tof.ids; id != nil; id = id.next){ + print("\nvoid\n%s_%s(void *fp)\n{\n\tF_%s_%s *f = fp;\n", + id.dot.sym.name, id.sym.name, + id.dot.sym.name, id.sym.name); + if(id.ty.tof != tnone && tattr[id.ty.tof.kind].isptr) + print("\n\tdestroy(*f->ret);\n\t*f->ret = H;\n"); + print("}\n"); + } + + if(emitdyn) + print("\n#include \"%smod.h\"\n", buf); +} + +modtab(globals: ref Decl) +{ + print("typedef struct{char *name; long sig; void (*fn)(void*); int size; int np; uchar map[16];} Runtab;\n"); + for(d := globals; d != nil; d = d.next){ + if(d.store == Dtype && d.ty.kind == Tmodule && d.sym.name == emittab){ + n := 0; + print("Runtab %smodtab[]={\n", d.sym.name); + for(id := d.ty.tof.ids; id != nil; id = id.next){ + n++; + print("\t\""); + if(id.dot != d) + print("%s.", id.dot.sym.name); + print("%s\",0x%ux,%s_%s,", id.sym.name, sign(id), + id.dot.sym.name, id.sym.name); + if(id.ty.varargs != byte 0) + print("0,0,{0},"); + else{ + md := mkdesc(idoffsets(id.ty.ids, MaxTemp, MaxAlign), id.ty.ids); + print("%d,%d,%s,", md.size, md.nmap, mapconv(md)); + } + print("\n"); + } + print("\t0\n};\n"); + print("#define %smodlen %d\n", d.sym.name, n); + } + } +} + +# +# produce activation records for all the functions in modules +# +modstub(globals: ref Decl) +{ + for(d := globals; d != nil; d = d.next){ + if(d.store != Dtype || d.ty.kind != Tmodule) + continue; + arg := 0; + for(id := d.ty.tof.ids; id != nil; id = id.next){ + s := id.dot.sym.name + "_" + id.sym.name; + if(emitdyn && id.dot.dot != nil) + s = id.dot.dot.sym.name + "_" + s; + print("void %s(void*);\ntypedef struct F_%s F_%s;\nstruct F_%s\n{\n", + s, s, s, s); + print(" WORD regs[NREG-1];\n"); + if(id.ty.tof != tnone) + print(" %s* ret;\n", ctypeconv(id.ty.tof)); + else + print(" WORD noret;\n"); + print(" uchar temps[%d];\n", MaxTemp-NREG*IBY2WD); + offset := MaxTemp; + for(m := id.ty.ids; m != nil; m = m.next){ + p := ""; + if(m.sym != nil) + p = m.sym.name; + else + p = "arg"+string arg; + + # + # explicit pads for structure alignment + # + t := m.ty; + (offset, nil) = stubalign(offset, t.align, nil); + if(offset != m.offset) + yyerror("module stub must not contain data objects"); + # fatal("modstub bad offset"); + print(" %s %s;\n", ctypeconv(t), p); + arg++; + offset += t.size; +#ZZZ need to align? + } + if(id.ty.varargs != byte 0) + print(" WORD vargs;\n"); + print("};\n"); + } + for(id = d.ty.ids; id != nil; id = id.next) + if(id.store == Dconst) + constub(id); + } +} + +chanstub(in: string, id: ref Decl) +{ + print("typedef %s %s_%s;\n", ctypeconv(id.ty.tof), in, id.sym.name); + desc := mktdesc(id.ty.tof); + print("#define %s_%s_size %d\n", in, id.sym.name, desc.size); + print("#define %s_%s_map %s\n", in, id.sym.name, mapconv(desc)); +} + +# +# produce c structs for all adts +# +adtstub(globals: ref Decl) +{ + t, tt: ref Type; + m, d, id: ref Decl; + + for(m = globals; m != nil; m = m.next){ + if(m.store != Dtype || m.ty.kind != Tmodule) + continue; + for(d = m.ty.ids; d != nil; d = d.next){ + if(d.store != Dtype) + continue; + t = usetype(d.ty); + d.ty = t; + s := dotprint(d.ty.decl, '_'); + case d.ty.kind{ + Tadt => + print("typedef struct %s %s;\n", s, s); + Tint or + Tbyte or + Treal or + Tbig or + Tfix => + print("typedef %s %s;\n", ctypeconv(t), s); + } + } + } + for(m = globals; m != nil; m = m.next){ + if(m.store != Dtype || m.ty.kind != Tmodule) + continue; + for(d = m.ty.ids; d != nil; d = d.next){ + if(d.store != Dtype) + continue; + t = d.ty; + if(t.kind == Tadt || t.kind == Ttuple && t.decl.sym != anontupsym){ + if(t.tags != nil){ + pickadtstub(t); + continue; + } + s := dotprint(t.decl, '_'); + print("struct %s\n{\n", s); + + offset := 0; + for(id = t.ids; id != nil; id = id.next){ + if(id.store == Dfield){ + tt = id.ty; + (offset, nil) = stubalign(offset, tt.align, nil); + if(offset != id.offset) + fatal("adtstub bad offset"); + print(" %s %s;\n", ctypeconv(tt), id.sym.name); + offset += tt.size; + } + } + if(t.ids == nil){ + print(" char dummy[1];\n"); + offset = 1; + } + (offset, nil)= stubalign(offset, t.align, nil); +#ZZZ +(offset, nil) = stubalign(offset, IBY2WD, nil); + if(offset != t.size && t.ids != nil) + fatal("adtstub: bad size"); + print("};\n"); + + for(id = t.ids; id != nil; id = id.next) + if(id.store == Dconst) + constub(id); + + for(id = t.ids; id != nil; id = id.next) + if(id.ty.kind == Tchan) + chanstub(s, id); + + desc := mktdesc(t); + if(offset != desc.size && t.ids != nil) + fatal("adtstub: bad desc size"); + print("#define %s_size %d\n", s, offset); + print("#define %s_map %s\n", s, mapconv(desc)); +#ZZZ +if(0) + print("struct %s_check {int s[2*(sizeof(%s)==%s_size)-1];};\n", s, s, s); + }else if(t.kind == Tchan) + chanstub(m.sym.name, d); + } + } +} + +# +# emit an expicit pad field for aligning emitted c structs +# according to limbo's definition +# +stubalign(offset: int, a: int, s: string): (int, string) +{ + x := offset & (a-1); + if(x == 0) + return (offset, s); + x = a - x; + if(s != nil) + s += sprint("uchar\t_pad%d[%d]; ", offset, x); + else + print("\tuchar\t_pad%d[%d];\n", offset, x); + offset += x; + if((offset & (a-1)) || x >= a) + fatal("compiler stub misalign"); + return (offset, s); +} + +constub(id: ref Decl) +{ + s := id.dot.sym.name + "_" + id.sym.name; + case id.ty.kind{ + Tbyte => + print("#define %s %d\n", s, int id.init.c.val & 16rff); + Tint or + Tfix => + print("#define %s %d\n", s, int id.init.c.val); + Tbig => + print("#define %s %bd\n", s, id.init.c.val); + Treal => + print("#define %s %g\n", s, id.init.c.rval); + Tstring => + print("#define %s \"%s\"\n", s, id.init.decl.sym.name); + } +} + +mapconv(d: ref Desc): string +{ + s := "{"; + for(i := 0; i < d.nmap; i++) + s += "0x" + hex(int d.map[i], 0) + ","; + if(i == 0) + s += "0"; + s += "}"; + return s; +} + +dotprint(d: ref Decl, dot: int): string +{ + s : string; + if(d.dot != nil){ + s = dotprint(d.dot, dot); + s[len s] = dot; + } + if(d.sym == nil) + return s; + return s + d.sym.name; +} + +ckindname := array[Tend] of +{ + Tnone => "void", + Tadt => "struct", + Tadtpick => "?adtpick?", + Tarray => "Array*", + Tbig => "LONG", + Tbyte => "BYTE", + Tchan => "Channel*", + Treal => "REAL", + Tfn => "?fn?", + Tint => "WORD", + Tlist => "List*", + Tmodule => "Modlink*", + Tref => "?ref?", + Tstring => "String*", + Ttuple => "?tuple?", + Texception => "?exception", + Tfix => "WORD", + Tpoly => "void*", + + Tainit => "?ainit?", + Talt => "?alt?", + Tany => "void*", + Tarrow => "?arrow?", + Tcase => "?case?", + Tcasel => "?casel?", + Tcasec => "?casec?", + Tdot => "?dot?", + Terror => "?error?", + Tgoto => "?goto?", + Tid => "?id?", + Tiface => "?iface?", + Texcept => "?except?", + Tinst => "?inst?", +}; + +ctypeconv(t: ref Type): string +{ + if(t == nil) + return "void"; + s := ""; + case t.kind{ + Terror => + return "type error"; + Tref => + s = ctypeconv(t.tof); + s += "*"; + Tarray or + Tlist or + Tint or + Tbig or + Tstring or + Treal or + Tbyte or + Tnone or + Tany or + Tchan or + Tmodule or + Tfix or + Tpoly => + return ckindname[t.kind]; + Tadt or + Ttuple => + if(t.decl.sym != anontupsym) + return dotprint(t.decl, '_'); + s += "struct{ "; + offset := 0; + for(id := t.ids; id != nil; id = id.next){ + tt := id.ty; + (offset, s) = stubalign(offset, tt.align, s); + if(offset != id.offset) + fatal("ctypeconv tuple bad offset"); + s += ctypeconv(tt); + s += " "; + s += id.sym.name; + s += "; "; + offset += tt.size; + } + (offset, s) = stubalign(offset, t.align, s); + if(offset != t.size) + fatal(sprint("ctypeconv tuple bad t=%s size=%d offset=%d", typeconv(t), t.size, offset)); + s += "}"; + * => + fatal("no C equivalent for type " + string t.kind); + } + return s; +} + +pickadtstub(t: ref Type) +{ + tt: ref Type; + desc: ref Desc; + id, tg: ref Decl; + ok: byte; + offset, tgoffset: int; + + buf := dotprint(t.decl, '_'); + offset = 0; + for(tg = t.tags; tg != nil; tg = tg.next) + print("#define %s_%s %d\n", buf, tg.sym.name, offset++); + print("struct %s\n{\n", buf); + print(" int pick;\n"); + offset = IBY2WD; + for(id = t.ids; id != nil; id = id.next){ + if(id.store == Dfield){ + tt = id.ty; + (offset, nil) = stubalign(offset, tt.align, nil); + if(offset != id.offset) + fatal("pickadtstub bad offset"); + print(" %s %s;\n", ctypeconv(tt), id.sym.name); + offset += tt.size; + } + } + print(" union{\n"); + for(tg = t.tags; tg != nil; tg = tg.next){ + tgoffset = offset; + print(" struct{\n"); + for(id = tg.ty.ids; id != nil; id = id.next){ + if(id.store == Dfield){ + tt = id.ty; + (tgoffset, nil) = stubalign(tgoffset, tt.align, nil); + if(tgoffset != id.offset) + fatal("pickadtstub bad offset"); + print(" %s %s;\n", ctypeconv(tt), id.sym.name); + tgoffset += tt.size; + } + } + if(tg.ty.ids == nil) + print(" char dummy[1];\n"); + print(" } %s;\n", tg.sym.name); + } + print(" } u;\n"); + print("};\n"); + + for(id = t.ids; id != nil; id = id.next) + if(id.store == Dconst) + constub(id); + + for(id = t.ids; id != nil; id = id.next) + if(id.ty.kind == Tchan) + chanstub(buf, id); + + for(tg = t.tags; tg != nil; tg = tg.next){ + ok = tg.ty.tof.ok; + tg.ty.tof.ok = OKverify; + sizetype(tg.ty.tof); + tg.ty.tof.ok = OKmask; + desc = mktdesc(tg.ty.tof); + tg.ty.tof.ok = ok; + print("#define %s_%s_size %d\n", buf, tg.sym.name, tg.ty.size); + print("#define %s_%s_map %s\n", buf, tg.sym.name, mapconv(desc)); + } +} diff --git a/appl/cmd/limbo/typecheck.b b/appl/cmd/limbo/typecheck.b new file mode 100644 index 00000000..fc0d43e4 --- /dev/null +++ b/appl/cmd/limbo/typecheck.b @@ -0,0 +1,3223 @@ +fndecls: ref Decl; +labstack: array of ref Node; +maxlabdep: int; +inexcept: ref Node; +nexc: int; +fndec: ref Decl; + +increfs(id: ref Decl) +{ + for( ; id != nil; id = id.link) + id.refs++; +} + +fninline(d: ref Decl): int +{ + left, right: ref Node; + + n := d.init; + if(dontinline || d.inline < byte 0 || d.locals != nil || ispoly(d) || n.ty.tof.kind == Tnone || nodes(n) >= 100) + return 0; + n = n.right; + if(n.op == Oseq && n.right == nil) + n = n.left; + # + # inline + # (a) return e; + # (b) if(c) return e1; else return e2; + # (c) if(c) return e1; return e2; + # + case(n.op){ + Oret => + break; + Oif => + right = n.right; + if(right.right == nil || right.left.op != Oret || right.right.op != Oret || !tequal(right.left.left.ty, right.right.left.ty)) + return 0; + break; + Oseq => + left = n.left; + right = n.right; + if(left.op != Oif || left.right.right != nil || left.right.left.op != Oret || right.op != Oseq || right.right != nil || right.left.op != Oret || !tequal(left.right.left.left.ty, right.left.left.ty)) + return 0; + break; + * => + return 0; + } + if(occurs(d, n) || hasasgns(n)) + return 0; + if(n.op == Oseq){ + left.right.right = right.left; + n = left; + right = n.right; + d.init.right.right = nil; + } + if(n.op == Oif){ + n.ty = right.ty = right.left.left.ty; + right.left = right.left.left; + right.right = right.right.left; + d.init.right.left = mkunary(Oret, n); + } + return 1; +} + +rewind(n: ref Node) +{ + r, nn: ref Node; + + r = n; + nn = n.left; + for(n = n.right; n != nil; n = n.right){ + if(n.right == nil){ + r.left = nn; + r.right = n.left; + } + else + nn = mkbin(Oindex, nn, n.left); + } +} + +ckmod(n: ref Node, id: ref Decl) +{ + t: ref Type; + d, idc: ref Decl; + mod: ref Node; + + if(id == nil) + fatal("can't find function: " + nodeconv(n)); + idc = nil; + mod = nil; + if(n.op == Oname){ + idc = id; + mod = id.eimport; + } + else if(n.op == Omdot) + mod = n.left; + else if(n.op == Odot){ + idc = id.dot; + t = n.left.ty; + if(t.kind == Tref) + t = t.tof; + if(t.kind == Tadtpick) + t = t.decl.dot.ty; + d = t.decl; + while(d != nil && d.link != nil) + d = d.link; + if(d != nil && d.timport != nil) + mod = d.timport.eimport; + n.right.left = mod; + } + if(mod != nil && mod.ty.kind != Tmodule){ + nerror(n, "cannot use " + expconv(n) + " as a function reference"); + return; + } + if(mod != nil){ + if(valistype(mod)){ + nerror(n, "cannot use " + expconv(n) + " as a function reference because " + expconv(mod) + " is a module interface"); + return; + } + }else if(idc != nil && idc.dot != nil && !isimpmod(idc.dot.sym)){ + nerror(n, "cannot use " + expconv(n) + " without importing " + idc.sym.name + " from a variable"); + return; + } + if(mod != nil) + modrefable(n.ty); +} + +addref(n: ref Node) +{ + nn: ref Node; + + nn = mkn(0, nil, nil); + *nn = *n; + n.op = Oref; + n.left = nn; + n.right = nil; + n.decl = nil; + n.ty = usetype(mktype(n.src.start, n.src.stop, Tref, nn.ty, nil)); +} + +fnref(n: ref Node, id: ref Decl) +{ + id.inline = byte -1; + ckmod(n, id); + addref(n); + while(id.link != nil) + id = id.link; + if(ispoly(id) && encpolys(id) != nil) + nerror(n, "cannot have a polymorphic adt function reference " + id.sym.name); +} + +typecheck(checkimp: int): ref Decl +{ + entry, d, m: ref Decl; + + if(errors) + return nil; + + # + # generate the set of all functions + # compile one function at a time + # + gdecl(tree); + gbind(tree); + fns = array[nfns] of ref Decl; + i := gcheck(tree, fns, 0); + if(i != nfns) + fatal("wrong number of functions found in gcheck"); + + maxlabdep = 0; + for(i = 0; i < nfns; i++){ + d = fns[i]; + if(d != nil) + fndec = d; + if(d != nil) + fncheck(d); + fndec = nil; + } + + if(errors) + return nil; + + entry = nil; + if(checkimp){ + im: ref Decl; + dm: ref Dlist; + + if(impmods == nil){ + yyerror("no implementation module"); + return nil; + } + for(im = impmods; im != nil; im = im.next){ + for(dm = impdecls; dm != nil; dm = dm.next) + if(dm.d.sym == im.sym) + break; + if(dm == nil || dm.d.ty == nil){ + yyerror("no definition for implementation module "+im.sym.name); + return nil; + } + } + + # + # can't check the module spec until all types and imports are determined, + # which happens in scheck + # + for(dm = impdecls; dm != nil; dm = dm.next){ + im = dm.d; + im.refs++; + im.ty = usetype(im.ty); + if(im.store != Dtype || im.ty.kind != Tmodule){ + error(im.src.start, "cannot implement "+declconv(im)); + return nil; + } + } + + # now check any multiple implementations + impdecl = modimp(impdecls, impmods); + + s := enter("init", 0); + for(dm = impdecls; dm != nil; dm = dm.next){ + im = dm.d; + for(m = im.ty.ids; m != nil; m = m.next){ + m.ty = usetype(m.ty); + m.refs++; + + if(m.sym == s && m.ty.kind == Tfn && entry == nil) + entry = m; + + if(m.store == Dglobal || m.store == Dfn) + modrefable(m.ty); + + if(m.store == Dtype && m.ty.kind == Tadt){ + for(d = m.ty.ids; d != nil; d = d.next){ + d.ty = usetype(d.ty); + modrefable(d.ty); + d.refs++; + } + } + } + checkrefs(im.ty.ids); + } + } + if(errors) + return nil; + gsort(tree); + tree = nil; + return entry; +} +# +# introduce all global declarations +# also adds all fields to adts and modules +# note the complications due to nested Odas expressions +# +gdecl(n: ref Node) +{ + for(;;){ + if(n == nil) + return; + if(n.op != Oseq) + break; + gdecl(n.left); + n = n.right; + } + case n.op{ + Oimport => + importdecled(n); + gdasdecl(n.right); + Oadtdecl => + adtdecled(n); + Ocondecl => + condecled(n); + gdasdecl(n.right); + Oexdecl => + exdecled(n); + Omoddecl => + moddecled(n); + Otypedecl => + typedecled(n); + Ovardecl => + vardecled(n); + Ovardecli => + vardecled(n.left); + gdasdecl(n.right); + Ofunc => + fndecled(n); + Oas or + Odas or + Onothing => + gdasdecl(n); + * => + fatal("can't deal with "+opconv(n.op)+" in gdecl"); + } +} + +# +# bind all global type ids, +# including those nested inside modules +# this needs to be done, since we may use such +# a type later in a nested scope, so if we bound +# the type ids then, the type could get bound +# to a nested declaration +# +gbind(n: ref Node) +{ + ids: ref Decl; + + for(;;){ + if(n == nil) + return; + if(n.op != Oseq) + break; + gbind(n.left); + n = n.right; + } + case n.op{ + Oas or + Ocondecl or + Odas or + Oexdecl or + Ofunc or + Oimport or + Onothing or + Ovardecl or + Ovardecli => + break; + Ofielddecl => + bindtypes(n.decl.ty); + Otypedecl => + bindtypes(n.decl.ty); + if(n.left != nil) + gbind(n.left); + Opickdecl => + gbind(n.left); + d := n.right.left.decl; + bindtypes(d.ty); + repushids(d.ty.ids); + gbind(n.right.right); + # get new ids for undefined types; propagate outwards + ids = popids(d.ty.ids); + if(ids != nil) + installids(Dundef, ids); + Oadtdecl or + Omoddecl => + bindtypes(n.ty); + if(n.ty.polys != nil) + repushids(n.ty.polys); + repushids(n.ty.ids); + gbind(n.left); + # get new ids for undefined types; propagate outwards + ids = popids(n.ty.ids); + if(ids != nil) + installids(Dundef, ids); + if(n.ty.polys != nil) + popids(n.ty.polys); + * => + fatal("can't deal with "+opconv(n.op)+" in gbind"); + } +} + +# +# check all of the global declarations +# bind all type ids referred to within types at the global level +# record decls for defined functions +# +gcheck(n: ref Node, fns: array of ref Decl, nfns: int): int +{ + ok, allok: int; + + for(;;){ + if(n == nil) + return nfns; + if(n.op != Oseq) + break; + nfns = gcheck(n.left, fns, nfns); + n = n.right; + } + + case n.op{ + Ofielddecl => + if(n.decl.ty.eraises != nil) + raisescheck(n.decl.ty); + Onothing or + Opickdecl => + break; + Otypedecl => + tcycle(n.ty); + Oadtdecl or + Omoddecl => + if(n.ty.polys != nil) + repushids(n.ty.polys); + repushids(n.ty.ids); + if(gcheck(n.left, nil, 0)) + fatal("gcheck fn decls nested in modules or adts"); + if(popids(n.ty.ids) != nil) + fatal("gcheck installs new ids in a module or adt"); + if(n.ty.polys != nil) + popids(n.ty.polys); + Ovardecl => + varcheck(n, 1); + Ocondecl => + concheck(n, 1); + Oexdecl => + excheck(n, 1); + Oimport => + importcheck(n, 1); + Ovardecli => + varcheck(n.left, 1); + (ok, allok) = echeck(n.right, 0, 1, nil); + if(ok){ + if(allok) + n.right = fold(n.right); + globalas(n.right.left, n.right.right, allok); + } + Oas or + Odas => + (ok, allok) = echeck(n, 0, 1, nil); + if(ok){ + if(allok) + n = fold(n); + globalas(n.left, n.right, allok); + } + Ofunc => + (ok, allok) = echeck(n.left, 0, 1, n); + if(ok && n.ty.eraises != nil) + raisescheck(n.ty); + d : ref Decl = nil; + if(ok) + d = fnchk(n); + fns[nfns++] = d; + * => + fatal("can't deal with "+opconv(n.op)+" in gcheck"); + } + return nfns; +} + +# +# check for unused expression results +# make sure the any calculated expression has +# a destination +# +checkused(n: ref Node): ref Node +{ + # + # only nil; and nil = nil; should have type tany + # + if(n.ty == tany){ + if(n.op == Oname) + return n; + if(n.op == Oas) + return checkused(n.right); + fatal("line "+lineconv(n.src.start)+" checkused "+nodeconv(n)); + } + + if(n.op == Ocall && n.left.ty.kind == Tfn && n.left.ty.tof != tnone){ + n = mkunary(Oused, n); + n.ty = n.left.ty; + return n; + } + if(n.op == Ocall && isfnrefty(n.left.ty)){ + if(n.left.ty.tof.tof != tnone){ + n = mkunary(Oused, n); + n.ty = n.left.ty; + } + return n; + } + if(isused[n.op] && (n.op != Ocall || n.left.ty.kind == Tfn)) + return n; + t := n.ty; + if(t.kind == Tfn) + nerror(n, "function "+expconv(n)+" not called"); + else if(t.kind == Tadt && t.tags != nil || t.kind == Tadtpick) + nerror(n, "expressions cannot have type "+typeconv(t)); + else if(n.op == Otuple){ + for(nn := n.left; nn != nil; nn = nn.right) + checkused(nn.left); + } + else + nwarn(n, "result of expression "+expconv(n)+" not used"); + n = mkunary(Oused, n); + n.ty = n.left.ty; + return n; +} + +fncheck(d: ref Decl) +{ + n := d.init; + if(debug['t']) + print("typecheck tree: %s\n", nodeconv(n)); + + fndecls = nil; + adtp := outerpolys(n.left); + if(n.left.op == Odot) + repushids(adtp); + if(d.ty.polys != nil) + repushids(d.ty.polys); + repushids(d.ty.ids); + + labdep = 0; + labstack = array[maxlabdep] of ref Node; + n.right = scheck(n.right, d.ty.tof, Sother); + if(labdep != 0) + fatal("unbalanced label stack in fncheck"); + labstack = nil; + + d.locals = appdecls(popids(d.ty.ids), fndecls); + if(d.ty.polys != nil) + popids(d.ty.polys); + if(n.left.op == Odot) + popids(adtp); + fndecls = nil; + + checkrefs(d.ty.ids); + checkrefs(d.ty.polys); + checkrefs(d.locals); + + checkraises(n); + + d.inline = byte fninline(d); +} + +scheck(n: ref Node, ret: ref Type, kind : int): ref Node +{ + s: ref Sym; + rok: int; + + top := n; + last: ref Node = nil; + for(; n != nil; n = n.right){ + left := n.left; + right := n.right; + case n.op{ + Ovardecl => + vardecled(n); + varcheck(n, 0); + if (nested() && tmustzero(n.decl.ty)) + decltozero(n); +# else if (inloop() && tmustzero(n.decl.ty)) +# decltozero(n); + return top; + Ovardecli => + vardecled(left); + varcheck(left, 0); + echeck(right, 0, 0, nil); + if (nested() && tmustzero(left.decl.ty)) + decltozero(left); + return top; + Otypedecl => + typedecled(n); + bindtypes(n.ty); + tcycle(n.ty); + return top; + Ocondecl => + condecled(n); + concheck(n, 0); + return top; + Oexdecl => + exdecled(n); + excheck(n, 0); + return top; + Oimport => + importdecled(n); + importcheck(n, 0); + return top; + Ofunc => + fatal("scheck func"); + Oscope => + if (kind == Sother) + kind = Sscope; + pushscope(n, kind); + if (left != nil) + fatal("Oscope has left field"); + echeck(left, 0, 0, nil); + n.right = scheck(right, ret, Sother); + d := popscope(); + fndecls = appdecls(fndecls, d); + return top; + Olabel => + echeck(left, 0, 0, nil); + n.right = scheck(right, ret, Sother); + return top; + Oseq => + n.left = scheck(left, ret, Sother); + # next time will check n.right + Oif => + (rok, nil) = echeck(left, 0, 0, nil); + if(rok && left.op != Onothing && left.ty != tint) + nerror(n, "if conditional must be an int, not "+etconv(left)); + right.left = scheck(right.left, ret, Sother); + # next time will check n.right.right + n = right; + Ofor => + (rok, nil) = echeck(left, 0, 0, nil); + if(rok && left.op != Onothing && left.ty != tint) + nerror(n, "for conditional must be an int, not "+etconv(left)); + # + # do the continue clause before the body + # this reflects the ordering of declarations + # + pushlabel(n); + right.right = scheck(right.right, ret, Sother); + right.left = scheck(right.left, ret, Sloop); + labdep--; + if(n.decl != nil && !n.decl.refs) + nwarn(n, "label "+n.decl.sym.name+" never referenced"); + return top; + Odo => + (rok, nil) = echeck(left, 0, 0, nil); + if(rok && left.op != Onothing && left.ty != tint) + nerror(n, "do conditional must be an int, not "+etconv(left)); + pushlabel(n); + n.right = scheck(n.right, ret, Sloop); + labdep--; + if(n.decl != nil && !n.decl.refs) + nwarn(n, "label "+n.decl.sym.name+" never referenced"); + return top; + Oalt or + Ocase or + Opick or + Oexcept => + pushlabel(n); + case n.op{ + Oalt => + altcheck(n, ret); + Ocase => + casecheck(n, ret); + Opick => + pickcheck(n, ret); + Oexcept => + exccheck(n, ret); + } + labdep--; + if(n.decl != nil && !n.decl.refs) + nwarn(n, "label "+n.decl.sym.name+" never referenced"); + return top; + Oret => + (rok, nil) = echeck(left, 0, 0, nil); + if(!rok) + return top; + if(left == nil){ + if(ret != tnone) + nerror(n, "return of nothing from a fn of "+typeconv(ret)); + }else if(ret == tnone){ + if(left.ty != tnone) + nerror(n, "return "+etconv(left)+" from a fn with no return type"); + }else if(!tcompat(ret, left.ty, 0)) + nerror(n, "return "+etconv(left)+" from a fn of "+typeconv(ret)); + return top; + Obreak or + Ocont => + s = nil; + if(n.decl != nil) + s = n.decl.sym; + for(i := 0; i < labdep; i++){ + if(s == nil || labstack[i].decl != nil && labstack[i].decl.sym == s){ + if(n.op == Ocont + && labstack[i].op != Ofor && labstack[i].op != Odo) + continue; + if(s != nil) + labstack[i].decl.refs++; + return top; + } + } + nerror(n, "no appropriate target for "+expconv(n)); + return top; + Oexit or + Onothing => + return top; + Oexstmt => + fndec.handler = byte 1; + n.left = scheck(left, ret, Sother); + n.right = scheck(right, ret, Sother); + return top; + * => + (nil, rok) = echeck(n, 0, 0, nil); + if(rok) + n = checkused(n); + if(last == nil) + return n; + last.right = n; + return top; + } + last = n; + } + return top; +} + +pushlabel(n: ref Node) +{ + s: ref Sym; + + if(labdep >= maxlabdep){ + maxlabdep += MaxScope; + labs := array[maxlabdep] of ref Node; + labs[:] = labstack; + labstack = labs; + } + if(n.decl != nil){ + s = n.decl.sym; + n.decl.refs = 0; + for(i := 0; i < labdep; i++) + if(labstack[i].decl != nil && labstack[i].decl.sym == s) + nerror(n, "label " + s.name + " duplicated on line " + lineconv(labstack[i].decl.src.start)); + } + labstack[labdep++] = n; +} + +varcheck(n: ref Node, isglobal: int) +{ + t := validtype(n.ty, nil); + t = topvartype(t, n.decl, isglobal, 0); + last := n.left.decl; + for(ids := n.decl; ids != last.next; ids = ids.next){ + ids.ty = t; + shareloc(ids); + } + if(t.eraises != nil) + raisescheck(t); +} + +concheck(n: ref Node, isglobal: int) +{ + t: ref Type; + init: ref Node; + + pushscope(nil, Sother); + installids(Dconst, iota); + (ok, allok) := echeck(n.right, 0, isglobal, nil); + popscope(); + + init = n.right; + if(!ok){ + t = terror; + }else{ + t = init.ty; + if(!tattr[t.kind].conable){ + nerror(init, "cannot have a "+typeconv(t)+" constant"); + allok = 0; + } + } + + last := n.left.decl; + for(ids := n.decl; ids != last.next; ids = ids.next) + ids.ty = t; + + if(!allok) + return; + + i := 0; + for(ids = n.decl; ids != last.next; ids = ids.next){ + if(ok){ + iota.init.c.val = big i; + ids.init = dupn(0, nosrc, init); + if(!varcom(ids)) + ok = 0; + } + i++; + } +} + +exname(d: ref Decl): string +{ + s := ""; + m := impmods.sym; + if(d.dot != nil) + m = d.dot.sym; + if(m != nil) + s += m.name+"."; + if(fndec != nil) + s += fndec.sym.name+"."; + s += string (scope-ScopeGlobal)+"."+d.sym.name; + return s; +} + +excheck(n: ref Node, isglobal: int) +{ + t: ref Type; + ids, last: ref Decl; + + t = validtype(n.ty, nil); + t = topvartype(t, n.decl, isglobal, 0); + last = n.left.decl; + for(ids = n.decl; ids != last.next; ids = ids.next){ + ids.ty = t; + ids.init = mksconst(n.src, enterstring(exname(ids))); + # ids.init = mksconst(n.src, enterstring(ids.sym.name)); + } +} + +importcheck(n: ref Node, isglobal: int) +{ + (ok, nil) := echeck(n.right, 1, isglobal, nil); + if(!ok) + return; + + m := n.right; + if(m.ty.kind != Tmodule || m.op != Oname){ + nerror(n, "cannot import from "+etconv(m)); + return; + } + + last := n.left.decl; + for(id := n.decl; id != last.next; id = id.next){ + v := namedot(m.ty.ids, id.sym); + if(v == nil){ + error(id.src.start, id.sym.name+" is not a member of "+expconv(m)); + id.store = Dwundef; + continue; + } + id.store = v.store; + v.ty = validtype(v.ty, nil); + id.ty = t := v.ty; + if(id.store == Dtype && t.decl != nil){ + id.timport = t.decl.timport; + t.decl.timport = id; + } + id.init = v.init; + id.importid = v; + id.eimport = m; + } +} + +rewcall(n: ref Node, d: ref Decl): ref Decl +{ + # put original function back now we're type checked + while(d.link != nil) + d = d.link; + if(n.op == Odot) + n.right.decl = d; + else if(n.op == Omdot){ + n.right.right.decl = d; + n.right.right.ty = d.ty; + } + else + fatal("bad op in Ocall rewcall"); + n.ty = n.right.ty = d.ty; + d.refs++; + usetype(d.ty); + return d; +} + +isfnrefty(t: ref Type): int +{ + return t.kind == Tref && t.tof.kind == Tfn; +} + +isfnref(d: ref Decl): int +{ + case(d.store){ + Dglobal or + Darg or + Dlocal or + Dfield or + Dimport => + return isfnrefty(d.ty); + } + return 0; +} + +tagopt: int; + +# +# annotate the expression with types +# +echeck(n: ref Node, typeok, isglobal: int, par: ref Node): (int, int) +{ + tg, id, callee: ref Decl; + t, tt: ref Type; + ok, allok, max, nocheck, kidsok: int; + + ok = allok = 1; + if(n == nil) + return (1, 1); + + if(n.op == Oseq){ + for( ; n != nil && n.op == Oseq; n = n.right){ + (okl, allokl) := echeck(n.left, typeok == 2, isglobal, n); + ok &= okl; + allok &= allokl; + n.ty = tnone; + } + if(n == nil) + return (ok, allok); + } + + left := n.left; + right := n.right; + + nocheck = 0; + if(n.op == Odot || n.op == Omdot || n.op == Ocall || n.op == Oref || n.op == Otagof || n.op == Oindex) + nocheck = 1; + if(n.op != Odas # special case + && n.op != Oload) # can have better error recovery + (ok, allok) = echeck(left, nocheck, isglobal, n); + if(n.op != Odas # special case + && n.op != Odot # special check + && n.op != Omdot # special check + && n.op != Ocall # can have better error recovery + && n.op != Oindex){ + (okr, allokr) := echeck(right, 0, isglobal, n); + ok &= okr; + allok &= allokr; + } + if(!ok){ + n.ty = terror; + return (0, 0); + } + + case n.op{ + Odas => + (ok, allok) = echeck(right, 0, isglobal, n); + if(!ok) + right.ty = terror; + if(!isglobal && !dasdecl(left)){ + ok = 0; + }else if(!specific(right.ty) || !declasinfer(left, right.ty)){ + nerror(n, "cannot declare "+expconv(left)+" from "+etconv(right)); + declaserr(left); + ok = 0; + } + if(right.ty.kind == Texception) + left.ty = n.ty = mkextuptype(right.ty); + else{ + left.ty = n.ty = right.ty; + usedty(n.ty); + } + if (nested() && tmustzero(left.ty)) + decltozero(left); + return (ok, allok & ok); + Oseq or + Onothing => + n.ty = tnone; + Owild => + n.ty = tint; + Ocast => + t = usetype(n.ty); + n.ty = t; + tt = left.ty; + if(tcompat(t, tt, 0)){ + left.ty = t; + break; + } + if(tt.kind == Tarray){ + if(tt.tof == tbyte && t == tstring) + break; + }else if(t.kind == Tarray){ + if(t.tof == tbyte && tt == tstring) + break; + }else if(casttab[tt.kind][t.kind]){ + break; + } + nerror(n, "cannot make a "+typeconv(n.ty)+" from "+etconv(left)); + return (0, 0); + Ochan => + n.ty = usetype(n.ty); + if(left != nil && left.ty.kind != Tint){ + nerror(n, "channel size "+etconv(left)+" is not an int"); + return (0, 0); + } + Oload => + n.ty = usetype(n.ty); + (nil, kidsok) = echeck(left, 0, isglobal, n); + if(n.ty.kind != Tmodule){ + nerror(n, "cannot load a "+typeconv(n.ty)); + return (0, 0); + } + if(!kidsok){ + allok = 0; + break; + } + if(left.ty != tstring){ + nerror(n, "cannot load a module from "+etconv(left)); + allok = 0; + break; + } +if(n.ty.tof.decl.refs != 0) +n.ty.tof.decl.refs++; +n.ty.decl.refs++; + usetype(n.ty.tof); + Oref => + t = left.ty; + if(t.kind != Tadt && t.kind != Tadtpick && t.kind != Tfn && t.kind != Ttuple){ + nerror(n, "cannot make a ref from "+etconv(left)); + return (0, 0); + } + if(!tagopt && t.kind == Tadt && t.tags != nil && valistype(left)){ + nerror(n, "instances of ref "+expconv(left)+" must be qualified with a pick tag"); + return (0, 0); + } + if(t.kind == Tadtpick) + t.tof = usetype(t.tof); + n.ty = usetype(mktype(n.src.start, n.src.stop, Tref, t, nil)); + Oarray => + max = 0; + if(right != nil){ + max = assignindices(n); + if(max < 0) + return (0, 0); + if(!specific(right.left.ty)){ + nerror(n, "type for array not specific"); + return (0, 0); + } + n.ty = mktype(n.src.start, n.src.stop, Tarray, right.left.ty, nil); + } + n.ty = usetype(n.ty); + + if(left.op == Onothing) + n.left = left = mkconst(n.left.src, big max); + + if(left.ty.kind != Tint){ + nerror(n, "array size "+etconv(left)+" is not an int"); + return (0, 0); + } + Oelem => + n.ty = right.ty; + Orange => + if(left.ty != right.ty + || left.ty != tint && left.ty != tstring){ + nerror(left, "range "+etconv(left)+" to "+etconv(right)+" is not an int or string range"); + return (0, 0); + } + n.ty = left.ty; + Oname => + id = n.decl; + if(id == nil){ + nerror(n, "name with no declaration"); + return (0, 0); + } + if(id.store == Dunbound){ + s := id.sym; + id = s.decl; + if(id == nil) + id = undefed(n.src, s); + # save a little space + s.unbound = nil; + n.decl = id; + id.refs++; + } + n.ty = id.ty = usetype(id.ty); + case id.store{ + Dfn or + Dglobal or + Darg or + Dlocal or + Dimport or + Dfield or + Dtag => + break; + Dunbound => + fatal("unbound symbol found in echeck"); + Dundef => + nerror(n, id.sym.name+" is not declared"); + id.store = Dwundef; + return (0, 0); + Dwundef => + return (0, 0); + Dconst => + if(id.init == nil){ + nerror(n, id.sym.name+"'s value cannot be determined"); + id.store = Dwundef; + return (0, 0); + } + Dtype => + if(typeok) + break; + nerror(n, declconv(id)+" is not a variable"); + return (0, 0); + * => + fatal("echeck: unknown symbol storage"); + } + + if(n.ty == nil){ + nerror(n, declconv(id)+"'s type is not fully defined"); + id.store = Dwundef; + return (0, 0); + } + if(id.importid != nil && valistype(id.eimport) + && id.store != Dconst && id.store != Dtype && id.store != Dfn){ + nerror(n, "cannot use "+expconv(n)+" because "+expconv(id.eimport)+" is a module interface"); + return (0, 0); + } + if(n.ty.kind == Texception && !int n.ty.cons && par != nil && par.op != Oraise && par.op != Odot){ + nn := mkn(0, nil, nil); + *nn = *n; + n.op = Ocast; + n.left = nn; + n.decl = nil; + n.ty = usetype(mkextuptype(n.ty)); + } + # function name as function reference + if(id.store == Dfn && (par == nil || (par.op != Odot && par.op != Omdot && par.op != Ocall && par.op != Ofunc))) + fnref(n, id); + Oconst => + if(n.ty == nil){ + nerror(n, "no type in "+expconv(n)); + return (0, 0); + } + Oas => + t = right.ty; + if(t.kind == Texception) + t = mkextuptype(t); + if(!tcompat(left.ty, t, 1)){ + nerror(n, "type clash in "+etconv(left)+" = "+etconv(right)); + return (0, 0); + } + if(t == tany) + t = left.ty; + n.ty = t; + left.ty = t; + if(t.kind == Tadt && t.tags != nil || t.kind == Tadtpick) + if(left.ty.kind != Tadtpick || right.ty.kind != Tadtpick) + nerror(n, "expressions cannot have type "+typeconv(t)); + if(left.ty.kind == Texception){ + nerror(n, "cannot assign to an exception"); + return (0, 0); + } + if(islval(left)) + break; + return (0, 0); + Osnd => + if(left.ty.kind != Tchan){ + nerror(n, "cannot send on "+etconv(left)); + return (0, 0); + } + if(!tcompat(left.ty.tof, right.ty, 0)){ + nerror(n, "type clash in "+etconv(left)+" <-= "+etconv(right)); + return (0, 0); + } + t = right.ty; + if(t == tany) + t = left.ty.tof; + n.ty = t; + Orcv => + t = left.ty; + if(t.kind == Tarray) + t = t.tof; + if(t.kind != Tchan){ + nerror(n, "cannot receive on "+etconv(left)); + return (0, 0); + } + if(left.ty.kind == Tarray) + n.ty = usetype(mktype(n.src.start, n.src.stop, Ttuple, nil, + mkids(n.src, nil, tint, mkids(n.src, nil, t.tof, nil)))); + else + n.ty = t.tof; + Ocons => + if(right.ty.kind != Tlist && right.ty != tany){ + nerror(n, "cannot :: to "+etconv(right)); + return (0, 0); + } + n.ty = right.ty; + if(right.ty == tany) + n.ty = usetype(mktype(n.src.start, n.src.stop, Tlist, left.ty, nil)); + else if(!tcompat(right.ty.tof, left.ty, 0)){ + t = tparent(right.ty.tof, left.ty); + if(!tcompat(t, left.ty, 0)){ + nerror(n, "type clash in "+etconv(left)+" :: "+etconv(right)); + return (0, 0); + } + else + n.ty = usetype(mktype(n.src.start, n.src.stop, Tlist, t, nil)); + } + Ohd or + Otl => + if(left.ty.kind != Tlist || left.ty.tof == nil){ + nerror(n, "cannot "+opconv(n.op)+" "+etconv(left)); + return (0, 0); + } + if(n.op == Ohd) + n.ty = left.ty.tof; + else + n.ty = left.ty; + Otuple => + n.ty = usetype(mktype(n.src.start, n.src.stop, Ttuple, nil, tuplefields(left))); + Ospawn => + if(left.op != Ocall || left.left.ty.kind != Tfn && !isfnrefty(left.left.ty)){ + nerror(left, "cannot spawn "+expconv(left)); + return (0, 0); + } + if(left.ty != tnone){ + nerror(left, "cannot spawn functions which return values, such as "+etconv(left)); + return (0, 0); + } + Oraise => + if(left.op == Onothing){ + if(inexcept == nil){ + nerror(n, expconv(n)+": empty raise not in exception handler"); + return (0, 0); + } + n.left = dupn(1, n.src, inexcept); + break; + } + if(left.ty != tstring && left.ty.kind != Texception){ + nerror(n, expconv(n)+": raise argument "+etconv(left)+" is not a string or exception"); + return (0, 0); + } + if((left.op != Ocall || left.left.ty.kind == Tfn) && left.ty.ids != nil && int left.ty.cons){ + nerror(n, "too few exception arguments"); + return (0, 0); + } + Ocall => + (nil, kidsok) = echeck(right, 0, isglobal, nil); + t = left.ty; + usedty(t); + pure := 1; + if(t.kind == Tref){ + pure = 0; + t = t.tof; + } + if(t.kind != Tfn) + return callcast(n, kidsok, allok); + n.ty = t.tof; + if(!kidsok){ + allok = 0; + break; + } + + # + # get the name to call and any associated module + # + mod: ref Node = nil; + callee = nil; + id = nil; + tt = nil; + if(left.op == Odot){ + callee = left.right.decl; + id = callee.dot; + right = passimplicit(left, right); + n.right = right; + tt = left.left.ty; + if(tt.kind == Tref) + tt = tt.tof; + ttt := tt; + if(tt.kind == Tadtpick) + ttt = tt.decl.dot.ty; + dd := ttt.decl; + while(dd != nil && dd.link != nil) + dd = dd.link; + if(dd != nil && dd.timport != nil) + mod = dd.timport.eimport; + + # + # stash the import module under a rock, + # because we won't be able to get it later + # after scopes are popped + # + left.right.left = mod; + }else if(left.op == Omdot){ + if(left.right.op == Odot){ + callee = left.right.right.decl; + right = passimplicit(left.right, right); + n.right = right; + tt = left.right.left.ty; + if(tt.kind == Tref) + tt = tt.tof; + }else + callee = left.right.decl; + mod = left.left; + }else if(left.op == Oname){ + callee = left.decl; + id = callee; + mod = id.eimport; + }else if(pure){ + nerror(left, expconv(left)+" is not a function name"); + allok = 0; + break; + } + if(pure && callee == nil) + fatal("can't find called function: "+nodeconv(left)); + if(callee != nil && callee.store != Dfn && !isfnref(callee)){ + nerror(left, expconv(left)+" is not a function"); + allok = 0; + break; + } + if(mod != nil && mod.ty.kind != Tmodule){ + nerror(left, "cannot call "+expconv(left)); + allok = 0; + break; + } + if(mod != nil){ + if(valistype(mod)){ + nerror(left, "cannot call "+expconv(left)+" because "+expconv(mod)+" is a module interface"); + allok = 0; + break; + } + }else if(id != nil && id.dot != nil && !isimpmod(id.dot.sym)){ + nerror(left, "cannot call "+expconv(left)+" without importing "+id.sym.name+" from a variable"); + allok = 0; + break; + } + if(mod != nil) + modrefable(left.ty); + if(callee != nil && callee.store != Dfn) + callee = nil; + if(t.varargs != byte 0){ + t = mkvarargs(left, right); + if(left.ty.kind == Tref) + left.ty = usetype(mktype(t.src.start, t.src.stop, Tref, t, nil)); + else + left.ty = t; + } + else if(ispoly(callee) || isfnrefty(left.ty) && left.ty.tof.polys != nil){ + unifysrc = n.src; + if(!argncompat(n, t.ids, right)){ + allok = 0; + break; + } + (okp, tp) := tunify(left.ty, calltype(left.ty, right, n.ty)); + if(!okp){ + nerror(n, "function call type mismatch (" + typeconv(left.ty)+" vs "+typeconv(calltype(left.ty, right, n.ty))+")"); + allok = 0; + } + else{ + (n.ty, tp) = expandtype(n.ty, nil, nil, tp); + n.ty = usetype(n.ty); + if(ispoly(callee) && tt != nil && (tt.kind == Tadt || tt.kind == Tadtpick) && int (tt.flags&INST)) + callee = rewcall(left, callee); + n.right = passfns(n.src, callee, left, right, tt, tp); + } + } + else if(!argcompat(n, t.ids, right)) + allok = 0; + Odot => + t = left.ty; + if(t.kind == Tref) + t = t.tof; + case t.kind{ + Tadt or + Tadtpick or + Ttuple or + Texception or + Tpoly => + id = namedot(t.ids, right.decl.sym); + if(id == nil){ + id = namedot(t.tags, right.decl.sym); + if(id != nil && !valistype(left)){ + nerror(n, expconv(left)+" is not a type"); + return (0, 0); + } + } + if(id == nil){ + id = namedot(t.polys, right.decl.sym); + if(id != nil && !valistype(left)){ + nerror(n, expconv(left)+" is not a type"); + return (0, 0); + } + } + if(id == nil && t.kind == Tadtpick) + id = namedot(t.decl.dot.ty.ids, right.decl.sym); + if(id == nil){ + for(tg = t.tags; tg != nil; tg = tg.next){ + id = namedot(tg.ty.ids, right.decl.sym); + if(id != nil) + break; + } + if(id != nil){ + nerror(n, "cannot yet index field "+right.decl.sym.name+" of "+etconv(left)); + return (0, 0); + } + } + if(id == nil) + break; + if(id.store == Dfield && valistype(left)){ + nerror(n, expconv(left)+" is not a value"); + return (0, 0); + } + id.ty = validtype(id.ty, t.decl); + id.ty = usetype(id.ty); + break; + * => + nerror(left, etconv(left)+" cannot be qualified with ."); + return (0, 0); + } + if(id == nil){ + nerror(n, expconv(right)+" is not a member of "+etconv(left)); + return (0, 0); + } + if(id.ty == tunknown){ + nerror(n, "illegal forward reference to "+expconv(n)); + return (0, 0); + } + + increfs(id); + right.decl = id; + n.ty = id.ty; + if((id.store == Dconst || id.store == Dtag) && hasside(left, 1)) + nwarn(left, "result of expression "+etconv(left)+" ignored"); + # function name as function reference + if(id.store == Dfn && (par == nil || (par.op != Omdot && par.op != Ocall && par.op != Ofunc))) + fnref(n, id); + Omdot => + t = left.ty; + if(t.kind != Tmodule){ + nerror(left, etconv(left)+" cannot be qualified with ->"); + return (0, 0); + } + id = nil; + if(right.op == Oname){ + id = namedot(t.ids, right.decl.sym); + }else if(right.op == Odot){ + (ok, kidsok) = echeck(right, 0, isglobal, n); + allok &= kidsok; + if(!ok) + return (0, 0); + tt = right.left.ty; + if(tt.kind == Tref) + tt = tt.tof; + if(right.ty.kind == Tfn + && tt.kind == Tadt + && tt.decl.dot == t.decl) + id = right.right.decl; + } + if(id == nil){ + nerror(n, expconv(right)+" is not a member of "+etconv(left)); + return (0, 0); + } + if(id.store != Dconst && id.store != Dtype && id.store != Dtag){ + if(valistype(left)){ + nerror(n, expconv(left)+" is not a value"); + return (0, 0); + } + }else if(hasside(left, 1)) + nwarn(left, "result of expression "+etconv(left)+" ignored"); + if(!typeok && id.store == Dtype){ + nerror(n, expconv(n)+" is a type, not a value"); + return (0, 0); + } + if(id.ty == tunknown){ + nerror(n, "illegal forward reference to "+expconv(n)); + return (0, 0); + } + id.refs++; + right.decl = id; + n.ty = id.ty = usetype(id.ty); + if(id.store == Dglobal) + modrefable(id.ty); + # function name as function reference + if(id.store == Dfn && (par == nil || (par.op != Ocall && par.op != Ofunc))) + fnref(n, id); + Otagof => + n.ty = tint; + t = left.ty; + if(t.kind == Tref) + t = t.tof; + id = nil; + case left.op{ + Oname => + id = left.decl; + Odot => + id = left.right.decl; + Omdot => + if(left.right.op == Odot) + id = left.right.right.decl; + } + if(id != nil && id.store == Dtag + || id != nil && id.store == Dtype && t.kind == Tadt && t.tags != nil) + n.decl = id; + else if(t.kind == Tadt && t.tags != nil || t.kind == Tadtpick) + n.decl = nil; + else{ + nerror(n, "cannot get the tag value for "+etconv(left)); + return (1, 0); + } + Oind => + t = left.ty; + if(t.kind != Tref || (t.tof.kind != Tadt && t.tof.kind != Tadtpick && t.tof.kind != Ttuple)){ + nerror(n, "cannot * "+etconv(left)); + return (0, 0); + } + n.ty = t.tof; + for(tg = t.tof.tags; tg != nil; tg = tg.next) + tg.ty.tof = usetype(tg.ty.tof); + Oindex => + if(valistype(left)){ + tagopt = 1; + (nil, kidsok) = echeck(right, 2, isglobal, n); + tagopt = 0; + if(!kidsok) + return (0, 0); + if((t = exptotype(n)) == nil){ + nerror(n, expconv(right) + " is not a type list"); + return (0, 0); + } + if(!typeok){ + nerror(n, expconv(left) + " is not a variable"); + return (0, 0); + } + *n = *(n.left); + n.ty = usetype(t); + break; + } + if(0 && right.op == Oseq){ # a[e1, e2, ...] + # array creation to do before we allow this + rewind(n); + return echeck(n, typeok, isglobal, par); + } + t = left.ty; + (nil, kidsok) = echeck(right, 0, isglobal, n); + if(t.kind != Tarray && t != tstring){ + nerror(n, "cannot index "+etconv(left)); + return (0, 0); + } + if(t == tstring){ + n.op = Oinds; + n.ty = tint; + }else{ + n.ty = t.tof; + } + if(!kidsok){ + allok = 0; + break; + } + if(right.ty != tint){ + nerror(n, "cannot index "+etconv(left)+" with "+etconv(right)); + allok = 0; + break; + } + Oslice => + t = n.ty = left.ty; + if(t.kind != Tarray && t != tstring){ + nerror(n, "cannot slice "+etconv(left)+" with '"+subexpconv(right.left)+":"+subexpconv(right.right)+"'"); + return (0, 0); + } + if(right.left.ty != tint && right.left.op != Onothing + || right.right.ty != tint && right.right.op != Onothing){ + nerror(n, "cannot slice "+etconv(left)+" with '"+subexpconv(right.left)+":"+subexpconv(right.right)+"'"); + return (1, 0); + } + Olen => + t = left.ty; + n.ty = tint; + if(t.kind != Tarray && t.kind != Tlist && t != tstring){ + nerror(n, "len requires an array, string or list in "+etconv(left)); + return (1, 0); + } + Ocomp or + Onot or + Oneg => + n.ty = left.ty; +usedty(n.ty); + case left.ty.kind{ + Tint => + return (1, allok); + Treal or + Tfix => + if(n.op == Oneg) + return (1, allok); + Tbig or + Tbyte => + if(n.op == Oneg || n.op == Ocomp) + return (1, allok); + } + nerror(n, "cannot apply "+opconv(n.op)+" to "+etconv(left)); + return (0, 0); + Oinc or + Odec or + Opreinc or + Opredec => + n.ty = left.ty; + case left.ty.kind{ + Tint or + Tbig or + Tbyte or + Treal => + break; + * => + nerror(n, "cannot apply "+opconv(n.op)+" to "+etconv(left)); + return (0, 0); + } + if(islval(left)) + break; + return(0, 0); + Oadd or + Odiv or + Omul or + Osub => + if(mathchk(n, 1)) + break; + return (0, 0); + Oexp or + Oexpas => + n.ty = left.ty; + if(n.ty != tint && n.ty != tbig && n.ty != treal){ + nerror(n, "exponend " + etconv(left) + " is not int or real"); + return (0, 0); + } + if(right.ty != tint){ + nerror(n, "exponent " + etconv(right) + " is not int"); + return (0, 0); + } + if(n.op == Oexpas && !islval(left)) + return (0, 0); + break; + # if(mathchk(n, 0)){ + # if(n.ty != tint){ + # nerror(n, "exponentiation operands not int"); + # return (0, 0); + # } + # break; + # } + # return (0, 0); + Olsh or + Orsh => + if(shiftchk(n)) + break; + return (0, 0); + Oandand or + Ooror => + if(left.ty != tint){ + nerror(n, opconv(n.op)+"'s left operand is not an int: "+etconv(left)); + allok = 0; + } + if(right.ty != tint){ + nerror(n, opconv(n.op)+"'s right operand is not an int: "+etconv(right)); + allok = 0; + } + n.ty = tint; + Oand or + Omod or + Oor or + Oxor => + if(mathchk(n, 0)) + break; + return (0, 0); + Oaddas or + Odivas or + Omulas or + Osubas => + if(mathchk(n, 1) && islval(left)) + break; + return (0, 0); + Olshas or + Orshas => + if(shiftchk(n) && islval(left)) + break; + return (0, 0); + Oandas or + Omodas or + Oxoras or + Ooras => + if(mathchk(n, 0) && islval(left)) + break; + return (0, 0); + Olt or + Oleq or + Ogt or + Ogeq => + if(!mathchk(n, 1)) + return (0, 0); + n.ty = tint; + Oeq or + Oneq => + case left.ty.kind{ + Tint or + Tbig or + Tbyte or + Treal or + Tstring or + Tref or + Tlist or + Tarray or + Tchan or + Tany or + Tmodule or + Tfix or + Tpoly => + if(!tcompat(left.ty, right.ty, 0) && !tcompat(right.ty, left.ty, 0)) + break; + t = left.ty; + if(t == tany) + t = right.ty; + if(t == tany) + t = tint; + if(left.ty == tany) + left.ty = t; + if(right.ty == tany) + right.ty = t; + n.ty = tint; +usedty(n.ty); + return (1, allok); + } + nerror(n, "cannot compare "+etconv(left)+" to "+etconv(right)); + return (0, 0); + Otype => + if(!typeok){ + nerror(n, expconv(n) + " is not a variable"); + return (0, 0); + } + n.ty = usetype(n.ty); + * => + fatal("unknown op in typecheck: "+opconv(n.op)); + } +usedty(n.ty); + return (1, allok); +} + +# +# n is syntactically a call, but n.left is not a fn +# check if it's the contructor for an adt +# +callcast(n: ref Node, kidsok, allok: int): (int, int) +{ + id: ref Decl; + + left := n.left; + right := n.right; + id = nil; + case left.op{ + Oname => + id = left.decl; + Omdot => + if(left.right.op == Odot) + id = left.right.right.decl; + else + id = left.right.decl; + Odot => + id = left.right.decl; + } + if(id == nil || (id.store != Dtype && id.store != Dtag && id.ty.kind != Texception)){ + nerror(left, expconv(left)+" is not a function or type name"); + return (0, 0); + } + if(id.store == Dtag) + return tagcast(n, left, right, id, kidsok, allok); + t := left.ty; + n.ty = t; + if(!kidsok) + return (1, 0); + + if(t.kind == Tref) + t = t.tof; + tt := mktype(n.src.start, n.src.stop, Ttuple, nil, tuplefields(right)); + if(t.kind == Tadt && tcompat(t, tt, 1)){ + if(right == nil) + *n = *n.left; + return (1, allok); + } + + # try an exception with args + tt = mktype(n.src.start, n.src.stop, Texception, nil, tuplefields(right)); + tt.cons = byte 1; + if(t.kind == Texception && t.cons == byte 1 && tcompat(t, tt, 1)){ + if(right == nil) + *n = *n.left; + return (1, allok); + } + + # try a cast + if(t.kind != Texception && right != nil && right.right == nil){ # Oseq but single expression + right = right.left; + n.op = Ocast; + n.left = right; + n.right = nil; + n.ty = mkidtype(n.src, id.sym); + return echeck(n, 0, 0, nil); + } + + nerror(left, "cannot make a "+expconv(left)+" from '("+subexpconv(right)+")'"); + return (0, 0); +} + +tagcast(n, left, right: ref Node, id: ref Decl, kidsok, allok: int): (int, int) +{ + left.ty = id.ty; + if(left.op == Omdot) + left.right.ty = id.ty; + n.ty = id.ty; + if(!kidsok) + return (1, 0); + id.ty.tof = usetype(id.ty.tof); + if(right != nil) + right.ty = id.ty.tof; + tt := mktype(n.src.start, n.src.stop, Ttuple, nil, mkids(nosrc, nil, tint, tuplefields(right))); + tt.ids.store = Dfield; + if(tcompat(id.ty.tof, tt, 1)) + return (1, allok); + + nerror(left, "cannot make a "+expconv(left)+" from '("+subexpconv(right)+")'"); + return (0, 0); +} + +valistype(n: ref Node): int +{ + case n.op{ + Oname => + if(n.decl.store == Dtype) + return 1; + Omdot => + return valistype(n.right); + } + return 0; +} + +islval(n: ref Node): int +{ + s := marklval(n); + if(s == 1) + return 1; + if(s == 0) + nerror(n, "cannot assign to "+expconv(n)); + else + circlval(n, n); + return 0; +} + +# +# check to see if n is an lval +# +marklval(n: ref Node): int +{ + if(n == nil) + return 0; + case n.op{ + Oname => + return storespace[n.decl.store] && n.ty.kind != Texception; #ZZZZ && n.decl.tagged == nil; + Odot => + if(n.right.decl.store != Dfield) + return 0; + if(n.right.decl.cycle != byte 0 && n.right.decl.cyc == byte 0) + return -1; + if(n.left.ty.kind != Tref && marklval(n.left) == 0) + nwarn(n, "assignment to "+etconv(n)+" ignored"); + return 1; + Omdot => + if(n.right.decl.store == Dglobal) + return 1; + return 0; + Oind => + for(id := n.ty.ids; id != nil; id = id.next) + if(id.cycle != byte 0 && id.cyc == byte 0) + return -1; + return 1; + Oslice => + if(n.right.right.op != Onothing || n.ty == tstring) + return 0; + return 1; + Oinds => + # + # make sure we don't change a string constant + # + case n.left.op{ + Oconst => + return 0; + Oname => + return storespace[n.left.decl.store]; + Odot or + Omdot => + if(n.left.right.decl != nil) + return storespace[n.left.right.decl.store]; + } + return 1; + Oindex or + Oindx => + return 1; + Otuple => + for(nn := n.left; nn != nil; nn = nn.right){ + s := marklval(nn.left); + if(s != 1) + return s; + } + return 1; + * => + return 0; + } + return 0; +} + +# +# n has a circular field assignment. +# find it and print an error message. +# +circlval(n, lval: ref Node): int +{ + if(n == nil) + return 0; + case n.op{ + Oname => + break; + Odot => + if(n.right.decl.cycle != byte 0 && n.right.decl.cyc == byte 0){ + nerror(lval, "cannot assign to "+expconv(lval)+" because field '"+n.right.decl.sym.name + +"' of "+expconv(n.left)+" could complete a cycle to "+expconv(n.left)); + return -1; + } + return 1; + Oind => + for(id := n.ty.ids; id != nil; id = id.next){ + if(id.cycle != byte 0 && id.cyc == byte 0){ + nerror(lval, "cannot assign to "+expconv(lval)+" because field '"+id.sym.name + +"' of "+expconv(n)+" could complete a cycle to "+expconv(n)); + return -1; + } + } + return 1; + Oslice => + if(n.right.right.op != Onothing || n.ty == tstring) + return 0; + return 1; + Oindex or + Oinds or + Oindx => + return 1; + Otuple => + for(nn := n.left; nn != nil; nn = nn.right){ + s := circlval(nn.left, lval); + if(s != 1) + return s; + } + return 1; + * => + return 0; + } + return 0; +} + +mathchk(n: ref Node, realok: int): int +{ + lt := n.left.ty; + rt := n.right.ty; + if(rt != lt && !tequal(lt, rt)){ + nerror(n, "type clash in "+etconv(n.left)+" "+opconv(n.op)+" "+etconv(n.right)); + return 0; + } + n.ty = rt; + case rt.kind{ + Tint or + Tbig or + Tbyte => + return 1; + Tstring => + case n.op{ + Oadd or + Oaddas or + Ogt or + Ogeq or + Olt or + Oleq => + return 1; + } + Treal or + Tfix => + if(realok) + return 1; + } + nerror(n, "cannot "+opconv(n.op)+" "+etconv(n.left)+" and "+etconv(n.right)); + return 0; +} + +shiftchk(n: ref Node): int +{ + right := n.right; + left := n.left; + n.ty = left.ty; + case n.ty.kind{ + Tint or + Tbyte or + Tbig => + if(right.ty.kind != Tint){ + nerror(n, "shift "+etconv(right)+" is not an int"); + return 0; + } + return 1; + } + nerror(n, "cannot "+opconv(n.op)+" "+etconv(left)+" by "+etconv(right)); + return 0; +} + +# +# check for any tany's in t +# +specific(t: ref Type): int +{ + if(t == nil) + return 0; + case t.kind{ + Terror or + Tnone or + Tint or + Tbig or + Tstring or + Tbyte or + Treal or + Tfn or + Tadt or + Tadtpick or + Tmodule or + Tfix => + return 1; + Tany => + return 0; + Tpoly => + return 1; + Tref or + Tlist or + Tarray or + Tchan => + return specific(t.tof); + Ttuple or + Texception => + for(d := t.ids; d != nil; d = d.next) + if(!specific(d.ty)) + return 0; + return 1; + } + fatal("unknown type in specific: "+typeconv(t)); + return 0; +} + +# +# infer the type of all variable in n from t +# n is the left-hand exp of a := exp +# +declasinfer(n: ref Node, t: ref Type): int +{ + if(t.kind == Texception){ + if(int t.cons) + return 0; + t = mkextuptype(t); + } + case n.op{ + Otuple => + if(t.kind != Ttuple && t.kind != Tadt && t.kind != Tadtpick) + return 0; + ok := 1; + n.ty = t; + n = n.left; + ids := t.ids; + if(t.kind == Tadtpick) + ids = t.tof.ids.next; + for(; n != nil && ids != nil; ids = ids.next){ + if(ids.store != Dfield) + continue; + ok &= declasinfer(n.left, ids.ty); + n = n.right; + } + for(; ids != nil; ids = ids.next) + if(ids.store == Dfield) + break; + if(n != nil || ids != nil) + return 0; + return ok; + Oname => + topvartype(t, n.decl, 0, 0); + if(n.decl == nildecl) + return 1; + n.decl.ty = t; + n.ty = t; + shareloc(n.decl); + return 1; + } + fatal("unknown op in declasinfer: "+nodeconv(n)); + return 0; +} + +# +# an error occured in declaring n; +# set all decl identifiers to Dwundef +# so further errors are squashed. +# +declaserr(n: ref Node) +{ + case n.op{ + Otuple => + for(n = n.left; n != nil; n = n.right) + declaserr(n.left); + return; + Oname => + if(n.decl != nildecl) + n.decl.store = Dwundef; + return; + } + fatal("unknown op in declaserr: "+nodeconv(n)); +} + +argcompat(n: ref Node, f: ref Decl, a: ref Node): int +{ + for(; a != nil; a = a.right){ + if(f == nil){ + nerror(n, expconv(n.left)+": too many function arguments"); + return 0; + } + if(!tcompat(f.ty, a.left.ty, 0)){ + nerror(n, expconv(n.left)+": argument type mismatch: expected "+typeconv(f.ty)+" saw "+etconv(a.left)); + return 0; + } + if(a.left.ty == tany) + a.left.ty = f.ty; + f = f.next; + } + if(f != nil){ + nerror(n, expconv(n.left)+": too few function arguments"); + return 0; + } + return 1; +} + +argncompat(n: ref Node, f: ref Decl, a: ref Node): int +{ + for(; a != nil; a = a.right){ + if(f == nil){ + nerror(n, expconv(n.left)+": too many function arguments"); + return 0; + } + f = f.next; + } + if(f != nil){ + nerror(n, expconv(n.left)+": too few function arguments"); + return 0; + } + return 1; +} + +# +# fn is Odot(adt, methid) +# pass adt implicitly if needed +# if not, any side effect of adt will be ingored +# +passimplicit(fname, args: ref Node): ref Node +{ + t := fname.ty; + if(t.ids == nil || t.ids.implicit == byte 0){ + if(hasside(fname.left, 1)) + nwarn(fname, "result of expression "+expconv(fname.left)+" ignored"); + return args; + } + n := fname.left; + if(n.op == Oname && n.decl.store == Dtype){ + nerror(n, expconv(n)+" is a type and cannot be a self argument"); + n = mkn(Onothing, nil, nil); + n.src = fname.src; + n.ty = t.ids.ty; + } + args = mkn(Oseq, n, args); + args.src = n.src; + return args; +} + +mem(t: ref Type, d: ref Decl): int +{ + for( ; d != nil; d = d.next) + if(d.ty == t) # was if(d.ty == t || tequal(d.ty, t)) + return 1; + return 0; +} + +memp(t: ref Type, f: ref Decl): int +{ + return mem(t, f.ty.polys) || mem(t, encpolys(f)); +} + +passfns0(src: Src, fun: ref Decl, args0: ref Node, args: ref Node, a: ref Node, tp: ref Tpair, polys: ref Decl): (ref Node, ref Node) +{ + id, idt, idf: ref Decl; + sym: ref Sym; + tt: ref Type; + na, mod: ref Node; + + for(idt = polys; idt != nil; idt = idt.next){ + tt = valtmap(idt.ty, tp); + if(tt.kind == Tpoly && fndec != nil && !memp(tt, fndec)) + error(src.start, "cannot determine the instantiated type of " + typeconv(tt)); + for(idf = idt.ty.ids; idf != nil; idf = idf.next){ + sym = idf.sym; + (id, mod) = fnlookup(sym, tt); + while(id != nil && id.link != nil) + id = id.link; + if(id == nil) # error flagged already + continue; + id.refs++; + id.inline = byte -1; + if(tt.kind == Tmodule){ # mod an actual parameter + for(;;){ + if(args0 != nil && tequal(tt, args0.left.ty)){ + mod = args0.left; + break; + } + if(args0 != nil) + args0 = args0.right; + } + } + if(mod == nil && (dot := lmodule(id)) != nil && !isimpmod(dot.sym)) + error(src.start, "cannot use " + id.sym.name + " without importing " + id.dot.sym.name + " from a variable"); + + n := mkn(Ofnptr, mod, mkdeclname(src, id)); + n.src = src; + n.decl = fun; + if(tt.kind == Tpoly) + n.flags = byte FNPTRA; + else + n.flags = byte 0; + na = mkn(Oseq, n, nil); + if(a == nil) + args = na; + else + a.right = na; + + n = mkn(Ofnptr, mod, mkdeclname(src, id)); + n.src = src; + n.decl = fun; + if(tt.kind == Tpoly) + n.flags = byte (FNPTRA|FNPTR2); + else + n.flags = byte FNPTR2; + a = na.right = mkn(Oseq, n, nil); + } + if(args0 != nil) + args0 = args0.right; + } + return (args, a); +} + +passfns(src: Src, fun: ref Decl, left: ref Node, args: ref Node, adtt: ref Type, tp: ref Tpair): ref Node +{ + a, args0: ref Node; + + a = nil; + args0 = args; + if(args != nil) + for(a = args; a.right != nil; a = a.right) + ; + if(ispoly(fun)) + polys := fun.ty.polys; + else + polys = left.ty.tof.polys; + (args, a) = passfns0(src, fun, args0, args, a, tp, polys); + if(adtt != nil){ + if(ispoly(fun)) + polys = encpolys(fun); + else + polys = nil; + (args, a) = passfns0(src, fun, args0, args, a, adtt.tmap, polys); + } + return args; +} + +# +# check the types for a function with a variable number of arguments +# last typed argument must be a constant string, and must use the +# print format for describing arguments. +# +mkvarargs(n, args: ref Node): ref Type +{ + last: ref Decl; + + nt := copytypeids(n.ty); + n.ty = nt; + f := n.ty.ids; + last = nil; + if(f == nil){ + nerror(n, expconv(n)+"'s type is illegal"); + return nt; + } + s := args; + for(a := args; a != nil; a = a.right){ + if(f == nil) + break; + if(!tcompat(f.ty, a.left.ty, 0)){ + nerror(n, expconv(n)+": argument type mismatch: expected "+typeconv(f.ty)+" saw "+etconv(a.left)); + return nt; + } + if(a.left.ty == tany) + a.left.ty = f.ty; + last = f; + f = f.next; + s = a; + } + if(f != nil){ + nerror(n, expconv(n)+": too few function arguments"); + return nt; + } + s.left = fold(s.left); + s = s.left; + if(s.ty != tstring || s.op != Oconst){ + nerror(args, expconv(n)+": format argument "+etconv(s)+" is not a string constant"); + return nt; + } + fmtcheck(n, s, a); + va := tuplefields(a); + if(last == nil) + nt.ids = va; + else + last.next = va; + return nt; +} + +# +# check that a print style format string matches it's arguments +# +fmtcheck(f, fmtarg, va: ref Node) +{ + fmt := fmtarg.decl.sym; + s := fmt.name; + ns := 0; + while(ns < len s){ + c := s[ns++]; + if(c != '%') + continue; + + verb := -1; + n1 := 0; + n2 := 0; + dot := 0; + flag := 0; + flags := ""; + fmtstart := ns - 1; + while(ns < len s && verb < 0){ + c = s[ns++]; + case c{ + * => + nerror(f, expconv(f)+": invalid character "+s[ns-1:ns]+" in format '"+s[fmtstart:ns]+"'"); + return; + '.' => + if(dot){ + nerror(f, expconv(f)+": invalid format '"+s[fmtstart:ns]+"'"); + return; + } + n1 = 1; + dot = 1; + continue; + '*' => + if(!n1) + n1 = 1; + else if(!n2 && dot) + n2 = 1; + else{ + nerror(f, expconv(f)+": invalid format '"+s[fmtstart:ns]+"'"); + return; + } + if(va == nil){ + nerror(f, expconv(f)+": too few arguments for format '"+s[fmtstart:ns]+"'"); + return; + } + if(va.left.ty.kind != Tint){ + nerror(f, expconv(f)+": format '"+s[fmtstart:ns]+"' incompatible with argument "+etconv(va.left)); + return; + } + va = va.right; + '0' to '9' => + while(ns < len s && s[ns] >= '0' && s[ns] <= '9') + ns++; + if(!n1) + n1 = 1; + else if(!n2 && dot) + n2 = 1; + else{ + nerror(f, expconv(f)+": invalid format '"+s[fmtstart:ns]+"'"); + return; + } + '+' or + '-' or + '#' or + ',' or + 'b' or + 'u' => + for(i := 0; i < flag; i++){ + if(flags[i] == c){ + nerror(f, expconv(f)+": duplicate flag "+s[ns-1:ns]+" in format '"+s[fmtstart:ns]+"'"); + return; + } + } + flags[flag++] = c; + '%' or + 'r' => + verb = Tnone; + 'H' => + verb = Tany; + 'c' => + verb = Tint; + 'd' or + 'o' or + 'x' or + 'X' => + verb = Tint; + for(i := 0; i < flag; i++){ + if(flags[i] == 'b'){ + verb = Tbig; + break; + } + } + 'e' or + 'f' or + 'g' or + 'E' or + 'G' => + verb = Treal; + 's' or + 'q' => + verb = Tstring; + } + } + if(verb != Tnone){ + if(verb < 0){ + nerror(f, expconv(f)+": incomplete format '"+s[fmtstart:ns]+"'"); + return; + } + if(va == nil){ + nerror(f, expconv(f)+": too few arguments for format '"+s[fmtstart:ns]+"'"); + return; + } + ty := va.left.ty; + if(ty.kind == Texception) + ty = mkextuptype(ty); + case verb{ + Tint => + case ty.kind{ + Tstring or + Tarray or + Tref or + Tchan or + Tlist or + Tmodule => + if(c == 'x' || c == 'X') + verb = ty.kind; + } + Tany => + if(tattr[ty.kind].isptr) + verb = ty.kind; + } + if(verb != ty.kind){ + nerror(f, expconv(f)+": format '"+s[fmtstart:ns]+"' incompatible with argument "+etconv(va.left)); + return; + } + va = va.right; + } + } + if(va != nil) + nerror(f, expconv(f)+": more arguments than formats"); +} + +tuplefields(n: ref Node): ref Decl +{ + h, last: ref Decl; + + for(; n != nil; n = n.right){ + d := mkdecl(n.left.src, Dfield, n.left.ty); + if(h == nil) + h = d; + else + last.next = d; + last = d; + } + return h; +} + +# +# make explicit indices for every element in an array initializer +# return the maximum index +# sort the indices and check for duplicates +# +assignindices(ar: ref Node): int +{ + wild, off, q: ref Node; + + amax := 16r7fffffff; + size := dupn(0, nosrc, ar.left); + if(size.ty == tint){ + size = fold(size); + if(size.op == Oconst) + amax = int size.c.val; + } + + inits := ar.right; + max := -1; + last := -1; + t := inits.left.ty; + wild = nil; + nlab := 0; + ok := 1; + for(n := inits; n != nil; n = n.right){ + if(!tcompat(t, n.left.ty, 0)){ + t = tparent(t, n.left.ty); + if(!tcompat(t, n.left.ty, 0)){ + nerror(n.left, "inconsistent types "+typeconv(t)+" and "+typeconv(n.left.ty)+" in array initializer"); + return -1; + } + else + inits.left.ty = t; + } + if(t == tany) + t = n.left.ty; + + # + # make up an index if there isn't one + # + if(n.left.left == nil) + n.left.left = mkn(Oseq, mkconst(n.left.right.src, big(last + 1)), nil); + + for(q = n.left.left; q != nil; q = q.right){ + off = q.left; + if(off.ty != tint){ + nerror(off, "array index "+etconv(off)+" is not an int"); + ok = 0; + continue; + } + off = fold(off); + case off.op{ + Owild => + if(wild != nil) + nerror(off, "array index * duplicated on line "+lineconv(wild.src.start)); + wild = off; + continue; + Orange => + if(off.left.op != Oconst || off.right.op != Oconst){ + nerror(off, "range "+expconv(off)+" is not constant"); + off = nil; + }else if(off.left.c.val < big 0 || off.right.c.val >= big amax){ + nerror(off, "array index "+expconv(off)+" out of bounds"); + off = nil; + }else + last = int off.right.c.val; + Oconst => + last = int off.c.val; + if(off.c.val < big 0 || off.c.val >= big amax){ + nerror(off, "array index "+expconv(off)+" out of bounds"); + off = nil; + } + Onothing => + # get here from a syntax error + off = nil; + * => + nerror(off, "array index "+expconv(off)+" is not constant"); + off = nil; + } + + nlab++; + if(off == nil){ + off = mkconst(n.left.right.src, big(last)); + ok = 0; + } + if(last > max) + max = last; + q.left = off; + } + } + + # + # fix up types of nil elements + # + for(n = inits; n != nil; n = n.right) + if(n.left.ty == tany) + n.left.ty = t; + + if(!ok) + return -1; + + c := checklabels(inits, tint, nlab, "array index"); + t = mktype(inits.src.start, inits.src.stop, Tainit, nil, nil); + inits.ty = t; + t.cse = c; + + return max + 1; +} + +# +# check the labels of a case statment +# +casecheck(cn: ref Node, ret: ref Type) +{ + wild: ref Node; + + (rok, nil) := echeck(cn.left, 0, 0, nil); + cn.right = scheck(cn.right, ret, Sother); + if(!rok) + return; + arg := cn.left; + + t := arg.ty; + if(t != tint && t != tbig && t != tstring){ + nerror(cn, "case argument "+etconv(arg)+" is not an int or big or string"); + return; + } + + wild = nil; + nlab := 0; + ok := 1; + for(n := cn.right; n != nil; n = n.right){ + q := n.left.left; + if(n.left.right.right == nil) + nwarn(q, "no body for case qualifier "+expconv(q)); + for(; q != nil; q = q.right){ + left := fold(q.left); + q.left = left; + case left.op{ + Owild => + if(wild != nil) + nerror(left, "case qualifier * duplicated on line "+lineconv(wild.src.start)); + wild = left; + Orange => + if(left.ty != t) + nerror(left, "case qualifier "+etconv(left)+" clashes with "+etconv(arg)); + else if(left.left.op != Oconst || left.right.op != Oconst){ + nerror(left, "case range "+expconv(left)+" is not constant"); + ok = 0; + } + nlab++; + * => + if(left.ty != t){ + nerror(left, "case qualifier "+etconv(left)+" clashes with "+etconv(arg)); + ok = 0; + }else if(left.op != Oconst){ + nerror(left, "case qualifier "+expconv(left)+" is not constant"); + ok = 0; + } + nlab++; + } + } + } + + if(!ok) + return; + + c := checklabels(cn.right, t, nlab, "case qualifier"); + op := Tcase; + if(t == tbig) + op = Tcasel; + else if(t == tstring) + op = Tcasec; + t = mktype(cn.src.start, cn.src.stop, op, nil, nil); + cn.ty = t; + t.cse = c; +} + +# +# check the labels and bodies of a pick statment +# +pickcheck(n: ref Node, ret: ref Type) +{ + qs, q, w: ref Node; + + arg := n.left.right; + (nil, allok) := echeck(arg, 0, 0, nil); + if(!allok) + return; + t := arg.ty; + if(t.kind == Tref) + t = t.tof; + if(arg.ty.kind != Tref || t.kind != Tadt || t.tags == nil){ + nerror(arg, "pick argument "+etconv(arg)+" is not a ref adt with pick tags"); + return; + } + argty := usetype(mktype(arg.ty.src.start, arg.ty.src.stop, Tref, t, nil)); + + arg = n.left.left; + pushscope(nil, Sother); + dasdecl(arg); + arg.decl.ty = argty; + arg.ty = argty; + + tags := array[t.decl.tag] of ref Node; + w = nil; + ok := 1; + nlab := 0; + for(qs = n.right; qs != nil; qs = qs.right){ + qt : ref Node = nil; + for(q = qs.left.left; q != nil; q = q.right){ + left := q.left; + case left.op{ + Owild => + # left.ty = tnone; + left.ty = t; + if(w != nil) + nerror(left, "pick qualifier * duplicated on line "+lineconv(w.src.start)); + w = left; + Oname => + id := namedot(t.tags, left.decl.sym); + if(id == nil){ + nerror(left, "pick qualifier "+expconv(left)+" is not a member of "+etconv(arg)); + ok = 0; + continue; + } + + left.decl = id; + left.ty = id.ty; + + if(tags[id.tag] != nil){ + nerror(left, "pick qualifier "+expconv(left)+" duplicated on line "+lineconv(tags[id.tag].src.start)); + ok = 0; + } + tags[id.tag] = left; + nlab++; + * => + fatal("pickcheck can't handle "+nodeconv(q)); + } + + if(qt == nil) + qt = left; + else if(!tequal(qt.ty, left.ty)) + nerror(left, "type clash in pick qualifiers "+etconv(qt)+" and "+etconv(left)); + } + + argty.tof = t; + if(qt != nil) + argty.tof = qt.ty; + qs.left.right = scheck(qs.left.right, ret, Sother); + if(qs.left.right == nil) + nwarn(qs.left.left, "no body for pick qualifier "+expconv(qs.left.left)); + } + argty.tof = t; + for(qs = n.right; qs != nil; qs = qs.right) + for(q = qs.left.left; q != nil; q = q.right) + q.left = fold(q.left); + + d := popscope(); + d.refs++; + if(d.next != nil) + fatal("pickcheck: installing more than one id"); + fndecls = appdecls(fndecls, d); + + if(!ok) + return; + + c := checklabels(n.right, tint, nlab, "pick qualifier"); + t = mktype(n.src.start, n.src.stop, Tcase, nil, nil); + n.ty = t; + t.cse = c; +} + +exccheck(en: ref Node, ret: ref Type) +{ + ed: ref Decl; + wild: ref Node; + qt: ref Type; + + pushscope(nil, Sother); + if(en.left == nil) + en.left = mkdeclname(en.src, mkids(en.src, enter(".ex"+string nexc++, 0), texception, nil)); + oinexcept := inexcept; + inexcept = en.left; + dasdecl(en.left); + en.left.ty = en.left.decl.ty = texception; + ed = en.left.decl; + # en.right = scheck(en.right, ret, Sother); + t := tstring; + wild = nil; + nlab := 0; + ok := 1; + for(n := en.right; n != nil; n = n.right){ + qt = nil; + for(q := n.left.left; q != nil; q = q.right){ + left := q.left; + case left.op{ + Owild => + left.ty = texception; + if(wild != nil) + nerror(left, "exception qualifier * duplicated on line "+lineconv(wild.src.start)); + wild = left; + Orange => + left.ty = tnone; + nerror(left, "exception qualifier "+expconv(left)+" is illegal"); + ok = 0; + * => + (rok, nil) := echeck(left, 0, 0, nil); + if(!rok){ + ok = 0; + break; + } + left = q.left = fold(left); + if(left.ty != t && left.ty.kind != Texception){ + nerror(left, "exception qualifier "+etconv(left)+" is not a string or exception"); + ok = 0; + }else if(left.op != Oconst){ + nerror(left, "exception qualifier "+expconv(left)+" is not constant"); + ok = 0; + } + else if(left.ty != t) + left.ty = mkextype(left.ty); + nlab++; + } + + if(qt == nil) + qt = left.ty; + else if(!tequal(qt, left.ty)) + qt = texception; + } + + if(qt != nil) + ed.ty = qt; + n.left.right = scheck(n.left.right, ret, Sother); + if(n.left.right.right == nil) + nwarn(n.left.left, "no body for exception qualifier " + expconv(n.left.left)); + } + ed.ty = texception; + inexcept = oinexcept; + if(!ok) + return; + c := checklabels(en.right, texception, nlab, "exception qualifier"); + t = mktype(en.src.start, en.src.stop, Texcept, nil, nil); + en.ty = t; + t.cse = c; + ed = popscope(); + fndecls = appdecls(fndecls, ed); +} + +# +# check array and case labels for validity +# +checklabels(inits: ref Node, ctype: ref Type, nlab: int, title: string): ref Case +{ + n, q, wild: ref Node; + + labs := array[nlab] of Label; + i := 0; + wild = nil; + for(n = inits; n != nil; n = n.right){ + for(q = n.left.left; q != nil; q = q.right){ + case q.left.op{ + Oconst => + labs[i].start = q.left; + labs[i].stop = q.left; + labs[i++].node = n.left; + Orange => + labs[i].start = q.left.left; + labs[i].stop = q.left.right; + labs[i++].node = n.left; + Owild => + wild = n.left; + * => + fatal("bogus index in checklabels"); + } + } + } + + if(i != nlab) + fatal("bad label count: "+string nlab+" then "+string i); + + casesort(ctype, array[nlab] of Label, labs, 0, nlab); + for(i = 0; i < nlab; i++){ + p := labs[i].stop; + if(casecmp(ctype, labs[i].start, p) > 0) + nerror(labs[i].start, "unmatchable "+title+" "+expconv(labs[i].node)); + for(e := i + 1; e < nlab; e++){ + if(casecmp(ctype, labs[e].start, p) <= 0) + nerror(labs[e].start, title+" '"+eprintlist(labs[e].node.left, " or ") + +"' overlaps with '"+eprintlist(labs[e-1].node.left, " or ")+"' on line " + +lineconv(p.src.start)); + + # + # check for merging case labels + # + if(ctype != tint + || labs[e].start.c.val != p.c.val+big 1 + || labs[e].node != labs[i].node) + break; + p = labs[e].stop; + } + if(e != i + 1){ + labs[i].stop = p; + labs[i+1:] = labs[e:nlab]; + nlab -= e - (i + 1); + } + } + + c := ref Case; + c.nlab = nlab; + c.nsnd = 0; + c.labs = labs; + c.wild = wild; + + return c; +} + +symcmp(a: ref Sym, b: ref Sym): int +{ + if(a.name < b.name) + return -1; + if(a.name > b.name) + return 1; + return 0; +} + +matchcmp(na: ref Node, nb: ref Node): int +{ + a := na.decl.sym; + b := nb.decl.sym; + la := len a.name; + lb := len b.name; + sa := la > 0 && a.name[la-1] == '*'; + sb := lb > 0 && b.name[lb-1] == '*'; + if(sa){ + if(sb){ + if(la == lb) + return symcmp(a, b); + return lb-la; + } + else + return 1; + } + else{ + if(sb) + return -1; + else{ + if(na.ty == tstring){ + if(nb.ty == tstring) + return symcmp(a, b); + else + return 1; + } + else{ + if(nb.ty == tstring) + return -1; + else + return symcmp(a, b); + } + } + } +} + +casecmp(ty: ref Type, a, b: ref Node): int +{ + if(ty == tint || ty == tbig){ + if(a.c.val < b.c.val) + return -1; + if(a.c.val > b.c.val) + return 1; + return 0; + } + if(ty == texception) + return matchcmp(a, b); + return symcmp(a.decl.sym, b.decl.sym); +} + +casesort(t: ref Type, aux, labs: array of Label, start, stop: int) +{ + n := stop - start; + if(n <= 1) + return; + top := mid := start + n / 2; + + casesort(t, aux, labs, start, top); + casesort(t, aux, labs, mid, stop); + + # + # merge together two sorted label arrays, yielding a sorted array + # + n = 0; + base := start; + while(base < top && mid < stop){ + if(casecmp(t, labs[base].start, labs[mid].start) <= 0) + aux[n++] = labs[base++]; + else + aux[n++] = labs[mid++]; + } + if(base < top) + aux[n:] = labs[base:top]; + else if(mid < stop) + aux[n:] = labs[mid:stop]; + labs[start:] = aux[:stop-start]; +} + +# +# binary search for the label corresponding to a given value +# +findlab(ty: ref Type, v: ref Node, labs: array of Label, nlab: int): int +{ + if(nlab <= 1) + return 0; + m : int; + l := 1; + r := nlab - 1; + while(l <= r){ + m = (r + l) / 2; + if(casecmp(ty, labs[m].start, v) <= 0) + l = m + 1; + else + r = m - 1; + } + m = l - 1; + if(casecmp(ty, labs[m].start, v) > 0 + || casecmp(ty, labs[m].stop, v) < 0) + fatal("findlab out of range"); + return m; +} + +altcheck(an: ref Node, ret: ref Type) +{ + n, q, left, op, wild: ref Node; + + an.left = scheck(an.left, ret, Sother); + + ok := 1; + nsnd := 0; + nrcv := 0; + wild = nil; + for(n = an.left; n != nil; n = n.right){ + q = n.left.right.left; + if(n.left.right.right == nil) + nwarn(q, "no body for alt guard "+expconv(q)); + for(; q != nil; q = q.right){ + left = q.left; + case left.op{ + Owild => + if(wild != nil) + nerror(left, "alt guard * duplicated on line "+lineconv(wild.src.start)); + wild = left; + Orange => + nerror(left, "alt guard "+expconv(left)+" is illegal"); + ok = 0; + * => + op = hascomm(left); + if(op == nil){ + nerror(left, "alt guard "+expconv(left)+" has no communication"); + ok = 0; + break; + } + if(op.op == Osnd) + nsnd++; + else + nrcv++; + } + } + } + + if(!ok) + return; + + c := ref Case; + c.nlab = nsnd + nrcv; + c.nsnd = nsnd; + c.wild = wild; + + an.ty = mktalt(c); +} + +hascomm(n: ref Node): ref Node +{ + if(n == nil) + return nil; + if(n.op == Osnd || n.op == Orcv) + return n; + r := hascomm(n.left); + if(r != nil) + return r; + return hascomm(n.right); +} + +raisescheck(t: ref Type) +{ + if(t.kind != Tfn) + return; + n := t.eraises; + for(nn := n.left; nn != nil; nn = nn.right){ + (ok, nil) := echeck(nn.left, 0, 0, nil); + if(ok && nn.left.ty.kind != Texception) + nerror(n, expconv(nn.left) + ": illegal raises expression"); + } +} + +Elist: adt{ + d: ref Decl; + nxt: cyclic ref Elist; +}; + +emerge(el1: ref Elist, el2: ref Elist): ref Elist +{ + f: int; + el, nxt: ref Elist; + + for( ; el1 != nil; el1 = nxt){ + f = 0; + for(el = el2; el != nil; el = el.nxt){ + if(el1.d == el.d){ + f = 1; + break; + } + } + nxt = el1.nxt; + if(!f){ + el1.nxt = el2; + el2 = el1; + } + } + return el2; +} + +equals(n: ref Node): ref Elist +{ + q, nn: ref Node; + e, el: ref Elist; + + el = nil; + for(q = n.left.left; q != nil; q = q.right){ + nn = q.left; + if(nn.op == Owild) + return nil; + if(nn.ty.kind != Texception) + continue; + e = ref Elist(nn.decl, el); + el = e; + } + return el; +} + +caught(d: ref Decl, n: ref Node): int +{ + q, nn: ref Node; + + for(n = n.right; n != nil; n = n.right){ + for(q = n.left.left; q != nil; q = q.right){ + nn = q.left; + if(nn.op == Owild) + return 1; + if(nn.ty.kind != Texception) + continue; + if(d == nn.decl) + return 1; + } + } + return 0; +} + +raisecheck(n: ref Node, ql: ref Elist): ref Elist +{ + exc: int; + e: ref Node; + el, nel, nxt: ref Elist; + + if(n == nil) + return nil; + el = nil; + for(; n != nil; n = n.right){ + case(n.op){ + Oscope => + return raisecheck(n.right, ql); + Olabel or + Odo => + return raisecheck(n.right, ql); + Oif or + Ofor => + return emerge(raisecheck(n.right.left, ql), + raisecheck(n.right.right, ql)); + Oalt or + Ocase or + Opick or + Oexcept => + exc = n.op == Oexcept; + for(n = n.right; n != nil; n = n.right){ + ql = nil; + if(exc) + ql = equals(n); + el = emerge(raisecheck(n.left.right, ql), el); + } + return el; + Oseq => + el = emerge(raisecheck(n.left, ql), el); + break; + Oexstmt => + el = raisecheck(n.left, ql); + nel = nil; + for( ; el != nil; el = nxt){ + nxt = el.nxt; + if(!caught(el.d, n.right)){ + el.nxt = nel; + nel = el; + } + } + return emerge(nel, raisecheck(n.right, ql)); + Oraise => + e = n.left; + if(e.ty != nil && e.ty.kind == Texception){ + if(e.ty.cons == byte 0) + return ql; + if(e.op == Ocall) + e = e.left; + if(e.op == Omdot) + e = e.right; + if(e.op != Oname) + fatal("exception " + nodeconv(e) + " not a name"); + el = ref Elist(e.decl, nil); + return el; + } + return nil; + * => + return nil; + } + } + return el; +} + +checkraises(n: ref Node) +{ + f: int; + d: ref Decl; + e, el: ref Elist; + es, nn: ref Node; + + el = raisecheck(n.right, nil); + es = n.ty.eraises; + if(es != nil){ + for(nn = es.left; nn != nil; nn = nn.right){ + d = nn.left.decl; + f = 0; + for(e = el; e != nil; e = e.nxt){ + if(d == e.d){ + f = 1; + e.d = nil; + break; + } + } + if(!f) + nwarn(n, "function " + expconv(n.left) + " does not raise " + d.sym.name + " but declared"); + } + } + for(e = el; e != nil; e = e.nxt) + if(e.d != nil) + nwarn(n, "function " + expconv(n.left) + " raises " + e.d.sym.name + " but not declared"); +} + +# sort all globals in modules now that we've finished with 'last' pointers +# and before any code generation +# +gsort(n: ref Node) +{ + for(;;){ + if(n == nil) + return; + if(n.op != Oseq) + break; + gsort(n.left); + n = n.right; + } + if(n.op == Omoddecl && int (n.ty.ok & OKverify)){ + n.ty.ids = namesort(n.ty.ids); + sizeids(n.ty.ids, 0); + } +} diff --git a/appl/cmd/limbo/types.b b/appl/cmd/limbo/types.b new file mode 100644 index 00000000..8be8f16d --- /dev/null +++ b/appl/cmd/limbo/types.b @@ -0,0 +1,4234 @@ + +kindname := array [Tend] of +{ + Tnone => "no type", + Tadt => "adt", + Tadtpick => "adt", + Tarray => "array", + Tbig => "big", + Tbyte => "byte", + Tchan => "chan", + Treal => "real", + Tfn => "fn", + Tint => "int", + Tlist => "list", + Tmodule => "module", + Tref => "ref", + Tstring => "string", + Ttuple => "tuple", + Texception => "exception", + Tfix => "fixed point", + Tpoly => "polymorphic", + + Tainit => "array initializers", + Talt => "alt channels", + Tany => "polymorphic type", + Tarrow => "->", + Tcase => "case int labels", + Tcasel => "case big labels", + Tcasec => "case string labels", + Tdot => ".", + Terror => "type error", + Tgoto => "goto labels", + Tid => "id", + Tiface => "module interface", + Texcept => "exception handler table", + Tinst => "instantiated type", +}; + +tattr = array[Tend] of +{ + # isptr refable conable big vis + Tnone => Tattr(0, 0, 0, 0, 0), + Tadt => Tattr(0, 1, 1, 1, 1), + Tadtpick => Tattr(0, 1, 0, 1, 1), + Tarray => Tattr(1, 0, 0, 0, 1), + Tbig => Tattr(0, 0, 1, 1, 1), + Tbyte => Tattr(0, 0, 1, 0, 1), + Tchan => Tattr(1, 0, 0, 0, 1), + Treal => Tattr(0, 0, 1, 1, 1), + Tfn => Tattr(0, 1, 0, 0, 1), + Tint => Tattr(0, 0, 1, 0, 1), + Tlist => Tattr(1, 0, 0, 0, 1), + Tmodule => Tattr(1, 0, 0, 0, 1), + Tref => Tattr(1, 0, 0, 0, 1), + Tstring => Tattr(1, 0, 1, 0, 1), + Ttuple => Tattr(0, 1, 1, 1, 1), + Texception => Tattr(0, 0, 0, 1, 1), + Tfix => Tattr(0, 0, 1, 0, 1), + Tpoly => Tattr(1, 0, 0, 0, 1), + + Tainit => Tattr(0, 0, 0, 1, 0), + Talt => Tattr(0, 0, 0, 1, 0), + Tany => Tattr(1, 0, 0, 0, 0), + Tarrow => Tattr(0, 0, 0, 0, 1), + Tcase => Tattr(0, 0, 0, 1, 0), + Tcasel => Tattr(0, 0, 0, 1, 0), + Tcasec => Tattr(0, 0, 0, 1, 0), + Tdot => Tattr(0, 0, 0, 0, 1), + Terror => Tattr(0, 1, 1, 0, 0), + Tgoto => Tattr(0, 0, 0, 1, 0), + Tid => Tattr(0, 0, 0, 0, 1), + Tiface => Tattr(0, 0, 0, 1, 0), + Texcept => Tattr(0, 0, 0, 1, 0), + Tinst => Tattr(0, 1, 1, 1, 1), +}; + +eqclass: array of ref Teq; + +ztype: Type; +eqrec: int; +eqset: int; +adts: array of ref Decl; +nadts: int; +anontupsym: ref Sym; +unifysrc: Src; + +addtmap(t1: ref Type, t2: ref Type, tph: ref Tpair): ref Tpair +{ + tp: ref Tpair; + + tp = ref Tpair; + tp.t1 = t1; + tp.t2 = t2; + tp.nxt = tph; + return tp; +} + +valtmap(t: ref Type, tp: ref Tpair): ref Type +{ + for( ; tp != nil; tp = tp.nxt) + if(tp.t1 == t) + return tp.t2; + return t; +} + +addtype(t: ref Type, hdl: ref Typelist): ref Typelist +{ + tll := ref Typelist; + tll.t = t; + tll.nxt = nil; + if(hdl == nil) + return tll; + for(p := hdl; p.nxt != nil; p = p.nxt) + ; + p.nxt = tll; + return hdl; +} + +typeinit() +{ + anontupsym = enter(".tuple", 0); + + ztype.sbl = -1; + ztype.ok = byte 0; + ztype.rec = byte 0; + + tbig = mktype(noline, noline, Tbig, nil, nil); + tbig.size = IBY2LG; + tbig.align = IBY2LG; + tbig.ok = OKmask; + + tbyte = mktype(noline, noline, Tbyte, nil, nil); + tbyte.size = 1; + tbyte.align = 1; + tbyte.ok = OKmask; + + tint = mktype(noline, noline, Tint, nil, nil); + tint.size = IBY2WD; + tint.align = IBY2WD; + tint.ok = OKmask; + + treal = mktype(noline, noline, Treal, nil, nil); + treal.size = IBY2FT; + treal.align = IBY2FT; + treal.ok = OKmask; + + tstring = mktype(noline, noline, Tstring, nil, nil); + tstring.size = IBY2WD; + tstring.align = IBY2WD; + tstring.ok = OKmask; + + texception = mktype(noline, noline, Texception, nil, nil); + texception.size = IBY2WD; + texception.align = IBY2WD; + texception.ok = OKmask; + + tany = mktype(noline, noline, Tany, nil, nil); + tany.size = IBY2WD; + tany.align = IBY2WD; + tany.ok = OKmask; + + tnone = mktype(noline, noline, Tnone, nil, nil); + tnone.size = 0; + tnone.align = 1; + tnone.ok = OKmask; + + terror = mktype(noline, noline, Terror, nil, nil); + terror.size = 0; + terror.align = 1; + terror.ok = OKmask; + + tunknown = mktype(noline, noline, Terror, nil, nil); + tunknown.size = 0; + tunknown.align = 1; + tunknown.ok = OKmask; + + tfnptr = mktype(noline, noline, Ttuple, nil, nil); + id := tfnptr.ids = mkids(nosrc, nil, tany, nil); + id.store = Dfield; + id.offset = 0; + id.sym = enter("t0", 0); + id.src = Src(0, 0); + id = tfnptr.ids.next = mkids(nosrc, nil, tint, nil); + id.store = Dfield; + id.offset = IBY2WD; + id.sym = enter("t1", 0); + id.src = Src(0, 0); + + rtexception = mktype(noline, noline, Tref, texception, nil); + rtexception.size = IBY2WD; + rtexception.align = IBY2WD; + rtexception.ok = OKmask; +} + +typestart() +{ + descriptors = nil; + nfns = 0; + adts = nil; + nadts = 0; + selfdecl = nil; + if(tfnptr.decl != nil) + tfnptr.decl.desc = nil; + + eqclass = array[Tend] of ref Teq; + + typebuiltin(mkids(nosrc, enter("int", 0), nil, nil), tint); + typebuiltin(mkids(nosrc, enter("big", 0), nil, nil), tbig); + typebuiltin(mkids(nosrc, enter("byte", 0), nil, nil), tbyte); + typebuiltin(mkids(nosrc, enter("string", 0), nil, nil), tstring); + typebuiltin(mkids(nosrc, enter("real", 0), nil, nil), treal); +} + +modclass(): ref Teq +{ + return eqclass[Tmodule]; +} + +mktype(start: Line, stop: Line, kind: int, tof: ref Type, args: ref Decl): ref Type +{ + t := ref ztype; + t.src.start = start; + t.src.stop = stop; + t.kind = kind; + t.tof = tof; + t.ids = args; + return t; +} + +nalt: int; +mktalt(c: ref Case): ref Type +{ + t := mktype(noline, noline, Talt, nil, nil); + t.decl = mkdecl(nosrc, Dtype, t); + t.decl.sym = enter(".a"+string nalt++, 0); + t.cse = c; + return usetype(t); +} + +# +# copy t and the top level of ids +# +copytypeids(t: ref Type): ref Type +{ + last: ref Decl; + + nt := ref *t; + for(id := t.ids; id != nil; id = id.next){ + new := ref *id; + if(last == nil) + nt.ids = new; + else + last.next = new; + last = new; + } + return nt; +} + +# +# make each of the ids have type t +# +typeids(ids: ref Decl, t: ref Type): ref Decl +{ + if(ids == nil) + return nil; + + ids.ty = t; + for(id := ids.next; id != nil; id = id.next) + id.ty = t; + return ids; +} + +typebuiltin(d: ref Decl, t: ref Type) +{ + d.ty = t; + t.decl = d; + installids(Dtype, d); +} + +fielddecl(store: int, ids: ref Decl): ref Node +{ + n := mkn(Ofielddecl, nil, nil); + n.decl = ids; + for(; ids != nil; ids = ids.next) + ids.store = store; + return n; +} + +typedecl(ids: ref Decl, t: ref Type): ref Node +{ + if(t.decl == nil) + t.decl = ids; + n := mkn(Otypedecl, nil, nil); + n.decl = ids; + n.ty = t; + for(; ids != nil; ids = ids.next) + ids.ty = t; + return n; +} + +typedecled(n: ref Node) +{ + installids(Dtype, n.decl); +} + +adtdecl(ids: ref Decl, fields: ref Node): ref Node +{ + n := mkn(Oadtdecl, nil, nil); + t := mktype(ids.src.start, ids.src.stop, Tadt, nil, nil); + n.decl = ids; + n.left = fields; + n.ty = t; + t.decl = ids; + for(; ids != nil; ids = ids.next) + ids.ty = t; + return n; +} + +adtdecled(n: ref Node) +{ + d := n.ty.decl; + installids(Dtype, d); + if(n.ty.polys != nil){ + pushscope(nil, Sother); + installids(Dtype, n.ty.polys); + } + pushscope(nil, Sother); + fielddecled(n.left); + n.ty.ids = popscope(); + if(n.ty.polys != nil) + n.ty.polys = popscope(); + for(ids := n.ty.ids; ids != nil; ids = ids.next) + ids.dot = d; +} + +fielddecled(n: ref Node) +{ + for(; n != nil; n = n.right){ + case n.op{ + Oseq => + fielddecled(n.left); + Oadtdecl => + adtdecled(n); + return; + Otypedecl => + typedecled(n); + return; + Ofielddecl => + installids(Dfield, n.decl); + return; + Ocondecl => + condecled(n); + gdasdecl(n.right); + return; + Oexdecl => + exdecled(n); + return; + Opickdecl => + pickdecled(n); + return; + * => + fatal("can't deal with "+opname[n.op]+" in fielddecled"); + } + } +} + +pickdecled(n: ref Node): int +{ + if(n == nil) + return 0; + tag := pickdecled(n.left); + pushscope(nil, Sother); + fielddecled(n.right.right); + d := n.right.left.decl; + d.ty.ids = popscope(); + installids(Dtag, d); + for(; d != nil; d = d.next) + d.tag = tag++; + return tag; +} + +# +# make the tuple type used to initialize adt t +# +mkadtcon(t: ref Type): ref Type +{ + last: ref Decl; + + nt := ref *t; + nt.ids = nil; + nt.kind = Ttuple; + for(id := t.ids; id != nil; id = id.next){ + if(id.store != Dfield) + continue; + new := ref *id; + new.cyc = byte 0; + if(last == nil) + nt.ids = new; + else + last.next = new; + last = new; + } + last.next = nil; + return nt; +} + +# +# make the tuple type used to initialize t, +# an adt with pick fields tagged by tg +# +mkadtpickcon(t, tgt: ref Type): ref Type +{ + last := mkids(tgt.decl.src, nil, tint, nil); + last.store = Dfield; + nt := mktype(t.src.start, t.src.stop, Ttuple, nil, last); + for(id := t.ids; id != nil; id = id.next){ + if(id.store != Dfield) + continue; + new := ref *id; + new.cyc = byte 0; + last.next = new; + last = new; + } + for(id = tgt.ids; id != nil; id = id.next){ + if(id.store != Dfield) + continue; + new := ref *id; + new.cyc = byte 0; + last.next = new; + last = new; + } + last.next = nil; + return nt; +} + +# +# make an identifier type +# +mkidtype(src: Src, s: ref Sym): ref Type +{ + t := mktype(src.start, src.stop, Tid, nil, nil); + if(s.unbound == nil){ + s.unbound = mkdecl(src, Dunbound, nil); + s.unbound.sym = s; + } + t.decl = s.unbound; + return t; +} + +# +# make a qualified type for t->s +# +mkarrowtype(start: Line, stop: Line, t: ref Type, s: ref Sym): ref Type +{ + t = mktype(start, stop, Tarrow, t, nil); + if(s.unbound == nil){ + s.unbound = mkdecl(Src(start, stop), Dunbound, nil); + s.unbound.sym = s; + } + t.decl = s.unbound; + return t; +} + +# +# make a qualified type for t.s +# +mkdottype(start: Line, stop: Line, t: ref Type, s: ref Sym): ref Type +{ + t = mktype(start, stop, Tdot, t, nil); + if(s.unbound == nil){ + s.unbound = mkdecl(Src(start, stop), Dunbound, nil); + s.unbound.sym = s; + } + t.decl = s.unbound; + return t; +} + +mkinsttype(src: Src, tt: ref Type, tyl: ref Typelist): ref Type +{ + t := mktype(src.start, src.stop, Tinst, tt, nil); + t.tlist = tyl; + return t; +} + +# +# look up the name f in the fields of a module, adt, or tuple +# +namedot(ids: ref Decl, s: ref Sym): ref Decl +{ + for(; ids != nil; ids = ids.next) + if(ids.sym == s) + return ids; + return nil; +} + +# +# complete the declaration of an adt +# methods frames get sized in module definition or during function definition +# place the methods at the end of the field list +# +adtdefd(t: ref Type) +{ + next, aux, store, auxhd, tagnext: ref Decl; + + if(debug['x']) + print("adt %s defd\n", typeconv(t)); + d := t.decl; + tagnext = nil; + store = nil; + for(id := t.polys; id != nil; id = id.next){ + id.store = Dtype; + id.ty = verifytypes(id.ty, d, nil); + } + for(id = t.ids; id != nil; id = next){ + if(id.store == Dtag){ + if(t.tags != nil) + error(id.src.start, "only one set of pick fields allowed"); + tagnext = pickdefd(t, id); + next = tagnext; + if(store != nil) + store.next = next; + else + t.ids = next; + continue; + }else{ + id.dot = d; + next = id.next; + store = id; + } + } + aux = nil; + store = nil; + auxhd = nil; + seentags := 0; + for(id = t.ids; id != nil; id = next){ + if(id == tagnext) + seentags = 1; + + next = id.next; + id.dot = d; + id.ty = topvartype(verifytypes(id.ty, d, nil), id, 1, 1); + if(id.store == Dfield && id.ty.kind == Tfn) + id.store = Dfn; + if(id.store == Dfn || id.store == Dconst){ + if(store != nil) + store.next = next; + else + t.ids = next; + if(aux != nil) + aux.next = id; + else + auxhd = id; + aux = id; + }else{ + if(seentags) + error(id.src.start, "pick fields must be the last data fields in an adt"); + store = id; + } + } + if(aux != nil) + aux.next = nil; + if(store != nil) + store.next = auxhd; + else + t.ids = auxhd; + + for(id = t.tags; id != nil; id = id.next){ + id.ty = verifytypes(id.ty, d, nil); + if(id.ty.tof == nil) + id.ty.tof = mkadtpickcon(t, id.ty); + } +} + +# +# assemble the data structure for an adt with a pick clause. +# since the scoping rules for adt pick fields are strange, +# we have a cutomized check for overlapping defitions. +# +pickdefd(t: ref Type, tg: ref Decl): ref Decl +{ + lasttg : ref Decl = nil; + d := t.decl; + t.tags = tg; + tag := 0; + while(tg != nil){ + tt := tg.ty; + if(tt.kind != Tadtpick || tg.tag != tag) + break; + tt.decl = tg; + lasttg = tg; + for(; tg != nil; tg = tg.next){ + if(tg.ty != tt) + break; + tag++; + lasttg = tg; + tg.dot = d; + } + for(id := tt.ids; id != nil; id = id.next){ + xid := namedot(t.ids, id.sym); + if(xid != nil) + error(id.src.start, "redeclaration of "+declconv(id)+ + " previously declared as "+storeconv(xid)+" on line "+lineconv(xid.src.start)); + id.dot = d; + } + } + if(lasttg == nil){ + error(t.src.start, "empty pick field declaration in "+typeconv(t)); + t.tags = nil; + }else + lasttg.next = nil; + d.tag = tag; + return tg; +} + +moddecl(ids: ref Decl, fields: ref Node): ref Node +{ + n := mkn(Omoddecl, mkn(Oseq, nil, nil), nil); + t := mktype(ids.src.start, ids.src.stop, Tmodule, nil, nil); + n.decl = ids; + n.left = fields; + n.ty = t; + return n; +} + +moddecled(n: ref Node) +{ + d := n.decl; + installids(Dtype, d); + isimp := 0; + for(ids := d; ids != nil; ids = ids.next){ + for(im := impmods; im != nil; im = im.next){ + if(ids.sym == im.sym){ + isimp = 1; + d = ids; + dm := ref Dlist; + dm.d = ids; + dm.next = nil; + if(impdecls == nil) + impdecls = dm; + else{ + for(dl := impdecls; dl.next != nil; dl = dl.next) + ; + dl.next = dm; + } + } + } + ids.ty = n.ty; + } + pushscope(nil, Sother); + fielddecled(n.left); + + d.ty.ids = popscope(); + + # + # make the current module the . parent of all contained decls. + # + for(ids = d.ty.ids; ids != nil; ids = ids.next) + ids.dot = d; + + t := d.ty; + t.decl = d; + if(debug['m']) + print("declare module %s\n", d.sym.name); + + # + # add the iface declaration in case it's needed later + # + installids(Dglobal, mkids(d.src, enter(".m."+d.sym.name, 0), tnone, nil)); + + if(isimp){ + for(ids = d.ty.ids; ids != nil; ids = ids.next){ + s := ids.sym; + if(s.decl != nil && s.decl.scope >= scope){ + dot := s.decl.dot; + if(s.decl.store != Dwundef && dot != nil && dot != d && isimpmod(dot.sym) && dequal(ids, s.decl, 0)) + continue; + redecl(ids); + ids.old = s.decl.old; + }else + ids.old = s.decl; + s.decl = ids; + ids.scope = scope; + } + } +} + +# +# for each module in id, +# link by field ext all of the decls for +# functions needed in external linkage table +# collect globals and make a tuple for all of them +# +mkiface(m: ref Decl): ref Type +{ + iface := last := ref Decl; + globals := glast := mkdecl(m.src, Dglobal, mktype(m.src.start, m.src.stop, Tadt, nil, nil)); + for(id := m.ty.ids; id != nil; id = id.next){ + case id.store{ + Dglobal => + glast = glast.next = dupdecl(id); + id.iface = globals; + glast.iface = id; + Dfn => + id.iface = last = last.next = dupdecl(id); + last.iface = id; + Dtype => + if(id.ty.kind != Tadt) + break; + for(d := id.ty.ids; d != nil; d = d.next){ + if(d.store == Dfn){ + d.iface = last = last.next = dupdecl(d); + last.iface = d; + } + } + } + } + last.next = nil; + iface = namesort(iface.next); + + if(globals.next != nil){ + glast.next = nil; + globals.ty.ids = namesort(globals.next); + globals.ty.decl = globals; + globals.sym = enter(".mp", 0); + globals.dot = m; + globals.next = iface; + iface = globals; + } + + # + # make the interface type and install an identifier for it + # the iface has a ref count if it is loaded + # + t := mktype(m.src.start, m.src.stop, Tiface, nil, iface); + id = enter(".m."+m.sym.name, 0).decl; + t.decl = id; + id.ty = t; + + # + # dummy node so the interface is initialized + # + id.init = mkn(Onothing, nil, nil); + id.init.ty = t; + id.init.decl = id; + return t; +} + +joiniface(mt, t: ref Type) +{ + iface := t.ids; + globals := iface; + if(iface != nil && iface.store == Dglobal) + iface = iface.next; + for(id := mt.tof.ids; id != nil; id = id.next){ + case id.store{ + Dglobal => + for(d := id.ty.ids; d != nil; d = d.next) + d.iface.iface = globals; + Dfn => + id.iface.iface = iface; + iface = iface.next; + * => + fatal("unknown store "+storeconv(id)+" in joiniface"); + } + } + if(iface != nil) + fatal("join iface not matched"); + mt.tof = t; +} + +addiface(m: ref Decl, d: ref Decl) +{ + t: ref Type; + id, last, dd, lastorig: ref Decl; + + if(d == nil || !local(d)) + return; + modrefable(d.ty); + if(m == nil){ + if(impdecls.next != nil) + for(dl := impdecls; dl != nil; dl = dl.next) + if(dl.d.ty.tof != impdecl.ty.tof) # impdecl last + addiface(dl.d, d); + addiface(impdecl, d); + return; + } + t = m.ty.tof; + last = nil; + lastorig = nil; + for(id = t.ids; id != nil; id = id.next){ + if(d == id || d == id.iface) + return; + last = id; + if(id.tag == 0) + lastorig = id; + } + dd = dupdecl(d); + if(d.dot == nil) + d.dot = dd.dot = m; + d.iface = dd; + dd.iface = d; + if(last == nil) + t.ids = dd; + else + last.next = dd; + dd.tag = 1; # mark so not signed + if(lastorig == nil) + t.ids = namesort(t.ids); + else + lastorig.next = namesort(lastorig.next); +} + +# +# eliminate unused declarations from interfaces +# label offset within interface +# +narrowmods() +{ + id: ref Decl; + for(eq := modclass(); eq != nil; eq = eq.eq){ + t := eq.ty.tof; + + if(t.linkall == byte 0){ + last : ref Decl = nil; + for(id = t.ids; id != nil; id = id.next){ + if(id.refs == 0){ + if(last == nil) + t.ids = id.next; + else + last.next = id.next; + }else + last = id; + } + + # + # need to resize smaller interfaces + # + resizetype(t); + } + + offset := 0; + for(id = t.ids; id != nil; id = id.next) + id.offset = offset++; + + # + # rathole to stuff number of entries in interface + # + t.decl.init.c = ref Const; + t.decl.init.c.val = big offset; + } +} + +# +# check to see if any data field of module m if referenced. +# if so, mark all data in m +# +moddataref() +{ + for(eq := modclass(); eq != nil; eq = eq.eq){ + id := eq.ty.tof.ids; + if(id != nil && id.store == Dglobal && id.refs) + for(id = eq.ty.ids; id != nil; id = id.next) + if(id.store == Dglobal) + modrefable(id.ty); + } +} + +# +# move the global declarations in interface to the front +# +modglobals(mod, globals: ref Decl): ref Decl +{ + # + # make a copy of all the global declarations + # used for making a type descriptor for globals ONLY + # note we now have two declarations for the same variables, + # which is apt to cause problems if code changes + # + # here we fix up the offsets for the real declarations + # + idoffsets(mod.ty.ids, 0, 1); + + last := head := ref Decl; + for(id := mod.ty.ids; id != nil; id = id.next) + if(id.store == Dglobal) + last = last.next = dupdecl(id); + + last.next = globals; + return head.next; +} + +# +# snap all id type names to the actual type +# check that all types are completely defined +# verify that the types look ok +# +validtype(t: ref Type, inadt: ref Decl): ref Type +{ + if(t == nil) + return t; + bindtypes(t); + t = verifytypes(t, inadt, nil); + cycsizetype(t); + teqclass(t); + return t; +} + +usetype(t: ref Type): ref Type +{ + if(t == nil) + return t; + t = validtype(t, nil); + reftype(t); + return t; +} + +internaltype(t: ref Type): ref Type +{ + bindtypes(t); + t.ok = OKverify; + sizetype(t); + t.ok = OKmask; + return t; +} + +# +# checks that t is a valid top-level type +# +topvartype(t: ref Type, id: ref Decl, tyok: int, polyok: int): ref Type +{ + if(t.kind == Tadt && t.tags != nil || t.kind == Tadtpick) + error(id.src.start, "cannot declare "+id.sym.name+" with type "+typeconv(t)); + if(!tyok && t.kind == Tfn) + error(id.src.start, "cannot declare "+id.sym.name+" to be a function"); + if(!polyok && (t.kind == Tadt || t.kind == Tadtpick) && ispolyadt(t)) + error(id.src.start, "cannot declare " + id.sym.name + " of a polymorphic type"); + return t; +} + +toptype(src: Src, t: ref Type): ref Type +{ + if(t.kind == Tadt && t.tags != nil || t.kind == Tadtpick) + error(src.start, typeconv(t)+", an adt with pick fields, must be used with ref"); + if(t.kind == Tfn) + error(src.start, "data cannot have a fn type like "+typeconv(t)); + return t; +} + +comtype(src: Src, t: ref Type, adtd: ref Decl): ref Type +{ + if(adtd == nil && (t.kind == Tadt || t.kind == Tadtpick) && ispolyadt(t)) + error(src.start, "polymorphic type " + typeconv(t) + " illegal here"); + return t; +} + +usedty(t: ref Type) +{ + if(t != nil && (t.ok | OKmodref) != OKmask) + fatal("used ty " + stypeconv(t) + " " + hex(int t.ok, 2)); +} + +bindtypes(t: ref Type) +{ + id: ref Decl; + + if(t == nil) + return; + if((t.ok & OKbind) == OKbind) + return; + t.ok |= OKbind; + case t.kind{ + Tadt => + if(t.polys != nil){ + pushscope(nil, Sother); + installids(Dtype, t.polys); + } + if(t.val != nil) + mergepolydecs(t); + if(t.polys != nil){ + popscope(); + for(id = t.polys; id != nil; id = id.next) + bindtypes(id.ty); + } + Tadtpick or + Tmodule or + Terror or + Tint or + Tbig or + Tstring or + Treal or + Tbyte or + Tnone or + Tany or + Tiface or + Tainit or + Talt or + Tcase or + Tcasel or + Tcasec or + Tgoto or + Texcept or + Tfix or + Tpoly => + break; + Tarray or + Tarrow or + Tchan or + Tdot or + Tlist or + Tref => + bindtypes(t.tof); + Tid => + id = t.decl.sym.decl; + if(id == nil) + id = undefed(t.src, t.decl.sym); + # save a little space + id.sym.unbound = nil; + t.decl = id; + Ttuple or + Texception => + for(id = t.ids; id != nil; id = id.next) + bindtypes(id.ty); + Tfn => + if(t.polys != nil){ + pushscope(nil, Sother); + installids(Dtype, t.polys); + } + for(id = t.ids; id != nil; id = id.next) + bindtypes(id.ty); + bindtypes(t.tof); + if(t.val != nil) + mergepolydecs(t); + if(t.polys != nil){ + popscope(); + for(id = t.polys; id != nil; id = id.next) + bindtypes(id.ty); + } + Tinst => + bindtypes(t.tof); + for(tyl := t.tlist; tyl != nil; tyl = tyl.nxt) + bindtypes(tyl.t); + * => + fatal("bindtypes: unknown type kind "+string t.kind); + } +} + +# +# walk the type checking for validity +# +verifytypes(t: ref Type, adtt: ref Decl, poly: ref Decl): ref Type +{ + id: ref Decl; + + if(t == nil) + return nil; + if((t.ok & OKverify) == OKverify) + return t; + t.ok |= OKverify; +if((t.ok & (OKverify|OKbind)) != (OKverify|OKbind)) +fatal("verifytypes bogus ok for " + stypeconv(t)); + cyc := t.flags&CYCLIC; + case t.kind{ + Terror or + Tint or + Tbig or + Tstring or + Treal or + Tbyte or + Tnone or + Tany or + Tiface or + Tainit or + Talt or + Tcase or + Tcasel or + Tcasec or + Tgoto or + Texcept => + break; + Tfix => + n := t.val; + ok: int; + max := 0.0; + if(n.op == Oseq){ + (ok, nil) = echeck(n.left, 0, 0, n); + (ok1, nil) := echeck(n.right, 0, 0, n); + if(!ok || !ok1) + return terror; + if(n.left.ty != treal || n.right.ty != treal){ + error(t.src.start, "fixed point scale/maximum not real"); + return terror; + } + n.right = fold(n.right); + if(n.right.op != Oconst){ + error(t.src.start, "fixed point maximum not constant"); + return terror; + } + if((max = n.right.c.rval) <= 0.0){ + error(t.src.start, "non-positive fixed point maximum"); + return terror; + } + n = n.left; + } + else{ + (ok, nil) = echeck(n, 0, 0, nil); + if(!ok) + return terror; + if(n.ty != treal){ + error(t.src.start, "fixed point scale not real"); + return terror; + } + } + n = t.val = fold(n); + if(n.op != Oconst){ + error(t.src.start, "fixed point scale not constant"); + return terror; + } + if(n.c.rval <= 0.0){ + error(t.src.start, "non-positive fixed point scale"); + return terror; + } + ckfix(t, max); + Tref => + t.tof = comtype(t.src, verifytypes(t.tof, adtt, nil), adtt); + if(t.tof != nil && !tattr[t.tof.kind].refable){ + error(t.src.start, "cannot have a ref " + typeconv(t.tof)); + return terror; + } + if(0 && t.tof.kind == Tfn && t.tof.ids != nil && int t.tof.ids.implicit) + error(t.src.start, "function references cannot have a self argument"); + if(0 && t.tof.kind == Tfn && t.polys != nil) + error(t.src.start, "function references cannot be polymorphic"); + Tchan or + Tarray or + Tlist => + t.tof = comtype(t.src, toptype(t.src, verifytypes(t.tof, adtt, nil)), adtt); + Tid => + t.ok &= ~OKverify; + t = verifytypes(idtype(t), adtt, nil); + Tarrow => + t.ok &= ~OKverify; + t = verifytypes(arrowtype(t, adtt), adtt, nil); + Tdot => + # + # verify the parent adt & lookup the tag fields + # + t.ok &= ~OKverify; + t = verifytypes(dottype(t, adtt), adtt, nil); + Tadt => + # + # this is where Tadt may get tag fields added + # + adtdefd(t); + Tadtpick => + for(id = t.ids; id != nil; id = id.next){ + id.ty = topvartype(verifytypes(id.ty, id.dot, nil), id, 0, 1); + if(id.store == Dconst) + error(t.src.start, "cannot declare a con like "+id.sym.name+" within a pick"); + } + verifytypes(t.decl.dot.ty, nil, nil); + Tmodule => + for(id = t.ids; id != nil; id = id.next){ + id.ty = verifytypes(id.ty, nil, nil); + if(id.store == Dglobal && id.ty.kind == Tfn) + id.store = Dfn; + if(id.store != Dtype && id.store != Dfn) + topvartype(id.ty, id, 0, 0); + } + Ttuple or + Texception => + if(t.decl == nil){ + t.decl = mkdecl(t.src, Dtype, t); + t.decl.sym = anontupsym; + } + i := 0; + for(id = t.ids; id != nil; id = id.next){ + id.store = Dfield; + if(id.sym == nil) + id.sym = enter("t"+string i, 0); + i++; + id.ty = toptype(id.src, verifytypes(id.ty, adtt, nil)); + } + Tfn => + last : ref Decl = nil; + for(id = t.ids; id != nil; id = id.next){ + id.store = Darg; + id.ty = topvartype(verifytypes(id.ty, adtt, nil), id, 0, 1); + if(id.implicit != byte 0){ + if(poly != nil) + selfd := poly; + else + selfd = adtt; + if(selfd == nil) + error(t.src.start, "function is not a member of an adt, so can't use self"); + else if(id != t.ids) + error(id.src.start, "only the first argument can use self"); + else if(id.ty != selfd.ty && (id.ty.kind != Tref || id.ty.tof != selfd.ty)) + error(id.src.start, "self argument's type must be "+selfd.sym.name+" or ref "+selfd.sym.name); + } + last = id; + } + for(id = t.polys; id != nil; id = id.next){ + if(adtt != nil){ + for(id1 := adtt.ty.polys; id1 != nil; id1 = id1.next){ + if(id1.sym == id.sym) + id.ty = id1.ty; + } + } + id.store = Dtype; + id.ty = verifytypes(id.ty, adtt, nil); + } + t.tof = comtype(t.src, toptype(t.src, verifytypes(t.tof, adtt, nil)), adtt); + if(t.varargs != byte 0 && (last == nil || last.ty != tstring)) + error(t.src.start, "variable arguments must be preceded by a string"); + if(t.varargs != byte 0 && t.polys != nil) + error(t.src.start, "polymorphic functions must not have variable arguments"); + Tpoly => + for(id = t.ids; id != nil; id = id.next){ + id.store = Dfn; + id.ty = verifytypes(id.ty, adtt, t.decl); + } + Tinst => + t.ok &= ~OKverify; + t.tof = verifytypes(t.tof, adtt, nil); + for(tyl := t.tlist; tyl != nil; tyl = tyl.nxt) + tyl.t = verifytypes(tyl.t, adtt, nil); + (t, nil) = insttype(t, adtt, nil); + t = verifytypes(t, adtt, nil); + * => + fatal("verifytypes: unknown type kind "+string t.kind); + } + if(int cyc) + t.flags |= CYCLIC; + return t; +} + +# +# resolve an id type +# +idtype(t: ref Type): ref Type +{ + id := t.decl; + if(id.store == Dunbound) + fatal("idtype: unbound decl"); + tt := id.ty; + if(id.store != Dtype && id.store != Dtag){ + if(id.store == Dundef){ + id.store = Dwundef; + error(t.src.start, id.sym.name+" is not declared"); + }else if(id.store == Dimport){ + id.store = Dwundef; + error(t.src.start, id.sym.name+"'s type cannot be determined"); + }else if(id.store != Dwundef) + error(t.src.start, id.sym.name+" is not a type"); + return terror; + } + if(tt == nil){ + error(t.src.start, stypeconv(t)+" not fully defined"); + return terror; + } + return tt; +} + +# +# resolve a -> qualified type +# +arrowtype(t: ref Type, adtt: ref Decl): ref Type +{ + id := t.decl; + if(id.ty != nil){ + if(id.store == Dunbound) + fatal("arrowtype: unbound decl has a type"); + return id.ty; + } + + # + # special hack to allow module variables to derive other types + # + tt := t.tof; + if(tt.kind == Tid){ + id = tt.decl; + if(id.store == Dunbound) + fatal("arrowtype: Tid's decl unbound"); + if(id.store == Dimport){ + id.store = Dwundef; + error(t.src.start, id.sym.name+"'s type cannot be determined"); + return terror; + } + + # + # forward references to module variables can't be resolved + # + if(id.store != Dtype && (id.ty.ok & OKbind) != OKbind){ + error(t.src.start, id.sym.name+"'s type cannot be determined"); + return terror; + } + + if(id.store == Dwundef) + return terror; + tt = id.ty = verifytypes(id.ty, adtt, nil); + if(tt == nil){ + error(t.tof.src.start, typeconv(t.tof)+" is not a module"); + return terror; + } + }else + tt = verifytypes(t.tof, adtt, nil); + t.tof = tt; + if(tt == terror) + return terror; + if(tt.kind != Tmodule){ + error(t.src.start, typeconv(tt)+" is not a module"); + return terror; + } + id = namedot(tt.ids, t.decl.sym); + if(id == nil){ + error(t.src.start, t.decl.sym.name+" is not a member of "+typeconv(tt)); + return terror; + } + if(id.store == Dtype && id.ty != nil){ + t.decl = id; + return id.ty; + } + error(t.src.start, typeconv(t)+" is not a type"); + return terror; +} + +# +# resolve a . qualified type +# +dottype(t: ref Type, adtt: ref Decl): ref Type +{ + if(t.decl.ty != nil){ + if(t.decl.store == Dunbound) + fatal("dottype: unbound decl has a type"); + return t.decl.ty; + } + t.tof = tt := verifytypes(t.tof, adtt, nil); + if(tt == terror) + return terror; + if(tt.kind != Tadt){ + error(t.src.start, typeconv(tt)+" is not an adt"); + return terror; + } + id := namedot(tt.tags, t.decl.sym); + if(id != nil && id.ty != nil){ + t.decl = id; + return id.ty; + } + error(t.src.start, t.decl.sym.name+" is not a pick tag of "+typeconv(tt)); + return terror; +} + +insttype(t: ref Type, adtt: ref Decl, tp: ref Tpair): (ref Type, ref Tpair) +{ + src := t.src; + if(t.tof.kind != Tadt && t.tof.kind != Tadtpick){ + error(src.start, typeconv(t.tof) + " is not an adt"); + return (terror, nil); + } + if(t.tof.kind == Tadt) + ids := t.tof.polys; + else + ids = t.tof.decl.dot.ty.polys; + if(ids == nil){ + error(src.start, typeconv(t.tof) + " is not a polymorphic adt"); + return (terror, nil); + } + for(tyl := t.tlist; tyl != nil && ids != nil; tyl = tyl.nxt){ + tt := tyl.t; + if(!tattr[tt.kind].isptr){ + error(src.start, typeconv(tt) + " is not a pointer type"); + return (terror, nil); + } + unifysrc = src; + (ok, nil) := tunify(ids.ty, tt); + if(!ok){ + error(src.start, "type " + typeconv(tt) + " does not match " + typeconv(ids.ty)); + return (terror, nil); + } + # usetype(tt); + tt = verifytypes(tt, adtt, nil); + tp = addtmap(ids.ty, tt, tp); + ids = ids.next; + } + if(tyl != nil){ + error(src.start, "too many actual types in instantiation"); + return (terror, nil); + } + if(ids != nil){ + error(src.start, "too few actual types in instantiation"); + return (terror, nil); + } + tt := t.tof; + (t, nil) = expandtype(tt, t, adtt, tp); + if(t == tt && adtt == nil) + t = duptype(t); + if(t != tt) + t.tmap = tp; + t.src = src; + return (t, tp); +} + +# +# walk a type, putting all adts, modules, and tuples into equivalence classes +# +teqclass(t: ref Type) +{ + id: ref Decl; + + if(t == nil || (t.ok & OKclass) == OKclass) + return; + t.ok |= OKclass; + case t.kind{ + Terror or + Tint or + Tbig or + Tstring or + Treal or + Tbyte or + Tnone or + Tany or + Tiface or + Tainit or + Talt or + Tcase or + Tcasel or + Tcasec or + Tgoto or + Texcept or + Tfix or + Tpoly => + return; + Tref => + teqclass(t.tof); + return; + Tchan or + Tarray or + Tlist => + teqclass(t.tof); +#ZZZ elim return to fix recursive chans, etc + if(!debug['Z']) + return; + Tadt or + Tadtpick or + Ttuple or + Texception => + for(id = t.ids; id != nil; id = id.next) + teqclass(id.ty); + for(tg := t.tags; tg != nil; tg = tg.next) + teqclass(tg.ty); + for(id = t.polys; id != nil; id = id.next) + teqclass(id.ty); + Tmodule => + t.tof = mkiface(t.decl); + for(id = t.ids; id != nil; id = id.next) + teqclass(id.ty); + Tfn => + for(id = t.ids; id != nil; id = id.next) + teqclass(id.ty); + for(id = t.polys; id != nil; id = id.next) + teqclass(id.ty); + teqclass(t.tof); + return; + * => + fatal("teqclass: unknown type kind "+string t.kind); + } + + # + # find an equivalent type + # stupid linear lookup could be made faster + # + if((t.ok & OKsized) != OKsized) + fatal("eqclass type not sized: " + stypeconv(t)); + + for(teq := eqclass[t.kind]; teq != nil; teq = teq.eq){ + if(t.size == teq.ty.size && tequal(t, teq.ty)){ + t.eq = teq; + if(t.kind == Tmodule) + joiniface(t, t.eq.ty.tof); + return; + } + } + + # + # if no equiv type, make one + # + eqclass[t.kind] = t.eq = ref Teq(0, t, eqclass[t.kind]); +} + +# +# record that we've used the type +# using a type uses all types reachable from that type +# +reftype(t: ref Type) +{ + id: ref Decl; + + if(t == nil || (t.ok & OKref) == OKref) + return; + t.ok |= OKref; + if(t.decl != nil && t.decl.refs == 0) + t.decl.refs++; + case t.kind{ + Terror or + Tint or + Tbig or + Tstring or + Treal or + Tbyte or + Tnone or + Tany or + Tiface or + Tainit or + Talt or + Tcase or + Tcasel or + Tcasec or + Tgoto or + Texcept or + Tfix or + Tpoly => + break; + Tref or + Tchan or + Tarray or + Tlist => + if(t.decl != nil){ + if(nadts >= len adts){ + a := array[nadts + 32] of ref Decl; + a[0:] = adts; + adts = a; + } + adts[nadts++] = t.decl; + } + reftype(t.tof); + Tadt or + Tadtpick or + Ttuple or + Texception => + if(t.kind == Tadt || t.kind == Ttuple && t.decl.sym != anontupsym){ + if(nadts >= len adts){ + a := array[nadts + 32] of ref Decl; + a[0:] = adts; + adts = a; + } + adts[nadts++] = t.decl; + } + for(id = t.ids; id != nil; id = id.next) + if(id.store != Dfn) + reftype(id.ty); + for(tg := t.tags; tg != nil; tg = tg.next) + reftype(tg.ty); + for(id = t.polys; id != nil; id = id.next) + reftype(id.ty); + if(t.kind == Tadtpick) + reftype(t.decl.dot.ty); + Tmodule => + # + # a module's elements should get used individually + # but do the globals for any sbl file + # + if(bsym != nil) + for(id = t.ids; id != nil; id = id.next) + if(id.store == Dglobal) + reftype(id.ty); + break; + Tfn => + for(id = t.ids; id != nil; id = id.next) + reftype(id.ty); + for(id = t.polys; id != nil; id = id.next) + reftype(id.ty); + reftype(t.tof); + * => + fatal("reftype: unknown type kind "+string t.kind); + } +} + +# +# check all reachable types for cycles and illegal forward references +# find the size of all the types +# +cycsizetype(t: ref Type) +{ + id: ref Decl; + + if(t == nil || (t.ok & (OKcycsize|OKcyc|OKsized)) == (OKcycsize|OKcyc|OKsized)) + return; + t.ok |= OKcycsize; + case t.kind{ + Terror or + Tint or + Tbig or + Tstring or + Treal or + Tbyte or + Tnone or + Tany or + Tiface or + Tainit or + Talt or + Tcase or + Tcasel or + Tcasec or + Tgoto or + Texcept or + Tfix or + Tpoly => + t.ok |= OKcyc; + sizetype(t); + Tref or + Tchan or + Tarray or + Tlist => + cyctype(t); + sizetype(t); + cycsizetype(t.tof); + Tadt or + Ttuple or + Texception => + cyctype(t); + sizetype(t); + for(id = t.ids; id != nil; id = id.next) + cycsizetype(id.ty); + for(tg := t.tags; tg != nil; tg = tg.next){ + if((tg.ty.ok & (OKcycsize|OKcyc|OKsized)) == (OKcycsize|OKcyc|OKsized)) + continue; + tg.ty.ok |= (OKcycsize|OKcyc|OKsized); + for(id = tg.ty.ids; id != nil; id = id.next) + cycsizetype(id.ty); + } + for(id = t.polys; id != nil; id = id.next) + cycsizetype(id.ty); + Tadtpick => + t.ok &= ~OKcycsize; + cycsizetype(t.decl.dot.ty); + Tmodule => + cyctype(t); + sizetype(t); + for(id = t.ids; id != nil; id = id.next) + cycsizetype(id.ty); + sizeids(t.ids, 0); + Tfn => + cyctype(t); + sizetype(t); + for(id = t.ids; id != nil; id = id.next) + cycsizetype(id.ty); + for(id = t.polys; id != nil; id = id.next) + cycsizetype(id.ty); + cycsizetype(t.tof); + sizeids(t.ids, MaxTemp); +#ZZZ need to align? + * => + fatal("cycsizetype: unknown type kind "+string t.kind); + } +} + +# check for circularity in type declarations +# - has to be called before verifytypes +# +tcycle(t: ref Type) +{ + id: ref Decl; + tt: ref Type; + tll: ref Typelist; + + if(t == nil) + return; + case(t.kind){ + * => + ; + Tchan or + Tarray or + Tref or + Tlist or + Tdot => + tcycle(t.tof); + Tfn or + Ttuple => + tcycle(t.tof); + for(id = t.ids; id != nil; id = id.next) + tcycle(id.ty); + Tarrow => + if(int(t.rec&TRvis)){ + error(t.src.start, "circularity in definition of " + typeconv(t)); + *t = *terror; # break the cycle + return; + } + tt = t.tof; + t.rec |= TRvis; + tcycle(tt); + if(tt.kind == Tid) + tt = tt.decl.ty; + id = namedot(tt.ids, t.decl.sym); + if(id != nil) + tcycle(id.ty); + t.rec &= ~TRvis; + Tid => + if(int(t.rec&TRvis)){ + error(t.src.start, "circularity in definition of " + typeconv(t)); + *t = *terror; # break the cycle + return; + } + t.rec |= TRvis; + tcycle(t.decl.ty); + t.rec &= ~TRvis; + Tinst => + tcycle(t.tof); + for(tll = t.tlist; tll != nil; tll = tll.nxt) + tcycle(tll.t); + } +} + +# +# marks for checking for arcs +# + ArcValue, + ArcList, + ArcArray, + ArcRef, + ArcCyc, # cycle found + ArcPolycyc: + con 1 << iota; + +cyctype(t: ref Type) +{ + if((t.ok & OKcyc) == OKcyc) + return; + t.ok |= OKcyc; + t.rec |= TRcyc; + case t.kind{ + Terror or + Tint or + Tbig or + Tstring or + Treal or + Tbyte or + Tnone or + Tany or + Tfn or + Tchan or + Tarray or + Tref or + Tlist or + Tfix or + Tpoly => + break; + Tadt or + Tmodule or + Ttuple or + Texception => + for(id := t.ids; id != nil; id = id.next) + cycfield(t, id); + for(tg := t.tags; tg != nil; tg = tg.next){ + if((tg.ty.ok & OKcyc) == OKcyc) + continue; + tg.ty.ok |= OKcyc; + for(id = tg.ty.ids; id != nil; id = id.next) + cycfield(t, id); + } + * => + fatal("cyctype: unknown type kind "+string t.kind); + } + t.rec &= ~TRcyc; +} + +cycfield(base: ref Type, id: ref Decl) +{ + if(!storespace[id.store]) + return; + arc := cycarc(base, id.ty); + + if((arc & (ArcCyc|ArcValue)) == (ArcCyc|ArcValue)){ + if(id.cycerr == byte 0) + error(base.src.start, "illegal type cycle without a reference in field " + +id.sym.name+" of "+stypeconv(base)); + id.cycerr = byte 1; + }else if(arc & ArcCyc){ + if((arc & ArcArray) && id.cyc == byte 0 && !(arc & ArcPolycyc)){ + if(id.cycerr == byte 0) + error(base.src.start, "illegal circular reference to type "+typeconv(id.ty) + +" in field "+id.sym.name+" of "+stypeconv(base)); + id.cycerr = byte 1; + } + id.cycle = byte 1; + }else if(id.cyc != byte 0){ + if(id.cycerr == byte 0) + error(id.src.start, "spurious cyclic qualifier for field "+id.sym.name+" of "+stypeconv(base)); + id.cycerr = byte 1; + } +} + +cycarc(base, t: ref Type): int +{ + if(t == nil) + return 0; + if((t.rec & TRcyc) == TRcyc){ + if(tequal(t, base)){ + if(t.kind == Tmodule) + return ArcCyc | ArcRef; + else + return ArcCyc | ArcValue; + } + return 0; + } + t.rec |= TRcyc; + me := 0; + case t.kind{ + Terror or + Tint or + Tbig or + Tstring or + Treal or + Tbyte or + Tnone or + Tany or + Tchan or + Tfn or + Tfix or + Tpoly => + break; + Tarray => + me = cycarc(base, t.tof) & ~ArcValue | ArcArray; + Tref => + me = cycarc(base, t.tof) & ~ArcValue | ArcRef; + Tlist => + me = cycarc(base, t.tof) & ~ArcValue | ArcList; + Tadt or + Tadtpick or + Tmodule or + Ttuple or + Texception => + me = 0; + arc: int; + for(id := t.ids; id != nil; id = id.next){ + if(!storespace[id.store]) + continue; + arc = cycarc(base, id.ty); + if((arc & ArcCyc) && id.cycerr == byte 0) + me |= arc; + } + for(tg := t.tags; tg != nil; tg = tg.next){ + arc = cycarc(base, tg.ty); + if((arc & ArcCyc) && tg.cycerr == byte 0) + me |= arc; + } + + if(t.kind == Tmodule) + me = me & ArcCyc | ArcRef | ArcPolycyc; + else + me &= ArcCyc | ArcValue | ArcPolycyc; + * => + fatal("cycarc: unknown type kind "+string t.kind); + } + t.rec &= ~TRcyc; + if(int (t.flags&CYCLIC)) + me |= ArcPolycyc; + return me; +} + +# +# set the sizes and field offsets for t +# look only as deeply as needed to size this type. +# cycsize type will clean up the rest. +# +sizetype(t: ref Type) +{ + id: ref Decl; + sz, al, s, a: int; + + if(t == nil) + return; + if((t.ok & OKsized) == OKsized) + return; + t.ok |= OKsized; +if((t.ok & (OKverify|OKsized)) != (OKverify|OKsized)) +fatal("sizetype bogus ok for " + stypeconv(t)); + case t.kind{ + * => + fatal("sizetype: unknown type kind "+string t.kind); + Terror or + Tnone or + Tbyte or + Tint or + Tbig or + Tstring or + Tany or + Treal => + fatal(typeconv(t)+" should have a size"); + Tref or + Tchan or + Tarray or + Tlist or + Tmodule or + Tfix or + Tpoly => + t.size = t.align = IBY2WD; + Tadt or + Ttuple or + Texception => + if(t.tags == nil){ +#ZZZ + if(!debug['z']){ + (sz, t.align) = sizeids(t.ids, 0); + t.size = align(sz, t.align); + }else{ + (sz, nil) = sizeids(t.ids, 0); + t.align = IBY2LG; + t.size = align(sz, IBY2LG); + } + return; + } +#ZZZ + if(!debug['z']){ + (sz, al) = sizeids(t.ids, IBY2WD); + if(al < IBY2WD) + al = IBY2WD; + }else{ + (sz, nil) = sizeids(t.ids, IBY2WD); + al = IBY2LG; + } + for(tg := t.tags; tg != nil; tg = tg.next){ + if((tg.ty.ok & OKsized) == OKsized) + continue; + tg.ty.ok |= OKsized; +#ZZZ + if(!debug['z']){ + (s, a) = sizeids(tg.ty.ids, sz); + if(a < al) + a = al; + tg.ty.size = align(s, a); + tg.ty.align = a; + }else{ + (s, nil) = sizeids(tg.ty.ids, sz); + tg.ty.size = align(s, IBY2LG); + tg.ty.align = IBY2LG; + } + } + Tfn => + t.size = 0; + t.align = 1; + Tainit => + t.size = 0; + t.align = 1; + Talt => + t.size = t.cse.nlab * 2*IBY2WD + 2*IBY2WD; + t.align = IBY2WD; + Tcase or + Tcasec => + t.size = t.cse.nlab * 3*IBY2WD + 2*IBY2WD; + t.align = IBY2WD; + Tcasel => + t.size = t.cse.nlab * 6*IBY2WD + 3*IBY2WD; + t.align = IBY2LG; + Tgoto => + t.size = t.cse.nlab * IBY2WD + IBY2WD; + if(t.cse.iwild != nil) + t.size += IBY2WD; + t.align = IBY2WD; + Tiface => + sz = IBY2WD; + for(id = t.ids; id != nil; id = id.next){ + sz = align(sz, IBY2WD) + IBY2WD; + sz += len array of byte id.sym.name + 1; + if(id.dot.ty.kind == Tadt) + sz += len array of byte id.dot.sym.name + 1; + } + t.size = sz; + t.align = IBY2WD; + Texcept => + t.size = 0; + t.align = IBY2WD; + } +} + +sizeids(id: ref Decl, off: int): (int, int) +{ + al := 1; + for(; id != nil; id = id.next){ + if(storespace[id.store]){ + sizetype(id.ty); + # + # alignment can be 0 if we have + # illegal forward declarations. + # just patch a; other code will flag an error + # + a := id.ty.align; + if(a == 0) + a = 1; + + if(a > al) + al = a; + + off = align(off, a); + id.offset = off; + off += id.ty.size; + } + } + return (off, al); +} + +align(off, align: int): int +{ + if(align == 0) + fatal("align 0"); + while(off % align) + off++; + return off; +} + +# +# recalculate a type's size +# +resizetype(t: ref Type) +{ + if((t.ok & OKsized) == OKsized){ + t.ok &= ~OKsized; + cycsizetype(t); + } +} + +# +# check if a module is accessable from t +# if so, mark that module interface +# +modrefable(t: ref Type) +{ + id: ref Decl; + + if(t == nil || (t.ok & OKmodref) == OKmodref) + return; + if((t.ok & OKverify) != OKverify) + fatal("modrefable unused type "+stypeconv(t)); + t.ok |= OKmodref; + case t.kind{ + Terror or + Tint or + Tbig or + Tstring or + Treal or + Tbyte or + Tnone or + Tany or + Tfix or + Tpoly => + break; + Tchan or + Tref or + Tarray or + Tlist => + modrefable(t.tof); + Tmodule => + t.tof.linkall = byte 1; + t.decl.refs++; + for(id = t.ids; id != nil; id = id.next){ + case id.store{ + Dglobal or + Dfn => + modrefable(id.ty); + Dtype => + if(id.ty.kind != Tadt) + break; + for(m := id.ty.ids; m != nil; m = m.next) + if(m.store == Dfn) + modrefable(m.ty); + } + } + Tfn or + Tadt or + Ttuple or + Texception => + for(id = t.ids; id != nil; id = id.next) + if(id.store != Dfn) + modrefable(id.ty); + for(tg := t.tags; tg != nil; tg = tg.next){ + # if((tg.ty.ok & OKmodref) == OKmodref) + # continue; + tg.ty.ok |= OKmodref; + for(id = tg.ty.ids; id != nil; id = id.next) + modrefable(id.ty); + } + for(id = t.polys; id != nil; id = id.next) + modrefable(id.ty); + modrefable(t.tof); + Tadtpick => + modrefable(t.decl.dot.ty); + * => + fatal("modrefable: unknown type kind "+string t.kind); + } +} + +gendesc(d: ref Decl, size: int, decls: ref Decl): ref Desc +{ + if(debug['D']) + print("generate desc for %s\n", dotconv(d)); + if(ispoly(d)) + addfnptrs(d, 0); + desc := usedesc(mkdesc(size, decls)); + return desc; +} + +mkdesc(size: int, d: ref Decl): ref Desc +{ + pmap := array[(size+8*IBY2WD-1) / (8*IBY2WD)] of { * => byte 0 }; + n := descmap(d, pmap, 0); + if(n >= 0) + n = n / (8*IBY2WD) + 1; + else + n = 0; + return enterdesc(pmap, size, n); +} + +mktdesc(t: ref Type): ref Desc +{ +usedty(t); + if(debug['D']) + print("generate desc for %s\n", typeconv(t)); + if(t.decl == nil){ + t.decl = mkdecl(t.src, Dtype, t); + t.decl.sym = enter("_mktdesc_", 0); + } + if(t.decl.desc != nil) + return t.decl.desc; + pmap := array[(t.size+8*IBY2WD-1) / (8*IBY2WD)] of {* => byte 0}; + n := tdescmap(t, pmap, 0); + if(n >= 0) + n = n / (8*IBY2WD) + 1; + else + n = 0; + d := enterdesc(pmap, t.size, n); + t.decl.desc = d; + return d; +} + +enterdesc(map: array of byte, size, nmap: int): ref Desc +{ + last : ref Desc = nil; + for(d := descriptors; d != nil; d = d.next){ + if(d.size > size || d.size == size && d.nmap > nmap) + break; + if(d.size == size && d.nmap == nmap){ + c := mapcmp(d.map, map, nmap); + if(c == 0) + return d; + if(c > 0) + break; + } + last = d; + } + + d = ref Desc(-1, 0, map, size, nmap, nil); + if(last == nil){ + d.next = descriptors; + descriptors = d; + }else{ + d.next = last.next; + last.next = d; + } + return d; +} + +mapcmp(a, b: array of byte, n: int): int +{ + for(i := 0; i < n; i++) + if(a[i] != b[i]) + return int a[i] - int b[i]; + return 0; +} + +usedesc(d: ref Desc): ref Desc +{ + d.used = 1; + return d; +} + +# +# create the pointer description byte map for every type in decls +# each bit corresponds to a word, and is 1 if occupied by a pointer +# the high bit in the byte maps the first word +# +descmap(decls: ref Decl, map: array of byte, start: int): int +{ + if(debug['D']) + print("descmap offset %d\n", start); + last := -1; + for(d := decls; d != nil; d = d.next){ + if(d.store == Dtype && d.ty.kind == Tmodule + || d.store == Dfn + || d.store == Dconst) + continue; + if(d.store == Dlocal && d.link != nil) + continue; + m := tdescmap(d.ty, map, d.offset + start); + if(debug['D']){ + if(d.sym != nil) + print("descmap %s type %s offset %d returns %d\n", d.sym.name, typeconv(d.ty), d.offset+start, m); + else + print("descmap type %s offset %d returns %d\n", typeconv(d.ty), d.offset+start, m); + } + if(m >= 0) + last = m; + } + return last; +} + +tdescmap(t: ref Type, map: array of byte, offset: int): int +{ + i, e, bit: int; + + if(t == nil) + return -1; + + m := -1; + if(t.kind == Talt){ + lab := t.cse.labs; + e = t.cse.nlab; + offset += IBY2WD * 2; + for(i = 0; i < e; i++){ + if(lab[i].isptr){ + bit = offset / IBY2WD % 8; + map[offset / (8*IBY2WD)] |= byte 1 << (7 - bit); + m = offset; + } + offset += 2*IBY2WD; + } + return m; + } + if(t.kind == Tcasec){ + e = t.cse.nlab; + offset += IBY2WD; + for(i = 0; i < e; i++){ + bit = offset / IBY2WD % 8; + map[offset / (8*IBY2WD)] |= byte 1 << (7 - bit); + offset += IBY2WD; + bit = offset / IBY2WD % 8; + map[offset / (8*IBY2WD)] |= byte 1 << (7 - bit); + m = offset; + offset += 2*IBY2WD; + } + return m; + } + + if(tattr[t.kind].isptr){ + bit = offset / IBY2WD % 8; + map[offset / (8*IBY2WD)] |= byte 1 << (7 - bit); + return offset; + } + if(t.kind == Tadtpick) + t = t.tof; + if(t.kind == Ttuple || t.kind == Tadt || t.kind == Texception){ + if(debug['D']) + print("descmap adt offset %d\n", offset); + if(t.rec != byte 0) + fatal("illegal cyclic type "+stypeconv(t)+" in tdescmap"); + t.rec = byte 1; + offset = descmap(t.ids, map, offset); + t.rec = byte 0; + return offset; + } + + return -1; +} + +tcomset: int; + +# +# can a t2 be assigned to a t1? +# any means Tany matches all types, +# not just references +# +tcompat(t1, t2: ref Type, any: int): int +{ + if(t1 == t2) + return 1; + if(t1 == nil || t2 == nil) + return 0; + if(t2.kind == Texception && t1.kind != Texception) + t2 = mkextuptype(t2); + tcomset = 0; + ok := rtcompat(t1, t2, any, 0); + v := cleartcomrec(t1) + cleartcomrec(t2); + if(v != tcomset) + fatal("recid t1 "+stypeconv(t1)+" and t2 "+stypeconv(t2)+" not balanced in tcompat: "+string v+" "+string tcomset); + return ok; +} + +rtcompat(t1, t2: ref Type, any: int, inaorc: int): int +{ + if(t1 == t2) + return 1; + if(t1 == nil || t2 == nil) + return 0; + if(t1.kind == Terror || t2.kind == Terror) + return 1; + if(t2.kind == Texception && t1.kind != Texception) + t2 = mkextuptype(t2); + + t1.rec |= TRcom; + t2.rec |= TRcom; + case t1.kind{ + * => + fatal("unknown type "+stypeconv(t1)+" v "+stypeconv(t2)+" in rtcompat"); + return 0; + Tstring => + return t2.kind == Tstring || t2.kind == Tany; + Texception => + if(t2.kind == Texception && t1.cons == t2.cons){ + if(assumetcom(t1, t2)) + return 1; + return idcompat(t1.ids, t2.ids, 0, inaorc); + } + return 0; + Tnone or + Tint or + Tbig or + Tbyte or + Treal => + return t1.kind == t2.kind; + Tfix => + return t1.kind == t2.kind && sametree(t1.val, t2.val); + Tany => + if(tattr[t2.kind].isptr) + return 1; + return any; + Tref or + Tlist or + Tarray or + Tchan => + if(t1.kind != t2.kind){ + if(t2.kind == Tany) + return 1; + return 0; + } + if(t1.kind != Tref && assumetcom(t1, t2)) + return 1; + return rtcompat(t1.tof, t2.tof, 0, t1.kind == Tarray || t1.kind == Tchan || inaorc); + Tfn => + break; + Ttuple => + if(t2.kind == Tadt && t2.tags == nil + || t2.kind == Ttuple){ + if(assumetcom(t1, t2)) + return 1; + return idcompat(t1.ids, t2.ids, any, inaorc); + } + if(t2.kind == Tadtpick){ + t2.tof.rec |= TRcom; + if(assumetcom(t1, t2.tof)) + return 1; + return idcompat(t1.ids, t2.tof.ids.next, any, inaorc); + } + return 0; + Tadt => + if(t2.kind == Ttuple && t1.tags == nil){ + if(assumetcom(t1, t2)) + return 1; + return idcompat(t1.ids, t2.ids, any, inaorc); + } + if(t1.tags != nil && t2.kind == Tadtpick && !inaorc) + t2 = t2.decl.dot.ty; + Tadtpick => + #if(t2.kind == Ttuple) + # return idcompat(t1.tof.ids.next, t2.ids, any, inaorc); + break; + Tmodule => + if(t2.kind == Tany) + return 1; + Tpoly => + if(t2.kind == Tany) + return 1; + } + return tequal(t1, t2); +} + +# +# add the assumption that t1 and t2 are compatable +# +assumetcom(t1, t2: ref Type): int +{ + r1, r2: ref Type; + + if(t1.tcom == nil && t2.tcom == nil){ + tcomset += 2; + t1.tcom = t2.tcom = t1; + }else{ + if(t1.tcom == nil){ + r1 = t1; + t1 = t2; + t2 = r1; + } + for(r1 = t1.tcom; r1 != r1.tcom; r1 = r1.tcom) + ; + for(r2 = t2.tcom; r2 != nil && r2 != r2.tcom; r2 = r2.tcom) + ; + if(r1 == r2) + return 1; + if(r2 == nil) + tcomset++; + t2.tcom = t1; + for(; t2 != r1; t2 = r2){ + r2 = t2.tcom; + t2.tcom = r1; + } + } + return 0; +} + +cleartcomrec(t: ref Type): int +{ + n := 0; + for(; t != nil && (t.rec & TRcom) == TRcom; t = t.tof){ + t.rec &= ~TRcom; + if(t.tcom != nil){ + t.tcom = nil; + n++; + } + if(t.kind == Tadtpick) + n += cleartcomrec(t.tof); + if(t.kind == Tmodule) + t = t.tof; + for(id := t.ids; id != nil; id = id.next) + n += cleartcomrec(id.ty); + for(id = t.tags; id != nil; id = id.next) + n += cleartcomrec(id.ty); + for(id = t.polys; id != nil; id = id.next) + n += cleartcomrec(id.ty); + } + return n; +} + +# +# id1 and id2 are the fields in an adt or tuple +# simple structural check; ignore names +# +idcompat(id1, id2: ref Decl, any: int, inaorc: int): int +{ + for(; id1 != nil; id1 = id1.next){ + if(id1.store != Dfield) + continue; + while(id2 != nil && id2.store != Dfield) + id2 = id2.next; + if(id2 == nil + || id1.store != id2.store + || !rtcompat(id1.ty, id2.ty, any, inaorc)) + return 0; + id2 = id2.next; + } + while(id2 != nil && id2.store != Dfield) + id2 = id2.next; + return id2 == nil; +} + +# +# structural equality on types +# t->recid is used to detect cycles +# t->rec is used to clear t->recid +# +tequal(t1, t2: ref Type): int +{ + eqrec = 0; + eqset = 0; + ok := rtequal(t1, t2); + v := cleareqrec(t1) + cleareqrec(t2); + if(0 && v != eqset) + fatal("recid t1 "+stypeconv(t1)+" and t2 "+stypeconv(t2)+" not balanced in tequal: "+string v+" "+string eqset); + eqset = 0; + return ok; +} + +rtequal(t1, t2: ref Type): int +{ + # + # this is just a shortcut + # + if(t1 == t2) + return 1; + + if(t1 == nil || t2 == nil) + return 0; + if(t1.kind == Terror || t2.kind == Terror) + return 1; + + if(t1.kind != t2.kind) + return 0; + + if(t1.eq != nil && t2.eq != nil) + return t1.eq == t2.eq; + + t1.rec |= TReq; + t2.rec |= TReq; + case t1.kind{ + * => + fatal("bogus type "+stypeconv(t1)+" vs "+stypeconv(t2)+" in rtequal"); + return 0; + Tnone or + Tbig or + Tbyte or + Treal or + Tint or + Tstring => + # + # this should always be caught by t1 == t2 check + # + fatal("bogus value type "+stypeconv(t1)+" vs "+stypeconv(t2)+" in rtequal"); + return 1; + Tfix => + return sametree(t1.val, t2.val); + Tref or + Tlist or + Tarray or + Tchan => + if(t1.kind != Tref && assumeteq(t1, t2)) + return 1; + return rtequal(t1.tof, t2.tof); + Tfn => + if(t1.varargs != t2.varargs) + return 0; + if(!idequal(t1.ids, t2.ids, 0, storespace)) + return 0; + # if(!idequal(t1.polys, t2.polys, 1, nil)) + if(!pyequal(t1, t2)) + return 0; + return rtequal(t1.tof, t2.tof); + Ttuple or + Texception => + if(t1.kind != t2.kind || t1.cons != t2.cons) + return 0; + if(assumeteq(t1, t2)) + return 1; + return idequal(t1.ids, t2.ids, 0, storespace); + Tadt or + Tadtpick or + Tmodule => + if(assumeteq(t1, t2)) + return 1; + + # + # compare interfaces when comparing modules + # + if(t1.kind == Tmodule) + return idequal(t1.tof.ids, t2.tof.ids, 1, nil); + + # + # picked adts; check parent, + # assuming equiv picked fields, + # then check picked fields are equiv + # + if(t1.kind == Tadtpick && !rtequal(t1.decl.dot.ty, t2.decl.dot.ty)) + return 0; + + # + # adts with pick tags: check picked fields for equality + # + if(!idequal(t1.tags, t2.tags, 1, nil)) + return 0; + + # if(!idequal(t1.polys, t2.polys, 1, nil)) + if(!pyequal(t1, t2)) + return 0; + return idequal(t1.ids, t2.ids, 1, storespace); + Tpoly => + if(assumeteq(t1, t2)) + return 1; + if(t1.decl.sym != t2.decl.sym) + return 0; + return idequal(t1.ids, t2.ids, 1, nil); + } +} + +assumeteq(t1, t2: ref Type): int +{ + r1, r2: ref Type; + + if(t1.teq == nil && t2.teq == nil){ + eqrec++; + eqset += 2; + t1.teq = t2.teq = t1; + }else{ + if(t1.teq == nil){ + r1 = t1; + t1 = t2; + t2 = r1; + } + for(r1 = t1.teq; r1 != r1.teq; r1 = r1.teq) + ; + for(r2 = t2.teq; r2 != nil && r2 != r2.teq; r2 = r2.teq) + ; + if(r1 == r2) + return 1; + if(r2 == nil) + eqset++; + t2.teq = t1; + for(; t2 != r1; t2 = r2){ + r2 = t2.teq; + t2.teq = r1; + } + } + return 0; +} + +# +# checking structural equality for modules, adts, tuples, and fns +# +idequal(id1, id2: ref Decl, usenames: int, storeok: array of int): int +{ + # + # this is just a shortcut + # + if(id1 == id2) + return 1; + + for(; id1 != nil; id1 = id1.next){ + if(storeok != nil && !storeok[id1.store]) + continue; + while(id2 != nil && storeok != nil && !storeok[id2.store]) + id2 = id2.next; + if(id2 == nil + || usenames && id1.sym != id2.sym + || id1.store != id2.store + || id1.implicit != id2.implicit + || id1.cyc != id2.cyc + || (id1.dot == nil) != (id2.dot == nil) + || id1.dot != nil && id2.dot != nil && id1.dot.ty.kind != id2.dot.ty.kind + || !rtequal(id1.ty, id2.ty)) + return 0; + id2 = id2.next; + } + while(id2 != nil && storeok != nil && !storeok[id2.store]) + id2 = id2.next; + return id1 == nil && id2 == nil; +} + + +pyequal(t1: ref Type, t2: ref Type): int +{ + pt1, pt2: ref Type; + id1, id2: ref Decl; + + if(t1 == t2) + return 1; + id1 = t1.polys; + id2 = t2.polys; + for(; id1 != nil; id1 = id1.next){ + if(id2 == nil) + return 0; + pt1 = id1.ty; + pt2 = id2.ty; + if(!rtequal(pt1, pt2)){ + if(t1.tmap != nil) + pt1 = valtmap(pt1, t1.tmap); + if(t2.tmap != nil) + pt2 = valtmap(pt2, t2.tmap); + if(!rtequal(pt1, pt2)) + return 0; + } + id2 = id2.next; + } + return id1 == nil && id2 == nil; +} + +cleareqrec(t: ref Type): int +{ + n := 0; + for(; t != nil && (t.rec & TReq) == TReq; t = t.tof){ + t.rec &= ~TReq; + if(t.teq != nil){ + t.teq = nil; + n++; + } + if(t.kind == Tadtpick) + n += cleareqrec(t.decl.dot.ty); + if(t.kind == Tmodule) + t = t.tof; + for(id := t.ids; id != nil; id = id.next) + n += cleareqrec(id.ty); + for(id = t.tags; id != nil; id = id.next) + n += cleareqrec(id.ty); + for(id = t.polys; id != nil; id = id.next) + n += cleareqrec(id.ty); + } + return n; +} + +raisescompat(n1: ref Node, n2: ref Node): int +{ + if(n1 == n2) + return 1; + if(n2 == nil) + return 1; # no need to repeat in definition if given in declaration + if(n1 == nil) + return 0; + for((n1, n2) = (n1.left, n2.left); n1 != nil && n2 != nil; (n1, n2) = (n1.right, n2.right)){ + if(n1.left.decl != n2.left.decl) + return 0; + } + return n1 == n2; +} + +# t1 a polymorphic type +fnunify(t1: ref Type, t2: ref Type, tp: ref Tpair, swapped: int): (int, ref Tpair) +{ + id, ids: ref Decl; + sym: ref Sym; + ok: int; + + for(ids = t1.ids; ids != nil; ids = ids.next){ + sym = ids.sym; + (id, nil) = fnlookup(sym, t2); + if(id != nil) + usetype(id.ty); + if(id == nil){ + if(dowarn) + error(unifysrc.start, "type " + typeconv(t2) + " does not have a '" + sym.name + "' function"); + return (0, tp); + } + else if(id.ty.kind != Tfn){ + if(dowarn) + error(unifysrc.start, typeconv(id.ty) + " is not a function"); + return (0, tp); + } + else{ + (ok, tp) = rtunify(ids.ty, id.ty, tp, !swapped); + if(!ok){ + if(dowarn) + error(unifysrc.start, typeconv(ids.ty) + " and " + typeconv(id.ty) + " are not compatible wrt " + sym.name); + return (0, tp); + } + } + } + return (1, tp); +} + +fncleareqrec(t1: ref Type, t2: ref Type): int +{ + id, ids: ref Decl; + n: int; + + n = 0; + n += cleareqrec(t1); + n += cleareqrec(t2); + for(ids = t1.ids; ids != nil; ids = ids.next){ + (id, nil) = fnlookup(ids.sym, t2); + if(id == nil) + continue; + else{ + n += cleareqrec(ids.ty); + n += cleareqrec(id.ty); + } + } + return n; +} + +tunify(t1: ref Type, t2: ref Type): (int, ref Tpair) +{ + v: int; + p: ref Tpair; + + eqrec = 0; + eqset = 0; + (ok, tp) := rtunify(t1, t2, nil, 0); + v = cleareqrec(t1) + cleareqrec(t2); + for(p = tp; p != nil; p = p.nxt) + v += fncleareqrec(p.t1, p.t2); + if(0 && v != eqset) + fatal("recid t1 " + stypeconv(t1) + " and t2 " + stypeconv(t2) + " not balanced in tunify: " + string v + " " + string eqset); + return (ok, tp); +} + +rtunify(t1: ref Type, t2: ref Type, tp: ref Tpair, swapped: int): (int, ref Tpair) +{ + ok: int; + + t1 = valtmap(t1, tp); + t2 = valtmap(t2, tp); + if(t1 == t2) + return (1, tp); + if(t1 == nil || t2 == nil) + return (0, tp); + if(t1.kind == Terror || t2.kind == Terror) + return (1, tp); + if(t1.kind != Tpoly && t2.kind == Tpoly){ + (t1, t2) = (t2, t1); + swapped = !swapped; + } + if(t1.kind == Tpoly){ + # if(typein(t1, t2)) + # return (0, tp); + if(!tattr[t2.kind].isptr) + return (0, tp); + if(t2.kind != Tany) + tp = addtmap(t1, t2, tp); + return fnunify(t1, t2, tp, swapped); + } + if(t1.kind != Tany && t2.kind == Tany){ + (t1, t2) = (t2, t1); + swapped = !swapped; + } + if(t1.kind == Tadt && t1.tags != nil && t2.kind == Tadtpick && !swapped) + t2 = t2.decl.dot.ty; + if(t2.kind == Tadt && t2.tags != nil && t1.kind == Tadtpick && swapped) + t1 = t1.decl.dot.ty; + if(t1.kind != Tany && t1.kind != t2.kind) + return (0, tp); + t1.rec |= TReq; + t2.rec |= TReq; + case(t1.kind){ + * => + return (tequal(t1, t2), tp); + Tany => + return (tattr[t2.kind].isptr, tp); + Tref or + Tlist or + Tarray or + Tchan => + if(t1.kind != Tref && assumeteq(t1, t2)) + return (1, tp); + return rtunify(t1.tof, t2.tof, tp, swapped); + Tfn => + (ok, tp) = idunify(t1.ids, t2.ids, tp, swapped); + if(!ok) + return (0, tp); + (ok, tp) = idunify(t1.polys, t2.polys, tp, swapped); + if(!ok) + return (0, tp); + return rtunify(t1.tof, t2.tof, tp, swapped); + Ttuple => + if(assumeteq(t1, t2)) + return (1, tp); + return idunify(t1.ids, t2.ids, tp, swapped); + Tadt or + Tadtpick => + if(assumeteq(t1, t2)) + return (1, tp); + (ok, tp) = idunify(t1.polys, t2.polys, tp, swapped); + if(!ok) + return (0, tp); + (ok, tp) = idunify(t1.tags, t2.tags, tp, swapped); + if(!ok) + return (0, tp); + return idunify(t1.ids, t2.ids, tp, swapped); + Tmodule => + if(assumeteq(t1, t2)) + return (1, tp); + return idunify(t1.tof.ids, t2.tof.ids, tp, swapped); + Tpoly => + return (t1 == t2, tp); + } + return (1, tp); +} + +idunify(id1: ref Decl, id2: ref Decl, tp: ref Tpair, swapped: int): (int, ref Tpair) +{ + ok: int; + + if(id1 == id2) + return (1, tp); + for(; id1 != nil; id1 = id1.next){ + if(id2 == nil) + return (0, tp); + (ok, tp) = rtunify(id1.ty, id2.ty, tp, swapped); + if(!ok) + return (0, tp); + id2 = id2.next; + } + return (id1 == nil && id2 == nil, tp); +} + +polyequal(id1: ref Decl, id2: ref Decl): int +{ + # allow id2 list to have an optional for clause + ck2 := 0; + for(d := id2; d != nil; d = d.next) + if(d.ty.ids != nil) + ck2 = 1; + for(; id1 != nil; id1 = id1.next){ + if(id2 == nil + || id1.sym != id2.sym + || id1.ty.decl != nil && id2.ty.decl != nil && id1.ty.decl.sym != id2.ty.decl.sym) + return 0; + if(ck2 && !idequal(id1.ty.ids, id2.ty.ids, 1, nil)) + return 0; + id2 = id2.next; + } + return id1 == nil && id2 == nil; +} + +calltype(f: ref Type, a: ref Node, rt: ref Type): ref Type +{ + t: ref Type; + id, first, last: ref Decl; + + first = last = nil; + t = mktype(f.src.start, f.src.stop, Tfn, rt, nil); + if(f.kind == Tref) + t.polys = f.tof.polys; + else + t.polys = f.polys; + for( ; a != nil; a = a.right){ + id = mkdecl(f.src, Darg, a.left.ty); + if(last == nil) + first = id; + else + last.next = id; + last = id; + } + t.ids = first; + if(f.kind == Tref) + t = mktype(f.src.start, f.src.stop, Tref, t, nil); + return t; +} + +duptype(t: ref Type): ref Type +{ + nt: ref Type; + + nt = ref Type; + *nt = *t; + nt.ok &= ~(OKverify|OKref|OKclass|OKsized|OKcycsize|OKcyc); + nt.flags |= INST; + nt.eq = nil; + nt.sbl = -1; + if(t.decl != nil && (nt.kind == Tadt || nt.kind == Tadtpick || nt.kind == Ttuple)){ + nt.decl = dupdecl(t.decl); + nt.decl.ty = nt; + nt.decl.link = t.decl; + if(t.decl.dot != nil){ + nt.decl.dot = dupdecl(t.decl.dot); + nt.decl.dot.link = t.decl.dot; + } + } + else + nt.decl = nil; + return nt; +} + +dpolys(ids: ref Decl): int +{ + p: ref Decl; + + for(p = ids; p != nil; p = p.next) + if(tpolys(p.ty)) + return 1; + return 0; +} + +tpolys(t: ref Type): int +{ + v: int; + tyl: ref Typelist; + + if(t == nil) + return 0; + if(int(t.flags&(POLY|NOPOLY))) + return int(t.flags&POLY); + case(t.kind){ + * => + v = 0; + break; + Tarrow or + Tdot or + Tpoly => + v = 1; + break; + Tref or + Tlist or + Tarray or + Tchan => + v = tpolys(t.tof); + break; + Tid => + v = tpolys(t.decl.ty); + break; + Tinst => + for(tyl = t.tlist; tyl != nil; tyl = tyl.nxt) + if(tpolys(tyl.t)){ + v = 1; + break; + } + v = tpolys(t.tof); + break; + Tfn or + Tadt or + Tadtpick or + Ttuple or + Texception => + if(t.polys != nil){ + v = 1; + break; + } + if(int(t.rec&TRvis)) + return 0; + t.rec |= TRvis; + v = tpolys(t.tof) || dpolys(t.polys) || dpolys(t.ids) || dpolys(t.tags); + t.rec &= ~TRvis; + if(t.kind == Tadtpick && v == 0) + v = tpolys(t.decl.dot.ty); + break; + } + if(v) + t.flags |= POLY; + else + t.flags |= NOPOLY; + return v; +} + +doccurs(ids: ref Decl, tp: ref Tpair): int +{ + p: ref Decl; + + for(p = ids; p != nil; p = p.next){ + if(toccurs(p.ty, tp)) + return 1; + } + return 0; +} + +toccurs(t: ref Type, tp: ref Tpair): int +{ + o: int; + + if(t == nil) + return 0; + if(!int(t.flags&(POLY|NOPOLY))) + tpolys(t); + if(int(t.flags&NOPOLY)) + return 0; + case(t.kind){ + * => + fatal("unknown type " + string t.kind + " in toccurs"); + Tnone or + Tbig or + Tbyte or + Treal or + Tint or + Tstring or + Tfix or + Tmodule or + Terror => + return 0; + Tarrow or + Tdot => + return 1; + Tpoly => + return valtmap(t, tp) != t; + Tref or + Tlist or + Tarray or + Tchan => + return toccurs(t.tof, tp); + Tid => + return toccurs(t.decl.ty, tp); + Tinst => + for(tyl := t.tlist; tyl != nil; tyl = tyl.nxt) + if(toccurs(tyl.t, tp)) + return 1; + return toccurs(t.tof, tp); + Tfn or + Tadt or + Tadtpick or + Ttuple or + Texception => + if(int(t.rec&TRvis)) + return 0; + t.rec |= TRvis; + o = toccurs(t.tof, tp) || doccurs(t.polys, tp) || doccurs(t.ids, tp) || doccurs(t.tags, tp); + t.rec &= ~TRvis; + if(t.kind == Tadtpick && o == 0) + o = toccurs(t.decl.dot.ty, tp); + return o; + } + return 0; +} + +expandids(ids: ref Decl, adtt: ref Decl, tp: ref Tpair, sym: int): (ref Decl, ref Tpair) +{ + p, q, nids, last: ref Decl; + + nids = last = nil; + for(p = ids; p != nil; p = p.next){ + q = dupdecl(p); + (q.ty, tp) = expandtype(p.ty, nil, adtt, tp); + if(sym && q.ty.decl != nil) + q.sym = q.ty.decl.sym; + if(q.store == Dfn) + q.link = p; + if(nids == nil) + nids = q; + else + last.next = q; + last = q; + } + return (nids, tp); +} + +expandtype(t: ref Type, instt: ref Type, adtt: ref Decl, tp: ref Tpair): (ref Type, ref Tpair) +{ + nt: ref Type; + + if(t == nil) + return (nil, tp); + if(!toccurs(t, tp)) + return (t, tp); + case(t.kind){ + * => + fatal("unknown type " + string t.kind + " in expandtype"); + Tpoly => + return (valtmap(t, tp), tp); + Tref or + Tlist or + Tarray or + Tchan => + nt = duptype(t); + (nt.tof, tp) = expandtype(t.tof, nil, adtt, tp); + return (nt, tp); + Tid => + return expandtype(idtype(t), nil, adtt, tp); + Tdot => + return expandtype(dottype(t, adtt), nil, adtt, tp); + Tarrow => + return expandtype(arrowtype(t, adtt), nil, adtt, tp); + Tinst => + if((nt = valtmap(t, tp)) != t) + return (nt, tp); + (t, tp) = insttype(t, adtt, tp); + return expandtype(t, nil, adtt, tp); + Tfn or + Tadt or + Tadtpick or + Ttuple or + Texception => + if((nt = valtmap(t, tp)) != t) + return (nt, tp); + if(t.kind == Tadt) + adtt = t.decl; + nt = duptype(t); + tp = addtmap(t, nt, tp); + if(instt != nil) + tp = addtmap(instt, nt, tp); + (nt.tof, tp) = expandtype(t.tof, nil, adtt, tp); + (nt.polys, tp) = expandids(t.polys, adtt, tp, 1); + (nt.ids, tp) = expandids(t.ids, adtt, tp, 0); + (nt.tags, tp) = expandids(t.tags, adtt, tp, 0); + if(t.kind == Tadt){ + for(ids := nt.tags; ids != nil; ids = ids.next) + ids.ty.decl.dot = nt.decl; + } + if(t.kind == Tadtpick){ + (nt.decl.dot.ty, tp) = expandtype(t.decl.dot.ty, nil, adtt, tp); + } + if(t.tmap != nil){ + nt.tmap = nil; + for(p := t.tmap; p != nil; p = p.nxt) + nt.tmap = addtmap(valtmap(p.t1, tp), valtmap(p.t2, tp), nt.tmap); + } + return (nt, tp); + } + return (nil, tp); +} + +# +# create type signatures +# sign the same information used +# for testing type equality +# +sign(d: ref Decl): int +{ + t := d.ty; + if(t.sig != 0) + return t.sig; + + if(ispoly(d)) + rmfnptrs(d); + + sigend := -1; + sigalloc := 1024; + sig: array of byte; + while(sigend < 0 || sigend >= sigalloc){ + sigalloc *= 2; + sig = array[sigalloc] of byte; + eqrec = 0; + sigend = rtsign(t, sig, 0); + v := clearrec(t); + if(v != eqrec) + fatal("recid not balanced in sign: "+string v+" "+string eqrec); + eqrec = 0; + } + + if(signdump != "" && dotconv(d) == signdump){ + print("sign %s len %d\n", dotconv(d), sigend); + print("%s\n", string sig[:sigend]); + } + + md5sig := array[Keyring->MD5dlen] of {* => byte 0}; + md5(sig, sigend, md5sig, nil); + + for(i := 0; i < Keyring->MD5dlen; i += 4) + t.sig ^= int md5sig[i+0] | (int md5sig[i+1]<<8) | (int md5sig[i+2]<<16) | (int md5sig[i+3]<<24); + + if(debug['S']) + print("signed %s type %s len %d sig %#ux\n", dotconv(d), typeconv(t), sigend, t.sig); + return t.sig; +} + +SIGSELF: con byte 'S'; +SIGVARARGS: con byte '*'; +SIGCYC: con byte 'y'; +SIGREC: con byte '@'; + +sigkind := array[Tend] of +{ + Tnone => byte 'n', + Tadt => byte 'a', + Tadtpick => byte 'p', + Tarray => byte 'A', + Tbig => byte 'B', + Tbyte => byte 'b', + Tchan => byte 'C', + Treal => byte 'r', + Tfn => byte 'f', + Tint => byte 'i', + Tlist => byte 'L', + Tmodule => byte 'm', + Tref => byte 'R', + Tstring => byte 's', + Ttuple => byte 't', + Texception => byte 'e', + Tfix => byte 'x', + Tpoly => byte 'P', + + * => byte 0, +}; + +rtsign(t: ref Type, sig: array of byte, spos: int): int +{ + id: ref Decl; + + if(t == nil) + return spos; + + if(spos < 0 || spos + 8 >= len sig) + return -1; + + if(t.eq != nil && t.eq.id){ + if(t.eq.id < 0 || t.eq.id > eqrec) + fatal("sign rec "+typeconv(t)+" "+string t.eq.id+" "+string eqrec); + + sig[spos++] = SIGREC; + name := array of byte string t.eq.id; + if(spos + len name > len sig) + return -1; + sig[spos:] = name; + spos += len name; + return spos; + } + if(t.eq != nil){ + eqrec++; + t.eq.id = eqrec; + } + + kind := sigkind[t.kind]; + sig[spos++] = kind; + if(kind == byte 0) + fatal("no sigkind for "+typeconv(t)); + + t.rec = byte 1; + case t.kind{ + * => + fatal("bogus type "+stypeconv(t)+" in rtsign"); + return -1; + Tnone or + Tbig or + Tbyte or + Treal or + Tint or + Tstring or + Tpoly => + return spos; + Tfix => + name := array of byte string t.val.c.rval; + if(spos + len name - 1 >= len sig) + return -1; + sig[spos: ] = name; + spos += len name; + return spos; + Tref or + Tlist or + Tarray or + Tchan => + return rtsign(t.tof, sig, spos); + Tfn => + if(t.varargs != byte 0) + sig[spos++] = SIGVARARGS; + if(t.polys != nil) + spos = idsign(t.polys, 0, sig, spos); + spos = idsign(t.ids, 0, sig, spos); + if(t.eraises != nil) + spos = raisessign(t.eraises, sig, spos); + return rtsign(t.tof, sig, spos); + Ttuple => + return idsign(t.ids, 0, sig, spos); + Tadt => + # + # this is a little different than in rtequal, + # since we flatten the adt we used to represent the globals + # + if(t.eq == nil){ + if(t.decl.sym.name != ".mp") + fatal("no t.eq field for "+typeconv(t)); + spos--; + for(id = t.ids; id != nil; id = id.next){ + spos = idsign1(id, 1, sig, spos); + if(spos < 0 || spos >= len sig) + return -1; + sig[spos++] = byte ';'; + } + return spos; + } + if(t.polys != nil) + spos = idsign(t.polys, 0, sig, spos); + spos = idsign(t.ids, 1, sig, spos); + if(spos < 0 || t.tags == nil) + return spos; + + # + # convert closing ')' to a ',', then sign any tags + # + sig[spos-1] = byte ','; + for(tg := t.tags; tg != nil; tg = tg.next){ + name := array of byte (tg.sym.name + "=>"); + if(spos + len name > len sig) + return -1; + sig[spos:] = name; + spos += len name; + + spos = rtsign(tg.ty, sig, spos); + if(spos < 0 || spos >= len sig) + return -1; + + if(tg.next != nil) + sig[spos++] = byte ','; + } + if(spos >= len sig) + return -1; + sig[spos++] = byte ')'; + return spos; + Tadtpick => + spos = idsign(t.ids, 1, sig, spos); + if(spos < 0) + return spos; + return rtsign(t.decl.dot.ty, sig, spos); + Tmodule => + if(t.tof.linkall == byte 0) + fatal("signing a narrowed module"); + + if(spos >= len sig) + return -1; + sig[spos++] = byte '{'; + for(id = t.tof.ids; id != nil; id = id.next){ + if(id.tag) + continue; + if(id.sym.name == ".mp"){ + spos = rtsign(id.ty, sig, spos); + if(spos < 0) + return -1; + continue; + } + spos = idsign1(id, 1, sig, spos); + if(spos < 0 || spos >= len sig) + return -1; + sig[spos++] = byte ';'; + } + if(spos >= len sig) + return -1; + sig[spos++] = byte '}'; + return spos; + } +} + +idsign(id: ref Decl, usenames: int, sig: array of byte, spos: int): int +{ + if(spos >= len sig) + return -1; + sig[spos++] = byte '('; + first := 1; + for(; id != nil; id = id.next){ + if(id.store == Dlocal) + fatal("local "+id.sym.name+" in idsign"); + + if(!storespace[id.store]) + continue; + + if(!first){ + if(spos >= len sig) + return -1; + sig[spos++] = byte ','; + } + + spos = idsign1(id, usenames, sig, spos); + if(spos < 0) + return -1; + first = 0; + } + if(spos >= len sig) + return -1; + sig[spos++] = byte ')'; + return spos; +} + +idsign1(id: ref Decl, usenames: int, sig: array of byte, spos: int): int +{ + if(usenames){ + name := array of byte (id.sym.name+":"); + if(spos + len name >= len sig) + return -1; + sig[spos:] = name; + spos += len name; + } + + if(spos + 2 >= len sig) + return -1; + + if(id.implicit != byte 0) + sig[spos++] = SIGSELF; + + if(id.cyc != byte 0) + sig[spos++] = SIGCYC; + + return rtsign(id.ty, sig, spos); +} + +raisessign(n: ref Node, sig: array of byte, spos: int): int +{ + if(spos >= len sig) + return -1; + sig[spos++] = byte '('; + for(nn := n.left; nn != nil; nn = nn.right){ + s := array of byte nn.left.decl.sym.name; + if(spos+len s - 1 >= len sig) + return -1; + sig[spos: ] = s; + spos += len s; + if(nn.right != nil){ + if(spos >= len sig) + return -1; + sig[spos++] = byte ','; + } + } + if(spos >= len sig) + return -1; + sig[spos++] = byte ')'; + return spos; +} + +clearrec(t: ref Type): int +{ + id: ref Decl; + + n := 0; + for(; t != nil && t.rec != byte 0; t = t.tof){ + t.rec = byte 0; + if(t.eq != nil && t.eq.id != 0){ + t.eq.id = 0; + n++; + } + if(t.kind == Tmodule){ + for(id = t.tof.ids; id != nil; id = id.next) + n += clearrec(id.ty); + return n; + } + if(t.kind == Tadtpick) + n += clearrec(t.decl.dot.ty); + for(id = t.ids; id != nil; id = id.next) + n += clearrec(id.ty); + for(id = t.tags; id != nil; id = id.next) + n += clearrec(id.ty); + for(id = t.polys; id != nil; id = id.next) + n += clearrec(id.ty); + } + return n; +} + +# must a variable of the given type be zeroed ? (for uninitialized declarations inside loops) +tmustzero(t : ref Type) : int +{ + if(t==nil) + return 0; + if(tattr[t.kind].isptr) + return 1; + if(t.kind == Tadtpick) + t = t.tof; + if(t.kind == Ttuple || t.kind == Tadt) + return mustzero(t.ids); + return 0; +} + +mustzero(decls : ref Decl) : int +{ + d : ref Decl; + + for (d = decls; d != nil; d = d.next) + if (tmustzero(d.ty)) + return 1; + return 0; +} + +typeconv(t: ref Type): string +{ + if(t == nil) + return "nothing"; + return tprint(t); +} + +stypeconv(t: ref Type): string +{ + if(t == nil) + return "nothing"; + return stprint(t); +} + +tprint(t: ref Type): string +{ + id: ref Decl; + + if(t == nil) + return ""; + s := ""; + if(t.kind < 0 || t.kind >= Tend){ + s += "kind "; + s += string t.kind; + return s; + } + if(t.pr != byte 0 && t.decl != nil){ + if(t.decl.dot != nil && !isimpmod(t.decl.dot.sym)){ + s += t.decl.dot.sym.name; + s += "->"; + } + s += t.decl.sym.name; + return s; + } + t.pr = byte 1; + case t.kind{ + Tarrow => + s += tprint(t.tof); + s += "->"; + s += t.decl.sym.name; + Tdot => + s += tprint(t.tof); + s += "."; + s += t.decl.sym.name; + Tid or + Tpoly => + s += t.decl.sym.name; + Tinst => + s += tprint(t.tof); + s += "["; + for(tyl := t.tlist; tyl != nil; tyl = tyl.nxt){ + s += tprint(tyl.t); + if(tyl.nxt != nil) + s += ", "; + } + s += "]"; + Tint or + Tbig or + Tstring or + Treal or + Tbyte or + Tany or + Tnone or + Terror or + Tainit or + Talt or + Tcase or + Tcasel or + Tcasec or + Tgoto or + Tiface or + Texception or + Texcept => + s += kindname[t.kind]; + Tfix => + s += kindname[t.kind] + "(" + expconv(t.val) + ")"; + Tref => + s += "ref "; + s += tprint(t.tof); + Tchan or + Tarray or + Tlist => + s += kindname[t.kind]; + s += " of "; + s += tprint(t.tof); + Tadtpick => + s += t.decl.dot.sym.name + "." + t.decl.sym.name; + Tadt => + if(t.decl.dot != nil && !isimpmod(t.decl.dot.sym)) + s += t.decl.dot.sym.name + "->"; + s += t.decl.sym.name; + if(t.polys != nil){ + s += "["; + for(id = t.polys; id != nil; id = id.next){ + if(t.tmap != nil) + s += tprint(valtmap(id.ty, t.tmap)); + else + s += id.sym.name; + if(id.next != nil) + s += ", "; + } + s += "]"; + } + Tmodule => + s += t.decl.sym.name; + Ttuple => + s += "("; + for(id = t.ids; id != nil; id = id.next){ + s += tprint(id.ty); + if(id.next != nil) + s += ", "; + } + s += ")"; + Tfn => + s += "fn"; + if(t.polys != nil){ + s += "["; + for(id = t.polys; id != nil; id = id.next){ + s += id.sym.name; + if(id.next != nil) + s += ", "; + } + s += "]"; + } + s += "("; + for(id = t.ids; id != nil; id = id.next){ + if(id.sym == nil) + s += "nil: "; + else{ + s += id.sym.name; + s += ": "; + } + if(id.implicit != byte 0) + s += "self "; + s += tprint(id.ty); + if(id.next != nil) + s += ", "; + } + if(t.varargs != byte 0 && t.ids != nil) + s += ", *"; + else if(t.varargs != byte 0) + s += "*"; + if(t.tof != nil && t.tof.kind != Tnone){ + s += "): "; + s += tprint(t.tof); + }else + s += ")"; + * => + yyerror("tprint: unknown type kind "+string t.kind); + } + t.pr = byte 0; + return s; +} + +stprint(t: ref Type): string +{ + if(t == nil) + return ""; + s := ""; + case t.kind{ + Tid => + s += "id "; + s += t.decl.sym.name; + Tadt or + Tadtpick or + Tmodule => + return kindname[t.kind] + " " + tprint(t); + } + return tprint(t); +} + +# generalize ref P.A, ref P.B to ref P + +# tparent(t1: ref Type, t2: ref Type): ref Type +# { +# if(t1 == nil || t2 == nil || t1.kind != Tref || t2.kind != Tref) +# return t1; +# t1 = t1.tof; +# t2 = t2.tof; +# if(t1 == nil || t2 == nil || t1.kind != Tadtpick || t2.kind != Tadtpick) +# return t1; +# t1 = t1.decl.dot.ty; +# t2 = t2.decl.dot.ty; +# if(tequal(t1, t2)) +# return mktype(t1.src.start, t1.src.stop, Tref, t1, nil); +# return t1; +# } + +tparent0(t1: ref Type, t2: ref Type): int +{ + id1, id2: ref Decl; + + if(t1 == t2) + return 1; + if(t1 == nil || t2 == nil) + return 0; + if(t1.kind == Tadt && t2.kind == Tadtpick) + t2 = t2.decl.dot.ty; + if(t1.kind == Tadtpick && t2.kind == Tadt) + t1 = t1.decl.dot.ty; + if(t1.kind != t2.kind) + return 0; + case(t1.kind){ + * => + fatal("unknown type " + string t1.kind + " v " + string t2.kind + " in tparent"); + break; + Terror or + Tstring or + Tnone or + Tint or + Tbig or + Tbyte or + Treal or + Tany => + return 1; + Texception or + Tfix or + Tfn or + Tadt or + Tmodule or + Tpoly => + return tcompat(t1, t2, 0); + Tref or + Tlist or + Tarray or + Tchan => + return tparent0(t1.tof, t2.tof); + Ttuple => + for((id1, id2) = (t1.ids, t2.ids); id1 != nil && id2 != nil; (id1, id2) = (id1.next, id2.next)) + if(!tparent0(id1.ty, id2.ty)) + return 0; + return id1 == nil && id2 == nil; + Tadtpick => + return tequal(t1.decl.dot.ty, t2.decl.dot.ty); + } + return 0; +} + +tparent1(t1: ref Type, t2: ref Type): ref Type +{ + t, nt: ref Type; + id, id1, id2, idt: ref Decl; + + if(t1.kind == Tadt && t2.kind == Tadtpick) + t2 = t2.decl.dot.ty; + if(t1.kind == Tadtpick && t2.kind == Tadt) + t1 = t1.decl.dot.ty; + case(t1.kind){ + * => + return t1; + Tref or + Tlist or + Tarray or + Tchan => + t = tparent1(t1.tof, t2.tof); + if(t == t1.tof) + return t1; + return mktype(t1.src.start, t1.src.stop, t1.kind, t, nil); + Ttuple => + nt = nil; + id = nil; + for((id1, id2) = (t1.ids, t2.ids); id1 != nil && id2 != nil; (id1, id2) = (id1.next, id2.next)){ + t = tparent1(id1.ty, id2.ty); + if(t != id1.ty){ + if(nt == nil){ + nt = mktype(t1.src.start, t1.src.stop, Ttuple, nil, dupdecls(t1.ids)); + for((id, idt) = (nt.ids, t1.ids); idt != id1; (id, idt) = (id.next, idt.next)) + ; + } + id.ty = t; + } + if(id != nil) + id = id.next; + } + if(nt == nil) + return t1; + return nt; + Tadtpick => + if(tequal(t1, t2)) + return t1; + return t1.decl.dot.ty; + } + return t1; +} + +tparent(t1: ref Type, t2: ref Type): ref Type +{ + if(tparent0(t1, t2)) + return tparent1(t1, t2); + return t1; +} + +# +# make the tuple type used to initialize an exception type +# +mkexbasetype(t: ref Type): ref Type +{ + if(t.cons == byte 0) + fatal("mkexbasetype on non-constant"); + last := mkids(t.decl.src, nil, tstring, nil); + last.store = Dfield; + nt := mktype(t.src.start, t.src.stop, Texception, nil, last); + nt.cons = byte 0; + new := mkids(t.decl.src, nil, tint, nil); + new.store = Dfield; + last.next = new; + last = new; + for(id := t.ids; id != nil; id = id.next){ + new = ref *id; + new.cyc = byte 0; + last.next = new; + last = new; + } + last.next = nil; + return usetype(nt); +} + +# +# make an instantiated exception type +# +mkextype(t: ref Type): ref Type +{ + nt: ref Type; + + if(t.cons == byte 0) + fatal("mkextype on non-constant"); + if(t.tof != nil) + return t.tof; + nt = copytypeids(t); + nt.cons = byte 0; + t.tof = usetype(nt); + return t.tof; +} + +# +# convert an instantiated exception type to it's underlying type +# +mkextuptype(t: ref Type): ref Type +{ + id: ref Decl; + nt: ref Type; + + if(int t.cons) + return t; + if(t.tof != nil) + return t.tof; + id = t.ids; + if(id == nil) + nt = t; + else if(id.next == nil) + nt = id.ty; + else{ + nt = copytypeids(t); + nt.cons = byte 0; + nt.kind = Ttuple; + } + t.tof = usetype(nt); + return t.tof; +} + +ckfix(t: ref Type, max: real) +{ + s := t.val.c.rval; + if(max == 0.0) + k := (big 1<<32) - big 1; + else + k = big 2 * big (max/s) + big 1; + x := big 1; + for(p := 0; k > x; p++) + x *= big 2; + if(p == 0 || p > 32){ + error(t.src.start, "cannot fit fixed type into an int"); + return; + } + if(p < 32) + t.val.c.rval /= real (1<<(32-p)); +} + +scale(t: ref Type): real +{ + n: ref Node; + + if(t.kind == Tint || t.kind == Treal) + return 1.0; + if(t.kind != Tfix) + fatal("scale() on non fixed point type"); + n = t.val; + if(n.op != Oconst) + fatal("non constant scale"); + if(n.ty != treal) + fatal("non real scale"); + return n.c.rval; +} + +scale2(f: ref Type, t: ref Type): real +{ + return scale(f)/scale(t); +} + +# put x in normal form +nf(x: real): (int, int) +{ + p: int; + m: real; + + p = 0; + m = x; + while(m >= 1.0){ + p++; + m /= 2.0; + } + while(m < 0.5){ + p--; + m *= 2.0; + } + m *= real (1<<16)*real (1<<15); + if(m >= real 16r7fffffff - 0.5) + return (p, 16r7fffffff); + return (p, int m); +} + +ispow2(x: real): int +{ + m: int; + + (nil, m) = nf(x); + if(m != 1<<30) + return 0; + return 1; +} + +round(x: real, n: int): (int, int) +{ + if(n != 31) + fatal("not 31 in round"); + return nf(x); +} + +fixmul2(sx: real, sy: real, sr: real): (int, int, int) +{ + k, n, a: int; + alpha: real; + + alpha = (sx*sy)/sr; + n = 31; + (k, a) = round(1.0/alpha, n); + return (IMULX, 1-k, 0); +} + +fixdiv2(sx: real, sy: real, sr: real): (int, int, int) +{ + k, n, b: int; + beta: real; + + beta = sx/(sy*sr); + n = 31; + (k, b) = round(beta, n); + return (IDIVX, k-1, 0); +} + +fixmul(sx: real, sy: real, sr: real): (int, int, int) +{ + k, m, n, a, v: int; + W: big; + alpha, eps: real; + + alpha = (sx*sy)/sr; + if(ispow2(alpha)) + return fixmul2(sx, sy, sr); + n = 31; + (k, a) = round(1.0/alpha, n); + m = n-k; + if(m < -n-1) + return (IMOVW, 0, 0); # result is zero whatever the values + v = 0; + W = big 0; + eps = real(1<<m)/(alpha*real(a)) - 1.0; + if(eps < 0.0){ + v = a-1; + eps = -eps; + } + if(m < 0 && real(1<<n)*eps*real(a) >= real(a)-1.0+real(1<<m)) + W = (big(1)<<(-m)) - big 1; + if(v != 0 || W != big 0) + m = m<<2|(v != 0)<<1|(W != big 0); + if(v == 0 && W == big 0) + return (IMULX0, m, a); + else + return (IMULX1, m, a); +} + +fixdiv(sx: real, sy: real, sr: real): (int, int, int) +{ + k, m, n, b, v: int; + W: big; + beta, eps: real; + + beta = sx/(sy*sr); + if(ispow2(beta)) + return fixdiv2(sx, sy, sr); + n = 31; + (k, b) = round(beta, n); + m = k-n; + if(m <= -2*n) + return (IMOVW, 0, 0); #result is zero whatever the values + v = 0; + W = big 0; + eps = (real(1<<m)*real(b))/beta - 1.0; + if(eps < 0.0) + v = 1; + if(m < 0) + W = (big(1)<<(-m)) - big 1; + if(v != 0 || W != big 0) + m = m<<2|(v != 0)<<1|(W != big 0); + if(v == 0 && W == big 0) + return (IDIVX0, m, b); + else + return (IDIVX1, m, b); +} + +fixcast(sx: real, sr: real): (int, int, int) +{ + (op, p, a) := fixmul(sx, 1.0, sr); + return (op-IMULX+ICVTXX, p, a); +} + +fixop(op: int, tx: ref Type, ty: ref Type, tr: ref Type): (int, int, int) +{ + sx, sy, sr: real; + + sx = scale(tx); + sy = scale(ty); + sr = scale(tr); + if(op == IMULX) + return fixmul(sx, sy, sr); + else if(op == IDIVX) + return fixdiv(sx, sy, sr); + else + return fixcast(sx, sr); +} + +ispoly(d: ref Decl): int +{ + if(d == nil) + return 0; + t := d.ty; + if(t.kind == Tfn){ + if(t.polys != nil) + return 1; + if((d = d.dot) == nil) + return 0; + t = d.ty; + return t.kind == Tadt && t.polys != nil; + } + return 0; +} + +ispolyadt(t: ref Type): int +{ + return (t.kind == Tadt || t.kind == Tadtpick) && t.polys != nil && (t.flags & INST) == byte 0; +} + +polydecl(ids: ref Decl): ref Decl +{ + id: ref Decl; + t: ref Type; + + for(id = ids; id != nil; id = id.next){ + t = mktype(id.src.start, id.src.stop, Tpoly, nil, nil); + id.ty = t; + t.decl = id; + } + return ids; +} + +# try to convert an expression tree to a type +exptotype(n: ref Node): ref Type +{ + t, tt: ref Type; + d: ref Decl; + tll: ref Typelist; + src: Src; + + if(n == nil) + return nil; + t = nil; + case(n.op){ + Oname => + if((d = n.decl) != nil && d.store == Dtype) + t = d.ty; + Otype or Ochan => + t = n.ty; + Oref => + t = exptotype(n.left); + if(t != nil) + t = mktype(n.src.start, n.src.stop, Tref, t, nil); + Odot => + t = exptotype(n.left); + if(t != nil){ + d = namedot(t.tags, n.right.decl.sym); + if(d == nil) + t = nil; + else + t = d.ty; + } + if(t == nil) + t = exptotype(n.right); + Omdot => + t = exptotype(n.right); + Oindex => + t = exptotype(n.left); + if(t != nil){ + src = n.src; + tll = nil; + for(n = n.right; n != nil; n = n.right){ + if(n.op == Oseq) + tt = exptotype(n.left); + else + tt = exptotype(n); + if(tt == nil) + return nil; + tll = addtype(tt, tll); + if(n.op != Oseq) + break; + } + t = mkinsttype(src, t, tll); + } + } + return t; +} + +uname(im: ref Decl): string +{ + s := ""; + for(p := im; p != nil; p = p.next){ + s += p.sym.name; + if(p.next != nil) + s += "+"; + } + return s; +} + +# check all implementation modules have consistent declarations +# and create their union if needed +# +modimp(dl: ref Dlist, im: ref Decl): ref Decl +{ + u, d, dd, ids, dot, last: ref Decl; + s: ref Sym; + + if(dl.next == nil) + return dl.d; + dl0 := dl; + sg0 := 0; + un := uname(im); + installids(Dglobal, mkids(dl.d.src, enter(".m."+un, 0), tnone, nil)); + u = dupdecl(dl.d); + u.sym = enter(un, 0); + u.sym.decl = u; + u.ty = mktype(u.src.start, u.src.stop, Tmodule, nil, nil); + u.ty.decl = u; + for( ; dl != nil; dl = dl.next){ + d = dl.d; + ids = d.ty.tof.ids; # iface + if(ids != nil && ids.store == Dglobal) # .mp + sg := sign(ids); + else + sg = 0; + if(dl == dl0) + sg0 = sg; + else if(sg != sg0) + error(d.src.start, d.sym.name + "'s module data not consistent with that of " + dl0.d.sym.name + "\n"); + for(ids = d.ty.ids; ids != nil; ids = ids.next){ + s = ids.sym; + if(s.decl != nil && s.decl.scope >= scope){ + if(ids == s.decl){ + dd = dupdecl(ids); + if(u.ty.ids == nil) + u.ty.ids = dd; + else + last.next = dd; + last = dd; + continue; + } + dot = s.decl.dot; + if(s.decl.store != Dwundef && dot != nil && dot != d && isimpmod(dot.sym) && dequal(ids, s.decl, 1)) + ids.refs = s.decl.refs; + else + redecl(ids); + ids.init = s.decl.init; + } + } + } + u.ty = usetype(u.ty); + return u; +} + +modres(d: ref Decl) +{ + ids, id, n, i: ref Decl; + t: ref Type; + + for(ids = d.ty.ids; ids != nil; ids = ids.next){ + id = ids.sym.decl; + if(ids != id){ + n = ids.next; + i = ids.iface; + t = ids.ty; + *ids = *id; + ids.next = n; + ids.iface = i; + ids.ty = t; + } + } +} + +# update the fields of duplicate declarations in other implementation modules +# and their union +# +modresolve() +{ + dl: ref Dlist; + + dl = impdecls; + if(dl.next == nil) + return; + for( ; dl != nil; dl = dl.next) + modres(dl.d); + modres(impdecl); +} diff --git a/appl/cmd/listen.b b/appl/cmd/listen.b new file mode 100644 index 00000000..25869223 --- /dev/null +++ b/appl/cmd/listen.b @@ -0,0 +1,261 @@ +implement Listen; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; +include "keyring.m"; + keyring: Keyring; +include "security.m"; + auth: Auth; +include "sh.m"; + sh: Sh; + Context: import sh; + +Listen: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +badmodule(p: string) +{ + sys->fprint(stderr(), "listen: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +serverkey: ref Keyring->Authinfo; +verbose := 0; + +init(drawctxt: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + auth = load Auth Auth->PATH; + if (auth == nil) + badmodule(Auth->PATH); + sh = load Sh Sh->PATH; + if (sh == nil) + badmodule(Sh->PATH); + arg := load Arg Arg->PATH; + if (arg == nil) + badmodule(Arg->PATH); + auth->init(); + algs: list of string; + arg->init(argv); + keyfile: string; + initscript: string; + doauth := 1; + synchronous := 0; + trusted := 0; + arg->setusage("listen [-i {initscript}] [-Ast] [-k keyfile] [-a alg]... addr command [arg...]"); + while ((opt := arg->opt()) != 0) { + case opt { + 'a' => + algs = arg->earg() :: algs; + 'A' => + doauth = 0; + 'f' or + 'k' => + keyfile = arg->earg(); + if (! (keyfile[0] == '/' || (len keyfile > 2 && keyfile[0:2] == "./"))) + keyfile = "/usr/" + user() + "/keyring/" + keyfile; + 'i' => + initscript = arg->earg(); + 'v' => + verbose = 1; + 's' => + synchronous = 1; + 't' => + trusted = 1; + * => + arg->usage(); + } + } + if (doauth && algs == nil) + algs = getalgs(); + if (algs != nil) { + if (keyfile == nil) + keyfile = "/usr/" + user() + "/keyring/default"; + serverkey = keyring->readauthinfo(keyfile); + if (serverkey == nil) { + sys->fprint(stderr(), "listen: cannot read %s: %r\n", keyfile); + raise "fail:bad keyfile"; + } + } + if(!trusted){ + sys->unmount(nil, "/mnt/keys"); # should do for now + # become none? + } + + argv = arg->argv(); + n := len argv; + if (n < 2) + arg->usage(); + arg = nil; + + sync := chan[1] of string; + spawn listen(drawctxt, hd argv, tl argv, algs, initscript, sync); + e := <-sync; + if(e != nil) + raise "fail:" + e; + if(synchronous){ + e = <-sync; + if(e != nil) + raise "fail:" + e; + } +} + +listen(drawctxt: ref Draw->Context, addr: string, argv: list of string, + algs: list of string, initscript: string, sync: chan of string) +{ + { + listen1(drawctxt, addr, argv, algs, initscript, sync); + } exception e { + "fail:*" => + sync <-= e; + } +} + +listen1(drawctxt: ref Draw->Context, addr: string, argv: list of string, + algs: list of string, initscript: string, sync: chan of string) +{ + sys->pctl(Sys->FORKFD, nil); + + ctxt := Context.new(drawctxt); + (ok, acon) := sys->announce(addr); + if (ok == -1) { + sys->fprint(stderr(), "listen: failed to announce on '%s': %r\n", addr); + sync <-= "cannot announce"; + exit; + } + ctxt.set("user", nil); + if (initscript != nil) { + ctxt.setlocal("net", ref Sh->Listnode(nil, acon.dir) :: nil); + ctxt.run(ref Sh->Listnode(nil, initscript) :: nil, 0); + initscript = nil; + } + + # make sure the shell command is parsed only once. + cmd := sh->stringlist2list(argv); + if((hd argv) != nil && (hd argv)[0] == '{'){ + (c, e) := sh->parse(hd argv); + if(c == nil){ + sys->fprint(stderr(), "listen: %s\n", e); + sync <-= "parse error"; + exit; + } + cmd = ref Sh->Listnode(c, hd argv) :: tl cmd; + } + + sync <-= nil; + listench := chan of (int, Sys->Connection); + authch := chan of (string, Sys->Connection); + spawn listener(listench, acon, addr); + for (;;) { + user := ""; + ccon: Sys->Connection; + alt { + (lok, c) := <-listench => + if (lok == -1){ + sync <-= "listen"; + exit; + } + if (algs != nil) { + spawn authenticator(authch, c, algs, addr); + continue; + } + ccon = c; + (user, ccon) = <-authch => + ; + } + if (user != nil) + ctxt.set("user", sh->stringlist2list(user :: nil)); + ctxt.set("net", ref Sh->Listnode(nil, ccon.dir) :: nil); + + # XXX could do this in a separate process too, to + # allow new connections to arrive and start authenticating + # while the shell command is still running. + sys->dup(ccon.dfd.fd, 0); + sys->dup(ccon.dfd.fd, 1); + ccon.dfd = ccon.cfd = nil; + ctxt.run(cmd, 0); + sys->dup(2, 0); + sys->dup(2, 1); + } +} + +listener(listench: chan of (int, Sys->Connection), c: Sys->Connection, addr: string) +{ + for (;;) { + (ok, nc) := sys->listen(c); + if (ok == -1) { + sys->fprint(stderr(), "listen: listen error on '%s': %r\n", addr); + listench <-= (-1, nc); + exit; + } + if (verbose) + sys->fprint(stderr(), "listen: got connection on %s from %s", + addr, readfile(nc.dir + "/remote")); + nc.dfd = sys->open(nc.dir + "/data", Sys->ORDWR); + if (nc.dfd == nil) + sys->fprint(stderr(), "listen: cannot open %s: %r\n", nc.dir + "/data"); + else{ + if(nc.cfd != nil) + sys->fprint(nc.cfd, "keepalive"); + listench <-= (ok, nc); + } + } +} + +authenticator(authch: chan of (string, Sys->Connection), + c: Sys->Connection, algs: list of string, addr: string) +{ + err: string; + (c.dfd, err) = auth->server(algs, serverkey, c.dfd, 0); + if (c.dfd == nil) { + sys->fprint(stderr(), "listen: auth on %s failed: %s\n", addr, err); + return; + } + if (verbose) + sys->fprint(stderr(), "listen: authenticated on %s as %s\n", addr, err); + authch <-= (err, c); +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + +user(): string +{ + u := readfile("/dev/user"); + if (u == nil) + return "nobody"; + return u; +} + +readfile(f: string): string +{ + fd := sys->open(f, sys->OREAD); + if(fd == nil) + return nil; + + buf := array[1024] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return nil; + + return string buf[0:n]; +} + +getalgs(): list of string +{ + sslctl := readfile("#D/clone"); + if (sslctl == nil) { + sslctl = readfile("#D/ssl/clone"); + if (sslctl == nil) + return nil; + sslctl = "#D/ssl/" + sslctl; + } else + sslctl = "#D/" + sslctl; + (nil, algs) := sys->tokenize(readfile(sslctl + "/encalgs") + " " + readfile(sslctl + "/hashalgs"), " \t\n"); + return "none" :: algs; +} diff --git a/appl/cmd/lockfs.b b/appl/cmd/lockfs.b new file mode 100644 index 00000000..1b958de2 --- /dev/null +++ b/appl/cmd/lockfs.b @@ -0,0 +1,773 @@ +implement Lockfs; +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; +include "draw.m"; +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; +include "styxlib.m"; + styxlib: Styxlib; + Dirtab, Styxserver, Chan, + devdir, + Eperm, Ebadfid, Eexists, Enotdir, Enotfound, Einuse: import styxlib; +include "arg.m"; +include "keyring.m"; + keyring: Keyring; +include "security.m"; + auth: Auth; + +Lockfs: module { + init: fn(nil: ref Draw->Context, argv: list of string); + dirgen: fn(srv: ref Styxlib->Styxserver, c: ref Styxlib->Chan, + tab: array of Styxlib->Dirtab, i: int): (int, Sys->Dir); +}; + +Elocked: con "file is locked"; + +devgen: Dirgenmod; + +Openreq: adt { + srv: ref Styxserver; + tag: int; + omode: int; + c: ref Chan; + uproc: Uproc; +}; + +Lockqueue: adt { + h: list of ref Openreq; + t: list of ref Openreq; + put: fn(q: self ref Lockqueue, s: ref Openreq); + get: fn(q: self ref Lockqueue): ref Openreq; + peek: fn(q: self ref Lockqueue): ref Openreq; + flush: fn(q: self ref Lockqueue, srv: ref Styxserver, tag: int); +}; + +Lockfile: adt { + waitq: ref Lockqueue; + fd: ref Sys->FD; + readers: int; + writers: int; + d: Sys->Dir; +}; + +Ureq: adt { + fname: string; + pick { + Open => + omode: int; + Create => + omode: int; + perm: int; + Remove => + Wstat => + dir: Sys->Dir; + } +}; + +Uproc: type chan of (ref Ureq, chan of (ref Sys->FD, string)); + +maxqidpath := big 1; +locks: list of ref Lockfile; +lockdir: string; +authinfo: ref Keyring->Authinfo; +timefd: ref Sys->FD; + +MAXCONN: con 20; + +verbose := 0; + +usage() +{ + sys->fprint(stderr, "usage: lockfs [-A] [-a alg]... [-p addr] dir [mountpoint]\n"); + raise "fail:usage"; +} + +badmodule(p: string) +{ + sys->fprint(stderr, "lockfs: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + styx = load Styx Styx->PATH; + if (styx == nil) + badmodule(Styx->PATH); + styx->init(); + styxlib = load Styxlib Styxlib->PATH; + if (styxlib == nil) + badmodule(Styxlib->PATH); + styxlib->init(styx); + devgen = load Dirgenmod "$self"; + if (devgen == nil) + badmodule("self as Dirgenmod"); + timefd = sys->open("/dev/time", sys->OREAD); + if (timefd == nil) { + sys->fprint(stderr, "lockfs: cannot open /dev/time: %r\n"); + raise "fail:no time"; + } + arg := load Arg Arg->PATH; + if (arg == nil) + badmodule(Arg->PATH); + arg->init(argv); + + addr := ""; + doauth := 1; + algs: list of string; + while ((opt := arg->opt()) != 0) { + case opt { + 'p' => + addr = arg->arg(); + 'a' => + alg := arg->arg(); + if (alg == nil) + usage(); + algs = alg :: algs; + 'A' => + doauth = 0; + 'v' => + verbose = 1; + * => + usage(); + } + } + argv = arg->argv(); + if (argv == nil || (addr != nil && tl argv != nil)) + usage(); + if (addr == nil) + doauth = 0; # no authentication necessary for local mount + if (doauth) { + auth = load Auth Auth->PATH; + if (auth == nil) + badmodule(Auth->PATH); + if ((e := auth->init()) != nil) { + sys->fprint(stderr, "lockfs: cannot init auth: %s\n", e); + raise "fail:errors"; + } + keyring = load Keyring Keyring->PATH; + if (keyring == nil) + badmodule(Keyring->PATH); + authinfo = keyring->readauthinfo("/usr/" + user() + "/keyring/default"); + } + + mountpoint := lockdir = hd argv; + if (tl argv != nil) + mountpoint = hd tl argv; + if (addr != nil) { + if (doauth && algs == nil) + algs = "none" :: nil; # XXX is this default a bad idea? + srvrq := chan of (ref Sys->FD, string, Uproc); + srvsync := chan of (int, string); + spawn listener(addr, srvrq, srvsync, algs); + (srvpid, err) := <-srvsync; + srvsync = nil; + if (srvpid == -1) { + sys->fprint(stderr, "lockfs: failed to start listener: %s\n", err); + raise "fail:errors"; + } + sync := chan of int; + spawn server(srvrq, sync); + <-sync; + } else { + rq := chan of (ref Sys->FD, string, Uproc); + fds := array[2] of ref Sys->FD; + sys->pipe(fds); + sync := chan of int; + spawn server(rq, sync); + <-sync; + rq <-= (fds[0], "lock", nil); + rq <-= (nil, nil, nil); + if (sys->mount(fds[1], nil, mountpoint, Sys->MREPL | Sys->MCREATE, nil) == -1) { + sys->fprint(stderr, "lockfs: cannot mount: %r\n"); + raise "fail:cannot mount"; + } + } +} + +server(srvrq: chan of (ref Sys->FD, string, Uproc), sync: chan of int) +{ + sys->pctl(Sys->FORKNS, nil); + sync <-= 1; + down := 0; + nclient := 0; + tchans := array[MAXCONN] of chan of ref Tmsg; + srv := array[MAXCONN] of ref Styxserver; + uprocs := array[MAXCONN] of Uproc; + lockinit(); +Service: + for (;;) alt { + (fd, reqstr, uprocch) := <-srvrq => + if (fd == nil) { + if (verbose && reqstr != nil) + sys->print("lockfs: localserver going down (reason: %s)\n", reqstr); + down = 1; + } else { + if (verbose) + sys->print("lockfs: got new connection (s == '%s')\n", reqstr); + for (i := 0; i < len tchans; i++) + if (tchans[i] == nil) { + (tchans[i], srv[i]) = Styxserver.new(fd); + if(verbose) + sys->print("svc started\n"); + uprocs[i] = uprocch; + break; + } + if (i == len tchans) { + sys->fprint(stderr, "lockfs: too many clients\n"); # XXX expand arrays + if (uprocch != nil) + uprocch <-= (nil, nil); + } else + nclient++; + } + (n, gm) := <-tchans => + if (handletmsg(srv[n], gm, uprocs[n]) == -1) { + tchans[n] = nil; + srv[n] = nil; + if (uprocs[n] != nil) { + uprocs[n] <-= (nil, nil); + uprocs[n] = nil; + } + if (nclient-- <= 1 && down) + break Service; + } + } + if (verbose) + sys->print("lockfs: finished\n"); +} + +dirgen(nil: ref Styxserver, nil: ref Styxlib->Chan, + nil: array of Dirtab, s: int): (int, Sys->Dir) +{ + d: Sys->Dir; + ll := locks; + for (i := 0; i < s && ll != nil; i++) + ll = tl ll; + if (ll == nil) + return (-1, d); + return (1, (hd ll).d); +} + +handletmsg(srv: ref Styxserver, gm: ref Tmsg, uproc: Uproc): int +{ +{ + if (gm == nil) + gm = ref Tmsg.Readerror(-1, "eof"); + if(verbose) + sys->print("<- %s\n", gm.text()); + pick m := gm { + Readerror => + # could be more efficient... + for (cl := srv.chanlist(); cl != nil; cl = tl cl) { + c := hd cl; + for (ll := locks; ll != nil; ll = tl ll) { + if ((hd ll).d.qid.path == c.qid.path) { + l := hd ll; + l.waitq.flush(srv, -1); + if (c.open) + unlocked(l); + break; + } + } + } + if (m.error != "eof") + sys->fprint(stderr, "lockfs: read error: %s\n", m.error); + return -1; + Version => + srv.devversion(m); + Auth => + srv.devauth(m); + Walk => + c := fid2chan(srv, m.fid); + qids: array of Sys->Qid; + cc := ref *c; + if (len m.names > 0) { + qids = array[1] of Sys->Qid; # it's just one level + if ((cc.qid.qtype & Sys->QTDIR) == 0) { + srv.reply(ref Rmsg.Error(m.tag, Enotdir)); + break; + } + for (ll := locks; ll != nil; ll = tl ll) + if ((hd ll).d.name == m.names[0]) + break; + if (ll == nil) { + srv.reply(ref Rmsg.Error(m.tag, Enotfound)); + break; + } + d := (hd ll).d; + cc.qid = d.qid; + cc.path = d.name; + qids[0] = c.qid; + } + if(m.newfid != m.fid){ + nc := srv.clone(cc, m.newfid); + if(nc == nil){ + srv.reply(ref Rmsg.Error(m.tag, Einuse)); + break; + } + }else{ + c.qid = cc.qid; + c.path = cc.path; + } + srv.reply(ref Rmsg.Walk(m.tag, qids)); + Open => + c := fid2chan(srv, m.fid); + if (c.qid.qtype & Sys->QTDIR) { + srv.reply(ref Rmsg.Open(m.tag, c.qid, Styx->MAXFDATA)); + break; + } + for (ll := locks; ll != nil; ll = tl ll) + if ((hd ll).d.qid.path == c.qid.path) + break; + if (ll == nil) { + srv.reply(ref Rmsg.Error(m.tag, Enotfound)); + break; + } + l := hd ll; + req := ref Openreq(srv, m.tag, m.mode, c, uproc); + if (l.fd == nil || (m.mode == Sys->OREAD && l.writers == 0)) { + openlockfile(l, req); + } else { + l.waitq.put(req); + } + req = nil; + Create => + c := fid2chan(srv, m.fid); + if ((c.qid.qtype & Sys->QTDIR) == 0) { + srv.reply(ref Rmsg.Error(m.tag, Enotdir)); + break; + } + if (m.perm & Sys->DMDIR) { + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + for (ll := locks; ll != nil; ll = tl ll) + if ((hd ll).d.name == m.name) + break; + if (ll != nil) { + srv.reply(ref Rmsg.Error(m.tag, Eexists)); + break; + } + (fd, err) := create(uproc, lockdir + "/" + m.name, m.mode, m.perm); + if (fd == nil) { + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + (ok, d) := sys->fstat(fd); + if (ok == -1) { + srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); + break; + } + l := ref Lockfile(ref Lockqueue, fd, 0, 0, d); + l.d.qid = (maxqidpath++, 0, Sys->QTFILE); + l.d.mtime = l.d.atime = now(); + if (m.mode == Sys->OREAD) + l.readers = 1; + else + l.writers = 1; + locks = l :: locks; + c.qid.path = (hd locks).d.qid.path; + c.open = 1; + srv.reply(ref Rmsg.Create(m.tag, c.qid, Styx->MAXFDATA)); + Read => + c := fid2chan(srv, m.fid); + if (c.qid.qtype & Sys->QTDIR) + srv.devdirread(m, devgen, nil); + else { + l := qid2lock(c.qid); + if (l == nil) + srv.reply(ref Rmsg.Error(m.tag, Enotfound)); + else { + d := array[m.count] of byte; + sys->seek(l.fd, m.offset, Sys->SEEKSTART); + n := sys->read(l.fd, d, m.count); + if (n == -1) + srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); + else { + srv.reply(ref Rmsg.Read(m.tag, d[0:n])); + l.d.atime = now(); + } + } + } + Write => + c := fid2chan(srv, m.fid); + if (c.qid.qtype & Sys->QTDIR) { + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + l := qid2lock(c.qid); + if (l == nil) { + srv.reply(ref Rmsg.Error(m.tag, Enotfound)); + break; + } + sys->seek(l.fd, m.offset, Sys->SEEKSTART); + n := sys->write(l.fd, m.data, len m.data); + if (n == -1) + srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); + else { + srv.reply(ref Rmsg.Write(m.tag, n)); + nlength := m.offset + big n; + if (nlength > l.d.length) + l.d.length = nlength; + l.d.mtime = now(); + l.d.qid.vers++; + } + Clunk => + c := srv.devclunk(m); + if (c != nil && c.open && (l := qid2lock(c.qid)) != nil) + unlocked(l); + Flush => + for (ll := locks; ll != nil; ll = tl ll) + (hd ll).waitq.flush(srv, m.tag); + srv.reply(ref Rmsg.Flush(m.tag)); + Stat => + srv.devstat(m, devgen, nil); + Remove => + c := fid2chan(srv, m.fid); + srv.chanfree(c); + if (c.qid.qtype & Sys->QTDIR) { + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + l := qid2lock(c.qid); + if (l == nil) { + srv.reply(ref Rmsg.Error(m.tag, Enotfound)); + break; + } + if (l.fd != nil) { + srv.reply(ref Rmsg.Error(m.tag, Elocked)); + break; + } + if ((err := remove(uproc, lockdir + "/" + l.d.name)) == nil) { + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + ll: list of ref Lockfile; + for (; locks != nil; locks = tl locks) + if (hd locks != l) + ll = hd locks :: ll; + locks = ll; + srv.reply(ref Rmsg.Remove(m.tag)); + Wstat => + c := fid2chan(srv, m.fid); + if (c.qid.qtype & Sys->QTDIR) { + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + l := qid2lock(c.qid); + if (l == nil) { + srv.reply(ref Rmsg.Error(m.tag, Enotfound)); + break; + } + if ((err := wstat(uproc, lockdir + "/" + l.d.name, m.stat)) != nil) { + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + (ok, d) := sys->stat(lockdir + "/" + m.stat.name); + if (ok == -1) { + srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); + break; + } + d.qid = l.d.qid; + l.d = d; + srv.reply(ref Rmsg.Wstat(m.tag)); + Attach => + srv.devattach(m); + } + return 0; +} +exception e{ + "panic:*" => + sys->fprint(stderr, "lockfs: %s\n", e); + srv.reply(ref Rmsg.Error(gm.tag, e[len "panic:":])); + return 0; +} +} + +unlocked(l: ref Lockfile) +{ + if (l.readers > 0) + l.readers--; + else + l.writers--; + if (l.readers > 0) + return; + l.fd = nil; + + # unblock all readers at the head of the queue. + # XXX should we queuejump other readers? + while ((nreq := l.waitq.peek()) != nil && l.writers == 0) { + if (nreq.omode != Sys->OREAD && l.readers > 0) + break; + openlockfile(l, nreq); + l.waitq.get(); + } +} + +openlockfile(l: ref Lockfile, req: ref Openreq): int +{ + err: string; + (l.fd, err) = open(req.uproc, lockdir + "/" + l.d.name, req.omode); + if (l.fd == nil) { + req.srv.reply(ref Rmsg.Error(req.tag, err)); + return -1; + } + req.c.open = 1; + if (req.omode & Sys->OTRUNC) + l.d.length = big 0; + req.srv.reply(ref Rmsg.Open(req.tag, l.d.qid, Styx->MAXFDATA)); + if (req.omode == Sys->OREAD) + l.readers++; + else + l.writers++; + return 0; +} + +qid2lock(q: Sys->Qid): ref Lockfile +{ + for (ll := locks; ll != nil; ll = tl ll) + if ((hd ll).d.qid.path == q.path) + return hd ll; + return nil; +} + +lockinit() +{ + fd := sys->open(lockdir, Sys->OREAD); + if (fd == nil) + return; + + lockl: list of ref Lockfile; + # XXX if O(n²) behaviour is a problem, use Readdir module + for(;;){ + (n, e) := sys->dirread(fd); + if(n <= 0) + break; + for (i := 0; i < n; i++) { + for (l := lockl; l != nil; l = tl l) + if ((hd l).d.name == e[i].name) + break; + if (l == nil) { + e[i].qid = (maxqidpath++, 0, Sys->QTFILE); + lockl = ref Lockfile(ref Lockqueue, nil, 0, 0, e[i]) :: lockl; + } + } + } + # remove all directories from list + for (locks = nil; lockl != nil; lockl = tl lockl) + if (((hd lockl).d.mode & Sys->DMDIR) == 0) + locks = hd lockl :: locks; +} + + +fid2chan(srv: ref Styxserver, fid: int): ref Chan +{ + c := srv.fidtochan(fid); + if (c == nil) + raise "panic:bad fid"; + return c; +} + +Lockqueue.put(q: self ref Lockqueue, s: ref Openreq) +{ + q.t = s :: q.t; +} + +Lockqueue.get(q: self ref Lockqueue): ref Openreq +{ + s: ref Openreq; + if(q.h == nil) + (q.h, q.t) = (revrqlist(q.t), nil); + + if(q.h != nil) + (s, q.h) = (hd q.h, tl q.h); + + return s; +} + +Lockqueue.peek(q: self ref Lockqueue): ref Openreq +{ + s := q.get(); + if (s != nil) + q.h = s :: q.h; + return s; +} + +doflush(l: list of ref Openreq, srv: ref Styxserver, tag: int): list of ref Openreq +{ + oldl := l; + nl: list of ref Openreq; + doneone := 0; + while (l != nil) { + oreq := hd l; + if (oreq.srv != srv || (tag != -1 && oreq.tag != tag)) + nl = oreq :: nl; + else + doneone = 1; + l = tl l; + } + if (doneone) + return revrqlist(nl); + else + return oldl; +} + +Lockqueue.flush(q: self ref Lockqueue, srv: ref Styxserver, tag: int) +{ + q.h = doflush(q.h, srv, tag); + q.t = doflush(q.t, srv, tag); +} + +# or inline +revrqlist(ls: list of ref Openreq) : list of ref Openreq +{ + rs: list of ref Openreq; + while(ls != nil){ + rs = hd ls :: rs; + ls = tl ls; + } + return rs; +} + +# addr should be, e.g. tcp!*!2345 +listener(addr: string, ch: chan of (ref Sys->FD, string, Uproc), + sync: chan of (int, string), algs: list of string) +{ + addr = netmkaddr(addr, "tcp", "33234"); + (ok, c) := sys->announce(addr);; + if (ok == -1) { + sync <-= (-1, sys->sprint("cannot anounce on %s: %r", addr)); + return; + } + sync <-= (sys->pctl(0, nil), nil); + for (;;) { + (n, nc) := sys->listen(c); + if (n == -1) { + ch <-= (nil, sys->sprint("listen failed: %r"), nil); + return; + } + dfd := sys->open(nc.dir + "/data", Sys->ORDWR); + if (dfd != nil) { + if (algs == nil) + ch <-= (dfd, nil, nil); + else + spawn authenticator(dfd, ch, algs); + } + } +} + +# authenticate a connection, setting the user id appropriately, +# and then act as a server, performing file operations +# on behalf of the central process. +authenticator(dfd: ref Sys->FD, ch: chan of (ref Sys->FD, string, Uproc), algs: list of string) +{ + (fd, err) := auth->server(algs, authinfo, dfd, 1); + if (fd == nil) { + if (verbose) + sys->fprint(stderr, "lockfs: authentication failed: %s\n", err); + return; + } + uproc := chan of (ref Ureq, chan of (ref Sys->FD, string)); + ch <-= (fd, err, uproc); + for (;;) { + (req, reply) := <-uproc; + if (req == nil) + exit; + reply <-= doreq(req); + } +} + +create(uproc: Uproc, file: string, omode: int, perm: int): (ref Sys->FD, string) +{ + return proxydoreq(uproc, ref Ureq.Create(file, omode, perm)); +} + +open(uproc: Uproc, file: string, omode: int): (ref Sys->FD, string) +{ + return proxydoreq(uproc, ref Ureq.Open(file, omode)); +} + +remove(uproc: Uproc, file: string): string +{ + return proxydoreq(uproc, ref Ureq.Remove(file)).t1; +} + +wstat(uproc: Uproc, file: string, d: Sys->Dir): string +{ + return proxydoreq(uproc, ref Ureq.Wstat(file, d)).t1; +} + +proxydoreq(uproc: Uproc, req: ref Ureq): (ref Sys->FD, string) +{ + if (uproc == nil) + return doreq(req); + reply := chan of (ref Sys->FD, string); + uproc <-= (req, reply); + return <-reply; +} + +doreq(greq: ref Ureq): (ref Sys->FD, string) +{ + fd: ref Sys->FD; + err: string; + pick req := greq { + Open => + if ((fd = sys->open(req.fname, req.omode)) == nil) + err = sys->sprint("%r"); + Create => + if ((fd = sys->create(req.fname, req.omode, req.perm)) == nil) + err = sys->sprint("%r"); + Remove => + if (sys->remove(req.fname) == -1) + err = sys->sprint("%r"); + Wstat => + if (sys->wstat(req.fname, req.dir) == -1) + err = sys->sprint("%r"); + } + return (fd, err); +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} + +user(): string +{ + fd := sys->open("/dev/user", sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "lockfs: can't open /dev/user: %r\n"); + raise "fail:no user"; + } + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) { + sys->fprint(stderr, "lockfs: failed to read /dev/user: %r\n"); + raise "fail:no user"; + } + + return string buf[0:n]; +} + +now(): int +{ + buf := array[128] of byte; + sys->seek(timefd, big 0, 0); + if ((n := sys->read(timefd, buf, len buf)) < 0) + return 0; + return int (big string buf[0:n] / big 1000000); +} diff --git a/appl/cmd/logfile.b b/appl/cmd/logfile.b new file mode 100644 index 00000000..6ec8369e --- /dev/null +++ b/appl/cmd/logfile.b @@ -0,0 +1,259 @@ +implement Logfile; + +# +# Copyright © 1999 Vita Nuova Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; +include "draw.m"; + +Logfile: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +Fidrec: adt { + fid: int; # fid of read + rq: list of (int, Sys->Rread); # outstanding read requests + pos: int; # current position in the logfile +}; + +Circbuf: adt { + start: int; + data: array of byte; + new: fn(size: int): ref Circbuf; + put: fn(b: self ref Circbuf, d: array of byte): int; + get: fn(b: self ref Circbuf, s, n: int): (int, array of byte); +}; + +Fidhash: adt +{ + table: array of list of ref Fidrec; + get: fn(ht: self ref Fidhash, fid: int): ref Fidrec; + put: fn(ht: self ref Fidhash, fidrec: ref Fidrec); + del: fn(ht: self ref Fidhash, fidrec: ref Fidrec); + new: fn(): ref Fidhash; +}; + +usage() +{ + sys->fprint(stderr, "usage: logfile [-size] file\n"); + raise "fail: usage"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + bufsize := Sys->ATOMICIO * 4; + + if (argv != nil) + argv = tl argv; + if (argv != nil && len hd argv && (hd argv)[0] == '-' && len hd argv > 1) { + if ((bufsize = int ((hd argv)[1:])) <= 0) { + sys->fprint(stderr, "logfile: can't have a zero buffer size\n"); + usage(); + } + argv = tl argv; + } + if (argv == nil || tl argv != nil) + usage(); + path := hd argv; + + (dir, f) := pathsplit(path); + if (sys->bind("#s", dir, Sys->MBEFORE|Sys->MCREATE) == -1) { + sys->fprint(stderr, "logfile: bind #s failed: %r\n"); + return; + } + fio := sys->file2chan(dir, f); + if (fio == nil) { + sys->fprint(stderr, "logfile: couldn't make %s: %r\n", path); + return; + } + + spawn logserver(fio, bufsize); +} + +logserver(fio: ref Sys->FileIO, bufsize: int) +{ + waitlist: list of ref Fidrec; + readers := Fidhash.new(); + availcount := 0; + availchan := chan of int; + workchan := chan of (Sys->Rread, array of byte); + buf := Circbuf.new(bufsize); + for (;;) alt { + <-availchan => + availcount++; + (off, count, fid, rc) := <-fio.read => + r := readers.get(fid); + if (rc == nil) { + if (r != nil) + readers.del(r); + continue; + } + if (r == nil) { + r = ref Fidrec(fid, nil, buf.start); + if (r.pos < len buf.data) + r.pos = len buf.data; # first buffer's worth is garbage + readers.put(r); + } + + (s, d) := buf.get(r.pos, count); + r.pos = s + len d; + + if (d != nil) { + rc <-= (d, nil); + } else { + if (r.rq == nil) + waitlist = r :: waitlist; + r.rq = (count, rc) :: r.rq; + } + + (off, data, fid, wc) := <-fio.write => + if (wc == nil) + continue; + if ((n := buf.put(data)) < len data) + wc <-= (n, "write too long for buffer"); + else + wc <-= (n, nil); + + wl := waitlist; + for (waitlist = nil; wl != nil; wl = tl wl) { + r := hd wl; + if (availcount == 0) { + spawn worker(workchan, availchan); + availcount++; + } + (count, rc) := hd r.rq; + r.rq = tl r.rq; + + # optimisation: if the read request wants exactly the data provided + # in the write request, then use the original data buffer. + s: int; + d: array of byte; + if (count >= n && r.pos == buf.start + len buf.data - n) + (s, d) = (r.pos, data); + else + (s, d) = buf.get(r.pos, count); + r.pos = s + len d; + workchan <-= (rc, d); + availcount--; + if (r.rq != nil) + waitlist = r :: waitlist; + d = nil; + } + data = nil; + wl = nil; + } +} + +worker(work: chan of (Sys->Rread, array of byte), ready: chan of int) +{ + for (;;) { + (rc, data) := <-work; # blocks forever if the reading process is killed + rc <-= (data, nil); + (rc, data) = (nil, nil); + ready <-= 1; + } +} + +Circbuf.new(size: int): ref Circbuf +{ + return ref Circbuf(0, array[size] of byte); +} + +# return number of bytes actually written +Circbuf.put(b: self ref Circbuf, d: array of byte): int +{ + blen := len b.data; + # if too big to fit in buffer, truncate the write. + if (len d > blen) + d = d[0:blen]; + dlen := len d; + + offset := b.start % blen; + if (offset + dlen <= blen) { + b.data[offset:] = d; + } else { + b.data[offset:] = d[0:blen - offset]; + b.data[0:] = d[blen - offset:]; + } + b.start += dlen; + return dlen; +} + +# return (start, data) +Circbuf.get(b: self ref Circbuf, s, n: int): (int, array of byte) +{ + # if the beginning's been overrun, start from the earliest place we can. + # we could put some indication of elided bytes in the buffer. + if (s < b.start) + s = b.start; + blen := len b.data; + if (s + n > b.start + blen) + n = b.start + blen - s; + if (n <= 0) + return (s, nil); + o := s % blen; + d := array[n] of byte; + if (o + n <= blen) + d[0:] = b.data[o:o+n]; + else { + d[0:] = b.data[o:]; + d[blen - o:] = b.data[0:o+n-blen]; + } + return (s, d); +} + +FIDHASHSIZE: con 32; + +Fidhash.new(): ref Fidhash +{ + return ref Fidhash(array[FIDHASHSIZE] of list of ref Fidrec); +} + +# put an entry in the hash table. +# assumes there is no current entry for the fid. +Fidhash.put(ht: self ref Fidhash, f: ref Fidrec) +{ + slot := f.fid & (FIDHASHSIZE-1); + ht.table[slot] = f :: ht.table[slot]; +} + +Fidhash.get(ht: self ref Fidhash, fid: int): ref Fidrec +{ + for (l := ht.table[fid & (FIDHASHSIZE-1)]; l != nil; l = tl l) + if ((hd l).fid == fid) + return hd l; + return nil; +} + +Fidhash.del(ht: self ref Fidhash, f: ref Fidrec) +{ + slot := f.fid & (FIDHASHSIZE-1); + nl: list of ref Fidrec; + for (l := ht.table[slot]; l != nil; l = tl l) + if ((hd l).fid != f.fid) + nl = (hd l) :: nl; + ht.table[slot] = nl; +} + +pathsplit(p: string): (string, string) +{ + for (i := len p - 1; i >= 0; i--) + if (p[i] != '/') + break; + if (i < 0) + return (p, nil); + p = p[0:i+1]; + for (i = len p - 1; i >=0; i--) + if (p[i] == '/') + break; + if (i < 0) + return (".", p); + return (p[0:i+1], p[i+1:]); +} + diff --git a/appl/cmd/look.b b/appl/cmd/look.b new file mode 100755 index 00000000..8465db45 --- /dev/null +++ b/appl/cmd/look.b @@ -0,0 +1,393 @@ +implement Look; + +# +# Copyright © 2002 Lucent Technologies Inc. +# transliteration of the Plan 9 command; subject to the Lucent Public License 1.02 +# -r option added by Caerwyn Jones to print a range +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Look: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +filename := "/lib/words"; +dfile: ref Iobuf; +bout: ref Iobuf; +debug := 0; +fold, direc, exact, iflag, range: int; +rev := 1; # -1 for reverse-ordered file, not implemented +tab := '\t'; +nflag := 0; +entry: string; +word: string; +key: string; +orig: string; +targ: string; +latin_fold_tab := array[64] of { + # Table to fold latin 1 characters to ASCII equivalents + # based at Rune value 0xc0 + # + # À Á Â Ã Ä Å Æ Ç + # È É Ê Ë Ì Í Î Ï + # Ð Ñ Ò Ó Ô Õ Ö × + # Ø Ù Ú Û Ü Ý Þ ß + # à á â ã ä å æ ç + # è é ê ë ì í î ï + # ð ñ ò ó ô õ ö ÷ + # ø ù ú û ü ý þ ÿ + # + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', + 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', + 'd', 'n', 'o', 'o', 'o', 'o', 'o', 0, + 'o', 'u', 'u', 'u', 'u', 'y', 0, 0, + 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', + 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', + 'd', 'n', 'o', 'o', 'o', 'o', 'o', 0, + 'o', 'u', 'u', 'u', 'u', 'y', 0, 'y', +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + arg := load Arg Arg->PATH; + + arg->init(args); + arg->setusage(arg->progname()+" -[dfinx] [-r orig] [-t c] [string] [file]"); + while((c := arg->opt()) != 0) + case c { + 'd' => + direc++; + 'f' => + fold++; + 'i' => + iflag++; + 'n' => + nflag = 1; + 't' => + tab = (arg->earg())[0]; + 'x' => + exact++; + 'r' => + range++; + orig = arg->earg(); + targ = rcanon(orig); + * => + sys->fprint(sys->fildes(2), "%s: bad option %c\n", arg->progname(), c); + sys->fprint(sys->fildes(2), "usage: %s -[dfinx] [-t c] [-r limit] [string] [file]\n", arg->progname()); + raise "fail:usage"; + } + args = arg->argv(); + arg = nil; + + bin := bufio->fopen(sys->fildes(0), Sys->OREAD); + bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + if(!iflag) + if(args != nil){ + orig = hd args; + args = tl args; + key = rcanon(orig); + }else + iflag++; + if(args == nil){ + direc++; + fold++; + }else + filename = hd args; + if(debug) + sys->fprint(sys->fildes(2), "orig %s key %s %s\n", orig, key, filename); + dfile = bufio->open(filename, Sys->OREAD); + if(dfile == nil){ + sys->fprint(sys->fildes(2), "look: can't open %s\n", filename); + raise "fail:no dictionary"; + } + if(!iflag) + if(!locate() && !range && exact) + raise "fail:not found"; + do{ + if(iflag){ + bout.flush(); + if((orig = bin.gets('\n')) == nil) + exit; + key = rcanon(orig); + if(!locate()) + continue; + } + if(range){ + if(compare(key, word) <= 0 && compare(word, targ) <= 0) + bout.puts(entry); + }else if(!exact || !compare(word, key)) + bout.puts(entry); + while((entry = dfile.gets('\n')) != nil){ + word = rcanon(entry); + if(range) + n := compare(word, targ); + else + n = compare(key, word); + if(debug) + sys->print("compare %d\n", n); + case n { + -2 => + if(range){ + bout.puts(entry); + continue; + } + -1 => + if(exact) + break; + if(!exact || !compare(word, key)) + bout.puts(entry); + continue; + 0 => + if(!exact || !compare(word, key)) + bout.puts(entry); + continue; + } + break; + } + }while(iflag); + bout.flush(); +} + +locate(): int +{ + bot := big 0; + top := dfile.seek(big 0, 2); + mid: big; +Search: + for(;;){ + mid = (top+bot)/big 2; + if(debug) + sys->fprint(sys->fildes(2), "locate %bd %bd %bd\n", top, mid, bot); + dfile.seek(mid, 0); + c: int; + do + c = dfile.getc(); + while(c >= 0 && c != '\n'); + mid = dfile.offset(); + if((entry = dfile.gets('\n')) == nil) + break; + word = rcanon(entry); + if(debug) + sys->fprint(sys->fildes(2), "mid %bd key: %s entry: %s\n", mid, key, word); + n := compare(key, word); + if(debug) + sys->fprint(sys->fildes(2), "compare: %d\n", n); + case n { + -2 or -1 or 0 => + if(top <= mid) + break Search; + top = mid; + 1 or 2 => + bot = mid; + } + } + if(debug) + sys->fprint(sys->fildes(2), "locate %bd %bd %bd\n", top, mid, bot); + bot = dfile.seek(big bot, 0); + while((entry = dfile.gets('\n')) != nil){ + word = rcanon(entry); + if(debug) + sys->fprint(sys->fildes(2), "seekbot %bd key: %s entry: %s\n", bot, key, word); + n := compare(key, word); + if(debug) + sys->fprint(sys->fildes(2), "compare: %d\n", n); + case n { + -2 => + return 0; + -1 => + if(exact) + return 0; + return 1; + 0 => + return 1; + 1 or 2 => + continue; + } + } + return 0; +} + +compare(s, t: string): int +{ + if(nflag) + return ncomp(s, t); + else + return acomp(s, t); +} + +# +# acomp(s, t) returns: +# -2 if s strictly precedes t +# -1 if s is a prefix of t +# 0 if s is the same as t +# 1 if t is a prefix of s +# 2 if t strictly precedes s +# +acomp(s, t: string): int +{ + if(s == t) + return 0; + l := len s; + if(l > len t) + l = len t; + cs, ct: int; + for(i := 0; i < l; i++) { + cs = s[i]; + ct = t[i]; + if(cs != ct) + break; + } + if(i == len s) + return -1; + if(i == len t) + return 1; + if(cs < ct) + return -2; + return 2; +} + +rcanon(s: string): string +{ + if(s != nil && s[len s - 1] == '\n') + s = s[0: len s - 1]; + o := 0; + for(i := 0; i < len s && (r := s[i]) != tab; i++){ + if(16rc0 <= r && r <= 16rff && (mr := latin_fold_tab[r-16rc0]) != 0) + r = mr; + if(direc) + if(!(isalnum(r) || r == ' ' || r == '\t')) + continue; + if(fold) + if(isupper(r)) + r = tolower(r); + if(r != s[o]) # avoid copying s unless necessary + s[o] = r; + o++; + } + if(o != i) + return s[0:o]; + return s; +} + +sgn(v: int): int +{ + if(v < 0) + return -1; + if(v > 0) + return 1; + return 0; +} + +ncomp(s: string, t: string): int +{ + while(len s > 0 && isspace(s[0])) + s = s[1:]; + while(len t > 0 && isspace(t[0])) + t = t[1:]; + ssgn := tsgn := -2*rev; + if(s != nil && s[0] == '-'){ + s = s[1: ]; + ssgn = -ssgn; + } + if(t != nil && t[0] == '-'){ + t = t[1:]; + tsgn = -tsgn; + } + for(i := 0; i < len s && isdigit(s[i]); i++) + ; + is := s[0:i]; + js := s[i:]; + for(i = 0; i < len t && isdigit(t[i]); i++) + ; + it := t[0:i]; + jt := t[i:]; + a := 0; + i = len is; + j := len it; + if(ssgn == tsgn){ + while(j > 0 && i > 0) + if((b := it[--j] - is[--i]) != 0) + a = b; + } + while(i > 0) + if(is[--i] != '0') + return -ssgn; + while(j > 0) + if(it[--i] != '0') + return tsgn; + if(a) + return sgn(a)*ssgn; + s = js; + if(len s > 0 && s[0] == '.') + s = s[1: ]; + t = jt; + if(len t > 0 && t[0] == '.') + t = t[1: ]; + if(ssgn == tsgn) + while((len s > 0 && isdigit(s[0])) && (len t > 0 && isdigit(t[0]))){ + if(a = t[0] - s[0]) + return sgn(a)*ssgn; + s = s[1:]; + t = t[1:]; + } + for(; len s > 0 && isdigit(s[0]); s = s[1:]) + if(s[0] != '0') + return -ssgn; + for(; len t > 0 && isdigit(t[0]); t = t[1:]) + if(t[0] != '0') + return tsgn; + return 0; +} + +isupper(c: int): int +{ + return c >= 'A' && c <= 'Z'; +} + +islower(c: int): int +{ + return c >= 'a' && c <= 'z'; +} + +isalpha(c: int): int +{ + return islower(c) || isupper(c); +} + +islatin1(c: int): int +{ + return c >= 16rC0 && c <= 16rFF; +} + +isdigit(c: int): int +{ + return c >= '0' && c <= '9'; +} + +isalnum(c: int): int +{ + return isdigit(c) || islower(c) || isupper(c); +} + +isspace(c: int): int +{ + return c == ' ' || c == '\t' || c >= 16r0A && c <= 16r0D; +} + +tolower(c: int): int +{ + return c-'A'+'a'; +} diff --git a/appl/cmd/lookman.b b/appl/cmd/lookman.b new file mode 100644 index 00000000..53557c8b --- /dev/null +++ b/appl/cmd/lookman.b @@ -0,0 +1,250 @@ +implement Lookman; +include "sys.m"; +include "bufio.m"; +include "draw.m"; + + +Lookman : module { + init : fn (ctxt : ref Draw->Context, argv : list of string); +}; + +sys : Sys; +bufio : Bufio; +Iobuf : import bufio; + +ctype := array [256] of { * => byte 0 }; + +MANINDEX : con "/man/index"; + +init(nil : ref Draw->Context, argv : list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + + if (bufio == nil) + raise "init:fail"; + + # setup our char conversion table + # map upper-case to lower-case + for (i := 'A'; i <= 'Z'; i++) + ctype[i] = byte ((i - 'A') + 'a'); + + # only allow the following chars + okchars := "abcdefghijklmnopqrstuvwxyz0123456789+.:½ "; + for (i = 0; i < len okchars; i++) { + ch := okchars[i]; + ctype[ch] = byte ch; + } + + stdout := bufio->fopen(sys->fildes(1), Sys->OWRITE); + + argv = tl argv; + paths := lookup(argv); + for (; paths != nil; paths = tl paths) + stdout.puts(sys->sprint("%s\n", hd paths)); + stdout.flush(); +} + +lookup(words : list of string) : list of string +{ + # open the index file + manindex := bufio->open(MANINDEX, Sys->OREAD); + if (manindex == nil) { + sys->print("cannot open %s: %r\n", MANINDEX); + return nil; + } + + # convert to lower-case and discard funny chars + keywords : list of string; + for (; words != nil; words = tl words) { + word := hd words; + kw := ""; + for (i := 0; i < len word; i++) { + ch := word[i]; + if (ch < len ctype && ctype[ch] != byte 0) + kw[len kw] = int ctype[ch]; + } + if (kw != "") + keywords = kw :: keywords; + } + + if (keywords == nil) + return nil; + + keywords = sortuniq(keywords); + matches : list of list of string; + + for (; keywords != nil; keywords = tl keywords) { + kw := hd keywords; + matchlist := look(manindex, '\t', kw); + pathlist : list of string = nil; + for (; matchlist != nil; matchlist = tl matchlist) { + line := hd matchlist; + (n, toks) := sys->tokenize(line, "\t"); + if (n != 2) + continue; + pathlist = hd tl toks :: pathlist; + } + if (pathlist != nil) + matches = pathlist :: matches; + } + + return intersect(matches); +} + +getentry(iob : ref Iobuf) : (string, string) +{ + while ((s := iob.gets('\n')) != nil) { + if (s[len s -1] == '\n') + s = s[0:len s -1]; + if (s == nil) + continue; + (n, toks) := sys->tokenize(s, "\t"); + if (n != 2) + continue; + return (hd toks, hd tl toks); + } + return (nil, nil); +} + +sortuniq(strlist : list of string) : list of string +{ + strs := array [len strlist] of string; + for (i := 0; strlist != nil; (i, strlist) = (i+1, tl strlist)) + strs[i] = hd strlist; + + # simple sort (greatest first) + for (i = 0; i < len strs - 1; i++) { + for (j := i+1; j < len strs; j++) + if (strs[i] < strs[j]) + (strs[i], strs[j]) = (strs[j], strs[i]); + } + + # construct list (result is ascending) + r : list of string; + prev := ""; + for (i = 0; i < len strs; i++) { + if (strs[i] != prev) { + r = strs[i] :: r; + prev = strs[i]; + } + } + return r; +} + +intersect(strlists : list of list of string) : list of string +{ + if (strlists == nil) + return nil; + + okl := hd strlists; + for (strlists = tl strlists; okl != nil && strlists != nil; strlists = tl strlists) { + find := hd strlists; + found : list of string = nil; + for (; okl != nil; okl = tl okl) { + ok := hd okl; + for (scanl := find; scanl != nil; scanl = tl scanl) { + scan := hd scanl; + if (scan == ok) { + found = ok :: found; + break; + } + } + } + okl = found; + } + return sortuniq(okl); +} + +# binary search for key in f. +# based on Plan 9 look.c +# +look(f: ref Iobuf, sep: int, key: string): list of string +{ + bot := mid := 0; + top := int f.seek(big 0, Sys->SEEKEND); + key = canon(key, sep); + + for (;;) { + mid = (top + bot) / 2; + f.seek(big mid, Sys->SEEKSTART); + c: int; + do { + c = f.getb(); + mid++; + } while (c != Bufio->EOF && c != Bufio->ERROR && c != '\n'); + (entry, eof) := getword(f); + if (entry == nil && eof) + break; + entry = canon(entry, sep); + case comparewords(key, entry) { + -2 or -1 or 0 => + if (top <= mid) + break; + top = mid; + continue; + 1 or 2 => + bot = mid; + continue; + } + break; + } + matchlist : list of string; + f.seek(big bot, Sys->SEEKSTART); + for (;;) { + (entry, eof) := getword(f); + if (entry == nil && eof) + return matchlist; + word := canon(entry, sep); + case comparewords(key, word) { + -1 or 0 => + matchlist = entry :: matchlist; + continue; + 1 or 2 => + continue; + } + break; + } + return matchlist; +} + +comparewords(s, t: string): int +{ + if (s == t) + return 0; + i := 0; + for (; i < len s && i < len t && s[i] == t[i]; i++) + ; + if (i >= len s) + return -1; + if (i >= len t) + return 1; + if (s[i] < t[i]) + return -2; + return 2; +} + +getword(f: ref Iobuf): (string, int) +{ + ret := ""; + for (;;) { + c := f.getc(); + if (c == Bufio->EOF || c == Bufio->ERROR) + return (ret, 0); + if (c == '\n') + break; + ret[len ret] = c; + } + return (ret, 1); +} + +canon(s: string, sep: int): string +{ + if (sep < 0) + return s; + i := 0; + for (; i < len s; i++) + if (s[i] == sep) + break; + return s[0:i]; +} diff --git a/appl/cmd/ls.b b/appl/cmd/ls.b new file mode 100644 index 00000000..3d27d59e --- /dev/null +++ b/appl/cmd/ls.b @@ -0,0 +1,318 @@ +implement Ls; + +include "sys.m"; + sys: Sys; + FD, Dir: import Sys; + +include "draw.m"; + Context: import Draw; + +include "daytime.m"; + daytime: Daytime; + +include "readdir.m"; + readdir: Readdir; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "arg.m"; + +Ls: module +{ + init: fn(ctxt: ref Context, argv: list of string); +}; + +PREFIX: con 16r40000000; + +dopt := 0; +eopt := 0; +lopt := 0; +mopt := 0; +nopt := 0; +popt := 0; +qopt := 0; +sopt := 0; +topt := 0; +uopt := 0; +Topt := 0; +now: int; +sortby: int; + +out: ref Bufio->Iobuf; +stderr: ref FD; + +dwIndex: int; +dwQueue: array of Dir; + +badmodule(p: string) +{ + sys->fprint(stderr, "ls: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init(nil: ref Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + badmodule(Bufio->PATH); + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + badmodule(Readdir->PATH); + str = load String String->PATH; + if(str == nil) + badmodule(String->PATH); + + stderr = sys->fildes(2); + out = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + rev := 0; + sortby = Readdir->NAME; + compact := 0; + + arg := load Arg Arg->PATH; + if(arg == nil) + badmodule(Arg->PATH); + arg->init(argv); + while((o := arg->opt()) != 0){ + case o { + 'l' => + lopt++; + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + badmodule(Daytime->PATH); + now = daytime->now(); + 'p' => + popt++; + 'q' => + qopt++; + 'd' => + dopt++; + 'e' => + eopt++; + 'm' => + mopt++; + 'n' => + nopt++; + 'k' => + sopt++; + 't' => + topt++; + 'u' => + uopt++; + 's' => + sortby = Readdir->SIZE; + 'c' => + compact = Readdir->COMPACT; + 'r' => + rev = Readdir->DESCENDING; + 'T' => + Topt++; + * => + sys->fprint(stderr, "usage: ls [-delmnpqrstucT] [files]\n"); + raise "fail:usage"; + } + } + argv = arg->argv(); + arg = nil; + + if(nopt == 0) { + if(topt){ + if(uopt) + sortby = Readdir->ATIME; + else + sortby = Readdir->MTIME; + } + } else + sortby = Readdir->NONE; + sortby |= rev|compact; + + if(argv == nil) { + argv = list of {"."}; + popt++; + } + + for(; argv != nil; argv = tl argv) + ls(hd argv); + delayWrite(); + out.flush(); +} + +ls(file: string) +{ + dir: Dir; + ok: int; + + (ok, dir) = sys->stat(file); + if(ok == -1) { + sys->fprint(stderr, "ls: stat %s: %r\n", file); + return; + } + if(dopt || (dir.mode & Sys->DMDIR) == 0) { + # delay write: save it in the queue to sort by sortby + if(dwIndex == 0) + dwQueue = array[30] of Dir; + else if(len dwQueue == dwIndex) { + # expand dwQueue + tmp := array[2 * dwIndex] of Dir; + tmp[0:] = dwQueue; + dwQueue = tmp; + } + (dirname, filename) := str->splitstrr(file, "/"); + if(dirname != "") { + dir.name = dirname + filename; + dir.dev |= PREFIX; + } + dwQueue[dwIndex++] = dir; + return; + } + + delayWrite(); + + (d, n) := readdir->init(file, sortby); + if( n < 0) + sys->fprint(stderr, "ls: Readdir: %s: %r\n", file); + else + lsprint(file, d[0:n]); +} + +delayWrite() +{ + if(dwIndex == 0) + return; + + a := array[dwIndex] of ref Dir; + for (i := 0; i < dwIndex; i++) + a[i] = ref dwQueue[i]; + (b, n) := readdir->sortdir(a, sortby); + + lsprint("", b[0:n]); + + # reset dwIndex + dwIndex = 0; + dwQueue = nil; +} + +Widths: adt { + vers, dev, uid, gid, muid, length, size: int; +}; + +dowidths(dir: array of ref Dir): ref Widths +{ + w := Widths(0, 0, 0, 0, 0, 0, 0); + for (i := 0; i < len dir; i++) { + n: int; + d := dir[i]; + if(sopt) + if((n = len string ((d.length+big 1023)/big 1024)) > w.size) + w.size = n; + if(mopt) + if((n = len d.muid+2) > w.muid) + w.muid = n; + if(qopt) + if((n = len string d.qid.vers) > w.vers) + w.vers = n; + if(lopt) { + if((n = len string (d.dev & ~PREFIX)) > w.dev) + w.dev = n; + if((n = len d.uid) > w.uid) + w.uid = n; + if((n = len d.gid) > w.gid) + w.gid = n; + if((n = len string d.length) > w.length) + w.length = n; + } + } + return ref w; +} + + +lsprint(dirname: string, dir: array of ref Dir) +{ + w := dowidths(dir); + + for (i := 0; i < len dir; i++) + lslineprint(dirname, dir[i].name, dir[i], w); +} + +lslineprint(dirname, name: string, dir: ref Dir, w: ref Widths) +{ + if(sopt) + out.puts(sys->sprint("%*bd ", w.size, (dir.length+big 1023)/big 1024)); + if(mopt){ + out.puts(sys->sprint("[%s] ", dir.muid)); + for(i := len dir.muid+2; i < w.muid; i++) + out.putc(' '); + } + if(qopt) + out.puts(sys->sprint("(%.16bux %*ud %.2ux) ", dir.qid.path, w.vers, dir.qid.vers, dir.qid.qtype)); + if(Topt){ + if(dir.mode & Sys->DMTMP) + out.puts("t "); + else + out.puts("- "); + } + + file := name; + pf := dir.dev & PREFIX; + dir.dev &= ~PREFIX; + if(popt) { + if(pf) + (nil, file) = str->splitstrr(dir.name, "/"); + else + file = dir.name; + } else if(dirname != "") { + if(dirname[len dirname-1] == '/') + file = dirname + file; + else + file = dirname + "/" + file; + } + + + if(lopt) { + time := dir.mtime; + if(uopt) + time = dir.atime; + if(eopt) + out.puts(sys->sprint("%s %c %*d %*s %*s %*bud %d %s\n", + modes(dir.mode), dir.dtype, w.dev, dir.dev, + -w.uid, dir.uid, -w.gid, dir.gid, w.length, dir.length, + time, file)); + else + out.puts(sys->sprint("%s %c %*d %*s %*s %*bud %s %s\n", + modes(dir.mode), dir.dtype, w.dev, dir.dev, + -w.uid, dir.uid, -w.gid, dir.gid, w.length, dir.length, + daytime->filet(now, time), file)); + } else + out.puts(file+"\n"); +} + +mtab := array[] of { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" +}; + +modes(mode: int): string +{ + s: string; + + if(mode & Sys->DMDIR) + s = "d"; + else if(mode & Sys->DMAPPEND) + s = "a"; + else if(mode & Sys->DMAUTH) + s = "A"; + else + s = "-"; + if(mode & Sys->DMEXCL) + s += "l"; + else + s += "-"; + s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; + return s; +} + diff --git a/appl/cmd/lstar.b b/appl/cmd/lstar.b new file mode 100644 index 00000000..fddd19d2 --- /dev/null +++ b/appl/cmd/lstar.b @@ -0,0 +1,120 @@ +implement lstar; + +include "sys.m"; + sys: Sys; + print, sprint, fprint: import sys; + stdin, stderr: ref sys->FD; +include "draw.m"; + +TBLOCK: con 512; # tar logical blocksize +Header: adt{ + name: string; + size: int; + mtime: int; + skip: int; +}; + +lstar: module{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Error(mess: string){ + fprint(stderr,"lstar: %s: %r\n",mess); + exit; +} + + +NBLOCK: con 20; # blocking factor for efficient read +tarbuf := array[NBLOCK*TBLOCK] of byte; # static buffer +nblock := NBLOCK; # how many blocks of data are in tarbuf +recno := NBLOCK; # how many blocks in tarbuf have been consumed +getblock():array of byte{ + if(recno>=nblock){ + i := sys->read(stdin,tarbuf,TBLOCK*NBLOCK); + if(i==0) + return tarbuf[0:0]; + if(i<0) + Error("read error"); + if(i%TBLOCK!=0) + Error("blocksize error"); + nblock = i/TBLOCK; + recno = 0; + } + recno++; + return tarbuf[(recno-1)*TBLOCK:recno*TBLOCK]; +} + +octal(b:array of byte):int{ + sum := 0; + for(i:=0; i<len b; i++){ + bi := int b[i]; + if(bi==' ') continue; + if(bi==0) break; + sum = 8*sum + bi-'0'; + } + return sum; +} + +nullterm(b:array of byte):string{ + for(i:=0; i<len b; i++) + if(b[i]==byte 0) break; + return string b[0:i]; +} + +getdir():ref Header{ + dblock := getblock(); + if(len dblock==0) + return nil; + if(dblock[0]==byte 0) + return nil; + + name := nullterm(dblock[0:100]); + if(int dblock[345]!=0) + name = nullterm(dblock[345:500])+"/"+name; + + magic := string(dblock[257:262]); + if(magic[0]!=0 && magic!="ustar") + Error("bad magic "+name); + chksum := octal(dblock[148:156]); + for(ci:=148; ci<156; ci++) dblock[ci] = byte ' '; + for(i:=0; i<TBLOCK; i++) + chksum -= int dblock[i]; + if(chksum!=0) + Error("directory checksum error "+name); + + skip := 1; + size := 0; + mtime := 0; + case int dblock[156]{ + '0' or '5' or '7' or 0 => + skip = 0; + size = octal(dblock[124:136]); + mtime = octal(dblock[136:148]); + '1' => + fprint(stderr,"skipping link %s -> %s\n",name,string(dblock[157:257])); + '2' or 's' => + fprint(stderr,"skipping symlink %s\n",name); + '3' or '4' or '6' => + fprint(stderr,"skipping special file %s\n",name); + * => + Error(sprint("unrecognized typeflag %d for %s",int dblock[156],name)); + } + return ref Header(name,size,mtime,skip); +} + + +init(nil: ref Draw->Context, nil: list of string){ + sys = load Sys Sys->PATH; + stdin = sys->fildes(0); + stderr = sys->fildes(2); + ofile: ref sys->FD; + + while((file := getdir())!=nil){ + bytes := file.size; + blocks := (bytes+TBLOCK-1)/TBLOCK; + for(; blocks>0; blocks--) + getblock(); + print("%s %d %d 0\n",file.name,file.mtime,file.size); + ofile = nil; + } +} diff --git a/appl/cmd/man.b b/appl/cmd/man.b new file mode 100644 index 00000000..f0bc24b3 --- /dev/null +++ b/appl/cmd/man.b @@ -0,0 +1,199 @@ +implement Man, Command; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "filepat.m"; +include "bufio.m"; +include "man.m"; + +Command: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +MANPATH: con "/man/"; +PATHDEPTH: con 1; + +indices: list of (string, list of (string, string)); + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: man [-f] [0-9] ... name ...\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr := sys->fildes(2); + man2txt := load Command "/dis/man2txt.dis"; + if (man2txt == nil) { + sys->fprint(stderr, "man: cannot load /dis/man2txt.dis: %r\n"); + raise "fail:bad module"; + } + + argv = tl argv; + sections: list of string; + fflag := 0; + for (; argv != nil; argv = tl argv) { + arg := hd argv; + if (arg == nil) + continue; + if (arg == "-f") { + argv = tl argv; + if (argv == nil || sections != nil) + usage(); + fflag = 1; + break; + } + + if (!isint(arg)) + break; + sections = arg :: sections; + } + if (argv == nil) + usage(); + + paths := argv; + if (!fflag) { + err := loadsections(sections); + if (err != nil) { + sys->fprint(stderr, "%s\n", err); + raise "fail:error"; + } + files := getfiles(sections, argv); + paths = nil; + for (; files != nil; files = tl files) { + (nil, nil, path) := hd files; + paths = path :: paths; + } + paths = sortuniq(paths); + } + man2txt->init(nil, "man2txt" :: paths); +} + +loadsections(scanlist: list of string): string +{ + sys = load Sys Sys->PATH; + bufio := load Bufio Bufio->PATH; + Iobuf: import bufio; + + if (bufio == nil) + return sys->sprint("cannot load %s: %r", Bufio->PATH); + + indexpaths: list of string; + if (scanlist == nil) { + filepat := load Filepat Filepat->PATH; + if (filepat == nil) + return sys->sprint("cannot load %s: %r", Filepat->PATH); + + indexpaths = filepat->expand(MANPATH + "[0-9]*/INDEX"); + if (indexpaths == nil) + return sys->sprint("cannot find man pages"); + } else { + for (; scanlist != nil; scanlist = tl scanlist) + indexpaths = MANPATH + string hd scanlist + "/INDEX" :: indexpaths; + indexpaths = sortuniq(indexpaths); + } + + sections: list of string; + for (; indexpaths != nil; indexpaths = tl indexpaths) { + path := hd indexpaths; + (n, toks) := sys->tokenize(path, "/"); + for (d := 0; d < PATHDEPTH; d++) + toks = tl toks; + sections = hd toks :: sections; + } + + for (sl := sections; sl != nil; sl = tl sl) { + section := hd sl; + path := MANPATH + string section + "/INDEX"; + iob := bufio->open(path, Sys->OREAD); + if (iob == nil) + continue; + pairs: list of (string, string) = nil; + + while((s := iob.gets('\n')) != nil) { + if (s[len s - 1] == '\n') + s = s[0:len s - 1]; + (n, toks) := sys->tokenize(s, " "); + if (n != 2) + continue; + pairs = (hd toks, hd tl toks) :: pairs; + } + iob.close(); + indices = (section, pairs) :: indices; + } + return nil; +} + +getfiles(sections: list of string, keys: list of string): list of (int, string, string) +{ + ixl: list of (string, list of (string, string)); + + if (sections == nil) + ixl = indices; + else { + for (; sections != nil; sections = tl sections) { + section := hd sections; + for (il := indices; il != nil; il = tl il) { + (s, mapl) := hd il; + if (s == section) { + ixl = (s, mapl) :: ixl; + break; + } + } + } + } + paths: list of (int, string, string); + for(keyl := keys; keyl != nil; keyl = tl keyl){ + for (; ixl != nil; ixl = tl ixl) { + for ((s, mapl) := hd ixl; mapl != nil; mapl = tl mapl) { + (kw, file) := hd mapl; + if (hd keyl == kw) { + p := MANPATH + s + "/" + file; + paths = (int s, kw, p) :: paths; + } + } + # allow files not in the index + if(paths == nil || (hd paths).t0 != int s || (hd paths).t1 != hd keyl){ + p := MANPATH + string s + "/" + hd keyl; + if(sys->stat(p).t0 != -1) + paths = (int s, hd keyl, p) :: paths; + } + } + } + return paths; +} + +sortuniq(strlist: list of string): list of string +{ + strs := array [len strlist] of string; + for (i := 0; strlist != nil; (i, strlist) = (i+1, tl strlist)) + strs[i] = hd strlist; + + # simple sort (greatest first) + for (i = 0; i < len strs - 1; i++) { + for (j := i+1; j < len strs; j++) + if (strs[i] < strs[j]) + (strs[i], strs[j]) = (strs[j], strs[i]); + } + + # construct list (result is ascending) + r: list of string; + prev := ""; + for (i = 0; i < len strs; i++) { + if (strs[i] != prev) { + r = strs[i] :: r; + prev = strs[i]; + } + } + return r; +} + +isint(s: string): int +{ + for (i := 0; i < len s; i++) + if (s[i] < '0' || s[i] > '9') + return 0; + return 1; +} diff --git a/appl/cmd/man2txt.b b/appl/cmd/man2txt.b new file mode 100644 index 00000000..1a82027f --- /dev/null +++ b/appl/cmd/man2txt.b @@ -0,0 +1,79 @@ +implement Man2txt; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "man.m"; + +Man2txt: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +W: adt { + textwidth: fn(w: self ref W, text: Parseman->Text): int; +}; + +output: ref Iobuf; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if (bufio == nil) { + sys->print("cannot load Bufio module: %r\n"); + raise "fail:init"; + } + + stdout := sys->fildes(1); + output = bufio->fopen(stdout, Sys->OWRITE); + + parser := load Parseman Parseman->PATH; + parser->init(); + + argv = tl argv; + for (; argv != nil ; argv = tl argv) { + fname := hd argv; + fd := sys->open(fname, Sys->OREAD); + if (fd == nil) { + sys->print("cannot open %s: %r\n", fname); + continue; + } + m := Parseman->Metrics(65, 1, 1, 1, 1, 5, 2); + + datachan := chan of list of (int, Parseman->Text); + w: ref W; + spawn parser->parseman(fd, m, 1, w, datachan); + for (;;) { + line := <- datachan; + if (line == nil) + break; + setline(line); + } + output.flush(); + } + output.close(); +} + +W.textwidth(nil: self ref W, text: Parseman->Text): int +{ + return len text.text; +} + +setline(line: list of (int, Parseman->Text)) +{ +#return; + offset := 0; + for (; line != nil; line = tl line) { + (indent, txt) := hd line; + while (offset < indent) { + output.putc(' '); + offset++; + } + output.puts(txt.text); + offset += len txt.text; + } + output.putc('\n'); +} diff --git a/appl/cmd/manufacture.b b/appl/cmd/manufacture.b new file mode 100644 index 00000000..7be96de3 --- /dev/null +++ b/appl/cmd/manufacture.b @@ -0,0 +1,42 @@ +implement Manufacture; + +include "sys.m"; +FD, Dir: import Sys; +sys: Sys; + +include "draw.m"; +draw: Draw; +Context, Display, Font, Screen, Image, Point, Rect: import draw; + +Manufacture: module +{ + init: fn(ctxt: ref Context, argv: list of string); +}; + +stderr: ref FD; + +init(nil: ref Context, argv: list of string) +{ + s: string; + argv0: string; + + argv0 = hd argv; + argv = tl argv; + sys = load Sys Sys->PATH; + + stderr = sys->fildes(2); + + fd := sys->create("/nvfs/ID", sys->OWRITE, 8r666); + if(fd == nil){ + sys->fprint(stderr, "manufacture: can't create /nvfs/ID: %r\n"); + return; + } + + while(argv != nil) { + s = hd argv; + sys->fprint(fd, "%s", s); + argv = tl argv; + if(argv != nil) + sys->fprint(fd, " "); + } +} diff --git a/appl/cmd/mash/builtins.b b/appl/cmd/mash/builtins.b new file mode 100644 index 00000000..b4374581 --- /dev/null +++ b/appl/cmd/mash/builtins.b @@ -0,0 +1,347 @@ +implement Mashbuiltin; + +# +# "builtins" builtin, defines: +# +# env - print environment or individual elements +# eval - interpret arguments as mash input +# exit - exit toplevel, eval or subshell +# load - load a builtin +# prompt - print or set prompt +# quote - print arguments quoted as input for mash +# run - interpret a file as mash input +# status - report existence of error output +# time - time the execution of a command +# whatis - print variable, function and builtin +# + +include "mash.m"; +include "mashparse.m"; + +mashlib: Mashlib; + +Cmd, Env, Stab: import mashlib; +sys, bufio: import mashlib; + +Iobuf: import bufio; + +# +# Interface to catch the use as a command. +# +init(nil: ref Draw->Context, nil: list of string) +{ + ssys := load Sys Sys->PATH; + ssys->fprint(ssys->fildes(2), "builtins: cannot run as a command\n"); + raise "fail: error"; +} + +# +# Used by whatis. +# +name(): string +{ + return "builtins"; +} + +# +# Install commands. +# +mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env) +{ + mashlib = lib; + e.defbuiltin("env", this); + e.defbuiltin("eval", this); + e.defbuiltin("exit", this); + e.defbuiltin("load", this); + e.defbuiltin("prompt", this); + e.defbuiltin("quote", this); + e.defbuiltin("run", this); + e.defbuiltin("status", this); + e.defbuiltin("time", this); + e.defbuiltin("whatis", this); +} + +# +# Execute a builtin. +# +mashcmd(e: ref Env, l: list of string) +{ + case hd l { + "env" => + l = tl l; + if (l == nil) { + out := e.outfile(); + if (out == nil) + return; + prsymbs(out, e.global, "="); + prsymbs(out, e.local, ":="); + out.close(); + } else + e.usage("env"); + "eval" => + eval(e, tl l); + "exit" => + raise mashlib->EXIT; + "load" => + l = tl l; + if (len l == 1) + e.doload(hd l); + else + e.usage("load file"); + "prompt" => + l = tl l; + case len l { + 0 => + mashlib->prprompt(0); + 1 => + mashlib->prompt = hd l; + 2 => + mashlib->prompt = hd l; + mashlib->contin = hd tl l; + * => + e.usage("prompt [string]"); + } + "quote" => + l = tl l; + if (l != nil) { + out := e.outfile(); + if (out == nil) + return; + f := 0; + while (l != nil) { + if (f) + out.putc(' '); + else + f = 1; + out.puts(mashlib->quote(hd l)); + l = tl l; + } + out.putc('\n'); + out.close(); + } + "run" => + if (!run(e, tl l)) + e.usage("run [-] [-denx] file [arg ...]"); + "status" => + l = tl l; + if (l != nil) + status(e, l); + else + e.usage("status cmd [arg ...]"); + "time" => + l = tl l; + if (l != nil) + time(e, l); + else + e.usage("time cmd [arg ...]"); + "whatis" => + l = tl l; + if (l != nil) { + out := e.outfile(); + if (out == nil) + return; + while (l != nil) { + whatis(e, out, hd l); + l = tl l; + } + out.close(); + } + } +} + +# +# Print a variable and its value. +# +prone(out: ref Iobuf, eq, s: string, v: list of string) +{ + out.puts(s); + out.putc(' '); + out.puts(eq); + if (v != mashlib->empty) { + do { + out.putc(' '); + out.puts(mashlib->quote(hd v)); + v = tl v; + } while (v != nil); + } + out.puts(";\n"); +} + +# +# Print the contents of a symbol table. +# +prsymbs(out: ref Iobuf, t: ref Stab, eq: string) +{ + if (t == nil) + return; + for (l := t.all(); l != nil; l = tl l) { + s := hd l; + v := s.value; + if (v != nil) + prone(out, eq, s.name, v); + } +} + +# +# Print variables, functions and builtins. +# +whatis(e: ref Env, out: ref Iobuf, s: string) +{ + f := 0; + v := e.global.find(s); + if (v != nil) { + if (v.value != nil) + prone(out, "=", s, v.value); + if (v.func != nil) { + out.puts("fn "); + out.puts(s); + out.puts(" { "); + out.puts(v.func.text()); + out.puts(" };\n"); + } + if (v.builtin != nil) { + out.puts("load "); + out.puts(v.builtin->name()); + out.puts("; "); + out.puts(s); + out.puts(";\n"); + } + f = 1; + } + if (e.local != nil) { + v = e.local.find(s); + if (v != nil) { + prone(out, ":=", s, v.value); + f = 1; + } + } + if (!f) { + out.puts(s); + out.puts(": not found\n"); + } +} + +# +# Catenate arguments and interpret as mash input. +# +eval(e: ref Env, l: list of string) +{ + s: string; + while (l != nil) { + s = s + " " + hd l; + l = tl l; + } + e = e.copy(); + e.flags &= ~mashlib->EInter; + e.sopen(s); + mashlib->parse->parse(e); +} + +# +# Interpret file as mash input. +# +run(e: ref Env, l: list of string): int +{ + f := 0; + if (l == nil) + return 0; + e = e.copy(); + s := hd l; + while (s[0] == '-') { + if (s == "-") + f = 1; + else { + for (i := 1; i < len s; i++) { + case s[i] { + 'd' => + e.flags |= mashlib->EDumping; + 'e' => + e.flags |= mashlib->ERaise; + 'n' => + e.flags |= mashlib->ENoxeq; + 'x' => + e.flags |= mashlib->EEcho; + * => + return 0; + } + } + } + l = tl l; + if (l == nil) + return 0; + s = hd l; + } + fd := sys->open(s, Sys->OREAD); + if (fd == nil) { + err := mashlib->errstr(); + if (mashlib->nonexistent(err) && s[0] != '/' && s[0:2] != "./") { + fd = sys->open(mashlib->LIB + s, Sys->OREAD); + if (fd == nil) + err = mashlib->errstr(); + else + s = mashlib->LIB + s; + } + if (fd == nil) { + if (!f) + e.report(s + ": " + err); + return 1; + } + } + e.local = Stab.new(); + e.local.assign(mashlib->ARGS, tl l); + e.flags &= ~mashlib->EInter; + e.fopen(fd, s); + mashlib->parse->parse(e); + return 1; +} + +# +# Run a command and report true on no error output. +# +status(e: ref Env, l: list of string) +{ + in := child(e, l); + if (in == nil) + return; + b := array[256] of byte; + n := sys->read(in, b, len b); + if (n != 0) { + while (n > 0) + n = sys->read(in, b, len b); + if (n < 0) + e.couldnot("read", "pipe"); + } else + e.output(Mashlib->TRUE); +} + +# +# Status env child. +# +child(e: ref Env, l: list of string): ref Sys->FD +{ + e = e.copy(); + fds := e.pipe(); + if (fds == nil) + return nil; + if (sys->dup(fds[0].fd, 2) < 0) { + e.couldnot("dup", "pipe"); + return nil; + } + t := e.stderr; + e.stderr = fds[0]; + e.runit(l, nil, nil, 0); + e.stderr = t; + sys->dup(t.fd, 2); + return fds[1]; +} + +# +# Time the execution of a command. +# +time(e: ref Env, l: list of string) +{ + t1 := sys->millisec(); + e.runit(l, nil, nil, 1); + t2 := sys->millisec(); + sys->fprint(e.stderr, "%.4g\n", real (t2 - t1) / 1000.0); +} diff --git a/appl/cmd/mash/depends.b b/appl/cmd/mash/depends.b new file mode 100644 index 00000000..dfb14ef5 --- /dev/null +++ b/appl/cmd/mash/depends.b @@ -0,0 +1,228 @@ +# +# Dependency/rule routines. +# + +DHASH: con 127; # dephash size + +# +# Initialize. "make -clear" calls this. +# +initdep() +{ + dephash = array[DHASH] of list of ref Target; + rules = nil; +} + +# +# Lookup a target in dephash, maybe add it. +# +target(s: string, insert: int): ref Target +{ + h := hash->fun1(s, DHASH); + l := dephash[h]; + while (l != nil) { + if ((hd l).target == s) + return hd l; + l = tl l; + } + if (!insert) + return nil; + t := ref Target(s, nil); + dephash[h] = t :: dephash[h]; + return t; +} + +adddep(s: string, d: ref Depend) +{ + t := target(s, 1); + t.depends = d :: t.depends; +} + +# +# Dependency (:) command. +# Evaluate lhs and rhs, make dependency, and add to the targets. +# +Cmd.depend(c: self ref Cmd, e: ref Env) +{ + if ((e.flags & ETop) == 0) { + e.report("dependency not at top level"); + return; + } + if (dephash == nil) + initdep(); + w := pass1(e, c.words); + if (w == nil) + return; + l := pass2(e, w); + if (l == nil) + return; + r: list of string; + if (c.left.words != nil) { + w = pass1(e, c.left.words); + if (w == nil) + return; + r = pass2(e, w); + if (r == nil) + return; + } + d := ref Depend(l, r, c.left.op, c.left.left, 0); + while (l != nil) { + adddep(hd l, d); + l = tl l; + } +} + +# +# Evaluate rule lhs and break into path components. +# +rulelhs(e: ref Env, i: ref Item): ref Lhs +{ + i = i.ieval1(e); + if (i == nil) + return nil; + (s, l, nil) := i.ieval2(e); + if (l != nil) { + e.report("rule pattern evaluates to a list"); + return nil; + } + if (s == nil) { + e.report("rule pattern evaluates to nil"); + return nil; + } + (n, p) := sys->tokenize(s, "/"); + return ref Lhs(s, p, n); +} + +# +# Rule (:~) command. +# First pass of rhs evaluation is done here. +# +Cmd.rule(c: self ref Cmd, e: ref Env) +{ + if (e.flags & ETop) { + l := rulelhs(e, c.item); + if (l == nil) + return; + r := c.left.item.ieval1(e); + if (r == nil) + return; + rules = ref Rule(l, r, c.left.op, c.left.left) :: rules; + } else + e.report("rule not at top level"); +} + +Target.find(s: string): ref Target +{ + if (dephash == nil) + return nil; + return target(s, 0); +} + +# +# Match a path element. +# +matchelem(p, s: string): int +{ + m := len p; + n := len s; + if (m == n && p == s) + return 1; + for (i := 0; i < m; i++) { + if (p[i] == '*') { + j := i + 1; + if (j == m) + return 1; + q := p[j:]; + do { + if (matchelem(q, s[i:])) + return 1; + } while (++i < n); + return 0; + } else if (i >= n || p[i] != s[i]) + return 0; + } + return 0; +} + +# +# Match a path element and return a list of sub-matches. +# +matches(p, s: string): (int, list of string) +{ + m := len p; + n := len s; + for (i := 0; i < m; i++) { + if (p[i] == '*') { + j := i + 1; + if (j == m) + return (1, s[i:] :: nil); + q := p[j:]; + do { + (r, l) := matches(q, s[i:]); + if (r) + return (1, s[j - 1: i] :: l); + } while (++i < n); + return (0, nil); + } else if (i >= n || p[i] != s[i]) + return (0, nil); + } + return (m == n, nil); +} + +# +# Rule match. +# +Rule.match(r: self ref Rule, a, n: int, t: list of string): int +{ + l := r.lhs; + if (l.count != n || (l.text[0] == '/') != a) + return 0; + for (e := l.elems; e != nil; e = tl e) { + if (!matchelem(hd e, hd t)) + return 0; + t = tl t; + } + return 1; +} + +# +# Rule match with array of sub-matches. +# +Rule.matches(r: self ref Rule, t: list of string): array of string +{ + m: list of list of string; + c := 1; + for (e := r.lhs.elems; e != nil; e = tl e) { + (x, l) := matches(hd e, hd t); + if (!x) + return nil; + if (l != nil) { + c += len l; + m = revstrs(l) :: m; + } + t = tl t; + } + a := array[c] of string; + while (m != nil) { + for (l := hd m; l != nil; l = tl l) + a[--c] = hd l; + m = tl m; + } + return a; +} + +# +# Return list of rules that match a string. +# +rulematch(s: string): list of ref Rule +{ + m: list of ref Rule; + a := s[0] == '/'; + (n, t) := sys->tokenize(s, "/"); + for (l := rules; l != nil; l = tl l) { + r := hd l; + if (r.match(a, n, t)) + m = r :: m; + } + return m; +} diff --git a/appl/cmd/mash/dump.b b/appl/cmd/mash/dump.b new file mode 100644 index 00000000..ed4b6309 --- /dev/null +++ b/appl/cmd/mash/dump.b @@ -0,0 +1,199 @@ +# +# Output routines. +# + +# +# Echo list of strings. +# +echo(e: ref Env, s: list of string) +{ + out := e.outfile(); + if (out == nil) + return; + out.putc('+'); + for (t := s; t != nil; t = tl t) { + out.putc(' '); + out.puts(hd t); + } + out.putc('\n'); + out.close(); +} + +# +# Return text representation of Word/Item/Cmd. +# + +Word.word(w: self ref Word, d: string): string +{ + if (w == nil) + return nil; + if (d != nil) + return d + w.text; + if (w.flags & Wquoted) + return enquote(w.text); + return w.text; +} + +Item.text(i: self ref Item): string +{ + if (i == nil) + return nil; + case i.op { + Icaret => + return i.left.text() + " ^ " + i.right.text(); + Iicaret => + return i.left.text() + i.right.text(); + Idollarq => + return i.word.word("$\""); + Idollar or Imatch => + return i.word.word("$"); + Iword => + return i.word.word(nil); + Iexpr => + return "(" + i.cmd.text() + ")"; + Ibackq => + return "`" + group(i.cmd); + Iquote => + return "\"" + group(i.cmd); + Iinpipe => + return "<" + group(i.cmd); + Ioutpipe => + return ">" + group(i.cmd); + * => + return "?" + string i.op; + } +} + +words(l: list of ref Item): string +{ + s: string; + while (l != nil) { + if (s == nil) + s = (hd l).text(); + else + s = s + " " + (hd l).text(); + l = tl l; + } + return s; +} + +redir(s: string, c: ref Cmd): string +{ + if (c == nil) + return s; + for (l := c.redirs; l != nil; l = tl l) { + r := hd l; + s = s + " " + rdsymbs[r.op] + " " + r.word.text(); + } + return s; +} + +cmd2in(c: ref Cmd, s: string): string +{ + return c.left.text() + " " + s + " " + c.right.text(); +} + +group(c: ref Cmd): string +{ + if (c == nil) + return "{ }"; + return redir("{ " + c.text() + " }", c); +} + +sequence(c: ref Cmd): string +{ + s: string; + do { + r := c.right; + t := ";"; + if (r.op == Casync) { + r = r.left; + t = "&"; + } + if (s == nil) + s = r.text() + t; + else + s = r.text() + t + " " + s; + c = c.left; + } while (c != nil); + return s; +} + +Cmd.text(c: self ref Cmd): string +{ + if (c == nil) + return nil; + case c.op { + Csimple => + return redir(words(c.words), c); + Cseq => + return sequence(c); + Cfor => + return "for (" + c.item.text() + " in " + words(c.words) + ") " + c.left.text(); + Cif => + return "if (" + c.left.text() +") " + c.right.text(); + Celse => + return c.left.text() +" else " + c.right.text(); + Cwhile => + return "while (" + c.left.text() +") " + c.right.text(); + Ccase => + return redir("case " + c.left.text() + " { " + c.right.text() + "}", c); + Ccases => + s := c.left.text(); + if (s[len s - 1] != '&') + return s + "; " + c.right.text(); + return s + " " + c.right.text(); + Cmatched => + return cmd2in(c, "=>"); + Cdefeq => + return c.item.text() + " := " + words(c.words); + Ceq => + return c.item.text() + " = " + words(c.words); + Cfn => + return "fn " + c.item.text() + " " + group(c.left); + Crescue => + return "rescue " + c.item.text() + " " + group(c.left); + Casync => + return c.left.text() + "&"; + Cgroup => + return group(c.left); + Clistgroup => + return ":" + group(c.left); + Csubgroup => + return "@" + group(c.left); + Cnop => + return nil; + Cword => + return c.item.text(); + Ccaret => + return cmd2in(c, "^"); + Chd => + return "hd " + c.left.text(); + Clen => + return "len " + c.left.text(); + Cnot => + return "!" + c.left.text(); + Ctl => + return "tl " + c.left.text(); + Ccons => + return cmd2in(c, "::"); + Ceqeq => + return cmd2in(c, "=="); + Cnoteq => + return cmd2in(c, "!="); + Cmatch => + return cmd2in(c, "~"); + Cpipe => + return cmd2in(c, "|"); + Cdepend => + return words(c.words) + " : " + words(c.left.words) + " " + c.left.text(); + Crule => + return c.item.text() + " :~ " + c.left.item.text() + " " + c.left.text(); + * => + if (c.op >= Cprivate) + return "Priv+" + string (c.op - Cprivate); + else + return "?" + string c.op; + } + return nil; +} diff --git a/appl/cmd/mash/exec.b b/appl/cmd/mash/exec.b new file mode 100644 index 00000000..faa003b3 --- /dev/null +++ b/appl/cmd/mash/exec.b @@ -0,0 +1,401 @@ +# +# Manage the execution of a command. +# + +srv: string; # srv file proto +nsrv: int = 0; # srv file unique id + +# +# Return error string. +# +errstr(): string +{ + return sys->sprint("%r"); +} + +# +# Server thread for servefd. +# +server(c: ref Sys->FileIO, fd: ref Sys->FD, write: int) +{ + a: array of byte; + if (!write) + a = array[Sys->ATOMICIO] of byte; + for (;;) { + alt { + (nil, b, nil, wc) := <- c.write => + if (wc == nil) + return; + if (!write) { + wc <- = (0, EPIPE); + return; + } + r := sys->write(fd, b, len b); + if (r < 0) { + wc <- = (0, errstr()); + return; + } + wc <- = (r, nil); + (nil, n, nil, rc) := <- c.read => + if (rc == nil) + return; + if (write) { + rc <- = (array[0] of byte, nil); + return; + } + if (n > Sys->ATOMICIO) + n = Sys->ATOMICIO; + r := sys->read(fd, a, n); + if (r < 0) { + rc <- = (nil, errstr()); + return; + } + rc <- = (a[0:r], nil); + } + } +} + +# +# Serve FD as a #s file. Used to implement generators. +# +Env.servefd(e: self ref Env, fd: ref Sys->FD, write: int): string +{ + (s, c) := e.servefile(nil); + spawn server(c, fd, write); + return s; +} + +# +# Generate name and FileIO adt for a served filed. +# +Env.servefile(e: self ref Env, n: string): (string, ref Sys->FileIO) +{ + c: ref Sys->FileIO; + s: string; + if (srv == nil) { + (ok, d) := sys->stat(CHAN); + if (ok < 0) + e.couldnot("stat", CHAN); + if (d.dtype != 's') { + if (sys->bind("#s", CHAN, Sys->MBEFORE) < 0) + e.couldnot("bind", CHAN); + } + srv = "mash." + string sys->pctl(0, nil); + } + retry := 0; + for (;;) { + if (retry || n == nil) + s = srv + "." + string nsrv++; + else + s = n; + c = sys->file2chan(CHAN, s); + s = CHAN + "/" + s; + if (c == nil) { + if (retry || n == nil || errstr() != EEXISTS) + e.couldnot("file2chan", s); + retry = 1; + continue; + } + break; + } + if (n != nil) + n = CHAN + "/" + n; + else + n = s; + if (retry && sys->bind(s, n, Sys->MREPL) < 0) + e.couldnot("bind", n); + return (n, c); +} + +# +# Shorthand for string output. +# +Env.output(e: self ref Env, s: string) +{ + if (s == nil) + return; + out := e.outfile(); + if (out == nil) + return; + out.puts(s); + out.close(); +} + +# +# Return Iobuf for stdout. +# +Env.outfile(e: self ref Env): ref Bufio->Iobuf +{ + fd := e.out; + if (fd == nil) + fd = sys->fildes(1); + out := bufio->fopen(fd, Bufio->OWRITE); + if (out == nil) + e.report(sys->sprint("fopen failed: %r")); + return out; +} + +# +# Return FD for /dev/null. +# +Env.devnull(e: self ref Env): ref Sys->FD +{ + fd := sys->open(DEVNULL, Sys->OREAD); + if (fd == nil) + e.couldnot("open", DEVNULL); + return fd; +} + +# +# Make a pipe. +# +Env.pipe(e: self ref Env): array of ref Sys->FD +{ + fds := array[2] of ref Sys->FD; + if (sys->pipe(fds) < 0) { + e.report(sys->sprint("pipe failed: %r")); + return nil; + } + return fds; +} + +# +# Open wait file for an env. +# +waitfd(e: ref Env) +{ + w := "#p/" + string sys->pctl(0, nil) + "/wait"; + fd := sys->open(w, sys->OREAD); + if (fd == nil) + e.couldnot("open", w); + e.wait = fd; +} + +# +# Wait for a thread. Perhaps propagate exception or exit. +# +waitfor(e: ref Env, pid: int, wc: chan of int, ec, xc: chan of string) +{ + if (ec != nil || xc != nil) { + spawn waiter(e, pid, wc); + if (ec == nil) + ec = chan of string; + if (xc == nil) + xc = chan of string; + alt { + <-wc => + return; + x := <-ec => + <-wc; + exitmash(); + x := <-xc => + <-wc; + s := x; + if (len s < FAILLEN || s[0:FAILLEN] != FAIL) + s = FAIL + s; + raise s; + } + } else + waiter(e, pid, nil); +} + +# +# Wait for a specific pid. +# +waiter(e: ref Env, pid: int, wc: chan of int) +{ + buf := array[sys->WAITLEN] of byte; + for(;;) { + n := sys->read(e.wait, buf, len buf); + if (n < 0) { + e.report(sys->sprint("read wait: %r\n")); + break; + } + status := string buf[0:n]; + if (status[len status - 1] != ':') + sys->fprint(e.stderr, "%s\n", status); + who := int status; + if (who != 0 && who == pid) + break; + } + if (wc != nil) + wc <-= 0; +} + +# +# Preparse IO for a new thread. +# Make a new FD group and redirect stdin/stdout. +# +prepareio(in, out: ref sys->FD): (int, ref Sys->FD) +{ + fds := list of { 0, 1, 2}; + if (in != nil) + fds = in.fd :: fds; + if (out != nil) + fds = out.fd :: fds; + pid := sys->pctl(sys->NEWFD, fds); + console := sys->fildes(2); + if (in != nil) { + sys->dup(in.fd, 0); + in = nil; + } + if (out != nil) { + sys->dup(out.fd, 1); + out = nil; + } + return (pid, console); +} + +# +# Add ".dis" to a command if missing. +# +dis(s: string): string +{ + if (len s < 4 || s[len s - 4:] != ".dis") + return s + ".dis"; + return s; +} + +# +# Load a builtin. +# +Env.doload(e: self ref Env, s: string) +{ + file := dis(s); + l := load Mashbuiltin file; + if (l == nil) { + err := errstr(); + if (nonexistent(err) && file[0] != '/' && file[0:2] != "./") { + l = load Mashbuiltin LIB + file; + if (l == nil) + err = errstr(); + } + if (l == nil) { + e.report(s + ": " + err); + return; + } + } + l->mashinit("load" :: s :: nil, lib, l, e); +} + +# +# Execute a spawned thread (dis module or builtin). +# +mkprog(args: list of string, e: ref Env, in, out: ref Sys->FD, wc: chan of int, ec, xc: chan of string) +{ + (pid, console) := prepareio(in, out); + wc <-= pid; + if (pid < 0) + return; + cmd := hd args; + { + b := e.builtin(cmd); + if (b != nil) { + e = e.copy(); + e.in = in; + e.out = out; + e.stderr = console; + e.wait = nil; + b->mashcmd(e, args); + } else { + file := dis(cmd); + c := load Command file; + if (c == nil) { + err := errstr(); + if (nonexistent(err) && file[0] != '/' && file[0:2] != "./") { + c = load Command "/dis/" + file; + if (c == nil) + err = errstr(); + } + if (c == nil) { + sys->fprint(console, "%s: %s\n", file, err); + return; + } + } + c->init(gctxt, args); + } + }exception x{ + FAILPAT => + if (xc != nil) + xc <-= x; + # the command failure should be propagated silently to + # a higher level, where $status can be set.. - wrtp. + #else + # sys->fprint(console, "%s: %s\n", cmd, x.name); + exit; + EPIPE => + if (xc != nil) + xc <-= x; + #else + # sys->fprint(console, "%s: %s\n", cmd, x.name); + exit; + EXIT => + if (ec != nil) + ec <-= x; + exit; + } +} + +# +# Open/create files for redirection. +# +redirect(e: ref Env, f: array of string, in, out: ref Sys->FD): (int, ref Sys->FD, ref Sys->FD) +{ + s: string; + err := 0; + if (f[Rinout] != nil) { + s = f[Rinout]; + in = sys->open(s, Sys->ORDWR); + if (in == nil) { + sys->fprint(e.stderr, "%s: %r\n", s); + err = 1; + } + out = in; + } else if (f[Rin] != nil) { + s = f[Rin]; + in = sys->open(s, Sys->OREAD); + if (in == nil) { + sys->fprint(e.stderr, "%s: %r\n", s); + err = 1; + } + } + if (f[Rout] != nil || f[Rappend] != nil) { + if (f[Rappend] != nil) { + s = f[Rappend]; + out = sys->open(s, Sys->OWRITE); + if (out != nil) + sys->seek(out, big 0, Sys->SEEKEND); + } else { + s = f[Rout]; + out = nil; + } + if (out == nil) { + out = sys->create(s, Sys->OWRITE, 8r666); + if (out == nil) { + sys->fprint(e.stderr, "%s: %r\n", s); + err = 1; + } + } + } + if (err) + return (0, nil, nil); + return (1, in, out); +} + +# +# Spawn a command and maybe wait for it. +# +exec(a: list of string, e: ref Env, infd, outfd: ref Sys->FD, wait: int) +{ + if (wait && e.wait == nil) + waitfd(e); + wc := chan of int; + if (wait && (e.flags & ERaise)) + xc := chan of string; + if (wait && (e.flags & ETop)) + ec := chan of string; + spawn mkprog(a, e, infd, outfd, wc, ec, xc); + pid := <-wc; + if (wait) + waitfor(e, pid, wc, ec, xc); +} diff --git a/appl/cmd/mash/expr.b b/appl/cmd/mash/expr.b new file mode 100644 index 00000000..00e45069 --- /dev/null +++ b/appl/cmd/mash/expr.b @@ -0,0 +1,158 @@ +# +# Expression evaluation. +# + +# +# Filename pattern matching. +# +glob(e: ref Env, s: string): (string, list of string) +{ + if (filepat == nil) { + filepat = load Filepat Filepat->PATH; + if (filepat == nil) + e.couldnot("load", Filepat->PATH); + } + l := filepat->expand(s); + if (l != nil) + return (nil, l); + return (s, nil); +} + +# +# RE pattern matching. +# +match(s1, s2: string): int +{ + (re, nil) := regex->compile(s2, 0); + return regex->execute(re, s1) != nil; +} + +# +# RE match of two lists. Two non-singleton lists never match. +# +match2(e: ref Env, s1: string, l1: list of string, s2: string, l2: list of string): int +{ + if (regex == nil) { + regex = load Regex Regex->PATH; + if (regex == nil) + e.couldnot("load", Regex->PATH); + } + if (s1 != nil) { + if (s2 != nil) + return match(s1, s2); + while (l2 != nil) { + if (match(s1, hd l2)) + return 1; + l2 = tl l2; + } + } else if (l1 != nil) { + if (s2 == nil) + return 0; + while (l1 != nil) { + if (match(hd l1, s2)) + return 1; + l1 = tl l1; + } + } else if (s2 != nil) + return match(nil, s2); + else if (l2 != nil) { + while (l2 != nil) { + if (match(nil, hd l2)) + return 1; + l2 = tl l2; + } + } else + return 1; + return 0; +} + +# +# Test list equality. Same length and identical members. +# +eqlist(l1, l2: list of string): int +{ + while (l1 != nil && l2 != nil) { + if (hd l1 != hd l2) + return 0; + l1 = tl l1; + l2 = tl l2; + } + return l1 == nil && l2 == nil; +} + +# +# Equality operator. +# +Cmd.evaleq(c: self ref Cmd, e: ref Env): int +{ + (s1, l1, nil) := c.left.eeval2(e); + (s2, l2, nil) := c.right.eeval2(e); + if (s1 != nil) + return s1 == s2; + if (l1 != nil) + return eqlist(l1, l2); + return s2 == nil && l2 == nil; +} + +# +# Match operator. +# +Cmd.evalmatch(c: self ref Cmd, e: ref Env): int +{ + (s1, l1, nil) := c.left.eeval2(e); + (s2, l2, nil) := c.right.eeval2(e); + return match2(e, s1, l1, s2, l2); +} + +# +# Catenation operator. +# +Item.caret(i: self ref Item, e: ref Env): (string, list of string, int) +{ + (s1, l1, x1) := i.left.ieval2(e); + (s2, l2, x2) := i.right.ieval2(e); + return caret(s1, l1, x1, s2, l2, x2); +} + +# +# Caret of lists. A singleton distributes. Otherwise pairwise, padded with nils. +# +caret(s1: string, l1: list of string, x1: int, s2: string, l2: list of string, x2: int): (string, list of string, int) +{ + l: list of string; + if (s1 != nil) { + if (s2 != nil) + return (s1 + s2, nil, x1 | x2); + if (l2 == nil) + return (s1, nil, x1); + while (l2 != nil) { + l = (s1 + hd l2) :: l; + l2 = tl l2; + } + } else if (s2 != nil) { + if (l1 == nil) + return (s2, nil, x2); + while (l1 != nil) { + l = (hd l1 + s2) :: l; + l1 = tl l1; + } + } else if (l1 != nil) { + if (l2 == nil) + return (nil, l1, 0); + while (l1 != nil || l2 != nil) { + if (l1 != nil) { + s1 = hd l1; + l1 = tl l1; + } else + s1 = nil; + if (l2 != nil) { + s2 = hd l2; + l2 = tl l2; + } else + s2 = nil; + l = (s1 + s2) :: l; + } + } else if (l2 != nil) + return (nil, l2, 0); + return (nil, revstrs(l), 0); +} diff --git a/appl/cmd/mash/eyacc.b b/appl/cmd/mash/eyacc.b new file mode 100644 index 00000000..96b6e412 --- /dev/null +++ b/appl/cmd/mash/eyacc.b @@ -0,0 +1,2785 @@ +implement Yacc; + +include "sys.m"; + sys: Sys; + print, fprint, sprint: import sys; + UTFmax: import Sys; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "draw.m"; + +Yacc: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +Arg: adt +{ + argv: list of string; + c: int; + opts: string; + + init: fn(argv: list of string): ref Arg; + opt: fn(arg: self ref Arg): int; + arg: fn(arg: self ref Arg): string; +}; + +PARSER: con "./eyaccpar"; +OFILE: con "tab.b"; +FILEU: con "output"; +FILED: con "tab.m"; +FILEDEBUG: con "debug"; + +# the following are adjustable +# according to memory size +ACTSIZE: con 30000; +NSTATES: con 2000; +TEMPSIZE: con 2000; + +SYMINC: con 50; # increase for non-term or term +RULEINC: con 50; # increase for max rule length prodptr[i] +PRODINC: con 100; # increase for productions prodptr +WSETINC: con 50; # increase for working sets wsets +STATEINC: con 200; # increase for states statemem + +NAMESIZE: con 50; +NTYPES: con 63; +ISIZE: con 400; + +PRIVATE: con 16rE000; # unicode private use + +# relationships which must hold: +# TEMPSIZE >= NTERMS + NNONTERM + 1 +# TEMPSIZE >= NSTATES +# + +NTBASE: con 8r10000; +ERRCODE: con 8190; +ACCEPTCODE: con 8191; +YYLEXUNK: con 3; +TOKSTART: con 4; #index of first defined token + +# no, left, right, binary assoc. +NOASC, LASC, RASC, BASC: con iota; + +# flags for state generation +DONE, MUSTDO, MUSTLOOKAHEAD: con iota; + +# flags for a rule having an action, and being reduced +ACTFLAG: con 16r4; +REDFLAG: con 16r8; + +# output parser flags +YYFLAG1: con -1000; + +# parse tokens +IDENTIFIER, MARK, TERM, LEFT, RIGHT, BINARY, PREC, LCURLY, IDENTCOLON, NUMBER, START, TYPEDEF, TYPENAME, MODULE: con PRIVATE+iota; + +ENDFILE: con 0; + +EMPTY: con 1; +WHOKNOWS: con 0; +OK: con 1; +NOMORE: con -1000; + +# macros for getting associativity and precedence levels +ASSOC(i: int): int +{ + return i & 3; +} + +PLEVEL(i: int): int +{ + return (i >> 4) & 16r3f; +} + +TYPE(i: int): int +{ + return (i >> 10) & 16r3f; +} + +# macros for setting associativity and precedence levels +SETASC(i, j: int): int +{ + return i | j; +} + +SETPLEV(i, j: int): int +{ + return i | (j << 4); +} + +SETTYPE(i, j: int): int +{ + return i | (j << 10); +} + +# I/O descriptors +stderr: ref Sys->FD; +fdefine: ref Iobuf; # file for module definition +fdebug: ref Iobuf; # y.debug for strings for debugging +ftable: ref Iobuf; # y.tab.c file +finput: ref Iobuf; # input file +foutput: ref Iobuf; # y.output file + +CodeData, CodeMod, CodeAct: con iota; +NCode: con 8192; + +Code: adt +{ + kind: int; + data: array of byte; + ndata: int; + next: cyclic ref Code; +}; + +codehead: ref Code; +codetail: ref Code; + +modname: string; # name of module + +# communication variables between various I/O routines +infile: string; # input file name +numbval: int; # value of an input number +tokname: string; # input token name, slop for runes and 0 + +# structure declarations +Lkset: type array of int; + +Pitem: adt +{ + prod: array of int; + off: int; # offset within the production + first: int; # first term or non-term in item + prodno: int; # production number for sorting +}; + +Item: adt +{ + pitem: Pitem; + look: Lkset; +}; + +Symb: adt +{ + name: string; + value: int; +}; + +Wset: adt +{ + pitem: Pitem; + flag: int; + ws: Lkset; +}; + + # storage of names + +parser := PARSER; +yydebug: string; + + # storage of types +ntypes: int; # number of types defined +typeset := array[NTYPES] of string; # pointers to type tags + + # token information + +ntokens := 0; # number of tokens +tokset: array of Symb; +toklev: array of int; # vector with the precedence of the terminals + + # nonterminal information + +nnonter := -1; # the number of nonterminals +nontrst: array of Symb; +start: int; # start symbol + + # state information + +nstate := 0; # number of states +pstate := array[NSTATES+2] of int; # index into statemem to the descriptions of the states +statemem : array of Item; +tystate := array[NSTATES] of int; # contains type information about the states +tstates : array of int; # states generated by terminal gotos +ntstates : array of int; # states generated by nonterminal gotos +mstates := array[NSTATES] of {* => 0}; # chain of overflows of term/nonterm generation lists +lastred: int; # number of last reduction of a state +defact := array[NSTATES] of int; # default actions of states + + # lookahead set information + +lkst: array of Lkset; +nolook := 0; # flag to turn off lookahead computations +tbitset := 0; # size of lookahead sets +clset: Lkset; # temporary storage for lookahead computations + + # working set information + +wsets: array of Wset; +cwp: int; + + # storage for action table + +amem: array of int; # action table storage +memp: int; # next free action table position +indgo := array[NSTATES] of int; # index to the stored goto table + + # temporary vector, indexable by states, terms, or ntokens + +temp1 := array[TEMPSIZE] of int; # temporary storage, indexed by terms + ntokens or states +lineno := 1; # current input line number +fatfl := 1; # if on, error is fatal +nerrors := 0; # number of errors + + # assigned token type values +extval := 0; + +ytabc := OFILE; # name of y.tab.c + + # grammar rule information + +nprod := 1; # number of productions +prdptr: array of array of int; # pointers to descriptions of productions +levprd: array of int; # precedence levels for the productions +rlines: array of int; # line number for this rule + + + # statistics collection variables + +zzgoent := 0; +zzgobest := 0; +zzacent := 0; +zzexcp := 0; +zzclose := 0; +zzrrconf := 0; +zzsrconf := 0; +zzstate := 0; + + # optimizer arrays +yypgo: array of array of int; +optst: array of array of int; +ggreed: array of int; +pgo: array of int; + +maxspr: int; # maximum spread of any entry +maxoff: int; # maximum offset into a array +maxa: int; + + # storage for information about the nonterminals + +pres: array of array of array of int; # vector of pointers to productions yielding each nonterminal +pfirst: array of Lkset; +pempty: array of int; # vector of nonterminals nontrivially deriving e + # random stuff picked out from between functions + +indebug := 0; # debugging flag for cpfir +pidebug := 0; # debugging flag for putitem +gsdebug := 0; # debugging flag for stagen +cldebug := 0; # debugging flag for closure +pkdebug := 0; # debugging flag for apack +g2debug := 0; # debugging for go2gen +adb := 0; # debugging for callopt + +Resrv : adt +{ + name: string; + value: int; +}; + +resrv := array[] of { + Resrv("binary", BINARY), + Resrv("module", MODULE), + Resrv("left", LEFT), + Resrv("nonassoc", BINARY), + Resrv("prec", PREC), + Resrv("right", RIGHT), + Resrv("start", START), + Resrv("term", TERM), + Resrv("token", TERM), + Resrv("type", TYPEDEF),}; + +zznewstate := 0; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + + stderr = sys->fildes(2); + + setup(argv); # initialize and read productions + + tbitset = (ntokens+32)/32; + cpres(); # make table of which productions yield a given nonterminal + cempty(); # make a table of which nonterminals can match the empty string + cpfir(); # make a table of firsts of nonterminals + + stagen(); # generate the states + + yypgo = array[nnonter+1] of array of int; + optst = array[nstate] of array of int; + output(); # write the states and the tables + go2out(); + + hideprod(); + summary(); + + callopt(); + + others(); + + bufio->flush(); +} + +setup(argv: list of string) +{ + j, ty: int; + + ytab := 0; + vflag := 0; + dflag := 0; + stem := 0; + stemc := "y"; + foutput = nil; + fdefine = nil; + fdebug = nil; + arg := Arg.init(argv); + while(c := arg.opt()){ + case c{ + 'v' or 'V' => + vflag++; + 'D' => + yydebug = arg.arg(); + 'd' => + dflag++; + 'o' => + ytab++; + ytabc = arg.arg(); + 's' => + stem++; + stemc = arg.arg(); + * => + usage(); + } + } + argv = arg.argv; + if(len argv != 1) + usage(); + infile = hd argv; + finput = bufio->open(infile, Bufio->OREAD); + if(finput == nil) + error("cannot open '"+infile+"'"); + + openup(stemc, dflag, vflag, ytab, ytabc); + + defin(0, "$end"); + extval = PRIVATE; # tokens start in unicode 'private use' + defin(0, "error"); + defin(1, "$accept"); + defin(0, "$unk"); + i := 0; + + for(t := gettok(); t != MARK && t != ENDFILE; ) + case t { + ';' => + t = gettok(); + + START => + if(gettok() != IDENTIFIER) + error("bad %%start construction"); + start = chfind(1, tokname); + t = gettok(); + + TYPEDEF => + if(gettok() != TYPENAME) + error("bad syntax in %%type"); + ty = numbval; + for(;;) { + t = gettok(); + case t { + IDENTIFIER => + if((t=chfind(1, tokname)) < NTBASE) { + j = TYPE(toklev[t]); + if(j != 0 && j != ty) + error("type redeclaration of token "+ + tokset[t].name); + else + toklev[t] = SETTYPE(toklev[t], ty); + } else { + j = nontrst[t-NTBASE].value; + if(j != 0 && j != ty) + error("type redeclaration of nonterminal "+ + nontrst[t-NTBASE].name); + else + nontrst[t-NTBASE].value = ty; + } + continue; + ',' => + continue; + ';' => + t = gettok(); + } + break; + } + + MODULE => + cpymodule(); + t = gettok(); + + LEFT or BINARY or RIGHT or TERM => + # nonzero means new prec. and assoc. + lev := t-TERM; + if(lev) + i++; + ty = 0; + + # get identifiers so defined + t = gettok(); + + # there is a type defined + if(t == TYPENAME) { + ty = numbval; + t = gettok(); + } + for(;;) { + case t { + ',' => + t = gettok(); + continue; + + ';' => + break; + + IDENTIFIER => + j = chfind(0, tokname); + if(j >= NTBASE) + error(tokname+" defined earlier as nonterminal"); + if(lev) { + if(ASSOC(toklev[j])) + error("redeclaration of precedence of "+tokname); + toklev[j] = SETASC(toklev[j], lev); + toklev[j] = SETPLEV(toklev[j], i); + } + if(ty) { + if(TYPE(toklev[j])) + error("redeclaration of type of "+tokname); + toklev[j] = SETTYPE(toklev[j],ty); + } + t = gettok(); + if(t == NUMBER) { + tokset[j].value = numbval; + t = gettok(); + } + continue; + } + break; + } + + LCURLY => + cpycode(); + t = gettok(); + + * => + error("syntax error"); + } + if(t == ENDFILE) + error("unexpected EOF before %%"); + if(modname == nil) + error("missing %module specification"); + + moreprod(); + prdptr[0] = array[4] of { + NTBASE, # added production + start, # if start is 0, we will overwrite with the lhs of the first rule + 1, + 0 + }; + nprod = 1; + curprod := array[RULEINC] of int; + t = gettok(); + if(t != IDENTCOLON) + error("bad syntax on first rule"); + + if(!start) + prdptr[0][1] = chfind(1, tokname); + + # read rules + # put into prdptr array in the format + # target + # followed by id's of terminals and non-terminals + # followd by -nprod + while(t != MARK && t != ENDFILE) { + mem := 0; + # process a rule + rlines[nprod] = lineno; + if(t == '|') + curprod[mem++] = prdptr[nprod-1][0]; + else if(t == IDENTCOLON) { + curprod[mem] = chfind(1, tokname); + if(curprod[mem] < NTBASE) + error("token illegal on LHS of grammar rule"); + mem++; + } else + error("illegal rule: missing semicolon or | ?"); + + # read rule body + t = gettok(); + + for(;;){ + while(t == IDENTIFIER) { + curprod[mem] = chfind(1, tokname); + if(curprod[mem] < NTBASE) + levprd[nprod] = toklev[curprod[mem]]; + mem++; + if(mem >= len curprod){ + ncurprod := array[mem+RULEINC] of int; + ncurprod[0:] = curprod; + curprod = ncurprod; + } + t = gettok(); + } + if(t == PREC) { + if(gettok() != IDENTIFIER) + error("illegal %%prec syntax"); + j = chfind(2, tokname); + if(j >= NTBASE) + error("nonterminal "+nontrst[j-NTBASE].name+" illegal after %%prec"); + levprd[nprod] = toklev[j]; + t = gettok(); + } + if(t != '=') + break; + levprd[nprod] |= ACTFLAG; + addcode(CodeAct, "\n"+string nprod+"=>"); + cpyact(curprod, mem); + + # action within rule... + if((t=gettok()) == IDENTIFIER) { + # make it a nonterminal + j = chfind(1, "$$"+string nprod); + + # + # the current rule will become rule number nprod+1 + # enter null production for action + # + prdptr[nprod] = array[2] of {j, -nprod}; + + # update the production information + nprod++; + moreprod(); + levprd[nprod] = levprd[nprod-1] & ~ACTFLAG; + levprd[nprod-1] = ACTFLAG; + rlines[nprod] = lineno; + + # make the action appear in the original rule + curprod[mem++] = j; + if(mem >= len curprod){ + ncurprod := array[mem+RULEINC] of int; + ncurprod[0:] = curprod; + curprod = ncurprod; + } + } + } + + while(t == ';') + t = gettok(); + curprod[mem++] = -nprod; + + # check that default action is reasonable + if(ntypes && !(levprd[nprod]&ACTFLAG) && nontrst[curprod[0]-NTBASE].value) { + # no explicit action, LHS has value + + tempty := curprod[1]; + if(tempty < 0) + error("must return a value, since LHS has a type"); + else + if(tempty >= NTBASE) + tempty = nontrst[tempty-NTBASE].value; + else + tempty = TYPE(toklev[tempty]); + if(tempty != nontrst[curprod[0]-NTBASE].value) + error("default action causes potential type clash"); + else{ + addcodec(CodeAct, '\n'); + addcode(CodeAct, string nprod); + addcode(CodeAct, "=>\ne.yyval."); + addcode(CodeAct, typeset[tempty]); + addcode(CodeAct, " = yys[yyp+1].yyv."); + addcode(CodeAct, typeset[tempty]); + addcodec(CodeAct, ';'); + } + } + moreprod(); + prdptr[nprod] = array[mem] of int; + prdptr[nprod][0:] = curprod[:mem]; + nprod++; + moreprod(); + levprd[nprod] = 0; + } + + # + # end of all rules + # dump out the prefix code + # + ftable.puts("implement "); + ftable.puts(modname); + ftable.puts(";\n"); + + dumpcode(CodeMod); + dumpmod(); + dumpcode(CodeAct); + + ftable.puts("YYEOFCODE: con 1;\n"); + ftable.puts("YYERRCODE: con 2;\n"); + ftable.puts("YYMAXDEPTH: con 200;\n"); # was 150 +# ftable.puts("yyval: YYSTYPE;\n"); + + # + # copy any postfix code + # + if(t == MARK) { + ftable.puts("\n#line\t"); + ftable.puts(string lineno); + ftable.puts("\t\""); + ftable.puts(infile); + ftable.puts("\"\n"); + while((c=finput.getc()) != Bufio->EOF) + ftable.putc(c); + } + finput.close(); +} + +# +# allocate enough room to hold another production +# +moreprod() +{ + n := len prdptr; + if(nprod < n) + return; + n += PRODINC; + aprod := array[n] of array of int; + aprod[0:] = prdptr; + prdptr = aprod; + + alevprd := array[n] of int; + alevprd[0:] = levprd; + levprd = alevprd; + + arlines := array[n] of int; + arlines[0:] = rlines; + rlines = arlines; +} + +# +# define s to be a terminal if t=0 +# or a nonterminal if t=1 +# +defin(nt: int, s: string): int +{ + val := 0; + if(nt) { + nnonter++; + if(nnonter >= len nontrst){ + anontrst := array[nnonter + SYMINC] of Symb; + anontrst[0:] = nontrst; + nontrst = anontrst; + } + nontrst[nnonter] = Symb(s, 0); + return NTBASE + nnonter; + } + + # must be a token + ntokens++; + if(ntokens >= len tokset){ + atokset := array[ntokens + SYMINC] of Symb; + atokset[0:] = tokset; + tokset = atokset; + + atoklev := array[ntokens + SYMINC] of int; + atoklev[0:] = toklev; + toklev = atoklev; + } + tokset[ntokens].name = s; + toklev[ntokens] = 0; + + # establish value for token + # single character literal + if(s[0] == ' ' && len s == 1+1){ + val = s[1]; + }else if(s[0] == ' ' && s[1] == '\\') { # escape sequence + if(len s == 2+1) { + # single character escape sequence + case s[2] { + '\'' => val = '\''; + '"' => val = '"'; + '\\' => val = '\\'; + 'a' => val = '\a'; + 'b' => val = '\b'; + 'n' => val = '\n'; + 'r' => val = '\r'; + 't' => val = '\t'; + 'v' => val = '\v'; + * => + error("invalid escape "+s[1:3]); + } + }else if(s[2] == 'u' && len s == 2+1+4) { # \unnnn sequence + val = 0; + s = s[3:]; + while(s != ""){ + c := s[0]; + if(c >= '0' && c <= '9') + c -= '0'; + else if(c >= 'a' && c <= 'f') + c -= 'a' - 10; + else if(c >= 'A' && c <= 'F') + c -= 'A' - 10; + else + error("illegal \\unnnn construction"); + val = val * 16 + c; + s = s[1:]; + } + if(val == 0) + error("'\\u0000' is illegal"); + }else + error("unknown escape"); + }else + val = extval++; + + tokset[ntokens].value = val; + return ntokens; +} + +peekline := 0; +gettok(): int +{ + i, match, c: int; + + tokname = ""; + for(;;){ + reserve := 0; + lineno += peekline; + peekline = 0; + c = finput.getc(); + while(c == ' ' || c == '\n' || c == '\t' || c == '\v' || c == '\r') { + if(c == '\n') + lineno++; + c = finput.getc(); + } + + # skip comment + if(c != '#') + break; + lineno += skipcom(); + } + case c { + Bufio->EOF => + return ENDFILE; + + '{' => + finput.ungetc(); + return '='; + + '<' => + # get, and look up, a type name (union member name) + i = 0; + while((c=finput.getc()) != '>' && c != Bufio->EOF && c != '\n') + tokname[i++] = c; + if(c != '>') + error("unterminated < ... > clause"); + for(i=1; i<=ntypes; i++) + if(typeset[i] == tokname) { + numbval = i; + return TYPENAME; + } + ntypes++; + numbval = ntypes; + typeset[numbval] = tokname; + return TYPENAME; + + '"' or '\'' => + match = c; + tokname[0] = ' '; + i = 1; + for(;;) { + c = finput.getc(); + if(c == '\n' || c == Bufio->EOF) + error("illegal or missing ' or \"" ); + if(c == '\\') { + tokname[i++] = '\\'; + c = finput.getc(); + } else if(c == match) + return IDENTIFIER; + tokname[i++] = c; + } + + '%' => + case c = finput.getc(){ + '%' => return MARK; + '=' => return PREC; + '{' => return LCURLY; + } + + getword(c); + # find a reserved word + for(c=0; c < len resrv; c++) + if(tokname == resrv[c].name) + return resrv[c].value; + error("invalid escape, or illegal reserved word: "+tokname); + + '0' to '9' => + numbval = c - '0'; + while(isdigit(c = finput.getc())) + numbval = numbval*10 + c-'0'; + finput.ungetc(); + return NUMBER; + + * => + if(isword(c) || c=='.' || c=='$') + getword(c); + else + return c; + } + + # look ahead to distinguish IDENTIFIER from IDENTCOLON + c = finput.getc(); + while(c == ' ' || c == '\t'|| c == '\n' || c == '\v' || c == '\r' || c == '#') { + if(c == '\n') + peekline++; + # look for comments + if(c == '#') + peekline += skipcom(); + c = finput.getc(); + } + if(c == ':') + return IDENTCOLON; + finput.ungetc(); + return IDENTIFIER; +} + +getword(c: int) +{ + i := 0; + while(isword(c) || isdigit(c) || c == '_' || c=='.' || c=='$') { + tokname[i++] = c; + c = finput.getc(); + } + finput.ungetc(); +} + +# +# determine the type of a symbol +# +fdtype(t: int): int +{ + v : int; + s: string; + + if(t >= NTBASE) { + v = nontrst[t-NTBASE].value; + s = nontrst[t-NTBASE].name; + } else { + v = TYPE(toklev[t]); + s = tokset[t].name; + } + if(v <= 0) + error("must specify type for "+s); + return v; +} + +chfind(t: int, s: string): int +{ + if(s[0] == ' ') + t = 0; + for(i:=0; i<=ntokens; i++) + if(s == tokset[i].name) + return i; + for(i=0; i<=nnonter; i++) + if(s == nontrst[i].name) + return NTBASE+i; + + # cannot find name + if(t > 1) + error(s+" should have been defined earlier"); + return defin(t, s); +} + +# +# saves module definition in Code +# +cpymodule() +{ + if(gettok() != IDENTIFIER) + error("bad %%module construction"); + if(modname != nil) + error("duplicate %%module construction"); + modname = tokname; + + level := 0; + for(;;) { + if((c:=finput.getc()) == Bufio->EOF) + error("EOF encountered while processing %%module"); + case c { + '\n' => + lineno++; + '{' => + level++; + if(level == 1) + continue; + '}' => + level--; + + # we are finished copying + if(level == 0) + return; + } + addcodec(CodeMod, c); + } +} + +# +# saves code between %{ and %} +# +cpycode() +{ + c := finput.getc(); + if(c == '\n') { + c = finput.getc(); + lineno++; + } + addcode(CodeData, "\n#line\t" + string lineno + "\t\"" + infile + "\"\n"); + while(c != Bufio->EOF) { + if(c == '%') { + if((c=finput.getc()) == '}') + return; + addcodec(CodeData, '%'); + } + addcodec(CodeData, c); + if(c == '\n') + lineno++; + c = finput.getc(); + } + error("eof before %%}"); +} + +addcode(k: int, s: string) +{ + for(i := 0; i < len s; i++) + addcodec(k, s[i]); +} + +addcodec(k, c: int) +{ + if(codehead == nil + || k != codetail.kind + || codetail.ndata >= NCode){ + cd := ref Code(k, array[NCode+UTFmax] of byte, 0, nil); + if(codehead == nil) + codehead = cd; + else + codetail.next = cd; + codetail = cd; + } + + codetail.ndata += sys->char2byte(c, codetail.data, codetail.ndata); +} + +dumpcode(til: int) +{ + for(; codehead != nil; codehead = codehead.next){ + if(codehead.kind == til) + return; + if(ftable.write(codehead.data, codehead.ndata) != codehead.ndata) + error("can't write output file"); + } +} + +# +# write out the module declaration and any token info +# +dumpmod() +{ + if(fdefine != nil) { + fdefine.puts(modname); + fdefine.puts(": module {\n"); + } + ftable.puts(modname); + ftable.puts(": module {\n"); + + for(; codehead != nil; codehead = codehead.next){ + if(codehead.kind != CodeMod) + break; + if(ftable.write(codehead.data, codehead.ndata) != codehead.ndata) + error("can't write output file"); + if(fdefine != nil && fdefine.write(codehead.data, codehead.ndata) != codehead.ndata) + error("can't write define file"); + } + + for(i:=TOKSTART; i<=ntokens; i++) { + # non-literals + c := tokset[i].name[0]; + if(c != ' ' && c != '$') { + s := tokset[i].name+": con "+string tokset[i].value+";\n"; + ftable.puts(s); + if(fdefine != nil) + fdefine.puts(s); + } + } + + if(fdefine != nil) + fdefine.puts("};\n"); + ftable.puts("\n};\n"); + + if(fdebug != nil) { + fdebug.puts("yytoknames = array[] of {\n"); + for(i=1; i<=ntokens; i++) { + if(tokset[i].name != nil) + fdebug.puts("\t\""+chcopy(tokset[i].name)+"\",\n"); + else + fdebug.puts("\t\"\",\n"); + } + fdebug.puts("};\n"); + } +} + +# +# skip over comments +# skipcom is called after reading a '#' +# +skipcom(): int +{ + c := finput.getc(); + while(c != Bufio->EOF) { + if(c == '\n') + return 1; + c = finput.getc(); + } + error("EOF inside comment"); + return 0; +} + +# +# copy limbo action to the next ; or closing } +# +cpyact(curprod: array of int, max: int) +{ + addcode(CodeAct, "\n#line\t"); + addcode(CodeAct, string lineno); + addcode(CodeAct, "\t\""); + addcode(CodeAct, infile); + addcode(CodeAct, "\"\n"); + + brac := 0; + +loop: for(;;){ + c := finput.getc(); + swt: case c { + ';' => + if(brac == 0) { + addcodec(CodeAct, c); + return; + } + + '{' => + brac++; + + '$' => + s := 1; + tok := -1; + c = finput.getc(); + + # type description + if(c == '<') { + finput.ungetc(); + if(gettok() != TYPENAME) + error("bad syntax on $<ident> clause"); + tok = numbval; + c = finput.getc(); + } + if(c == '$') { + addcode(CodeAct, "e.yyval"); + + # put out the proper tag... + if(ntypes) { + if(tok < 0) + tok = fdtype(curprod[0]); + addcode(CodeAct, "."+typeset[tok]); + } + continue loop; + } + if(c == '-') { + s = -s; + c = finput.getc(); + } + j := 0; + if(isdigit(c)) { + while(isdigit(c)) { + j = j*10 + c-'0'; + c = finput.getc(); + } + finput.ungetc(); + j = j*s; + if(j >= max) + error("Illegal use of $" + string j); + }else if(isword(c) || c == '_' || c == '.') { + # look for $name + finput.ungetc(); + if(gettok() != IDENTIFIER) + error("$ must be followed by an identifier"); + tokn := chfind(2, tokname); + fnd := -1; + if((c = finput.getc()) != '@') + finput.ungetc(); + else if(gettok() != NUMBER) + error("@ must be followed by number"); + else + fnd = numbval; + for(j=1; j<max; j++){ + if(tokn == curprod[j]) { + fnd--; + if(fnd <= 0) + break; + } + } + if(j >= max) + error("$name or $name@number not found"); + }else{ + addcodec(CodeAct, '$'); + if(s < 0) + addcodec(CodeAct, '-'); + finput.ungetc(); + continue loop; + } + addcode(CodeAct, "yys[yypt-" + string(max-j-1) + "].yyv"); + + # put out the proper tag + if(ntypes) { + if(j <= 0 && tok < 0) + error("must specify type of $" + string j); + if(tok < 0) + tok = fdtype(curprod[j]); + addcodec(CodeAct, '.'); + addcode(CodeAct, typeset[tok]); + } + continue loop; + + '}' => + brac--; + if(brac) + break; + addcodec(CodeAct, c); + return; + + '#' => + # a comment + addcodec(CodeAct, c); + c = finput.getc(); + while(c != Bufio->EOF) { + if(c == '\n') { + lineno++; + break swt; + } + addcodec(CodeAct, c); + c = finput.getc(); + } + error("EOF inside comment"); + + '\''or '"' => + # character string or constant + match := c; + addcodec(CodeAct, c); + while(c = finput.getc()) { + if(c == '\\') { + addcodec(CodeAct, c); + c = finput.getc(); + if(c == '\n') + lineno++; + } else if(c == match) + break swt; + if(c == '\n') + error("newline in string or char const."); + addcodec(CodeAct, c); + } + error("EOF in string or character constant"); + + Bufio->EOF => + error("action does not terminate"); + + '\n' => + lineno++; + } + + addcodec(CodeAct, c); + } +} + +openup(stem: string, dflag, vflag, ytab: int, ytabc: string) +{ + buf: string; + if(vflag) { + buf = stem + "." + FILEU; + foutput = bufio->create(buf, Bufio->OWRITE, 8r666); + if(foutput == nil) + error("can't create " + buf); + } + if(yydebug != nil) { + buf = stem + "." + FILEDEBUG; + fdebug = bufio->create(buf, Bufio->OWRITE, 8r666); + if(fdebug == nil) + error("can't create " + buf); + } + if(dflag) { + buf = stem + "." + FILED; + fdefine = bufio->create(buf, Bufio->OWRITE, 8r666); + if(fdefine == nil) + error("can't create " + buf); + } + if(ytab == 0) + buf = stem + "." + OFILE; + else + buf = ytabc; + ftable = bufio->create(buf, Bufio->OWRITE, 8r666); + if(ftable == nil) + error("can't create file " + buf); +} + +# +# return a pointer to the name of symbol i +# +symnam(i: int): string +{ + s: string; + if(i >= NTBASE) + s = nontrst[i-NTBASE].name; + else + s = tokset[i].name; + if(s[0] == ' ') + s = s[1:]; + return s; +} + +# +# write out error comment +# +error(s: string) +{ + nerrors++; + fprint(stderr, "\n fatal error: %s, %s:%d\n", s, infile, lineno); + if(!fatfl) + return; + summary(); + exit; +# exits("error"); +} + +# +# set elements 0 through n-1 to c +# +aryfil(v: array of int, n, c: int) +{ + for(i:=0; i<n; i++) + v[i] = c; +} + +# +# compute an array with the beginnings of productions yielding given nonterminals +# The array pres points to these lists +# the array pyield has the lists: the total size is only NPROD+1 +# +cpres() +{ + pres = array[nnonter+1] of array of array of int; + curres := array[nprod] of array of int; + for(i:=0; i<=nnonter; i++) { + n := 0; + c := i+NTBASE; + fatfl = 0; # make undefined symbols nonfatal + for(j:=0; j<nprod; j++) + if(prdptr[j][0] == c) + curres[n++] = prdptr[j][1:]; + if(n == 0) + error("nonterminal " + nontrst[i].name + " not defined!"); + else{ + pres[i] = array[n] of array of int; + pres[i][0:] = curres[:n]; + } + } + fatfl = 1; + if(nerrors) { + summary(); + exit; #exits("error"); + } +} + +dumppres() +{ + for(i := 0; i <= nnonter; i++){ + print("nonterm %d\n", i); + curres := pres[i]; + for(j := 0; j < len curres; j++){ + print("\tproduction %d:", j); + prd := curres[j]; + for(k := 0; k < len prd; k++) + print(" %d", prd[k]); + print("\n"); + } + } +} + +# +# mark nonterminals which derive the empty string +# also, look for nonterminals which don't derive any token strings +# +cempty() +{ + i, p, np: int; + prd: array of int; + + pempty = array[nnonter+1] of int; + + # first, use the array pempty to detect productions that can never be reduced + # set pempty to WHONOWS + aryfil(pempty, nnonter+1, WHOKNOWS); + + # now, look at productions, marking nonterminals which derive something +more: for(;;){ + for(i=0; i<nprod; i++) { + prd = prdptr[i]; + if(pempty[prd[0] - NTBASE]) + continue; + np = len prd - 1; + for(p = 1; p < np; p++) + if(prd[p] >= NTBASE && pempty[prd[p]-NTBASE] == WHOKNOWS) + break; + # production can be derived + if(p == np) { + pempty[prd[0]-NTBASE] = OK; + continue more; + } + } + break; + } + + # now, look at the nonterminals, to see if they are all OK + for(i=0; i<=nnonter; i++) { + # the added production rises or falls as the start symbol ... + if(i == 0) + continue; + if(pempty[i] != OK) { + fatfl = 0; + error("nonterminal " + nontrst[i].name + " never derives any token string"); + } + } + + if(nerrors) { + summary(); + exit; #exits("error"); + } + + # now, compute the pempty array, to see which nonterminals derive the empty string + # set pempty to WHOKNOWS + aryfil(pempty, nnonter+1, WHOKNOWS); + + # loop as long as we keep finding empty nonterminals + +again: for(;;){ + next: for(i=1; i<nprod; i++) { + # not known to be empty + prd = prdptr[i]; + if(pempty[prd[0]-NTBASE] != WHOKNOWS) + continue; + np = len prd - 1; + for(p = 1; p < np; p++) + if(prd[p] < NTBASE || pempty[prd[p]-NTBASE] != EMPTY) + continue next; + + # we have a nontrivially empty nonterminal + pempty[prd[0]-NTBASE] = EMPTY; + # got one ... try for another + continue again; + } + return; + } +} + +dumpempty() +{ + for(i := 0; i <= nnonter; i++) + if(pempty[i] == EMPTY) + print("non-term %d %s matches empty\n", i, symnam(i+NTBASE)); +} + +# +# compute an array with the first of nonterminals +# +cpfir() +{ + s, n, p, np, ch: int; + curres: array of array of int; + prd: array of int; + + wsets = array[nnonter+WSETINC] of Wset; + pfirst = array[nnonter+1] of Lkset; + for(i:=0; i<=nnonter; i++) { + wsets[i].ws = mkset(); + pfirst[i] = mkset(); + curres = pres[i]; + n = len curres; + # initially fill the sets + for(s = 0; s < n; s++) { + prd = curres[s]; + np = len prd - 1; + for(p = 0; p < np; p++) { + ch = prd[p]; + if(ch < NTBASE) { + setbit(pfirst[i], ch); + break; + } + if(!pempty[ch-NTBASE]) + break; + } + } + } + + # now, reflect transitivity + changes := 1; + while(changes) { + changes = 0; + for(i=0; i<=nnonter; i++) { + curres = pres[i]; + n = len curres; + for(s = 0; s < n; s++) { + prd = curres[s]; + np = len prd - 1; + for(p = 0; p < np; p++) { + ch = prd[p] - NTBASE; + if(ch < 0) + break; + changes |= setunion(pfirst[i], pfirst[ch]); + if(!pempty[ch]) + break; + } + } + } + } + + if(!indebug) + return; + if(foutput != nil){ + for(i=0; i<=nnonter; i++) { + foutput.putc('\n'); + foutput.puts(nontrst[i].name); + foutput.puts(": "); + prlook(pfirst[i]); + foutput.putc(' '); + foutput.puts(string pempty[i]); + foutput.putc('\n'); + } + } +} + +# +# generate the states +# +stagen() +{ + # initialize + nstate = 0; + tstates = array[ntokens+1] of {* => 0}; # states generated by terminal gotos + ntstates = array[nnonter+1] of {* => 0};# states generated by nonterminal gotos + amem = array[ACTSIZE] of {* => 0}; + memp = 0; + + clset = mkset(); + pstate[0] = pstate[1] = 0; + aryfil(clset, tbitset, 0); + putitem(Pitem(prdptr[0], 0, 0, 0), clset); + tystate[0] = MUSTDO; + nstate = 1; + pstate[2] = pstate[1]; + + # + # now, the main state generation loop + # first pass generates all of the states + # later passes fix up lookahead + # could be sped up a lot by remembering + # results of the first pass rather than recomputing + # + first := 1; + for(more := 1; more; first = 0){ + more = 0; + for(i:=0; i<nstate; i++) { + if(tystate[i] != MUSTDO) + continue; + + tystate[i] = DONE; + aryfil(temp1, nnonter+1, 0); + + # take state i, close it, and do gotos + closure(i); + + # generate goto's + for(p:=0; p<cwp; p++) { + pi := wsets[p]; + if(pi.flag) + continue; + wsets[p].flag = 1; + c := pi.pitem.first; + if(c <= 1) { + if(pstate[i+1]-pstate[i] <= p) + tystate[i] = MUSTLOOKAHEAD; + continue; + } + # do a goto on c + putitem(wsets[p].pitem, wsets[p].ws); + for(q:=p+1; q<cwp; q++) { + # this item contributes to the goto + if(c == wsets[q].pitem.first) { + putitem(wsets[q].pitem, wsets[q].ws); + wsets[q].flag = 1; + } + } + + if(c < NTBASE) + state(c); # register new state + else + temp1[c-NTBASE] = state(c); + } + + if(gsdebug && foutput != nil) { + foutput.puts(string i + ": "); + for(j:=0; j<=nnonter; j++) + if(temp1[j]) + foutput.puts(nontrst[j].name + " " + string temp1[j] + ", "); + foutput.putc('\n'); + } + + if(first) + indgo[i] = apack(temp1[1:], nnonter-1) - 1; + + more++; + } + } +} + +# +# generate the closure of state i +# +closure(i: int) +{ + zzclose++; + + # first, copy kernel of state i to wsets + cwp = 0; + q := pstate[i+1]; + for(p:=pstate[i]; p<q; p++) { + wsets[cwp].pitem = statemem[p].pitem; + wsets[cwp].flag = 1; # this item must get closed + wsets[cwp].ws[0:] = statemem[p].look; + cwp++; + } + + # now, go through the loop, closing each item + work := 1; + while(work) { + work = 0; + for(u:=0; u<cwp; u++) { + if(wsets[u].flag == 0) + continue; + # dot is before c + c := wsets[u].pitem.first; + if(c < NTBASE) { + wsets[u].flag = 0; + # only interesting case is where . is before nonterminal + continue; + } + + # compute the lookahead + aryfil(clset, tbitset, 0); + + # find items involving c + for(v:=u; v<cwp; v++) { + if(wsets[v].flag != 1 + || wsets[v].pitem.first != c) + continue; + pi := wsets[v].pitem.prod; + ipi := wsets[v].pitem.off + 1; + + wsets[v].flag = 0; + if(nolook) + continue; + while((ch := pi[ipi++]) > 0) { + # terminal symbol + if(ch < NTBASE) { + setbit(clset, ch); + break; + } + # nonterminal symbol + setunion(clset, pfirst[ch-NTBASE]); + if(!pempty[ch-NTBASE]) + break; + } + if(ch <= 0) + setunion(clset, wsets[v].ws); + } + + # + # now loop over productions derived from c + # + curres := pres[c - NTBASE]; + n := len curres; + # initially fill the sets + nexts: for(s := 0; s < n; s++) { + prd := curres[s]; + # + # put these items into the closure + # is the item there + # + for(v=0; v<cwp; v++) { + # yes, it is there + if(wsets[v].pitem.off == 0 + && wsets[v].pitem.prod == prd) { + if(!nolook && setunion(wsets[v].ws, clset)) + wsets[v].flag = work = 1; + continue nexts; + } + } + + # not there; make a new entry + if(cwp >= len wsets){ + awsets := array[cwp + WSETINC] of Wset; + awsets[0:] = wsets; + wsets = awsets; + } + wsets[cwp].pitem = Pitem(prd, 0, prd[0], -prd[len prd-1]); + wsets[cwp].flag = 1; + wsets[cwp].ws = mkset(); + if(!nolook) { + work = 1; + wsets[cwp].ws[0:] = clset; + } + cwp++; + } + } + } + + # have computed closure; flags are reset; return + if(cldebug && foutput != nil) { + foutput.puts("\nState " + string i + ", nolook = " + string nolook + "\n"); + for(u:=0; u<cwp; u++) { + if(wsets[u].flag) + foutput.puts("flag set!\n"); + wsets[u].flag = 0; + foutput.putc('\t'); + foutput.puts(writem(wsets[u].pitem)); + prlook(wsets[u].ws); + foutput.putc('\n'); + } + } +} + +# +# sorts last state,and sees if it equals earlier ones. returns state number +# +state(c: int): int +{ + zzstate++; + p1 := pstate[nstate]; + p2 := pstate[nstate+1]; + if(p1 == p2) + return 0; # null state + # sort the items + k, l: int; + for(k = p1+1; k < p2; k++) { # make k the biggest + for(l = k; l > p1; l--) { + if(statemem[l].pitem.prodno < statemem[l-1].pitem.prodno + || statemem[l].pitem.prodno == statemem[l-1].pitem.prodno + && statemem[l].pitem.off < statemem[l-1].pitem.off) { + s := statemem[l]; + statemem[l] = statemem[l-1]; + statemem[l-1] = s; + }else + break; + } + } + + size1 := p2 - p1; # size of state + + if(c >= NTBASE) + i := ntstates[c-NTBASE]; + else + i = tstates[c]; + +look: for(; i != 0; i = mstates[i]) { + # get ith state + q1 := pstate[i]; + q2 := pstate[i+1]; + size2 := q2 - q1; + if(size1 != size2) + continue; + k = p1; + for(l = q1; l < q2; l++) { + if(statemem[l].pitem.prod != statemem[k].pitem.prod + || statemem[l].pitem.off != statemem[k].pitem.off) + continue look; + k++; + } + + # found it + pstate[nstate+1] = pstate[nstate]; # delete last state + # fix up lookaheads + if(nolook) + return i; + k = p1; + for(l = q1; l < q2; l++) { + if(setunion(statemem[l].look, statemem[k].look)) + tystate[i] = MUSTDO; + k++; + } + return i; + } + # state is new + zznewstate++; + if(nolook) + error("yacc state/nolook error"); + pstate[nstate+2] = p2; + if(nstate+1 >= NSTATES) + error("too many states"); + if(c >= NTBASE) { + mstates[nstate] = ntstates[c-NTBASE]; + ntstates[c-NTBASE] = nstate; + } else { + mstates[nstate] = tstates[c]; + tstates[c] = nstate; + } + tystate[nstate] = MUSTDO; + return nstate++; +} + +putitem(p: Pitem, set: Lkset) +{ + p.off++; + p.first = p.prod[p.off]; + + if(pidebug && foutput != nil) + foutput.puts("putitem(" + writem(p) + "), state " + string nstate + "\n"); + j := pstate[nstate+1]; + if(j >= len statemem){ + asm := array[j + STATEINC] of Item; + asm[0:] = statemem; + statemem = asm; + } + statemem[j].pitem = p; + if(!nolook){ + s := mkset(); + s[0:] = set; + statemem[j].look = s; + } + j++; + pstate[nstate+1] = j; +} + +# +# creates output string for item pointed to by pp +# +writem(pp: Pitem): string +{ + i: int; + p := pp.prod; + q := chcopy(nontrst[prdptr[pp.prodno][0]-NTBASE].name) + ": "; + npi := pp.off; + pi := p == prdptr[pp.prodno]; + for(;;){ + c := ' '; + if(pi == npi) + c = '.'; + q[len q] = c; + i = p[pi++]; + if(i <= 0) + break; + q += chcopy(symnam(i)); + } + + # an item calling for a reduction + i = p[npi]; + if(i < 0) + q += " (" + string -i + ")"; + return q; +} + +# +# pack state i from temp1 into amem +# +apack(p: array of int, n: int): int +{ + # + # we don't need to worry about checking because + # we will only look at entries known to be there... + # eliminate leading and trailing 0's + # + off := 0; + for(pp := 0; pp <= n && p[pp] == 0; pp++) + off--; + # no actions + if(pp > n) + return 0; + for(; n > pp && p[n] == 0; n--) + ; + p = p[pp:n+1]; + + # now, find a place for the elements from p to q, inclusive + r := len amem - len p; +nextk: for(rr := 0; rr <= r; rr++) { + qq := rr; + for(pp = 0; pp < len p; pp++) { + if(p[pp] != 0) + if(p[pp] != amem[qq] && amem[qq] != 0) + continue nextk; + qq++; + } + + # we have found an acceptable k + if(pkdebug && foutput != nil) + foutput.puts("off = " + string(off+rr) + ", k = " + string rr + "\n"); + qq = rr; + for(pp = 0; pp < len p; pp++) { + if(p[pp]) { + if(qq > memp) + memp = qq; + amem[qq] = p[pp]; + } + qq++; + } + if(pkdebug && foutput != nil) { + for(pp = 0; pp <= memp; pp += 10) { + foutput.putc('\t'); + for(qq = pp; qq <= pp+9; qq++) + foutput.puts(string amem[qq] + " "); + foutput.putc('\n'); + } + } + return off + rr; + } + error("no space in action table"); + return 0; +} + +# +# print the output for the states +# +output() +{ + c, u, v: int; + + ftable.puts("yyexca := array[] of {"); + if(fdebug != nil) + fdebug.puts("yystates = array [] of {\n"); + + noset := mkset(); + + # output the stuff for state i + for(i:=0; i<nstate; i++) { + nolook = tystate[i]!=MUSTLOOKAHEAD; + closure(i); + + # output actions + nolook = 1; + aryfil(temp1, ntokens+nnonter+1, 0); + for(u=0; u<cwp; u++) { + c = wsets[u].pitem.first; + if(c > 1 && c < NTBASE && temp1[c] == 0) { + for(v=u; v<cwp; v++) + if(c == wsets[v].pitem.first) + putitem(wsets[v].pitem, noset); + temp1[c] = state(c); + } else + if(c > NTBASE && temp1[(c -= NTBASE) + ntokens] == 0) + temp1[c+ntokens] = amem[indgo[i]+c]; + } + if(i == 1) + temp1[1] = ACCEPTCODE; + + # now, we have the shifts; look at the reductions + lastred = 0; + for(u=0; u<cwp; u++) { + c = wsets[u].pitem.first; + + # reduction + if(c > 0) + continue; + lastred = -c; + us := wsets[u].ws; + for(k:=0; k<=ntokens; k++) { + if(!bitset(us, k)) + continue; + if(temp1[k] == 0) + temp1[k] = c; + else + if(temp1[k] < 0) { # reduce/reduce conflict + if(foutput != nil) + foutput.puts( + "\n" + string i + ": reduce/reduce conflict (red'ns " + + string -temp1[k] + " and " + string lastred + " ) on " + symnam(k)); + if(-temp1[k] > lastred) + temp1[k] = -lastred; + zzrrconf++; + } else + # potential shift/reduce conflict + precftn(lastred, k, i); + } + } + wract(i); + } + + if(fdebug != nil) + fdebug.puts("};\n"); + ftable.puts("};\n"); + ftable.puts("YYNPROD: con " + string nprod + ";\n"); + ftable.puts("YYPRIVATE: con " + string PRIVATE + ";\n"); + ftable.puts("yytoknames: array of string;\n"); + ftable.puts("yystates: array of string;\n"); + if(yydebug != nil){ + ftable.puts("include \"y.debug\";\n"); + ftable.puts("yydebug: con " + yydebug + ";\n"); + }else{ + ftable.puts("yydebug: con 0;\n"); + } +} + +# +# decide a shift/reduce conflict by precedence. +# r is a rule number, t a token number +# the conflict is in state s +# temp1[t] is changed to reflect the action +# +precftn(r, t, s: int) +{ + action: int; + + lp := levprd[r]; + lt := toklev[t]; + if(PLEVEL(lt) == 0 || PLEVEL(lp) == 0) { + + # conflict + if(foutput != nil) + foutput.puts( + "\n" + string s + ": shift/reduce conflict (shift " + + string temp1[t] + "(" + string PLEVEL(lt) + "), red'n " + + string r + "(" + string PLEVEL(lp) + ")) on " + symnam(t)); + zzsrconf++; + return; + } + if(PLEVEL(lt) == PLEVEL(lp)) + action = ASSOC(lt); + else if(PLEVEL(lt) > PLEVEL(lp)) + action = RASC; # shift + else + action = LASC; # reduce + case action{ + BASC => # error action + temp1[t] = ERRCODE; + LASC => # reduce + temp1[t] = -r; + } +} + +# +# output state i +# temp1 has the actions, lastred the default +# +wract(i: int) +{ + p, p1: int; + + # find the best choice for lastred + lastred = 0; + ntimes := 0; + for(j:=0; j<=ntokens; j++) { + if(temp1[j] >= 0) + continue; + if(temp1[j]+lastred == 0) + continue; + # count the number of appearances of temp1[j] + count := 0; + tred := -temp1[j]; + levprd[tred] |= REDFLAG; + for(p=0; p<=ntokens; p++) + if(temp1[p]+tred == 0) + count++; + if(count > ntimes) { + lastred = tred; + ntimes = count; + } + } + + # + # for error recovery, arrange that, if there is a shift on the + # error recovery token, `error', that the default be the error action + # + if(temp1[2] > 0) + lastred = 0; + + # clear out entries in temp1 which equal lastred + # count entries in optst table + n := 0; + for(p=0; p<=ntokens; p++) { + p1 = temp1[p]; + if(p1+lastred == 0) + temp1[p] = p1 = 0; + if(p1 > 0 && p1 != ACCEPTCODE && p1 != ERRCODE) + n++; + } + + wrstate(i); + defact[i] = lastred; + flag := 0; + os := array[n*2] of int; + n = 0; + for(p=0; p<=ntokens; p++) { + if((p1=temp1[p]) != 0) { + if(p1 < 0) { + p1 = -p1; + } else if(p1 == ACCEPTCODE) { + p1 = -1; + } else if(p1 == ERRCODE) { + p1 = 0; + } else { + os[n++] = p; + os[n++] = p1; + zzacent++; + continue; + } + if(flag++ == 0) + ftable.puts("-1, " + string i + ",\n"); + ftable.puts("\t" + string p + ", " + string p1 + ",\n"); + zzexcp++; + } + } + if(flag) { + defact[i] = -2; + ftable.puts("\t-2, " + string lastred + ",\n"); + } + optst[i] = os; +} + +# +# writes state i +# +wrstate(i: int) +{ + j0, j1, u: int; + pp, qq: int; + + if(fdebug != nil) { + if(lastred) { + fdebug.puts(" nil, #" + string i + "\n"); + } else { + fdebug.puts(" \""); + qq = pstate[i+1]; + for(pp=pstate[i]; pp<qq; pp++){ + fdebug.puts(writem(statemem[pp].pitem)); + fdebug.puts("\\n"); + } + if(tystate[i] == MUSTLOOKAHEAD) + for(u = pstate[i+1] - pstate[i]; u < cwp; u++) + if(wsets[u].pitem.first < 0){ + fdebug.puts(writem(wsets[u].pitem)); + fdebug.puts("\\n"); + } + fdebug.puts("\", #" + string i + "/\n"); + } + } + if(foutput == nil) + return; + foutput.puts("\nstate " + string i + "\n"); + qq = pstate[i+1]; + for(pp=pstate[i]; pp<qq; pp++){ + foutput.putc('\t'); + foutput.puts(writem(statemem[pp].pitem)); + foutput.putc('\n'); + } + if(tystate[i] == MUSTLOOKAHEAD) { + # print out empty productions in closure + for(u = pstate[i+1] - pstate[i]; u < cwp; u++) { + if(wsets[u].pitem.first < 0) { + foutput.putc('\t'); + foutput.puts(writem(wsets[u].pitem)); + foutput.putc('\n'); + } + } + } + + # check for state equal to another + for(j0=0; j0<=ntokens; j0++) + if((j1=temp1[j0]) != 0) { + foutput.puts("\n\t" + symnam(j0) + " "); + # shift, error, or accept + if(j1 > 0) { + if(j1 == ACCEPTCODE) + foutput.puts("accept"); + else if(j1 == ERRCODE) + foutput.puts("error"); + else + foutput.puts("shift "+string j1); + } else + foutput.puts("reduce " + string -j1 + " (src line " + string rlines[-j1] + ")"); + } + + # output the final production + if(lastred) + foutput.puts("\n\t. reduce " + string lastred + " (src line " + string rlines[lastred] + ")\n\n"); + else + foutput.puts("\n\t. error\n\n"); + + # now, output nonterminal actions + j1 = ntokens; + for(j0 = 1; j0 <= nnonter; j0++) { + j1++; + if(temp1[j1]) + foutput.puts("\t" + symnam(j0+NTBASE) + " goto " + string temp1[j1] + "\n"); + } +} + +# +# output the gotos for the nontermninals +# +go2out() +{ + for(i := 1; i <= nnonter; i++) { + go2gen(i); + + # find the best one to make default + best := -1; + times := 0; + + # is j the most frequent + for(j := 0; j < nstate; j++) { + if(tystate[j] == 0) + continue; + if(tystate[j] == best) + continue; + + # is tystate[j] the most frequent + count := 0; + cbest := tystate[j]; + for(k := j; k < nstate; k++) + if(tystate[k] == cbest) + count++; + if(count > times) { + best = cbest; + times = count; + } + } + + # best is now the default entry + zzgobest += times-1; + n := 0; + for(j = 0; j < nstate; j++) + if(tystate[j] != 0 && tystate[j] != best) + n++; + goent := array[2*n+1] of int; + n = 0; + for(j = 0; j < nstate; j++) + if(tystate[j] != 0 && tystate[j] != best) { + goent[n++] = j; + goent[n++] = tystate[j]; + zzgoent++; + } + + # now, the default + if(best == -1) + best = 0; + zzgoent++; + goent[n] = best; + yypgo[i] = goent; + } +} + +# +# output the gotos for nonterminal c +# +go2gen(c: int) +{ + i, cc, p, q: int; + + # first, find nonterminals with gotos on c + aryfil(temp1, nnonter+1, 0); + temp1[c] = 1; + work := 1; + while(work) { + work = 0; + for(i=0; i<nprod; i++) { + # cc is a nonterminal with a goto on c + cc = prdptr[i][1]-NTBASE; + if(cc >= 0 && temp1[cc] != 0) { + # thus, the left side of production i does too + cc = prdptr[i][0]-NTBASE; + if(temp1[cc] == 0) { + work = 1; + temp1[cc] = 1; + } + } + } + } + + # now, we have temp1[c] = 1 if a goto on c in closure of cc + if(g2debug && foutput != nil) { + foutput.puts(nontrst[c].name); + foutput.puts(": gotos on "); + for(i=0; i<=nnonter; i++) + if(temp1[i]){ + foutput.puts(nontrst[i].name); + foutput.putc(' '); + } + foutput.putc('\n'); + } + + # now, go through and put gotos into tystate + aryfil(tystate, nstate, 0); + for(i=0; i<nstate; i++) { + q = pstate[i+1]; + for(p=pstate[i]; p<q; p++) { + if((cc = statemem[p].pitem.first) >= NTBASE) { + # goto on c is possible + if(temp1[cc-NTBASE]) { + tystate[i] = amem[indgo[i]+c]; + break; + } + } + } + } +} + +# +# in order to free up the mem and amem arrays for the optimizer, +# and still be able to output yyr1, etc., after the sizes of +# the action array is known, we hide the nonterminals +# derived by productions in levprd. +# +hideprod() +{ + j := 0; + levprd[0] = 0; + for(i:=1; i<nprod; i++) { + if(!(levprd[i] & REDFLAG)) { + j++; + if(foutput != nil) { + foutput.puts("Rule not reduced: "); + foutput.puts(writem(Pitem(prdptr[i], 0, 0, i))); + foutput.putc('\n'); + } + } + levprd[i] = prdptr[i][0] - NTBASE; + } + if(j) + print("%d rules never reduced\n", j); +} + +callopt() +{ + j, k, p, q: int; + v: array of int; + + pgo = array[nnonter+1] of int; + pgo[0] = 0; + maxoff = 0; + maxspr = 0; + for(i := 0; i < nstate; i++) { + k = 32000; + j = 0; + v = optst[i]; + q = len v; + for(p = 0; p < q; p += 2) { + if(v[p] > j) + j = v[p]; + if(v[p] < k) + k = v[p]; + } + # nontrivial situation + if(k <= j) { + # j is now the range +# j -= k; # call scj + if(k > maxoff) + maxoff = k; + } + tystate[i] = q + 2*j; + if(j > maxspr) + maxspr = j; + } + + # initialize ggreed table + ggreed = array[nnonter+1] of int; + for(i = 1; i <= nnonter; i++) { + ggreed[i] = 1; + j = 0; + + # minimum entry index is always 0 + v = yypgo[i]; + q = len v - 1; + for(p = 0; p < q ; p += 2) { + ggreed[i] += 2; + if(v[p] > j) + j = v[p]; + } + ggreed[i] = ggreed[i] + 2*j; + if(j > maxoff) + maxoff = j; + } + + # now, prepare to put the shift actions into the amem array + for(i = 0; i < ACTSIZE; i++) + amem[i] = 0; + maxa = 0; + for(i = 0; i < nstate; i++) { + if(tystate[i] == 0 && adb > 1) + ftable.puts("State " + string i + ": null\n"); + indgo[i] = YYFLAG1; + } + while((i = nxti()) != NOMORE) + if(i >= 0) + stin(i); + else + gin(-i); + + # print amem array + if(adb > 2) + for(p = 0; p <= maxa; p += 10) { + ftable.puts(string p + " "); + for(i = 0; i < 10; i++) + ftable.puts(string amem[p+i] + " "); + ftable.putc('\n'); + } + + aoutput(); + osummary(); +} + +# +# finds the next i +# +nxti(): int +{ + max := 0; + maxi := 0; + for(i := 1; i <= nnonter; i++) + if(ggreed[i] >= max) { + max = ggreed[i]; + maxi = -i; + } + for(i = 0; i < nstate; i++) + if(tystate[i] >= max) { + max = tystate[i]; + maxi = i; + } + if(max == 0) + return NOMORE; + return maxi; +} + +gin(i: int) +{ + s: int; + + # enter gotos on nonterminal i into array amem + ggreed[i] = 0; + + q := yypgo[i]; + nq := len q - 1; + # now, find amem place for it +nextgp: for(p := 0; p < ACTSIZE; p++) { + if(amem[p]) + continue; + for(r := 0; r < nq; r += 2) { + s = p + q[r] + 1; + if(s > maxa){ + maxa = s; + if(maxa >= ACTSIZE) + error("a array overflow"); + } + if(amem[s]) + continue nextgp; + } + # we have found amem spot + amem[p] = q[nq]; + if(p > maxa) + maxa = p; + for(r = 0; r < nq; r += 2) { + s = p + q[r] + 1; + amem[s] = q[r+1]; + } + pgo[i] = p; + if(adb > 1) + ftable.puts("Nonterminal " + string i + ", entry at " + string pgo[i] + "\n"); + return; + } + error("cannot place goto " + string i + "\n"); +} + +stin(i: int) +{ + s: int; + + tystate[i] = 0; + + # enter state i into the amem array + q := optst[i]; + nq := len q; + # find an acceptable place +nextn: for(n := -maxoff; n < ACTSIZE; n++) { + flag := 0; + for(r := 0; r < nq; r += 2) { + s = q[r] + n; + if(s < 0 || s > ACTSIZE) + continue nextn; + if(amem[s] == 0) + flag++; + else if(amem[s] != q[r+1]) + continue nextn; + } + + # check the position equals another only if the states are identical + for(j:=0; j<nstate; j++) { + if(indgo[j] == n) { + + # we have some disagreement + if(flag) + continue nextn; + if(nq == len optst[j]) { + + # states are equal + indgo[i] = n; + if(adb > 1) + ftable.puts("State " + string i + ": entry at " + + string n + " equals state " + string j + "\n"); + return; + } + + # we have some disagreement + continue nextn; + } + } + + for(r = 0; r < nq; r += 2) { + s = q[r] + n; + if(s > maxa) + maxa = s; + if(amem[s] != 0 && amem[s] != q[r+1]) + error("clobber of a array, pos'n " + string s + ", by " + string q[r+1] + ""); + amem[s] = q[r+1]; + } + indgo[i] = n; + if(adb > 1) + ftable.puts("State " + string i + ": entry at " + string indgo[i] + "\n"); + return; + } + error("Error; failure to place state " + string i + "\n"); +} + +# +# this version is for limbo +# write out the optimized parser +# +aoutput() +{ + ftable.puts("YYLAST:\tcon "+string (maxa+1)+";\n"); + arout("yyact", amem, maxa+1); + arout("yypact", indgo, nstate); + arout("yypgo", pgo, nnonter+1); +} + +# +# put out other arrays, copy the parsers +# +others() +{ + finput = bufio->open(parser, Bufio->OREAD); + if(finput == nil) + error("cannot find parser " + parser); + arout("yyr1", levprd, nprod); + aryfil(temp1, nprod, 0); + + # + #yyr2 is the number of rules for each production + # + for(i:=1; i<nprod; i++) + temp1[i] = len prdptr[i] - 2; + arout("yyr2", temp1, nprod); + + aryfil(temp1, nstate, -1000); + for(i=0; i<=ntokens; i++) + for(j:=tstates[i]; j!=0; j=mstates[j]) + temp1[j] = i; + for(i=0; i<=nnonter; i++) + for(j=ntstates[i]; j!=0; j=mstates[j]) + temp1[j] = -i; + arout("yychk", temp1, nstate); + arout("yydef", defact, nstate); + + # put out token translation tables + # table 1 has 0-256 + aryfil(temp1, 256, 0); + c := 0; + for(i=1; i<=ntokens; i++) { + j = tokset[i].value; + if(j >= 0 && j < 256) { + if(temp1[j]) { + print("yacc bug -- cant have 2 different Ts with same value\n"); + print(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name); + nerrors++; + } + temp1[j] = i; + if(j > c) + c = j; + } + } + for(i = 0; i <= c; i++) + if(temp1[i] == 0) + temp1[i] = YYLEXUNK; + arout("yytok1", temp1, c+1); + + # table 2 has PRIVATE-PRIVATE+256 + aryfil(temp1, 256, 0); + c = 0; + for(i=1; i<=ntokens; i++) { + j = tokset[i].value - PRIVATE; + if(j >= 0 && j < 256) { + if(temp1[j]) { + print("yacc bug -- cant have 2 different Ts with same value\n"); + print(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name); + nerrors++; + } + temp1[j] = i; + if(j > c) + c = j; + } + } + arout("yytok2", temp1, c+1); + + # table 3 has everything else + ftable.puts("yytok3 := array[] of {\n"); + c = 0; + for(i=1; i<=ntokens; i++) { + j = tokset[i].value; + if(j >= 0 && j < 256) + continue; + if(j >= PRIVATE && j < 256+PRIVATE) + continue; + + ftable.puts(sprint("%4d,%4d,", j, i)); + c++; + if(c%5 == 0) + ftable.putc('\n'); + } + ftable.puts(sprint("%4d\n};\n", 0)); + + # copy parser text + while((c=finput.getc()) != Bufio->EOF) { + if(c == '$') { + if((c = finput.getc()) != 'A') + ftable.putc('$'); + else { # copy actions + if(codehead == nil) + ftable.puts("* => ;"); + else + dumpcode(-1); + c = finput.getc(); + } + } + ftable.putc(c); + } + ftable.close(); +} + +arout(s: string, v: array of int, n: int) +{ + ftable.puts(s+" := array[] of {"); + for(i := 0; i < n; i++) { + if(i%10 == 0) + ftable.putc('\n'); + ftable.puts(sprint("%4d", v[i])); + ftable.putc(','); + } + ftable.puts("\n};\n"); +} + +# +# output the summary on y.output +# +summary() +{ + if(foutput != nil) { + foutput.puts("\n" + string ntokens + " terminals, " + string(nnonter + 1) + " nonterminals\n"); + foutput.puts("" + string nprod + " grammar rules, " + string nstate + "/" + string NSTATES + " states\n"); + foutput.puts("" + string zzsrconf + " shift/reduce, " + string zzrrconf + " reduce/reduce conflicts reported\n"); + foutput.puts("" + string len wsets + " working sets used\n"); + foutput.puts("memory: parser " + string memp + "/" + string ACTSIZE + "\n"); + foutput.puts(string (zzclose - 2*nstate) + " extra closures\n"); + foutput.puts(string zzacent + " shift entries, " + string zzexcp + " exceptions\n"); + foutput.puts(string zzgoent + " goto entries\n"); + foutput.puts(string zzgobest + " entries saved by goto default\n"); + } + if(zzsrconf != 0 || zzrrconf != 0) { + print("\nconflicts: "); + if(zzsrconf) + print("%d shift/reduce", zzsrconf); + if(zzsrconf && zzrrconf) + print(", "); + if(zzrrconf) + print("%d reduce/reduce", zzrrconf); + print("\n"); + } + if(fdefine != nil) + fdefine.close(); +} + +# +# write optimizer summary +# +osummary() +{ + if(foutput == nil) + return; + i := 0; + for(p := maxa; p >= 0; p--) + if(amem[p] == 0) + i++; + + foutput.puts("Optimizer space used: output " + string (maxa+1) + "/" + string ACTSIZE + "\n"); + foutput.puts(string(maxa+1) + " table entries, " + string i + " zero\n"); + foutput.puts("maximum spread: " + string maxspr + ", maximum offset: " + string maxoff + "\n"); +} + +# +# copies and protects "'s in q +# +chcopy(q: string): string +{ + s := ""; + j := 0; + for(i := 0; i < len q; i++) { + if(q[i] == '"') { + s += q[j:i] + "\\"; + j = i; + } + } + return s + q[j:i]; +} + +usage() +{ + fprint(stderr, "usage: yacc [-vd] [-Dn] [-o output] [-s stem] file\n"); + exit; +} + +bitset(set: Lkset, bit: int): int +{ + return set[bit>>5] & (1<<(bit&31)); +} + +setbit(set: Lkset, bit: int): int +{ + return set[bit>>5] |= (1<<(bit&31)); +} + +mkset(): Lkset +{ + return array[tbitset] of {* => 0}; +} + +# +# set a to the union of a and b +# return 1 if b is not a subset of a, 0 otherwise +# +setunion(a, b: array of int): int +{ + sub := 0; + for(i:=0; i<tbitset; i++) { + x := a[i]; + y := x | b[i]; + a[i] = y; + if(y != x) + sub = 1; + } + return sub; +} + +prlook(p: Lkset) +{ + if(p == nil){ + foutput.puts("\tNULL"); + return; + } + foutput.puts(" { "); + for(j:=0; j<=ntokens; j++){ + if(bitset(p, j)){ + foutput.puts(symnam(j)); + foutput.putc(' '); + } + } + foutput.putc('}'); +} + +# +# utility routines +# +isdigit(c: int): int +{ + return c >= '0' && c <= '9'; +} + +isword(c: int): int +{ + return c >= 16ra0 || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; +} + +mktemp(t: string): string +{ + return t; +} + +# +# arg processing +# +Arg.init(argv: list of string): ref Arg +{ + if(argv != nil) + argv = tl argv; + return ref Arg(argv, 0, ""); +} + +Arg.opt(arg: self ref Arg): int +{ + opts := arg.opts; + if(opts != ""){ + arg.c = opts[0]; + arg.opts = opts[1:]; + return arg.c; + } + argv := arg.argv; + if(argv == nil) + return arg.c = 0; + opts = hd argv; + if(len opts < 2 || opts[0] != '-') + return arg.c = 0; + arg.argv = tl argv; + if(opts == "--") + return arg.c = 0; + arg.opts = opts[2:]; + return arg.c = opts[1]; +} + +Arg.arg(arg: self ref Arg): string +{ + s := arg.opts; + arg.opts = ""; + if(s != "") + return s; + argv := arg.argv; + if(argv == nil) + return ""; + arg.argv = tl argv; + return hd argv; +} diff --git a/appl/cmd/mash/eyaccpar b/appl/cmd/mash/eyaccpar new file mode 100644 index 00000000..2bbb0355 --- /dev/null +++ b/appl/cmd/mash/eyaccpar @@ -0,0 +1,223 @@ +YYFLAG: con -1000; + +# parser for yacc output +YYENV: adt +{ + yylval: ref YYSTYPE; # lexical value + yyval: YYSTYPE; # goto value + yyenv: YYETYPE; # useer environment + yynerrs: int; # number of errors + yyerrflag: int; # error recovery flag + yysys: Sys; + yystderr: ref Sys->FD; +}; + +yytokname(yyc: int): string +{ + if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil) + return yytoknames[yyc-1]; + return "<"+string yyc+">"; +} + +yystatname(yys: int): string +{ + if(yys >= 0 && yys < len yystates && yystates[yys] != nil) + return yystates[yys]; + return "<"+string yys+">\n"; +} + +yylex1(e: ref YYENV): int +{ + c, yychar : int; + yychar = yyelex(e); + if(yychar <= 0) + c = yytok1[0]; + else if(yychar < len yytok1) + c = yytok1[yychar]; + else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2) + c = yytok2[yychar-YYPRIVATE]; + else{ + n := len yytok3; + c = 0; + for(i := 0; i < n; i+=2) { + if(yytok3[i+0] == yychar) { + c = yytok3[i+1]; + break; + } + } + if(c == 0) + c = yytok2[1]; # unknown char + } + if(yydebug >= 3) + e.yysys->fprint(e.yystderr, "lex %.4ux %s\n", yychar, yytokname(c)); + return c; +} + +YYS: adt +{ + yyv: YYSTYPE; + yys: int; +}; + +yyparse(): int +{ + return yyeparse(nil); +} + +yyeparse(e: ref YYENV): int +{ + if(e == nil) + e = ref YYENV; + if(e.yylval == nil) + e.yylval = ref YYSTYPE; + if(e.yysys == nil) { + e.yysys = load Sys "$Sys"; + e.yystderr = e.yysys->fildes(2); + } + + yys := array[YYMAXDEPTH] of YYS; + + yystate := 0; + yychar := -1; + e.yynerrs = 0; + e.yyerrflag = 0; + yyp := -1; + yyn := 0; + +yystack: + for(;;){ + # put a state and value onto the stack + if(yydebug >= 4) + e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + + yyp++; + if(yyp >= YYMAXDEPTH) { + yyerror(e, "yacc stack overflow"); + yyn = 1; + break yystack; + } + yys[yyp].yys = yystate; + yys[yyp].yyv = e.yyval; + + for(;;){ + yyn = yypact[yystate]; + if(yyn > YYFLAG) { # simple state + if(yychar < 0) + yychar = yylex1(e); + yyn += yychar; + if(yyn >= 0 && yyn < YYLAST) { + yyn = yyact[yyn]; + if(yychk[yyn] == yychar) { # valid shift + yychar = -1; + yyp++; + if(yyp >= YYMAXDEPTH) { + yyerror(e, "yacc stack overflow"); + yyn = 1; + break yystack; + } + yystate = yyn; + yys[yyp].yys = yystate; + yys[yyp].yyv = *e.yylval; + if(e.yyerrflag > 0) + e.yyerrflag--; + if(yydebug >= 4) + e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + continue; + } + } + } + + # default state action + yyn = yydef[yystate]; + if(yyn == -2) { + if(yychar < 0) + yychar = yylex1(e); + + # look through exception table + for(yyxi:=0;; yyxi+=2) + if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate) + break; + for(yyxi += 2;; yyxi += 2) { + yyn = yyexca[yyxi]; + if(yyn < 0 || yyn == yychar) + break; + } + yyn = yyexca[yyxi+1]; + if(yyn < 0){ + yyn = 0; + break yystack; + } + } + + if(yyn != 0) + break; + + # error ... attempt to resume parsing + if(e.yyerrflag == 0) { # brand new error + yyerror(e, "syntax error"); + e.yynerrs++; + if(yydebug >= 1) { + e.yysys->fprint(e.yystderr, "%s", yystatname(yystate)); + e.yysys->fprint(e.yystderr, "saw %s\n", yytokname(yychar)); + } + } + + if(e.yyerrflag != 3) { # incompletely recovered error ... try again + e.yyerrflag = 3; + + # find a state where "error" is a legal shift action + while(yyp >= 0) { + yyn = yypact[yys[yyp].yys] + YYERRCODE; + if(yyn >= 0 && yyn < YYLAST) { + yystate = yyact[yyn]; # simulate a shift of "error" + if(yychk[yystate] == YYERRCODE) { + yychar = -1; + continue yystack; + } + } + + # the current yyp has no shift on "error", pop stack + if(yydebug >= 2) + e.yysys->fprint(e.yystderr, "error recovery pops state %d, uncovers %d\n", + yys[yyp].yys, yys[yyp-1].yys ); + yyp--; + } + # there is no state on the stack with an error shift ... abort + yyn = 1; + break yystack; + } + + # no shift yet; clobber input char + if(yydebug >= 2) + e.yysys->fprint(e.yystderr, "error recovery discards %s\n", yytokname(yychar)); + if(yychar == YYEOFCODE) { + yyn = 1; + break yystack; + } + yychar = -1; + # try again in the same state + } + + # reduction by production yyn + if(yydebug >= 2) + e.yysys->fprint(e.yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); + + yypt := yyp; + yyp -= yyr2[yyn]; +# yyval = yys[yyp+1].yyv; + yym := yyn; + + # consult goto table to find next state + yyn = yyr1[yyn]; + yyg := yypgo[yyn]; + yyj := yyg + yys[yyp].yys + 1; + + if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) + yystate = yyact[yyg]; + case yym { + $A + } + } + + return yyn; +} diff --git a/appl/cmd/mash/history.b b/appl/cmd/mash/history.b new file mode 100644 index 00000000..7f7cf9b6 --- /dev/null +++ b/appl/cmd/mash/history.b @@ -0,0 +1,206 @@ +implement Mashbuiltin; + +# +# "history" builtin, defines: +# + +include "mash.m"; +include "mashparse.m"; + +mashlib: Mashlib; +chanfill: ChanFill; + +Env: import mashlib; +sys, bufio: import mashlib; + +Iobuf: import bufio; + +Hcmd: adt +{ + seek: int; + text: array of byte; +}; + +Reader: adt +{ + fid: int; + offset: int; + hint: int; + next: cyclic ref Reader; +}; + +history: array of ref Hcmd; +lhist: int; +nhist: int; +seek: int; +readers: ref Reader; +eof := array[0] of byte; + +# +# Interface to catch the use as a command. +# +init(nil: ref Draw->Context, args: list of string) +{ + raise "fail: " + hd args + " not loaded"; +} + +# +# Used by whatis. +# +name(): string +{ + return "history"; +} + +# +# Install commands. +# +mashinit(nil: list of string, lib: Mashlib, nil: Mashbuiltin, e: ref Env) +{ + mashlib = lib; + if (mashlib->histchan != nil) + return; + mashlib->startserve = 1; + nhist = 0; + lhist = 256; + history = array[lhist] of ref Hcmd; + seek = 0; + (f, c) := e.servefile(mashlib->HISTF); + spawn servehist(f, c); + (f, c) = e.servefile(mashlib->MASHF); + spawn servemash(f, c); +} + +mashcmd(nil: ref Env, nil: list of string) +{ +} + +addhist(b: array of byte) +{ + if (nhist == lhist) { + n := 3 * nhist / 4; + part := history[:n]; + part[:] = history[nhist - n:]; + nhist = n; + } + history[nhist] = ref Hcmd(seek, b); + nhist++; + seek += len b; +} + +getfid(fid: int, del: int): ref Reader +{ + prev: ref Reader; + for (r := readers; r != nil; r = r.next) { + if (r.fid == fid) { + if (del) { + if (prev == nil) + readers = r.next; + else + prev.next = r.next; + return nil; + } + return r; + } + prev = r; + } + o := 0; + if (nhist > 0) + o = history[0].seek; + return readers = ref Reader(fid, o, 0, readers); +} + +readhist(off, count, fid: int): (array of byte, string) +{ + r := getfid(fid, 0); + off += r.offset; + if (nhist == 0 || off >= seek) + return (eof, nil); + i := r.hint; + if (i >= nhist) + i = nhist - 1; + s := history[i].seek; + if (off == s) { + r.hint = i + 1; + return (history[i].text, nil); + } + if (off > s) { + do { + if (++i == nhist) + break; + s = history[i].seek; + } while (off >= s); + i--; + } else { + do { + if (--i < 0) + return (eof, "data truncated"); + s = history[i].seek; + } while (off < s); + } + r.hint = i + 1; + b := history[i].text; + if (off != s) + b = b[off - s:]; + return (b, nil); +} + +loadhist(data: array of byte, fid: int, wc: Sys->Rwrite, c: ref Sys->FileIO) +{ + in: ref Iobuf; + if (chanfill == nil) + chanfill = load ChanFill ChanFill->PATH; + if (chanfill != nil) + in = chanfill->init(data, fid, wc, c, mashlib->bufio); + if (in == nil) { + in = bufio->sopen(string data); + if (in == nil) { + wc <-= (0, mashlib->errstr()); + return; + } + wc <-= (len data, nil); + } + while ((s := in.gets('\n')) != nil) + addhist(array of byte s); + in.close(); +} + +servehist(f: string, c: ref Sys->FileIO) +{ + mashlib->reap(); + h := chan of array of byte; + mashlib->histchan = h; + for (;;) { + alt { + b := <-h => + addhist(b); + (off, count, fid, rc) := <-c.read => + if (rc == nil) { + getfid(fid, 1); + continue; + } + rc <-= readhist(off, count, fid); + (off, data, fid, wc) := <-c.write => + if (wc != nil) + loadhist(data, fid, wc, c); + } + } +} + +servemash(f: string, c: ref Sys->FileIO) +{ + mashlib->reap(); + for (;;) { + alt { + (off, count, fid, rc) := <-c.read => + if (rc != nil) + rc <-= (nil, "not supported"); + (off, data, fid, wc) := <-c.write => + if (wc != nil) { + wc <-= (len data, nil); + if (mashlib->servechan != nil && len data > 0) + mashlib->servechan <-= data; + } + } + } +} diff --git a/appl/cmd/mash/lex.b b/appl/cmd/mash/lex.b new file mode 100644 index 00000000..c9c3789b --- /dev/null +++ b/appl/cmd/mash/lex.b @@ -0,0 +1,547 @@ +# +# Lexical analyzer. +# + +lexdebug : con 0; + +# +# Import tokens from parser. +# +Land, +Lat, +Lbackq, +Lcaret, +Lcase, +Lcolon, +Lcolonmatch, +Lcons, +Ldefeq, +Lelse, +Leof, +Leq, +Leqeq, +Lerror, +Lfn, +Lfor, +Lgreat, +Lgreatgreat, +Lhd, +Lif, +Lin, +Llen, +Lless, +Llessgreat, +Lmatch, +Lmatched, +Lnot, +Lnoteq, +Loffcurly, +Loffparen, +Loncurly, +Lonparen, +Lpipe, +Lquote, +Lrescue, +Lsemi, +Ltl, +Lwhile, +Lword + : import Mashparse; + +KWSIZE: con 31; # keyword hashtable size +NCTYPE: con 128; # character class array size + +ALPHA, +NUMERIC, +ONE, +WS, +META + : con 1 << iota; + +keywords := array[] of +{ + ("case", Lcase), + ("else", Lelse), + ("fn", Lfn), + ("for", Lfor), + ("hd", Lhd), + ("if", Lif), + ("in", Lin), + ("len", Llen), + ("rescue", Lrescue), + ("tl", Ltl), + ("while", Lwhile) +}; + +ctype := array[NCTYPE] of +{ + 0 or ' ' or '\t' or '\n' or '\r' or '\v' => WS, + ':' or '#' or ';' or '&' or '|' or '^' or '$' or '=' or '@' + or '~' or '`'or '{' or '}' or '(' or ')' or '<' or '>' => ONE, + 'a' to 'z' or 'A' to 'Z' or '_' => ALPHA, + '0' to '9' => NUMERIC, + '*' or '[' or ']' or '?' => META, + * => 0 +}; + +keytab: ref HashTable; + +# +# Initialize hashtable. +# +initlex() +{ + keytab = hash->new(KWSIZE); + for (i := 0; i < len keywords; i++) { + (s, v) := keywords[i]; + keytab.insert(s, HashVal(v, 0.0, nil)); + } +} + +# +# Keyword value, or -1. +# +keyval(i: ref Item): int +{ + if (i.op != Iword) + return -1; + w := i.word; + if (w.flags & Wquoted) + return -1; + v := keytab.find(w.text); + if (v == nil) + return -1; + return v.i; +} + +# +# Attach a source file to an environment. +# +Env.fopen(e: self ref Env, fd: ref Sys->FD, s: string) +{ + in := bufio->fopen(fd, Bufio->OREAD); + if (in == nil) + e.error(sys->sprint("could not fopen %s: %r\n", s)); + e.file = ref File(in, s, 1, 0); +} + +# +# Attach a source string to an environment. +# +Env.sopen(e: self ref Env, s: string) +{ + in := bufio->sopen(s); + if (in == nil) + e.error(sys->sprint("Bufio->sopen failed: %r\n")); + e.file = ref File(in, "<string>", 1, 0); +} + +# +# Close source file. +# +fclose(e: ref Env, c: int) +{ + if (c == Bufio->ERROR) + readerror(e, e.file); + e.file.in.close(); + e.file = nil; +} + +# +# Character class routines. +# + +isalpha(c: int): int +{ + return c >= NCTYPE || (c >= 0 && (ctype[c] & ALPHA) != 0); +} + +isalnum(c: int): int +{ + return c >= NCTYPE || (c >= 0 && (ctype[c] & (ALPHA | NUMERIC)) != 0); +} + +isdigit(c: int): int +{ + return c >= 0 && c < NCTYPE && (ctype[c] & NUMERIC) != 0; +} + +isquote(c: int): int +{ + return c < NCTYPE && (c < 0 || (ctype[c] & (ONE | WS | META)) != 0); +} + +isspace(c: int): int +{ + return c >= 0 && c < NCTYPE && (ctype[c] & WS) != 0; +} + +isterm(c: int): int +{ + return c < NCTYPE && (c < 0 || (ctype[c] & (ONE | WS)) != 0); +} + +# +# Test for an identifier. +# +ident(s: string): int +{ + if (s == nil || !isalpha(s[0])) + return 0; + n := len s; + for (x := 1; x < n; x++) { + if (!isalnum(s[x])) + return 0; + } + return 1; +} + +# +# Quote text. +# +enquote(s: string): string +{ + r := "'"; + j := 1; + n := len s; + for (i := 0; i < n; i++) { + c := s[i]; + if (c == '\'' || c == '\\') + r[j++] = '\\'; + r[j++] = c; + } + r[j] = '\''; + return r; +} + +# +# Quote text if needed. +# +quote(s: string): string +{ + n := len s; + for (i := 0; i < n; i++) { + if (isquote(s[i])) + return enquote(s); + } + return s; +} + +# +# Test for single word and identifier. +# +Item.sword(i: self ref Item, e: ref Env): ref Item +{ + if (i.op == Iword && ident(i.word.text)) + return i; + e.report("malformed identifier: " + i.text()); + return nil; +} + +readerror(e: ref Env, f: ref File) +{ + sys->fprint(e.stderr, "error reading %s: %r\n", f.name); +} + +where(e: ref Env): string +{ + if ((e.flags & EInter) || e.file == nil) + return nil; + return e.file.name + ":" + string e.file.line + ": "; +} + +# +# Suck input (on error). +# +Env.suck(e: self ref Env) +{ + if (e.file == nil) + return; + in := e.file.in; + while ((c := in.getc()) >= 0 && c != '\n') + ; +} + +# +# Lexical analyzer. +# +Env.lex(e: self ref Env, yylval: ref Mashparse->YYSTYPE): int +{ + i, r: ref Item; +reader: + for (;;) { + if (e.file == nil) + return -1; + f := e.file; + in := f.in; + while (isspace(c := in.getc())) { + if (c == '\n') + f.line++; + } + if (c < 0) { + fclose(e, c); + return Leof; + } + case c { + ':' => + if ((d := in.getc()) == ':') + return Lcons; + if (d == '=') + return Ldefeq; + if (d == '~') + return Lcolonmatch; + if (d >= 0) + in.ungetc(); + return Lcolon; + '#' => + for (;;) { + if ((c = in.getc()) < 0) { + fclose(e, c); + return Leof; + } + if (c == '\n') { + f.line++; + continue reader; + } + } + ';' => + return Lsemi; + '&' => + return Land; + '|' => + return Lpipe; + '^' => + return Lcaret; + '@' => + return Lat; + '!' => + if ((d := in.getc()) == '=') + return Lnoteq; + if (d >= 0) + in.ungetc(); + return Lnot; + '~' => + return Lmatch; + '=' => + if ((d := in.getc()) == '>') + return Lmatched; + if (d == '=') + return Leqeq; + if (d >= 0) + in.ungetc(); + return Leq; + '`' => + return Lbackq; + '"' => + return Lquote; + '{' => + return Loncurly; + '}' => + return Loffcurly; + '(' => + return Lonparen; + ')' => + return Loffparen; + '<' => + if ((d := in.getc()) == '>') + return Llessgreat; + if (d >= 0) + in.ungetc(); + return Lless; + '>' => + if ((d := in.getc()) == '>') + return Lgreatgreat; + if (d >= 0) + in.ungetc(); + return Lgreat; + '\\' => + if ((d := in.getc()) == '\n') { + f.line++; + continue reader; + } + if (d >= 0) + in.ungetc(); + } + # Loop over "carets for free". + for (;;) { + if (c == '$') + (i, c) = getdollar(f); + else + (i, c) = getword(e, f, c); + if (i == nil) + return Lerror; + if (isterm(c) && c != '$') + break; + if (r != nil) + r = ref Item(Iicaret, nil, r, i, nil, nil); + else + r = i; + } + if (c >= 0) + in.ungetc(); + if (r != nil) + yylval.item = ref Item(Iicaret, nil, r, i, nil, nil); + else if ((c = keyval(i)) >= 0) + return c; + else + yylval.item = i; + return Lword; + } +} + +# +# Get $n or $word. +# +getdollar(f: ref File): (ref Item, int) +{ + s: string; + in := f.in; + l := f.line; + o := Idollar; + if (isdigit(c := in.getc())) { + s[0] = c; + n := 1; + while (isdigit(c = in.getc())) + s[n++] = c; + o = Imatch; + } else { + if (c == '"') { + o = Idollarq; + c = in.getc(); + } + if (isalpha(c)) { + s[0] = c; + n := 1; + while (isalnum(c = in.getc())) + s[n++] = c; + } else { + if (o == Idollar) + s = "$"; + else + s = "$\""; + o = Iword; + } + } + return (ref Item(o, ref Word(s, 0, Src(l, f.name)), nil, nil, nil, nil), c); +} + +# +# Get word with quoting. +# +getword(e: ref Env, f: ref File, c: int): (ref Item, int) +{ + s: string; + in := f.in; + l := f.line; + wf := 0; + n := 0; + if (c == '\'') { + wf = Wquoted; + collect: + while ((c = in.getc()) >= 0) { + case c { + '\'' => + c = in.getc(); + break collect; + '\\' => + c = in.getc(); + if (c != '\'' && c != '\\') { + if (c == '\n') + continue collect; + if (c >= 0) + in.ungetc(); + c = '\\'; + } + '\n' => + f.line++; + e.report("newline in quoted word"); + return (nil, 0); + } + s[n++] = c; + } + } else { + do { + case c { + '*' or '[' or '?' => + wf |= Wexpand; + } + s[n++] = c; + } while (!isterm(c = in.getc()) && c != '\''); + } + if (lexdebug && s == "exit") + exit; + return (ref Item(Iword, ref Word(s, wf, Src(l, f.name)), nil, nil, nil, nil), c); +} + +# +# Get a line, mapping escape newline to space newline. +# +getline(in: ref Bufio->Iobuf): string +{ + if (inchan != nil) { + alt { + b := <-inchan => + if (inchan == nil) + return nil; + s := string b; + n := len s; + if (n > 1) { + while (s[n - 2] == '\\' && s[n - 1] == '\n') { + s[n - 2] = ' '; + s[n - 1] = ' '; + prprompt(1); + b = <-inchan; + if (b == nil) + break; + s += string b; + n = len s; + } + } + return s; + b := <-servechan => + s := string b; + sys->print("%s", s); + return s; + } + } else { + s := in.gets('\n'); + if (s == nil) + return nil; + n := len s; + if (n > 1) { + while (s[n - 2] == '\\' && s[n - 1] == '\n') { + s[n - 2] = ' '; + s[n - 1] = ' '; + prprompt(1); + t := in.gets('\n'); + if (t == nil) + break; + s += t; + n = len s; + } + } + return s; + } +} + +# +# Interactive shell loop. +# +Env.interactive(e: self ref Env, fd: ref Sys->FD) +{ + in := bufio->fopen(fd, Sys->OREAD); + if (in == nil) + e.error(sys->sprint("could not fopen stdin: %r\n")); + e.flags |= EInter; + for (;;) { + prprompt(0); + if (startserve) + e.serve(); + if ((s := getline(in)) == nil) + exitmash(); + e.sopen(s); + parse->parse(e); + if (histchan != nil) + histchan <-= array of byte s; + } +} diff --git a/appl/cmd/mash/make.b b/appl/cmd/mash/make.b new file mode 100644 index 00000000..4d566c00 --- /dev/null +++ b/appl/cmd/mash/make.b @@ -0,0 +1,723 @@ +implement Mashbuiltin; + +# +# "make" builtin, defines: +# +# depends - print dependencies +# make - make-like command +# match - print details of rule matches +# rules - print rules +# + +include "mash.m"; +include "mashparse.m"; + +verbose: con 0; # debug output + +mashlib: Mashlib; + +Cmd, Env, Item, Stab: import mashlib; +Depend, Rule, Target: import mashlib; +sys, bufio, hash: import mashlib; + +Iobuf: import bufio; + +# +# Interface to catch the use as a command. +# +init(nil: ref Draw->Context, args: list of string) +{ + raise "fail: " + hd args + " not loaded"; +} + +# +# Used by whatis. +# +name(): string +{ + return "make"; +} + +# +# Install commands. +# +mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env) +{ + mashlib = lib; + e.defbuiltin("depends", this); + e.defbuiltin("make", this); + e.defbuiltin("match", this); + e.defbuiltin("rules", this); +} + +# +# Execute a builtin. +# +mashcmd(e: ref Env, l: list of string) +{ + s := hd l; + l = tl l; + case s { + "depends" => + out := e.outfile(); + if (out == nil) + return; + if (l == nil) + alldeps(out); + else + depends(out, l); + out.close(); + "make" => + domake(e, l); + "match" => + domatch(e, l); + "rules" => + out := e.outfile(); + if (out == nil) + return; + if (l == nil) + allrules(out); + else + rules(out, l); + out.close(); + } +} + +# +# Node states. +# +SUnknown, SNoexist, SExist, SStale, SMade, SDir, SDirload + : con iota; + +# +# Node flags. +# +# FMark - marked as in progress +# +FMark + : con 1 << iota; + +Node: adt +{ + name: string; + state: int; + flags: int; + mtime: int; +}; + +# +# Step in implicit chain. +# +Step: type (ref Rule, array of string, ref Node); + +# +# Implicit match. +# +Match: adt +{ + node: ref Node; + path: list of Step; +}; + +NSIZE: con 127; # node hash size +DSIZE: con 32; # number of dir entries for read + +ntab: array of list of ref Node; # node hash table + +initnodes() +{ + ntab = array[NSIZE] of list of ref Node; +} + +# +# Find node for a pathname. +# +getnode(s: string): ref Node +{ + h := hash->fun1(s, NSIZE); + for (l := ntab[h]; l != nil; l = tl l) { + n := hd l; + if (n.name == s) + return n; + } + r := ref Node(s, SUnknown, 0, 0); + ntab[h] = r :: ntab[h]; + return r; +} + +# +# Make a pathname from a dir and an entry. +# +mkpath(d, s: string): string +{ + if (d == ".") + return s; + else if (d == "/") + return "/" + s; + else + return d + "/" + s; +} + +# +# Load a directory. +# +loaddir(s: string) +{ + if (verbose) + sys->print("loaddir %s\n", s); + fd := sys->open(s, Sys->OREAD); + if (fd == nil) + return; + for (;;) { + (c, dbuf) := sys->dirread(fd); + if(c <= 0) + break; + for (i := 0; i < c; i++) { + n := getnode(mkpath(s, dbuf[i].name)); + if (dbuf[i].mode & Sys->DMDIR) + n.state = SDir; + else + n.state = SExist; + n.mtime = dbuf[i].mtime; + } + } +} + +# +# Load a file. Get its node, maybe stat it or loaddir. +# +loadfile(s: string): ref Node +{ + n := getnode(s); + if (n.state == SUnknown) { + if (verbose) + sys->print("stat %s\n", s); + (ok, d) := sys->stat(s); + if (ok >= 0) { + n.mtime = d.mtime; + if (d.mode & Sys->DMDIR) { + loaddir(s); + n.state = SDirload; + } else + n.state = SExist; + } else + n.state = SNoexist; + } else if (n.state == SDir) { + loaddir(s); + n.state = SDirload; + } + return n; +} + +# +# Get the node for a file and load the directories in its path. +# +getfile(s: string): ref Node +{ + d: string; + n := len s; + while (n >= 2 && s[0:2] == "./") { + n -= 2; + s = s[2:]; + } + if (n > 0 && s[0] == '/') { + d = "/"; + s = s[1:]; + } else + d = "."; + (nil, l) := sys->tokenize(s, "/"); + for (;;) { + w := loadfile(d); + if (l == nil) + return w; + s = hd l; + l = tl l; + d = mkpath(d, s); + } +} + +# +# If a dependency rule makes more than one target propogate SMade. +# +propagate(l: list of string) +{ + if (tl l == nil) + return ; + while (l != nil) { + s := hd l; + if (verbose) + sys->print("propogate to %s\n", s); + getfile(s).state = SMade; + l = tl l; + } +} + +# +# Try to make a node, or mark it as stale. +# Return -1 on (reported) error, 0 on fail, 1 on success. +# +explicit(e: ref Env, t: ref Target, n: ref Node): int +{ + d: ref Depend; + for (l := t.depends; l != nil ; l = tl l) { + if ((hd l).op != Cnop) { + if (d != nil) { + e.report(sys->sprint("make: too many rules for %s", t.target)); + return -1; + } + d = hd l; + } + } + for (l = t.depends; l != nil ; l = tl l) { + for (u := (hd l).depends; u != nil; u = tl u) { + s := hd u; + m := getfile(s); + x := make(e, m, s); + if (x < 0) { + sys->print("don't know how to make %s\n", s); + return x; + } + if (m.state == SMade || m.mtime > n.mtime) { + if (verbose) + sys->print("%s makes %s stale\n", s, t.target); + n.state = SStale; + } + } + } + if (d != nil) { + if (n.state == SNoexist || n.state == SStale) { + if (verbose) + sys->print("build %s with explicit rule\n", t.target); + e = e.copy(); + e.flags |= mashlib->EEcho | Mashlib->ERaise; + e.flags &= ~mashlib->EInter; + d.cmd.xeq(e); + propagate(d.targets); + n.state = SMade; + } else if (verbose) + sys->print("%s up to date\n", t.target); + return 1; + } + return 0; +} + +# +# Report multiple implicit chains of equal length. +# +multimatch(e: ref Env, n: ref Node, l: list of Match) +{ + e.report(sys->sprint("%d rules match for %s", len l, n.name)); + f := e.stderr; + while (l != nil) { + m := hd l; + sys->fprint(f, "%s", m.node.name); + for (p := m.path; p != nil; p = tl p) { + (nil, nil, t) := hd p; + sys->fprint(f, " -> %s", t.name); + } + sys->fprint(f, "\n"); + l = tl l; + } +} + +cycle(e: ref Env, n: ref Node) +{ + e.report(sys->sprint("make: cycle in dependencies for target %s", n.name)); +} + +# +# Mark the nodes in an implicit chain. +# +markchain(e: ref Env, l: list of Step): int +{ + while (tl l != nil) { + (nil, nil, n) := hd l; + if (n.flags & FMark) { + cycle(e, n); + return 0; + } + n.flags |= FMark; + l = tl l; + } + return 1; +} + +# +# Unmark the nodes in an implicit chain. +# +unmarkchain(l: list of Step): int +{ + while (tl l != nil) { + (nil, nil, n) := hd l; + n.flags &= ~FMark; + l = tl l; + } + return 1; +} + +# +# Execute an implicit rule chain. +# +xeqmatch(e: ref Env, b, n: ref Node, l: list of Step): int +{ + if (!markchain(e, l)) + return -1; + if (verbose) + sys->print("making %s for implicit rule chain\n", n.name); + e.args = nil; + x := make(e, n, n.name); + if (x < 0) { + sys->print("don't know how to make %s\n", n.name); + return x; + } + if (n.state == SMade || n.mtime > b.mtime || b.state == SStale) { + e = e.copy(); + e.flags |= mashlib->EEcho | Mashlib->ERaise; + e.flags &= ~mashlib->EInter; + for (;;) { + (r, a, t) := hd l; + if (verbose) + sys->print("making %s with implicit rule\n", t.name); + e.args = a; + r.cmd.xeq(e); + t.state = SMade; + l = tl l; + if (l == nil) + break; + t.flags &= ~FMark; + } + } else + unmarkchain(l); + return 1; +} + +# +# Find the shortest implicit rule chain. +# +implicit(e: ref Env, base: ref Node): int +{ + win, lose: list of Match; + l: list of ref Rule; + cand := Match(base, nil) :: nil; + do { + # cand - list of candidate chains + # lose - list of extended chains that lose + # win - list of extended chains that win + lose = nil; + match: + # for each candidate + for (c := cand; c != nil; c = tl c) { + (b, x) := hd c; + s := b.name; + # find rules that match end of chain + m := mashlib->rulematch(s); + l = nil; + # exclude rules already in the chain + exclude: + for (n := m; n != nil; n = tl n) { + r := hd n; + for (y := x; y != nil; y = tl y) { + (u, nil, nil) := hd y; + if (u == r) + continue exclude; + } + l = r :: l; + } + if (l == nil) + continue match; + (nil, t) := sys->tokenize(s, "/"); + # for each new rule that matched + for (n = l; n != nil; n = tl n) { + r := hd n; + a := r.matches(t); + if (a == nil) { + e.report("rule match cock up"); + return -1; + } + a[0] = s; + e.args = a; + # eval rhs + (v, nil, nil) := r.rhs.ieval2(e); + if (v == nil) + continue; + y := (r, a, b) :: x; + z := getfile(v); + # winner or loser + if (z.state != SNoexist || Target.find(v) != nil) + win = (z, y) :: win; + else + lose = (z, y) :: lose; + } + } + # winner should be unique + if (win != nil) { + if (tl win != nil) { + multimatch(e, base, win); + return -1; + } else { + (a, p) := hd win; + return xeqmatch(e, base, a, p); + } + } + # losers are candidates in next round + cand = lose; + } while (cand != nil); + return 0; +} + +# +# Make a node (recursive). +# Return -1 on (reported) error, 0 on fail, 1 on success. +# +make(e: ref Env, n: ref Node, s: string): int +{ + if (n == nil) + n = getfile(s); + if (verbose) + sys->print("making %s\n", n.name); + if (n.state == SMade) + return 1; + if (n.flags & FMark) { + cycle(e, n); + return -1; + } + n.flags |= FMark; + t := Target.find(s); + if (t != nil) { + x := explicit(e, t, n); + if (x != 0) { + n.flags &= ~FMark; + return x; + } + } + x := implicit(e, n); + n.flags &= ~FMark; + if (x != 0) + return x; + if (n.state == SExist) + return 0; + return -1; +} + +makelevel: int = 0; # count recursion + +# +# Make driver routine. Maybe initialize and handle exceptions. +# +domake(e: ref Env, l: list of string) +{ + if ((e.flags & mashlib->ETop) == 0) { + e.report("make not at top level"); + return; + } + inited := 0; + if (makelevel > 0) + inited = 1; + makelevel++; + if (l == nil) + l = "default" :: nil; + while (l != nil) { + s := hd l; + l = tl l; + if (s[0] == '-') { + case s { + "-clear" => + mashlib->initdep(); + * => + e.report("make: unknown option: " + s); + } + } else { + if (!inited) { + initnodes(); + inited = 1; + } + { + if (make(e, nil, s) < 0) { + sys->print("don't know how to make %s\n", s); + raise "fail: make error"; + } + }exception x{ + mashlib->FAILPAT => + makelevel--; + raise x; + } + } + } + makelevel--; +} + +# +# Print dependency/rule command. +# +prcmd(out: ref Iobuf, op: int, c: ref Cmd) +{ + if (op == Clistgroup) + out.putc(':'); + if (c != nil) { + out.puts("{ "); + out.puts(c.text()); + out.puts(" }"); + } else + out.puts("{}"); +} + +# +# Print details of rule matches. +# +domatch(e: ref Env, l: list of string) +{ + out := e.outfile(); + if (out == nil) + return; + e = e.copy(); + while (l != nil) { + s := hd l; + out.puts(sys->sprint("%s:\n", s)); + m := mashlib->rulematch(s); + (nil, t) := sys->tokenize(s, "/"); + while (m != nil) { + r := hd m; + out.puts(sys->sprint("\tlhs %s\n", r.lhs.text)); + a := r.matches(t); + if (a != nil) { + a[0] = s; + n := len a; + for (i := 0; i < n; i++) + out.puts(sys->sprint("\t$%d '%s'\n", i, a[i])); + e.args = a; + (v, w, nil) := r.rhs.ieval2(e); + if (v != nil) + out.puts(sys->sprint("\trhs '%s'\n", v)); + else + out.puts(sys->sprint("\trhs list %d\n", len w)); + if (r.cmd != nil) { + out.putc('\t'); + prcmd(out, r.op, r.cmd); + out.puts(";\n"); + } + } else + out.puts("\tcock up\n"); + m = tl m; + } + l = tl l; + } + out.close(); +} + +# +# Print word list. +# +prwords(out: ref Iobuf, l: list of string, pre: int) +{ + while (l != nil) { + if (pre) + out.putc(' '); + out.puts(mashlib->quote(hd l)); + if (!pre) + out.putc(' '); + l = tl l; + } +} + +# +# Print dependency. +# +prdep(out: ref Iobuf, d: ref Depend) +{ + prwords(out, d.targets, 0); + out.putc(':'); + prwords(out, d.depends, 1); + if (d.op != Cnop) { + out.putc(' '); + prcmd(out, d.op, d.cmd); + } + out.puts(";\n"); +} + +# +# Print all dependencies, avoiding duplicates. +# +alldep(out: ref Iobuf, d: ref Depend, pass: int) +{ + case pass { + 0 => + d.mark = 0; + 1 => + if (!d.mark) { + prdep(out, d); + d.mark = 1; + } + } +} + +# +# Print all dependencies. +# +alldeps(out: ref Iobuf) +{ + a := mashlib->dephash; + n := len a; + for (p := 0; p < 2; p++) + for (i := 0; i < n; i++) + for (l := a[i]; l != nil; l = tl l) + for (d := (hd l).depends; d != nil; d = tl d) + alldep(out, hd d, p); +} + +# +# Print dependencies. +# +depends(out: ref Iobuf, l: list of string) +{ + while (l != nil) { + s := hd l; + out.puts(s); + out.puts(":\n"); + t := Target.find(s); + if (t != nil) { + for (d := t.depends; d != nil; d = tl d) + prdep(out, hd d); + } + l = tl l; + } +} + +# +# Print rule. +# +prrule(out: ref Iobuf, r: ref Rule) +{ + out.puts(r.lhs.text); + out.puts(" :~ "); + out.puts(r.rhs.text()); + out.putc(' '); + prcmd(out, r.op, r.cmd); + out.puts(";\n"); +} + +# +# Print all rules. +# +allrules(out: ref Iobuf) +{ + for (l := mashlib->rules; l != nil; l = tl l) + prrule(out, hd l); +} + +# +# Print matching rules. +# +rules(out: ref Iobuf, l: list of string) +{ + while (l != nil) { + s := hd l; + out.puts(s); + out.puts(":\n"); + r := mashlib->rulematch(s); + while (r != nil) { + prrule(out, hd r); + r = tl r; + } + l = tl l; + } +} diff --git a/appl/cmd/mash/mash.b b/appl/cmd/mash/mash.b new file mode 100644 index 00000000..4e2f2ded --- /dev/null +++ b/appl/cmd/mash/mash.b @@ -0,0 +1,154 @@ +implement Mash; + +# +# mash - Inferno make/shell +# +# Bruce Ellis - 1Q 98 +# + +include "mash.m"; +include "mashparse.m"; + +# +# mash consists of three modules plus library modules and loadable builtins. +# +# This module, Mash, loads the other two (Mashparse and Mashlib), loads +# the builtin "builtins", initializes things and calls the parser. +# +# It has two entry points. One is the traditional init() function and the other, +# tkinit, is an interface to WmMash that allows the "tk" builtin to cooperate +# with the command window. +# + +Mash: module +{ + tkinit: fn(ctxt: ref Draw->Context, top: ref Tk->Toplevel, args: list of string); + init: fn(ctxt: ref Draw->Context, args: list of string); +}; + +Iobuf: import Bufio; + +sys: Sys; +lib: Mashlib; +parse: Mashparse; + +Env, Stab: import lib; + +cmd: string; + +# +# Check for /dev/console. +# +isconsole(fd: ref Sys->FD): int +{ + (ok1, d1) := sys->fstat(fd); + (ok2, d2) := sys->stat(lib->CONSOLE); + if (ok1 < 0 || ok2 < 0) + return 0; + return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path; +} + +usage(e: ref Env) +{ + sys->fprint(e.stderr, "usage: mash [-denx] [-c command] [src [args]]\n"); + lib->exits("usage"); +} + +flags(e: ref Env, l: list of string): list of string +{ + while (l != nil && len hd l && (s := hd l)[0] == '-') { + l = tl l; + if (s == "--") + break; + n := len s; + for (i := 1; i < n; i++) { + case s[i] { + 'c' => + if (++i < n) { + if (l != nil) + usage(e); + cmd = s[i:]; + } else { + if (len l != 1) + usage(e); + cmd = hd l; + } + return nil; + 'd' => + e.flags |= lib->EDumping; + 'e' => + e.flags |= lib->ERaise; + 'n' => + e.flags |= lib->ENoxeq; + 'x' => + e.flags |= lib->EEcho; + * => + usage(e); + } + } + } + return l; +} + +tkinit(ctxt: ref Draw->Context, top: ref Tk->Toplevel, args: list of string) +{ + fd: ref Sys->FD; + sys = load Sys Sys->PATH; + stderr := sys->fildes(2); + lib = load Mashlib Mashlib->PATH; + if (lib == nil) { + sys->fprint(stderr, "could not load %s: %r\n", Mashlib->PATH); + exit; + } + parse = load Mashparse Mashparse->PATH; + if (parse == nil) { + sys->fprint(stderr, "could not load %s: %r\n", Mashparse->PATH); + exit; + } + e := Env.new(); + e.stderr = stderr; + stderr = nil; + lib->initmash(ctxt, top, sys, e, lib, parse); + parse->init(lib); + boot := args == nil; + if (!boot) + args = flags(e, tl args); + e.doload(lib->LIB + lib->BUILTINS); + lib->prompt = "mash% "; + lib->contin = "\t"; + if (cmd == nil && args == nil && !boot) { + e.global.assign(lib->MASHINIT, "true" :: nil); + fd = sys->open(lib->PROFILE, Sys->OREAD); + if (fd != nil) { + e.fopen(fd, lib->PROFILE); + parse->parse(e); + fd = nil; + } + } + e.global.assign(lib->MASHINIT, nil); + if (cmd == nil) { + if (args != nil) { + s := hd args; + args = tl args; + fd = sys->open(s, Sys->OREAD); + if (fd == nil) + e.couldnot("open", s); + e.fopen(fd, s); + e.global.assign(lib->ARGS, args); + } + if (fd == nil) { + fd = sys->fildes(0); + if (isconsole(fd)) + e.interactive(fd); + e.fopen(fd, "<stdin>"); + fd = nil; + } + } else + e.sopen(cmd); + parse->parse(e); +} + +init(ctxt: ref Draw->Context, args: list of string) +{ + tkinit(ctxt, nil, args); +} diff --git a/appl/cmd/mash/mash.m b/appl/cmd/mash/mash.m new file mode 100644 index 00000000..ae16fee6 --- /dev/null +++ b/appl/cmd/mash/mash.m @@ -0,0 +1,372 @@ +include "sys.m"; +include "bufio.m"; +include "draw.m"; +include "hash.m"; +include "filepat.m"; +include "regex.m"; +include "sh.m"; +include "string.m"; +include "tk.m"; + +# +# mash - Inferno make/shell +# +# Bruce Ellis - 1Q 98 +# + + Rin, + Rout, + Rappend, + Rinout, + Rcount + : con iota; # Redirections + + Icaret, + Iicaret, + Idollar, + Idollarq, + Imatch, + Iword, + Iexpr, + Ibackq, + Iquote, + Iinpipe, + Ioutpipe, + Iredir + : con iota; # Items + + Csimple, + Cseq, + Cfor, + Cif, + Celse, + Cwhile, + Ccase, + Ccases, + Cmatched, + Cdefeq, + Ceq, + Cfn, + Crescue, + Casync, + Cgroup, + Clistgroup, + Csubgroup, + Cnop, + Cword, + Clist, + Ccaret, + Chd, + Clen, + Cnot, + Ctl, + Ccons, + Ceqeq, + Cnoteq, + Cmatch, + Cpipe, + Cdepend, + Crule, + Cprivate + : con iota; # Commands + + Svalue, + Sfunc, + Sbuiltin + : con iota; # Symbol types + +Mashlib: module +{ + PATH: con "/dis/lib/mashlib.dis"; + + File: adt + { + in: ref Bufio->Iobuf; + name: string; + line: int; + eof: int; + }; + + Src: adt + { + line: int; + file: string; + }; + + Wquoted, + Wexpand + : con 1 << iota; + + Word: adt + { + text: string; + flags: int; + where: Src; + + word: fn(w: self ref Word, d: string): string; + }; + + Item: adt + { + op: int; + word: ref Word; + left, right: ref Item; + cmd: ref Cmd; + redir: ref Redir; + + item1: fn(op: int, l: ref Item): ref Item; + item2: fn(op: int, l, r: ref Item): ref Item; + itemc: fn(op: int, c: ref Cmd): ref Item; + iteml: fn(l: list of string): ref Item; + itemr: fn(op: int, i: ref Item): ref Item; + itemw: fn(s: string): ref Item; + + caret: fn(i: self ref Item, e: ref Env): (string, list of string, int); + ieval: fn(i: self ref Item, e: ref Env): (string, list of string, int); + ieval1: fn(i: self ref Item, e: ref Env): ref Item; + ieval2: fn(i: self ref Item, e: ref Env): (string, list of string, int); + reval: fn(i: self ref Item, e: ref Env): (int, string); + sword: fn(i: self ref Item, e: ref Env): ref Item; + text: fn(i: self ref Item): string; + }; + + Redir: adt + { + op: int; + word: ref Item; + }; + + Cmd: adt + { + op: int; + words: cyclic list of ref Item; + left, right: cyclic ref Cmd; + item: cyclic ref Item; + redirs: cyclic list of ref Redir; + value: list of string; + error: int; + + cmd1: fn(op: int, l: ref Cmd): ref Cmd; + cmd2: fn(op: int, l, r: ref Cmd): ref Cmd; + cmd1i: fn(op: int, l: ref Cmd, i: ref Item): ref Cmd; + cmd1w: fn(op: int, l: ref Cmd, w: list of ref Item): ref Cmd; + cmde: fn(c: self ref Cmd, op: int, l, r: ref Cmd): ref Cmd; + cmdiw: fn(op: int, i: ref Item, w: list of ref Item): ref Cmd; + + assign: fn(c: self ref Cmd, e: ref Env, def: int); + checkpipe: fn(c: self ref Cmd, e: ref Env, f: int): int; + cmdio: fn(c: self ref Cmd, e: ref Env, i: ref Item); + depend: fn(c: self ref Cmd, e: ref Env); + eeval: fn(c: self ref Cmd, e: ref Env): (string, list of string); + eeval1: fn(c: self ref Cmd, e: ref Env): ref Cmd; + eeval2: fn(c: self ref Cmd, e: ref Env): (string, list of string, int); + evaleq: fn(c: self ref Cmd, e: ref Env): int; + evalmatch: fn(c: self ref Cmd, e: ref Env): int; + mkcmd: fn(c: self ref Cmd, e: ref Env, async: int): ref Cmd; + quote: fn(c: self ref Cmd, e: ref Env, back: int): ref Item; + rotcases: fn(c: self ref Cmd): ref Cmd; + rule: fn(c: self ref Cmd, e: ref Env); + serve: fn(c: self ref Cmd, e: ref Env, write: int): ref Item; + simple: fn(c: self ref Cmd, e: ref Env, wait: int); + text: fn(c: self ref Cmd): string; + truth: fn(c: self ref Cmd, e: ref Env): int; + xeq: fn(c: self ref Cmd, e: ref Env); + xeqit: fn(c: self ref Cmd, e: ref Env, wait: int); + }; + + Depend: adt + { + targets: list of string; + depends: list of string; + op: int; + cmd: ref Cmd; + mark: int; + }; + + Target: adt + { + target: string; + depends: list of ref Depend; + + find: fn(s: string): ref Target; + }; + + Lhs: adt + { + text: string; + elems: list of string; + count: int; + }; + + Rule: adt + { + lhs: ref Lhs; + rhs: ref Item; + op: int; + cmd: ref Cmd; + + match: fn(r: self ref Rule, a, n: int, t: list of string): int; + matches: fn(r: self ref Rule, t: list of string): array of string; + }; + + SHASH: con 31; # Symbol table hash size + SMASK: con 16r7FFFFFFF; # Mask for SHASH bits + + Symb: adt + { + name: string; + value: list of string; + func: ref Cmd; + builtin: Mashbuiltin; + tag: int; + }; + + Stab: adt + { + tab: array of list of ref Symb; + wmask: int; + copy: int; + + new: fn(): ref Stab; + clone: fn(t: self ref Stab): ref Stab; + all: fn(t: self ref Stab): list of ref Symb; + assign: fn(t: self ref Stab, s: string, v: list of string); + defbuiltin: fn(t: self ref Stab, s: string, b: Mashbuiltin); + define: fn(t: self ref Stab, s: string, f: ref Cmd); + find: fn(t: self ref Stab, s: string): ref Symb; + func: fn(t: self ref Stab, s: string): ref Cmd; + update: fn(t: self ref Stab, s: string, tag: int, v: list of string, f: ref Cmd, b: Mashbuiltin): ref Symb; + }; + + ETop, EInter, EEcho, ERaise, EDumping, ENoxeq: + con 1 << iota; + + Env: adt + { + global: ref Stab; + local: ref Stab; + flags: int; + in, out: ref Sys->FD; + stderr: ref Sys->FD; + wait: ref Sys->FD; + file: ref File; + args: array of string; + level: int; + + new: fn(): ref Env; + clone: fn(e: self ref Env): ref Env; + copy: fn(e: self ref Env): ref Env; + + interactive: fn(e: self ref Env, fd: ref Sys->FD); + + arg: fn(e: self ref Env, s: string): string; + builtin: fn(e: self ref Env, s: string): Mashbuiltin; + defbuiltin: fn(e: self ref Env, s: string, b: Mashbuiltin); + define: fn(e: self ref Env, s: string, f: ref Cmd); + dollar: fn(e: self ref Env, s: string): ref Symb; + func: fn(e: self ref Env, s: string): ref Cmd; + let: fn(e: self ref Env, s: string, v: list of string); + set: fn(e: self ref Env, s: string, v: list of string); + + couldnot: fn(e: self ref Env, what, who: string); + diag: fn(e: self ref Env, s: string): string; + error: fn(e: self ref Env, s: string); + report: fn(e: self ref Env, s: string); + sopen: fn(e: self ref Env, s: string); + suck: fn(e: self ref Env); + undefined: fn(e: self ref Env, s: string); + usage: fn(e: self ref Env, s: string); + + devnull: fn(e: self ref Env): ref Sys->FD; + fopen: fn(e: self ref Env, fd: ref Sys->FD, s: string); + outfile: fn(e: self ref Env): ref Bufio->Iobuf; + output: fn(e: self ref Env, s: string); + pipe: fn(e: self ref Env): array of ref Sys->FD; + runit: fn(e: self ref Env, s: list of string, in, out: ref Sys->FD, wait: int); + serve: fn(e: self ref Env); + servefd: fn(e: self ref Env, fd: ref Sys->FD, write: int): string; + servefile: fn(e: self ref Env, n: string): (string, ref Sys->FileIO); + + doload: fn(e: self ref Env, s: string); + lex: fn(e: self ref Env, y: ref Mashparse->YYSTYPE): int; + mklist: fn(e: self ref Env, l: list of ref Item): list of ref Item; + mksimple: fn(e: self ref Env, l: list of ref Item): ref Cmd; + }; + + initmash: fn(ctxt: ref Draw->Context, top: ref Tk->Toplevel, s: Sys, e: ref Env, l: Mashlib, p: Mashparse); + nonexistent: fn(s: string): int; + + errstr: fn(): string; + exits: fn(s: string); + ident: fn(s: string): int; + initdep: fn(); + prepareio: fn(in, out: ref sys->FD): (int, ref Sys->FD); + prprompt: fn(n: int); + quote: fn(s: string): string; + reap: fn(); + revitems: fn(l: list of ref Item): list of ref Item; + revstrs: fn(l: list of string): list of string; + rulematch: fn(s: string): list of ref Rule; + + ARGS: con "args"; + BUILTINS: con "builtins.dis"; + CHAN: con "/chan"; + CONSOLE: con "/dev/cons"; + DEVNULL: con "/dev/null"; + EEXISTS: con "file exists"; + EPIPE: con "write on closed pipe"; + EXIT: con "exit"; + FAILPAT: con "fail:*"; + FAIL: con "fail:"; + FAILLEN: con len FAIL; + HISTF: con "history"; + LIB: con "/dis/lib/mash/"; + MASHF: con "mash"; + MASHINIT: con "mashinit"; + PROFILE: con "/lib/mashinit"; + TRUE: con "true"; + MAXELEV: con 256; + + sys: Sys; + bufio: Bufio; + filepat: Filepat; + hash: Hash; + regex: Regex; + str: String; + tk: Tk; + + gctxt: ref Draw->Context; + gtop: ref Tk->Toplevel; + + prompt: string; + contin: string; + + empty: list of string; + + PIDEXIT: con 0; + + histchan: chan of array of byte; + inchan: chan of array of byte; + pidchan: chan of int; + servechan: chan of array of byte; + startserve: int; + + rules: list of ref Rule; + dephash: array of list of ref Target; + + parse: Mashparse; +}; + +# +# Interface to loadable builtin modules. mashinit is called when a module +# is loaded. mashcmd is called for a builtin as defined by Env.defbuiltin(). +# init() is in the interface to catch the use of builtin modules as commands. +# name() is used by whatis. +# +Mashbuiltin: module +{ + mashinit: fn(l: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Mashlib->Env); + mashcmd: fn(e: ref Mashlib->Env, l: list of string); + init: fn(ctxt: ref Draw->Context, args: list of string); + name: fn(): string; +}; diff --git a/appl/cmd/mash/mash.y b/appl/cmd/mash/mash.y new file mode 100644 index 00000000..2417ef51 --- /dev/null +++ b/appl/cmd/mash/mash.y @@ -0,0 +1,269 @@ +%{ +include "mash.m"; + +# +# mash parser. Thread safe. +# +%} + +%module Mashparse +{ + PATH: con "/dis/lib/mashparse.dis"; + + init: fn(l: Mashlib); + parse: fn(e: ref Mashlib->Env); + + YYSTYPE: adt + { + cmd: ref Mashlib->Cmd; + item: ref Mashlib->Item; + items: list of ref Mashlib->Item; + flag: int; + }; + + YYETYPE: type ref Mashlib->Env; +} + +%{ + lib: Mashlib; + + Cmd, Item, Stab, Env: import lib; +%} + +%left Lcase Lfor Lif Lwhile Loffparen # low prec +%left Lelse +%left Lpipe +%left Leqeq Lmatch Lnoteq +%right Lcons +%left Lcaret +%left Lnot Lhd Ltl Llen +%type <flag> term +%type <item> item wgen witem word redir sword +%type <items> asimple list +%type <cmd> case cases cmd cmda cmds cmdt complex +%type <cmd> epilog expr cbrace cobrace obrace simple +%token <item> Lword +%token Lbackq Lcolon Lcolonmatch Ldefeq Leq Lmatched Lquote +%token Loncurly Lonparen Loffcurly Loffparen Lat +%token Lgreat Lgreatgreat Lless Llessgreat +%token Lfn Lin Lrescue +%token Land Leof Lsemi +%token Lerror + +%% + +script : tcmds + ; + +tcmds : # empty + | tcmds xeq + ; + +xeq : cmda + { $1.xeq(e.yyenv); } + | Leof + | error + ; + +cmdt : # empty + { $$ = nil; } + | cmdt cmda + { $$ = Cmd.cmd2(Cseq, $1, $2); } + ; + +cmda : cmd term + { $$ = $1.mkcmd(e.yyenv, $2); } + ; + +cmds : cmdt + | cmdt cmd + { $$ = Cmd.cmd2(Cseq, $1, $2.mkcmd(e.yyenv, 0)); } + ; + +cmd : simple + | complex + | cmd Lpipe cmd + { $$ = Cmd.cmd2(Cpipe, $1, $3); } + ; + +simple : asimple + { $$ = e.yyenv.mksimple($1); } + | asimple Lcolon list cobrace + { + $4.words = e.yyenv.mklist($3); + $$ = Cmd.cmd1w(Cdepend, $4, e.yyenv.mklist($1)); + } + ; + +complex : Loncurly cmds Loffcurly epilog + { $$ = $4.cmde(Cgroup, $2, nil); } + | Lat Loncurly cmds Loffcurly epilog + { $$ = $5.cmde(Csubgroup, $3, nil); } + | Lfor Lonparen sword Lin list Loffparen cmd + { $$ = Cmd.cmd1i(Cfor, $7, $3); $$.words = lib->revitems($5); } + | Lif Lonparen expr Loffparen cmd + { $$ = Cmd.cmd2(Cif, $3, $5); } + | Lif Lonparen expr Loffparen cmd Lelse cmd + { $$ = Cmd.cmd2(Cif, $3, Cmd.cmd2(Celse, $5, $7)); } + | Lwhile Lonparen expr Loffparen cmd + { $$ = Cmd.cmd2(Cwhile, $3, $5); } + | Lcase expr Loncurly cases Loffcurly + { $$ = Cmd.cmd2(Ccase, $2, $4.rotcases()); } + | sword Leq list + { $$ = Cmd.cmdiw(Ceq, $1, $3); } + | sword Ldefeq list + { $$ = Cmd.cmdiw(Cdefeq, $1, $3); } + | Lfn word obrace + { $$ = Cmd.cmd1i(Cfn, $3, $2); } + | Lrescue word obrace + { $$ = Cmd.cmd1i(Crescue, $3, $2); } + | word Lcolonmatch word cbrace + { + $4.item = $3; + $$ = Cmd.cmd1i(Crule, $4, $1); + } + ; + +cbrace : Lcolon Loncurly cmds Loffcurly + { $$ = Cmd.cmd1(Clistgroup, $3); } + | Loncurly cmds Loffcurly + { $$ = Cmd.cmd1(Cgroup, $2); } + ; + +cobrace : # empty + { $$ = Cmd.cmd1(Cnop, nil); } + | cbrace + ; + +obrace : # empty + { $$ = nil; } + | Loncurly cmds Loffcurly + { $$ = $2; } + ; + +cases : # empty + { $$ = nil; } + | cases case + { $$ = Cmd.cmd2(Ccases, $1, $2); } + ; + +case : expr Lmatched cmda + { $$ = Cmd.cmd2(Cmatched, $1, $3); } + ; + +asimple : word + { $$ = $1 :: nil; } + | asimple item + { $$ = $2 :: $1; } + ; + +item : witem + | redir + ; + +witem : word + | wgen + ; + +wgen : Lbackq Loncurly cmds Loffcurly + { $$ = Item.itemc(Ibackq, $3); } + | Lquote Loncurly cmds Loffcurly + { $$ = Item.itemc(Iquote, $3); } + | Lless Loncurly cmds Loffcurly + { $$ = Item.itemc(Iinpipe, $3); } + | Lgreat Loncurly cmds Loffcurly + { $$ = Item.itemc(Ioutpipe, $3); } + ; + +word : Lword + | word Lcaret word + { $$ = Item.item2(Icaret, $1, $3); } + | Lonparen expr Loffparen + { $$ = Item.itemc(Iexpr, $2); } + ; + +sword : Lword + { $$ = $1.sword(e.yyenv); } + ; + +list : # empty + { $$ = nil; } + | list witem + { $$ = $2 :: $1; } + ; + +epilog : # empty + { $$ = ref Cmd; $$.error = 0; } + | epilog redir + { $$ = $1; $1.cmdio(e.yyenv, $2); } + ; + +redir : Lless word + { $$ = Item.itemr(Rin, $2); } + | Lgreat word + { $$ = Item.itemr(Rout, $2); } + | Lgreatgreat word + { $$ = Item.itemr(Rappend, $2); } + | Llessgreat word + { $$ = Item.itemr(Rinout, $2); } + ; + +term : Lsemi + { $$ = 0; } + | Leof + { $$ = 0; } + | Land + { $$ = 1; } + ; + +expr : Lword + { $$ = Cmd.cmd1i(Cword, nil, $1); } + | wgen + { $$ = Cmd.cmd1i(Cword, nil, $1); } + | Lonparen expr Loffparen + { $$ = $2; } + | expr Lcaret expr + { $$ = Cmd.cmd2(Ccaret, $1, $3); } + | Lhd expr + { $$ = Cmd.cmd1(Chd, $2); } + | Ltl expr + { $$ = Cmd.cmd1(Ctl, $2); } + | Llen expr + { $$ = Cmd.cmd1(Clen, $2); } + | Lnot expr + { $$ = Cmd.cmd1(Cnot, $2); } + | expr Lcons expr + { $$ = Cmd.cmd2(Ccons, $1, $3); } + | expr Leqeq expr + { $$ = Cmd.cmd2(Ceqeq, $1, $3); } + | expr Lnoteq expr + { $$ = Cmd.cmd2(Cnoteq, $1, $3); } + | expr Lmatch expr + { $$ = Cmd.cmd2(Cmatch, $1, $3); } + ; +%% + +init(l: Mashlib) +{ + lib = l; +} + +parse(e: ref Env) +{ + y := ref YYENV; + y.yyenv = e; + y.yysys = lib->sys; + y.yystderr = e.stderr; + yyeparse(y); +} + +yyerror(e: ref YYENV, s: string) +{ + e.yyenv.report(s); + e.yyenv.suck(); +} + +yyelex(e: ref YYENV): int +{ + return e.yyenv.lex(e.yylval); +} diff --git a/appl/cmd/mash/mashfile b/appl/cmd/mash/mashfile new file mode 100644 index 00000000..0357c3dc --- /dev/null +++ b/appl/cmd/mash/mashfile @@ -0,0 +1,36 @@ +make -clear; +lflags = -wg; + +fn lc { + limbo $lflags $args; +}; + +libsrc = depends.b dump.b exec.b expr.b lex.b misc.b serve.b symb.b xeq.b; +bus = builtins.dis tk.dis make.dis history.dis; +core = mash.dis mashlib.dis mashparse.dis; + +bulib = /dis/lib/mash; +bulibs = $bulib/$bus; + +mashparse.b mashparse.m : mash.y +{ + eyacc -vd mash.y; + mv y.tab.m mashparse.m; + mv y.tab.b mashparse.b; +}; + +*.dis :~ $1.b { lc $1.b }; +$bulib/*.dis :~ $1.dis { cp $1.dis $bulib }; +/dis/*.dis :~ $1.dis { cp $1.dis /dis }; +/dis/lib/*.dis :~ $1.dis { cp $1.dis /dis/lib }; + +$core $bus : mash.m mashparse.m; +mashlib.dis : $libsrc; + +insbu : $bulibs {}; +insdis : /dis/mash.dis /dis/lib/mashlib.dis /dis/lib/mashparse.dis {}; + +all : eyacc.dis mash.dis mashlib.dis mashparse.dis $bus {}; +install : insbu insdis {}; + +clean : { rm mashparse.b mashparse.m *.dis }; diff --git a/appl/cmd/mash/mashlib.b b/appl/cmd/mash/mashlib.b new file mode 100644 index 00000000..c7ac7a29 --- /dev/null +++ b/appl/cmd/mash/mashlib.b @@ -0,0 +1,60 @@ +implement Mashlib; + +# +# Mashlib - All of the real work except for the parsing. +# + +include "mash.m"; +include "mashparse.m"; + +Iobuf: import bufio; +HashTable, HashVal: import hash; + +include "depends.b"; +include "dump.b"; +include "exec.b"; +include "expr.b"; +include "lex.b"; +include "misc.b"; +include "serve.b"; +include "symb.b"; +include "xeq.b"; + +lib: Mashlib; + +initmash(ctxt: ref Draw->Context, top: ref Tk->Toplevel, s: Sys, e: ref Env, l: Mashlib, p: Mashparse) +{ + gctxt = ctxt; + gtop = top; + sys = s; + lib = l; + parse = p; + if (top != nil) { + tk = load Tk Tk->PATH; + if (tk == nil) + e.couldnot("load", Tk->PATH); + } + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + e.couldnot("load", Bufio->PATH); + hash = load Hash Hash->PATH; + if (hash == nil) + e.couldnot("load", Hash->PATH); + str = load String String->PATH; + if (str == nil) + e.couldnot("load", String->PATH); + initlex(); + empty = "no" :: "value" :: nil; + startserve = 0; +} + +nonexistent(e: string): int +{ + errs := array[] of {"does not exist", "directory entry not found"}; + for (i := 0; i < len errs; i++){ + j := len errs[i]; + if (j <= len e && e[len e-j:] == errs[i]) + return 1; + } + return 0; +} diff --git a/appl/cmd/mash/mashparse.b b/appl/cmd/mash/mashparse.b new file mode 100644 index 00000000..b154f12c --- /dev/null +++ b/appl/cmd/mash/mashparse.b @@ -0,0 +1,662 @@ +implement Mashparse; + +#line 2 "mash.y" +include "mash.m"; + +# +# mash parser. Thread safe. +# +Mashparse: module { + + PATH: con "/dis/lib/mashparse.dis"; + + init: fn(l: Mashlib); + parse: fn(e: ref Mashlib->Env); + + YYSTYPE: adt + { + cmd: ref Mashlib->Cmd; + item: ref Mashlib->Item; + items: list of ref Mashlib->Item; + flag: int; + }; + + YYETYPE: type ref Mashlib->Env; +Lcase: con 57346; +Lfor: con 57347; +Lif: con 57348; +Lwhile: con 57349; +Loffparen: con 57350; +Lelse: con 57351; +Lpipe: con 57352; +Leqeq: con 57353; +Lmatch: con 57354; +Lnoteq: con 57355; +Lcons: con 57356; +Lcaret: con 57357; +Lnot: con 57358; +Lhd: con 57359; +Ltl: con 57360; +Llen: con 57361; +Lword: con 57362; +Lbackq: con 57363; +Lcolon: con 57364; +Lcolonmatch: con 57365; +Ldefeq: con 57366; +Leq: con 57367; +Lmatched: con 57368; +Lquote: con 57369; +Loncurly: con 57370; +Lonparen: con 57371; +Loffcurly: con 57372; +Lat: con 57373; +Lgreat: con 57374; +Lgreatgreat: con 57375; +Lless: con 57376; +Llessgreat: con 57377; +Lfn: con 57378; +Lin: con 57379; +Lrescue: con 57380; +Land: con 57381; +Leof: con 57382; +Lsemi: con 57383; +Lerror: con 57384; + +}; + +#line 28 "mash.y" + lib: Mashlib; + + Cmd, Item, Stab, Env: import lib; +YYEOFCODE: con 1; +YYERRCODE: con 2; +YYMAXDEPTH: con 150; + +#line 244 "mash.y" + + +init(l: Mashlib) +{ + lib = l; +} + +parse(e: ref Env) +{ + y := ref YYENV; + y.yyenv = e; + y.yysys = lib->sys; + y.yystderr = e.stderr; + yyeparse(y); +} + +yyerror(e: ref YYENV, s: string) +{ + e.yyenv.report(s); + e.yyenv.suck(); +} + +yyelex(e: ref YYENV): int +{ + return e.yyenv.lex(e.yylval); +} +yyexca := array[] of {-1, 1, + 1, -1, + -2, 0, +-1, 2, + 1, 1, + -2, 0, +-1, 21, + 24, 51, + 25, 51, + -2, 48, +}; +YYNPROD: con 75; +YYPRIVATE: con 57344; +yytoknames: array of string; +yystates: array of string; +yydebug: con 0; +YYLAST: con 249; +yyact := array[] of { + 7, 20, 4, 49, 41, 47, 110, 65, 103, 95, + 17, 24, 32, 112, 33, 146, 142, 38, 39, 28, + 59, 60, 140, 129, 40, 64, 22, 46, 63, 35, + 36, 34, 37, 128, 127, 38, 67, 69, 70, 71, + 27, 26, 25, 76, 22, 75, 126, 111, 77, 74, + 45, 80, 81, 38, 44, 78, 88, 89, 90, 91, + 92, 68, 22, 98, 99, 93, 94, 32, 124, 33, + 97, 106, 62, 107, 38, 39, 104, 108, 109, 104, + 68, 40, 105, 22, 66, 105, 56, 143, 55, 116, + 117, 118, 119, 120, 73, 32, 32, 33, 33, 38, + 39, 122, 132, 36, 131, 37, 40, 123, 22, 72, + 125, 56, 43, 55, 135, 136, 58, 57, 133, 62, + 134, 139, 38, 6, 62, 16, 13, 14, 15, 141, + 66, 22, 96, 67, 69, 62, 32, 79, 33, 84, + 83, 21, 24, 61, 147, 148, 144, 24, 149, 11, + 22, 3, 12, 16, 13, 14, 15, 18, 2, 19, + 1, 5, 85, 87, 86, 84, 83, 8, 101, 21, + 54, 51, 52, 53, 48, 39, 9, 11, 22, 82, + 12, 40, 42, 50, 137, 18, 56, 19, 55, 54, + 51, 52, 53, 48, 39, 115, 138, 38, 39, 130, + 40, 10, 50, 29, 40, 56, 22, 55, 102, 56, + 31, 55, 85, 87, 86, 84, 83, 121, 23, 30, + 85, 87, 86, 84, 83, 114, 0, 145, 85, 87, + 86, 84, 83, 113, 0, 0, 85, 87, 86, 84, + 83, 100, 0, 0, 85, 87, 86, 84, 83, +}; +yypact := array[] of { +-1000,-1000, 121,-1000,-1000,-1000,-1000, 1,-1000,-1000, + -3,-1000, 84, 25, 21, -2, 173, 92, 15, 15, + 120,-1000, 173,-1000, 149,-1000,-1000,-1000,-1000,-1000, +-1000,-1000, 109,-1000, 102, 33, 15, 15,-1000, 81, + 66, 19, 149,-1000, 117, 173, 173, 151,-1000,-1000, + 173, 173, 173, 173, 173, 56, 52,-1000,-1000, 104, + 104, 15, 15, 233,-1000, 54,-1000, 109,-1000, 109, + 109, 109,-1000,-1000,-1000,-1000, 1, 17, -24,-1000, + 225, 217,-1000, 173, 173, 173, 173, 173, 209,-1000, +-1000,-1000,-1000, 177, 177,-1000,-1000,-1000, 57,-1000, +-1000,-1000,-1000,-1000, 40,-1000, 16, 4, 3, -7, + 70,-1000,-1000, 149, 149, 154,-1000, 125, 125, 125, + 125,-1000, -8,-1000,-1000, -14,-1000,-1000,-1000,-1000, +-1000, 15, 15, 70, 79, 137, 132,-1000,-1000, 201, +-1000, -15,-1000, 149, 149, 149,-1000, 132, 132,-1000, +}; +yypgo := array[] of { + 0, 218, 203, 3, 208, 1, 199, 10, 201, 7, + 196, 195, 0, 2, 4, 182, 176, 6, 5, 8, + 168, 9, 167, 160, 158, 151, +}; +yyr1 := array[] of { + 0, 23, 24, 24, 25, 25, 25, 15, 15, 13, + 14, 14, 12, 12, 12, 22, 22, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 19, + 19, 20, 20, 21, 21, 11, 11, 10, 8, 8, + 2, 2, 4, 4, 3, 3, 3, 3, 5, 5, + 5, 7, 9, 9, 17, 17, 6, 6, 6, 6, + 1, 1, 1, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, +}; +yyr2 := array[] of { + 0, 1, 0, 2, 1, 1, 1, 0, 2, 2, + 1, 2, 1, 1, 3, 1, 4, 4, 5, 7, + 5, 7, 5, 5, 3, 3, 3, 3, 4, 4, + 3, 0, 1, 0, 3, 0, 2, 3, 1, 2, + 1, 1, 1, 1, 4, 4, 4, 4, 1, 3, + 3, 1, 0, 2, 0, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 3, 3, 2, 2, 2, + 2, 3, 3, 3, 3, +}; +yychk := array[] of { +-1000, -23, -24, -25, -13, 40, 2, -12, -22, -16, + -8, 28, 31, 5, 6, 7, 4, -7, 36, 38, + -5, 20, 29, -1, 10, 41, 40, 39, 22, -2, + -4, -6, -5, -3, 34, 32, 33, 35, 20, 21, + 27, -14, -15, 28, 29, 29, 29, -18, 20, -3, + 29, 17, 18, 19, 16, 34, 32, 25, 24, -5, + -5, 23, 15, -18, -12, -9, 28, -5, 28, -5, + -5, -5, 28, 28, 30, -13, -12, -14, -7, 20, + -18, -18, 28, 15, 14, 11, 13, 12, -18, -18, + -18, -18, -18, -9, -9, -21, 28, -21, -5, -5, + 8, -20, -4, -19, 22, 28, -14, -14, -14, -14, + -17, 30, 37, 8, 8, -11, -18, -18, -18, -18, + -18, 8, -14, -19, 28, -14, 30, 30, 30, 30, + -6, 34, 32, -17, -9, -12, -12, 30, -10, -18, + 30, -14, 30, 8, 9, 26, 30, -12, -12, -13, +}; +yydef := array[] of { + 2, -2, -2, 3, 4, 5, 6, 0, 12, 13, + 15, 7, 0, 0, 0, 0, 0, 0, 0, 0, + 38, -2, 0, 9, 0, 60, 61, 62, 52, 39, + 40, 41, 42, 43, 0, 0, 0, 0, 48, 0, + 0, 0, 10, 7, 0, 0, 0, 0, 63, 64, + 0, 0, 0, 0, 0, 0, 0, 52, 52, 33, + 33, 0, 0, 0, 14, 31, 7, 56, 7, 57, + 58, 59, 7, 7, 54, 8, 11, 0, 0, 51, + 0, 0, 35, 0, 0, 0, 0, 0, 0, 67, + 68, 69, 70, 24, 25, 26, 7, 27, 0, 49, + 50, 16, 53, 32, 0, 7, 0, 0, 0, 0, + 17, 54, 52, 0, 0, 0, 66, 71, 72, 73, + 74, 65, 0, 28, 7, 0, 46, 47, 44, 45, + 55, 0, 0, 18, 0, 20, 22, 23, 36, 0, + 34, 0, 30, 0, 0, 0, 29, 19, 21, 37, +}; +yytok1 := array[] of { + 1, +}; +yytok2 := array[] of { + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, + 42, +}; +yytok3 := array[] of { + 0 +}; + +YYFLAG: con -1000; + +# parser for yacc output +YYENV: adt +{ + yylval: ref YYSTYPE; # lexical value + yyval: YYSTYPE; # goto value + yyenv: YYETYPE; # useer environment + yynerrs: int; # number of errors + yyerrflag: int; # error recovery flag + yysys: Sys; + yystderr: ref Sys->FD; +}; + +yytokname(yyc: int): string +{ + if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil) + return yytoknames[yyc-1]; + return "<"+string yyc+">"; +} + +yystatname(yys: int): string +{ + if(yys >= 0 && yys < len yystates && yystates[yys] != nil) + return yystates[yys]; + return "<"+string yys+">\n"; +} + +yylex1(e: ref YYENV): int +{ + c, yychar : int; + yychar = yyelex(e); + if(yychar <= 0) + c = yytok1[0]; + else if(yychar < len yytok1) + c = yytok1[yychar]; + else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2) + c = yytok2[yychar-YYPRIVATE]; + else{ + n := len yytok3; + c = 0; + for(i := 0; i < n; i+=2) { + if(yytok3[i+0] == yychar) { + c = yytok3[i+1]; + break; + } + } + if(c == 0) + c = yytok2[1]; # unknown char + } + if(yydebug >= 3) + e.yysys->fprint(e.yystderr, "lex %.4ux %s\n", yychar, yytokname(c)); + return c; +} + +YYS: adt +{ + yyv: YYSTYPE; + yys: int; +}; + +yyparse(): int +{ + return yyeparse(nil); +} + +yyeparse(e: ref YYENV): int +{ + if(e == nil) + e = ref YYENV; + if(e.yylval == nil) + e.yylval = ref YYSTYPE; + if(e.yysys == nil) { + e.yysys = load Sys "$Sys"; + e.yystderr = e.yysys->fildes(2); + } + + yys := array[YYMAXDEPTH] of YYS; + + yystate := 0; + yychar := -1; + e.yynerrs = 0; + e.yyerrflag = 0; + yyp := -1; + yyn := 0; + +yystack: + for(;;){ + # put a state and value onto the stack + if(yydebug >= 4) + e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + + yyp++; + if(yyp >= YYMAXDEPTH) { + yyerror(e, "yacc stack overflow"); + yyn = 1; + break yystack; + } + yys[yyp].yys = yystate; + yys[yyp].yyv = e.yyval; + + for(;;){ + yyn = yypact[yystate]; + if(yyn > YYFLAG) { # simple state + if(yychar < 0) + yychar = yylex1(e); + yyn += yychar; + if(yyn >= 0 && yyn < YYLAST) { + yyn = yyact[yyn]; + if(yychk[yyn] == yychar) { # valid shift + yychar = -1; + yyp++; + if(yyp >= YYMAXDEPTH) { + yyerror(e, "yacc stack overflow"); + yyn = 1; + break yystack; + } + yystate = yyn; + yys[yyp].yys = yystate; + yys[yyp].yyv = *e.yylval; + if(e.yyerrflag > 0) + e.yyerrflag--; + if(yydebug >= 4) + e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + continue; + } + } + } + + # default state action + yyn = yydef[yystate]; + if(yyn == -2) { + if(yychar < 0) + yychar = yylex1(e); + + # look through exception table + for(yyxi:=0;; yyxi+=2) + if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate) + break; + for(yyxi += 2;; yyxi += 2) { + yyn = yyexca[yyxi]; + if(yyn < 0 || yyn == yychar) + break; + } + yyn = yyexca[yyxi+1]; + if(yyn < 0){ + yyn = 0; + break yystack; + } + } + + if(yyn != 0) + break; + + # error ... attempt to resume parsing + if(e.yyerrflag == 0) { # brand new error + yyerror(e, "syntax error"); + e.yynerrs++; + if(yydebug >= 1) { + e.yysys->fprint(e.yystderr, "%s", yystatname(yystate)); + e.yysys->fprint(e.yystderr, "saw %s\n", yytokname(yychar)); + } + } + + if(e.yyerrflag != 3) { # incompletely recovered error ... try again + e.yyerrflag = 3; + + # find a state where "error" is a legal shift action + while(yyp >= 0) { + yyn = yypact[yys[yyp].yys] + YYERRCODE; + if(yyn >= 0 && yyn < YYLAST) { + yystate = yyact[yyn]; # simulate a shift of "error" + if(yychk[yystate] == YYERRCODE) { + yychar = -1; + continue yystack; + } + } + + # the current yyp has no shift on "error", pop stack + if(yydebug >= 2) + e.yysys->fprint(e.yystderr, "error recovery pops state %d, uncovers %d\n", + yys[yyp].yys, yys[yyp-1].yys ); + yyp--; + } + # there is no state on the stack with an error shift ... abort + yyn = 1; + break yystack; + } + + # no shift yet; clobber input char + if(yydebug >= 2) + e.yysys->fprint(e.yystderr, "error recovery discards %s\n", yytokname(yychar)); + if(yychar == YYEOFCODE) { + yyn = 1; + break yystack; + } + yychar = -1; + # try again in the same state + } + + # reduction by production yyn + if(yydebug >= 2) + e.yysys->fprint(e.yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); + + yypt := yyp; + yyp -= yyr2[yyn]; +# yyval = yys[yyp+1].yyv; + yym := yyn; + + # consult goto table to find next state + yyn = yyr1[yyn]; + yyg := yypgo[yyn]; + yyj := yyg + yys[yyp].yys + 1; + + if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) + yystate = yyact[yyg]; + case yym { + +4=> +#line 63 "mash.y" +{ yys[yypt-0].yyv.cmd.xeq(e.yyenv); } +7=> +#line 69 "mash.y" +{ e.yyval.cmd = nil; } +8=> +#line 71 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Cseq, yys[yypt-1].yyv.cmd, yys[yypt-0].yyv.cmd); } +9=> +#line 75 "mash.y" +{ e.yyval.cmd = yys[yypt-1].yyv.cmd.mkcmd(e.yyenv, yys[yypt-0].yyv.flag); } +10=> +e.yyval.cmd = yys[yyp+1].yyv.cmd; +11=> +#line 80 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Cseq, yys[yypt-1].yyv.cmd, yys[yypt-0].yyv.cmd.mkcmd(e.yyenv, 0)); } +12=> +e.yyval.cmd = yys[yyp+1].yyv.cmd; +13=> +e.yyval.cmd = yys[yyp+1].yyv.cmd; +14=> +#line 86 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Cpipe, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); } +15=> +#line 90 "mash.y" +{ e.yyval.cmd = e.yyenv.mksimple(yys[yypt-0].yyv.items); } +16=> +#line 92 "mash.y" +{ + yys[yypt-0].yyv.cmd.words = e.yyenv.mklist(yys[yypt-1].yyv.items); + e.yyval.cmd = Cmd.cmd1w(Cdepend, yys[yypt-0].yyv.cmd, e.yyenv.mklist(yys[yypt-3].yyv.items)); + } +17=> +#line 99 "mash.y" +{ e.yyval.cmd = yys[yypt-0].yyv.cmd.cmde(Cgroup, yys[yypt-2].yyv.cmd, nil); } +18=> +#line 101 "mash.y" +{ e.yyval.cmd = yys[yypt-0].yyv.cmd.cmde(Csubgroup, yys[yypt-2].yyv.cmd, nil); } +19=> +#line 103 "mash.y" +{ e.yyval.cmd = Cmd.cmd1i(Cfor, yys[yypt-0].yyv.cmd, yys[yypt-4].yyv.item); e.yyval.cmd.words = lib->revitems(yys[yypt-2].yyv.items); } +20=> +#line 105 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Cif, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); } +21=> +#line 107 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Cif, yys[yypt-4].yyv.cmd, Cmd.cmd2(Celse, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd)); } +22=> +#line 109 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Cwhile, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); } +23=> +#line 111 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Ccase, yys[yypt-3].yyv.cmd, yys[yypt-1].yyv.cmd.rotcases()); } +24=> +#line 113 "mash.y" +{ e.yyval.cmd = Cmd.cmdiw(Ceq, yys[yypt-2].yyv.item, yys[yypt-0].yyv.items); } +25=> +#line 115 "mash.y" +{ e.yyval.cmd = Cmd.cmdiw(Cdefeq, yys[yypt-2].yyv.item, yys[yypt-0].yyv.items); } +26=> +#line 117 "mash.y" +{ e.yyval.cmd = Cmd.cmd1i(Cfn, yys[yypt-0].yyv.cmd, yys[yypt-1].yyv.item); } +27=> +#line 119 "mash.y" +{ e.yyval.cmd = Cmd.cmd1i(Crescue, yys[yypt-0].yyv.cmd, yys[yypt-1].yyv.item); } +28=> +#line 121 "mash.y" +{ + yys[yypt-0].yyv.cmd.item = yys[yypt-1].yyv.item; + e.yyval.cmd = Cmd.cmd1i(Crule, yys[yypt-0].yyv.cmd, yys[yypt-3].yyv.item); + } +29=> +#line 128 "mash.y" +{ e.yyval.cmd = Cmd.cmd1(Clistgroup, yys[yypt-1].yyv.cmd); } +30=> +#line 130 "mash.y" +{ e.yyval.cmd = Cmd.cmd1(Cgroup, yys[yypt-1].yyv.cmd); } +31=> +#line 134 "mash.y" +{ e.yyval.cmd = Cmd.cmd1(Cnop, nil); } +32=> +e.yyval.cmd = yys[yyp+1].yyv.cmd; +33=> +#line 139 "mash.y" +{ e.yyval.cmd = nil; } +34=> +#line 141 "mash.y" +{ e.yyval.cmd = yys[yypt-1].yyv.cmd; } +35=> +#line 145 "mash.y" +{ e.yyval.cmd = nil; } +36=> +#line 147 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Ccases, yys[yypt-1].yyv.cmd, yys[yypt-0].yyv.cmd); } +37=> +#line 151 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Cmatched, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); } +38=> +#line 155 "mash.y" +{ e.yyval.items = yys[yypt-0].yyv.item :: nil; } +39=> +#line 157 "mash.y" +{ e.yyval.items = yys[yypt-0].yyv.item :: yys[yypt-1].yyv.items; } +40=> +e.yyval.item = yys[yyp+1].yyv.item; +41=> +e.yyval.item = yys[yyp+1].yyv.item; +42=> +e.yyval.item = yys[yyp+1].yyv.item; +43=> +e.yyval.item = yys[yyp+1].yyv.item; +44=> +#line 169 "mash.y" +{ e.yyval.item = Item.itemc(Ibackq, yys[yypt-1].yyv.cmd); } +45=> +#line 171 "mash.y" +{ e.yyval.item = Item.itemc(Iquote, yys[yypt-1].yyv.cmd); } +46=> +#line 173 "mash.y" +{ e.yyval.item = Item.itemc(Iinpipe, yys[yypt-1].yyv.cmd); } +47=> +#line 175 "mash.y" +{ e.yyval.item = Item.itemc(Ioutpipe, yys[yypt-1].yyv.cmd); } +48=> +e.yyval.item = yys[yyp+1].yyv.item; +49=> +#line 180 "mash.y" +{ e.yyval.item = Item.item2(Icaret, yys[yypt-2].yyv.item, yys[yypt-0].yyv.item); } +50=> +#line 182 "mash.y" +{ e.yyval.item = Item.itemc(Iexpr, yys[yypt-1].yyv.cmd); } +51=> +#line 186 "mash.y" +{ e.yyval.item = yys[yypt-0].yyv.item.sword(e.yyenv); } +52=> +#line 190 "mash.y" +{ e.yyval.items = nil; } +53=> +#line 192 "mash.y" +{ e.yyval.items = yys[yypt-0].yyv.item :: yys[yypt-1].yyv.items; } +54=> +#line 196 "mash.y" +{ e.yyval.cmd = ref Cmd; e.yyval.cmd.error = 0; } +55=> +#line 198 "mash.y" +{ e.yyval.cmd = yys[yypt-1].yyv.cmd; yys[yypt-1].yyv.cmd.cmdio(e.yyenv, yys[yypt-0].yyv.item); } +56=> +#line 202 "mash.y" +{ e.yyval.item = Item.itemr(Rin, yys[yypt-0].yyv.item); } +57=> +#line 204 "mash.y" +{ e.yyval.item = Item.itemr(Rout, yys[yypt-0].yyv.item); } +58=> +#line 206 "mash.y" +{ e.yyval.item = Item.itemr(Rappend, yys[yypt-0].yyv.item); } +59=> +#line 208 "mash.y" +{ e.yyval.item = Item.itemr(Rinout, yys[yypt-0].yyv.item); } +60=> +#line 212 "mash.y" +{ e.yyval.flag = 0; } +61=> +#line 214 "mash.y" +{ e.yyval.flag = 0; } +62=> +#line 216 "mash.y" +{ e.yyval.flag = 1; } +63=> +#line 220 "mash.y" +{ e.yyval.cmd = Cmd.cmd1i(Cword, nil, yys[yypt-0].yyv.item); } +64=> +#line 222 "mash.y" +{ e.yyval.cmd = Cmd.cmd1i(Cword, nil, yys[yypt-0].yyv.item); } +65=> +#line 224 "mash.y" +{ e.yyval.cmd = yys[yypt-1].yyv.cmd; } +66=> +#line 226 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Ccaret, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); } +67=> +#line 228 "mash.y" +{ e.yyval.cmd = Cmd.cmd1(Chd, yys[yypt-0].yyv.cmd); } +68=> +#line 230 "mash.y" +{ e.yyval.cmd = Cmd.cmd1(Ctl, yys[yypt-0].yyv.cmd); } +69=> +#line 232 "mash.y" +{ e.yyval.cmd = Cmd.cmd1(Clen, yys[yypt-0].yyv.cmd); } +70=> +#line 234 "mash.y" +{ e.yyval.cmd = Cmd.cmd1(Cnot, yys[yypt-0].yyv.cmd); } +71=> +#line 236 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Ccons, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); } +72=> +#line 238 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Ceqeq, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); } +73=> +#line 240 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Cnoteq, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); } +74=> +#line 242 "mash.y" +{ e.yyval.cmd = Cmd.cmd2(Cmatch, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); } + } + } + + return yyn; +} diff --git a/appl/cmd/mash/mashparse.m b/appl/cmd/mash/mashparse.m new file mode 100644 index 00000000..157c2f54 --- /dev/null +++ b/appl/cmd/mash/mashparse.m @@ -0,0 +1,56 @@ +Mashparse: module { + + PATH: con "/dis/lib/mashparse.dis"; + + init: fn(l: Mashlib); + parse: fn(e: ref Mashlib->Env); + + YYSTYPE: adt + { + cmd: ref Mashlib->Cmd; + item: ref Mashlib->Item; + items: list of ref Mashlib->Item; + flag: int; + }; + + YYETYPE: type ref Mashlib->Env; +Lcase: con 57346; +Lfor: con 57347; +Lif: con 57348; +Lwhile: con 57349; +Loffparen: con 57350; +Lelse: con 57351; +Lpipe: con 57352; +Leqeq: con 57353; +Lmatch: con 57354; +Lnoteq: con 57355; +Lcons: con 57356; +Lcaret: con 57357; +Lnot: con 57358; +Lhd: con 57359; +Ltl: con 57360; +Llen: con 57361; +Lword: con 57362; +Lbackq: con 57363; +Lcolon: con 57364; +Lcolonmatch: con 57365; +Ldefeq: con 57366; +Leq: con 57367; +Lmatched: con 57368; +Lquote: con 57369; +Loncurly: con 57370; +Lonparen: con 57371; +Loffcurly: con 57372; +Lat: con 57373; +Lgreat: con 57374; +Lgreatgreat: con 57375; +Lless: con 57376; +Llessgreat: con 57377; +Lfn: con 57378; +Lin: con 57379; +Lrescue: con 57380; +Land: con 57381; +Leof: con 57382; +Lsemi: con 57383; +Lerror: con 57384; +}; diff --git a/appl/cmd/mash/misc.b b/appl/cmd/mash/misc.b new file mode 100644 index 00000000..749f8be2 --- /dev/null +++ b/appl/cmd/mash/misc.b @@ -0,0 +1,313 @@ +# +# Miscellaneous routines. +# + +Cmd.cmd1(op: int, l: ref Cmd): ref Cmd +{ + return ref Cmd(op, nil, l, nil, nil, nil, nil, 0); +} + +Cmd.cmd2(op: int, l, r: ref Cmd): ref Cmd +{ + return ref Cmd(op, nil, l, r, nil, nil, nil, 0); +} + +Cmd.cmd1i(op: int, l: ref Cmd, i: ref Item): ref Cmd +{ + return ref Cmd(op, nil, l, nil, i, nil, nil, 0); +} + +Cmd.cmd1w(op: int, l: ref Cmd, w: list of ref Item): ref Cmd +{ + return ref Cmd(op, w, l, nil, nil, nil, nil, 0); +} + +Cmd.cmde(c: self ref Cmd, op: int, l, r: ref Cmd): ref Cmd +{ + c.op = op; + c.left = l; + c.right = r; + return c; +} + +Cmd.cmdiw(op: int, i: ref Item, w: list of ref Item): ref Cmd +{ + return ref Cmd(op, revitems(w), nil, nil, i, nil, nil, 0); +} + +Pin, Pout: con 1 << iota; + +rdmap := array[] of +{ + Rin => Pin, + Rout or Rappend => Pout, + Rinout => Pin | Pout, +}; + +rdsymbs := array[] of +{ + Rin => "<", + Rout => ">", + Rappend => ">>", + Rinout => "<>", +}; + +ionames := array[] of +{ + Pin => "input", + Pout => "ouput", + Pin | Pout => "input/output", +}; + +# +# Check a pipeline for ambiguities. +# +Cmd.checkpipe(c: self ref Cmd, e: ref Env, f: int): int +{ + if (c.error) + return 0; + if (c.op == Cpipe) { + if (!c.left.checkpipe(e, f | Pout)) + return 0; + if (!c.right.checkpipe(e, f | Pin)) + return 0; + } + if (f) { + t := 0; + for (l := c.redirs; l != nil; l = tl l) + t |= rdmap[(hd l).op]; + f &= t; + if (f) { + e.report(sys->sprint("%s redirection conflicts with pipe", ionames[f])); + return 0; + } + } + return 1; +} + +# +# Update a command with another redirection. +# +Cmd.cmdio(c: self ref Cmd, e: ref Env, i: ref Item) +{ + f := 0; + for (l := c.redirs; l != nil; l = tl l) + f |= rdmap[(hd l).op]; + r := i.redir; + f &= rdmap[r.op]; + if (f != 0) { + e.report(sys->sprint("repeat %s redirection", ionames[f])); + c.error = 1; + } + c.redirs = r :: c.redirs; +} + +# +# Make a basic command. +# +Cmd.mkcmd(c: self ref Cmd, e: ref Env, async: int): ref Cmd +{ + if (!c.checkpipe(e, 0)) + return nil; + if (async) + return ref Cmd(Casync, nil, c, nil, nil, nil, nil, 0); + else + return c; +} + +# +# Rotate parse tree of cases. +# +Cmd.rotcases(c: self ref Cmd): ref Cmd +{ + l := c; + c = nil; + while (l != nil) { + t := l.right; + l.right = c; + c = l; + l = l.left; + c.left = t; + } + return c; +} + +Item.item1(op: int, l: ref Item): ref Item +{ + return ref Item(op, nil, l, nil, nil, nil); +} + +Item.item2(op: int, l, r: ref Item): ref Item +{ + return ref Item(op, nil, l, r, nil, nil); +} + +Item.itemc(op: int, c: ref Cmd): ref Item +{ + return ref Item(op, nil, nil, nil, c, nil); +} + +# +# Make an item from a list of strings. +# +Item.iteml(l: list of string): ref Item +{ + if (l != nil && tl l == nil) + return Item.itemw(hd l); + r: list of string; + while (l != nil) { + r = (hd l) :: r; + l = tl l; + } + c := ref Cmd; + c.op = Clist; + c.value = revstrs(r); + return Item.itemc(Iexpr, c); +} + +Item.itemr(op: int, i: ref Item): ref Item +{ + return ref Item(Iredir, nil, nil, nil, nil, ref Redir(op, i)); +} + +qword: Word = (nil, Wquoted, (0, nil)); + +Item.itemw(s: string): ref Item +{ + w := ref qword; + w.text = s; + return ref Item(Iword, w, nil, nil, nil, nil); +} + +revitems(l: list of ref Item): list of ref Item +{ + r: list of ref Item; + while (l != nil) { + r = (hd l) :: r; + l = tl l; + } + return r; +} + +revstrs(l: list of string): list of string +{ + r: list of string; + while (l != nil) { + r = (hd l) :: r; + l = tl l; + } + return r; +} + +prepend(l: list of string, r: list of string): list of string +{ + while (r != nil) { + l = (hd r) :: l; + r = tl r; + } + return l; +} + +concat(l: list of string): string +{ + s := hd l; + for (;;) { + l = tl l; + if (l == nil) + return s; + s += " "; + s += hd l; + } +} + +# +# Make an item list, no redirections allowed. +# +Env.mklist(e: self ref Env, l: list of ref Item): list of ref Item +{ + r: list of ref Item; + while (l != nil) { + i := hd l; + if (i.op == Iredir) + e.report("redirection in list"); + else + r = i :: r; + l = tl l; + } + return r; +} + +# +# Make a simple command. +# +Env.mksimple(e: self ref Env, l: list of ref Item): ref Cmd +{ + r: list of ref Item; + c := ref Cmd; + c.op = Csimple; + c.error = 0; + while (l != nil) { + i := hd l; + if (i.op == Iredir) + c.cmdio(e, i); + else + r = i :: r; + l = tl l; + } + c.words = r; + return c; +} + +Env.diag(e: self ref Env, s: string): string +{ + return where(e) + s; +} + +Env.usage(e: self ref Env, s: string) +{ + e.report("usage: " + s); +} + +Env.report(e: self ref Env, s: string) +{ + sys->fprint(e.stderr, "%s\n", e.diag(s)); + if (e.flags & ERaise) + exits("error"); +} + +Env.error(e: self ref Env, s: string) +{ + e.report(s); + cleanup(); +} + +panic(s: string) +{ + raise "panic: " + s; +} + +prprompt(n: int) +{ + case n { + 0 => + sys->print("%s", prompt); + 1 => + sys->print("%s", contin); + } +} + +Env.couldnot(e: self ref Env, what, who: string) +{ + sys->fprint(e.stderr, "could not %s %s: %r\n", what, who); + exits("system error"); +} + +cleanup() +{ + exit; +} + +exits(s: string) +{ + raise "fail: mash " + s; +} diff --git a/appl/cmd/mash/mkfile b/appl/cmd/mash/mkfile new file mode 100644 index 00000000..942f7b38 --- /dev/null +++ b/appl/cmd/mash/mkfile @@ -0,0 +1,78 @@ +<../../../mkconfig + +TARG= mash.dis\ + mashlib.dis\ + mashparse.dis\ + builtins.dis\ + history.dis\ + make.dis\ + +INS= $ROOT/dis/mash.dis\ + $ROOT/dis/lib/mashlib.dis\ + $ROOT/dis/lib/mashparse.dis\ + $ROOT/dis/lib/mash/builtins.dis\ + $ROOT/dis/lib/mash/history.dis\ + $ROOT/dis/lib/mash/make.dis\ + +MODULES=\ + mash.m\ + mashparse.m\ + +SYSMODULES=\ + bufio.m\ + draw.m\ + filepat.m\ + hash.m\ + regex.m\ + sh.m\ + string.m\ + sys.m\ + +LIBSRC=\ + depends.b\ + dump.b\ + exec.b\ + expr.b\ + lex.b\ + misc.b\ + serve.b\ + symb.b\ + xeq.b\ + +all:V: $TARG + +install:V: $INS + +nuke:V: clean + rm -f $INS + +clean:V: + rm -f *.dis *.sbl + +uninstall:V: + rm -f $INS + +MODDIR=$ROOT/module +SYS_MODULE=${SYSMODULES:%=$MODDIR/%} +LIMBOFLAGS=-I$MODDIR + +$ROOT/dis/mash.dis: mash.dis + rm -f $ROOT/dis/mash.dis && cp mash.dis $ROOT/dis/mash.dis + +$ROOT/dis/lib/mashlib.dis: mashlib.dis + rm -f $ROOT/dis/mashlib.dis && cp mashlib.dis $ROOT/dis/lib/mashlib.dis + +$ROOT/dis/lib/mashparse.dis: mashparse.dis + rm -f $ROOT/dis/mashparse.dis && cp mashparse.dis $ROOT/dis/lib/mashparse.dis + +$ROOT/dis/lib/mash/%.dis: %.dis + rm -f $ROOT/dis/$stem.dis && cp $stem.dis $ROOT/dis/lib/mash/$stem.dis + +%.dis: $MODULES $SYS_MODULE +mashlib.dis: $LIBSRC + +%.dis: %.b + limbo $LIMBOFLAGS -gw $stem.b + +%.s: %.b + limbo $LIMBOFLAGS -w -G -S $stem.b diff --git a/appl/cmd/mash/serve.b b/appl/cmd/mash/serve.b new file mode 100644 index 00000000..e293a8f4 --- /dev/null +++ b/appl/cmd/mash/serve.b @@ -0,0 +1,154 @@ +# +# This should be called by spawned (persistent) threads. +# It arranges for them to be killed at the end of the day. +# +reap() +{ + if (pidchan == nil) { + pidchan = chan of int; + spawn zombie(); + } + pidchan <-= sys->pctl(0, nil); +} + +# +# This thread records spawned threads and kills them. +# +zombie() +{ + pids := array[10] of int; + pidx := 0; + for (;;) { + pid := <- pidchan; + if (pid == PIDEXIT) { + for (i := 0; i < pidx; i++) + kill(pids[i]); + exit; + } + if (pidx == len pids) { + n := pidx * 3 / 2; + a := array[n] of int; + a[:] = pids; + pids = a; + } + pids[pidx++] = pid; + } +} + +# +# Kill a thread. +# +kill(pid: int) +{ + fd := sys->open("#p/" + string pid + "/ctl", sys->OWRITE); + if (fd != nil) + sys->fprint(fd, "kill"); +} + +# +# Exit top level, killing spawned threads. +# +exitmash() +{ + if (pidchan != nil) + pidchan <-= PIDEXIT; + exit; +} + +# +# Slice a buffer if needed. +# +restrict(buff: array of byte, count: int): array of byte +{ + if (count < len buff) + return buff[:count]; + else + return buff; +} + +# +# Serve mash console reads. Favours other programs +# ahead of the input loop. +# +serve_read(c: ref Sys->FileIO, sync: chan of int) +{ + s: string; + in := sys->fildes(0); + sys->pctl(Sys->NEWFD, in.fd :: nil); + sync <-= 0; + reap(); + buff := array[Sys->ATOMICIO] of byte; +outer: for (;;) { + n := sys->read(in, buff, len buff); + if (n < 0) { + n = 0; + s = errstr(); + } else + s = nil; + b := buff[:n]; + alt { + (off, count, fid, rc) := <-c.read => + if (rc == nil) + break; + rc <-= (restrict(b, count), s); + continue outer; + * => + ; + } + inner: for (;;) { + alt { + (off, count, fid, rc) := <-c.read => + if (rc == nil) + continue inner; + rc <-= (restrict(b, count), s); + inchan <-= b => + ; + } + break; + } + } +} + +# +# Serve mash console writes. +# +serve_write(c: ref Sys->FileIO, sync: chan of int) +{ + out := sys->fildes(1); + sys->pctl(Sys->NEWFD, out.fd :: nil); + sync <-= 0; + reap(); + for (;;) { + (off, data, fid, wc) := <-c.write; + if (wc == nil) + continue; + if (sys->write(out, data, len data) < 0) + wc <-= (0, errstr()); + else + wc <-= (len data, nil); + } +} + +# +# Begin serving the mash console. +# +Env.serve(e: self ref Env) +{ + if (servechan != nil) + return; + (s, c) := e.servefile(nil); + inchan = chan of array of byte; + servechan = chan of array of byte; + sync := chan of int; + spawn serve_read(c, sync); + spawn serve_write(c, sync); + <-sync; + <-sync; + if (sys->bind(s, CONSOLE, Sys->MREPL) < 0) + e.couldnot("bind", CONSOLE); + sys->pctl(Sys->NEWFD, nil); + e.in = sys->open(CONSOLE, sys->OREAD | sys->ORCLOSE); + e.out = sys->open(CONSOLE, sys->OWRITE); + e.stderr = sys->open(CONSOLE, sys->OWRITE); + e.wait = nil; +} diff --git a/appl/cmd/mash/symb.b b/appl/cmd/mash/symb.b new file mode 100644 index 00000000..8d317b37 --- /dev/null +++ b/appl/cmd/mash/symb.b @@ -0,0 +1,265 @@ +# +# Symbol table routines. A symbol table becomes copy-on-write +# when it is cloned. The first modification will copy the hash table. +# Every list is then copied on first modification. +# + +# +# Copy a hash list. +# +cpsymbs(l: list of ref Symb): list of ref Symb +{ + r: list of ref Symb; + while (l != nil) { + r = (ref *hd l) :: r; + l = tl l; + } + return r; +} + +# +# New symbol table. +# +Stab.new(): ref Stab +{ + return ref Stab(array[SHASH] of list of ref Symb, 0, 0); +} + +# +# Clone a symbol table. Copy Stab and mark contents copy-on-write. +# +Stab.clone(t: self ref Stab): ref Stab +{ + t.copy = 1; + t.wmask = SMASK; + return ref *t; +} + +# +# Update symbol table entry, or add new entry. +# +Stab.update(t: self ref Stab, s: string, tag: int, v: list of string, f: ref Cmd, b: Mashbuiltin): ref Symb +{ + if (t.copy) { + a := array[SHASH] of list of ref Symb; + a[:] = t.tab[:]; + t.tab = a; + t.copy = 0; + } + x := hash->fun1(s, SHASH); + l := t.tab[x]; + if (t.wmask & (1 << x)) { + l = cpsymbs(l); + t.tab[x] = l; + t.wmask &= ~(1 << x); + } + r := l; + while (r != nil) { + h := hd r; + if (h.name == s) { + case tag { + Svalue => + h.value = v; + Sfunc => + h.func = f; + Sbuiltin => + h.builtin = b; + } + return h; + } + r = tl r; + } + n := ref Symb(s, v, f, b, 0); + t.tab[x] = n :: l; + return n; +} + +# +# Make a list of a symbol table's contents. +# +Stab.all(t: self ref Stab): list of ref Symb +{ + r: list of ref Symb; + for (i := 0; i < SHASH; i++) { + for (l := t.tab[i]; l != nil; l = tl l) + r = (ref *hd l) :: r; + } + return r; +} + +# +# Assign a list of strings to a variable. The distinguished value +# "empty" is used to distinguish nil value from undefined. +# +Stab.assign(t: self ref Stab, s: string, v: list of string) +{ + if (v == nil) + v = empty; + t.update(s, Svalue, v, nil, nil); +} + +# +# Define a builtin. +# +Stab.defbuiltin(t: self ref Stab, s: string, b: Mashbuiltin) +{ + t.update(s, Sbuiltin, nil, nil, b); +} + +# +# Define a function. +# +Stab.define(t: self ref Stab, s: string, f: ref Cmd) +{ + t.update(s, Sfunc, nil, f, nil); +} + +# +# Symbol table lookup. +# +Stab.find(t: self ref Stab, s: string): ref Symb +{ + l := t.tab[hash->fun1(s, SHASH)]; + while (l != nil) { + h := hd l; + if (h.name == s) + return h; + l = tl l; + } + return nil; +} + +# +# Function lookup. +# +Stab.func(t: self ref Stab, s: string): ref Cmd +{ + v := t.find(s); + if (v == nil) + return nil; + return v.func; +} + +# +# New environment. +# +Env.new(): ref Env +{ + return ref Env(Stab.new(), nil, ETop, nil, nil, nil, nil, nil, nil, 0); +} + +# +# Clone environment. No longer top-level or interactive. +# +Env.clone(e: self ref Env): ref Env +{ + e = e.copy(); + e.flags &= ~(ETop | EInter); + e.global = e.global.clone(); + if (e.local != nil) + e.local = e.local.clone(); + return e; +} + +# +# Copy environment. +# +Env.copy(e: self ref Env): ref Env +{ + return ref *e; +} + +# +# Fetch $n argument. +# +Env.arg(e: self ref Env, s: string): string +{ + n := int s; + if (e.args == nil || n >= len e.args) + return "$" + s; + else + return e.args[n]; +} + +# +# Lookup builtin. +# +Env.builtin(e: self ref Env, s: string): Mashbuiltin +{ + v := e.global.find(s); + if (v == nil) + return nil; + return v.builtin; +} + +# +# Define a builtin. +# +Env.defbuiltin(e: self ref Env, s: string, b: Mashbuiltin) +{ + e.global.defbuiltin(s, b); +} + +# +# Define a function. +# +Env.define(e: self ref Env, s: string, f: ref Cmd) +{ + e.global.define(s, f); +} + +# +# Value of a shell variable (check locals then globals). +# +Env.dollar(e: self ref Env, s: string): ref Symb +{ + if (e.local != nil) { + l := e.local.find(s); + if (l != nil && l.value != nil) + return l; + } + g := e.global.find(s); + if (g != nil && g.value != nil) + return g; + return nil; +} + +# +# Lookup a function. +# +Env.func(e: self ref Env, s: string): ref Cmd +{ + v := e.global.find(s); + if (v == nil) + return nil; + return v.func; +} + +# +# Local assignment. +# +Env.let(e: self ref Env, s: string, v: list of string) +{ + if (e.local == nil) + e.local = Stab.new(); + e.local.assign(s, v); +} + +# +# Assignment. Update local or define global. +# +Env.set(e: self ref Env, s: string, v: list of string) +{ + if (e.local != nil && e.local.find(s) != nil) + e.local.assign(s, v); + else + e.global.assign(s, v); +} + +# +# Report undefined. +# +Env.undefined(e: self ref Env, s: string) +{ + e.report(s + ": undefined"); +} diff --git a/appl/cmd/mash/tk.b b/appl/cmd/mash/tk.b new file mode 100644 index 00000000..8b0f4f1a --- /dev/null +++ b/appl/cmd/mash/tk.b @@ -0,0 +1,603 @@ +implement Mashbuiltin; + +# +# "tk" builtin. +# +# tk clear - clears the text frame +# tk def button name value +# tk def ibutton name value image +# tk def menu name +# tk def item menu name value +# tk dialog title mesg default label ... +# tk dump - print commands to reconstruct toolbar +# tk dump name ... +# tk env - update tk execution env +# tk file title dir pattern ... +# tk geom +# tk layout name ... +# tk notice message +# tk sel - print selection +# tk sget - print snarf +# tk sput string - put snarf +# tk string mesg - get string +# tk taskbar string +# tk text - print window text +# + +include "mash.m"; +include "mashparse.m"; +include "wmlib.m"; +include "dialog.m"; +include "selectfile.m"; + +mashlib: Mashlib; +wmlib: Wmlib; +dialog: Dialog; +selectfile: Selectfile; + +Env, Stab, Symb: import mashlib; +sys, bufio, tk: import mashlib; +gtop, gctxt, ident: import mashlib; + +Iobuf: import bufio; + +tkitems: ref Stab; +tklayout: list of string; +tkenv: ref Env; +tkserving: int = 0; + +Cbutton, Cibutton, Cmenu: con Cprivate + iota; + +Cmark: con 3; +BUTT: con ".b."; + +# +# Interface to catch the use as a command. +# +init(nil: ref Draw->Context, args: list of string) +{ + raise "fail: " + hd args + " not loaded"; +} + +# +# Used by whatis. +# +name(): string +{ + return "tk"; +} + +# +# Install command and initialize state. +# +mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env) +{ + mashlib = lib; + if (gctxt == nil) { + e.report("tk: no graphics context"); + return; + } + if (gtop == nil) { + e.report("tk: not run from wmsh"); + return; + } + wmlib = load Wmlib Wmlib->PATH; + if (wmlib == nil) { + e.report(sys->sprint("tk: could not load %s: %r", Wmlib->PATH)); + return; + } + dialog = load Dialog Dialog->PATH; + if (dialog == nil) { + e.report(sys->sprint("tk: could not load %s: %r", Dialog->PATH)); + return; + } + selectfile = load Selectfile Selectfile->PATH; + if (selectfile == nil) { + e.report(sys->sprint("tk: could not load %s: %r", Selectfile->PATH)); + return; + } + wmlib->init(); + dialog->init(); + selectfile->init(); + e.defbuiltin("tk", this); + tkitems = Stab.new(); +} + +# +# Execute the "tk" builtin. +# +mashcmd(e: ref Env, l: list of string) +{ + # must lock + l = tl l; + if (l == nil) + return; + s := hd l; + l = tl l; + case s { + "clear" => + if (l != nil) { + e.usage("tk clear"); + return; + } + clear(e); + "def" => + define(e, l); + "dialog" => + if (len l < 4) { + e.usage("tk dialog title mesg default label ..."); + return; + } + dodialog(e, l); + "dump" => + dump(e, l); + "env" => + if (l != nil) { + e.usage("tk env"); + return; + } + tkenv = e.clone(); + tkenv.flags |= mashlib->ETop; + "file" => + if (len l < 3) { + e.usage("tk file title dir pattern ..."); + return; + } + dofile(e, hd l, hd tl l, tl tl l); + "geom" => + if (l != nil) { + e.usage("tk geom"); + return; + } + e.output(wmlib->geom(gtop)); + "layout" => + layout(e, l); + "notice" => + if (len l != 1) { + e.usage("tk notice message"); + return; + } + notice(hd l); + "sel" => + if (l != nil) { + e.usage("tk sel"); + return; + } + sel(e); + "sget" => + if (l != nil) { + e.usage("tk sget"); + return; + } + e.output(wmlib->snarfget()); + "sput" => + if (len l != 1) { + e.usage("tk sput string"); + return; + } + wmlib->snarfput(hd l); + "string" => + if (len l != 1) { + e.usage("tk string mesg"); + return; + } + e.output(dialog->getstring(gctxt, gtop.image, hd l)); + focus(e); + "taskbar" => + if (len l != 1) { + e.usage("tk taskbar string"); + return; + } + e.output(wmlib->taskbar(gtop, hd l)); + "text" => + if (l != nil) { + e.usage("tk text"); + return; + } + text(e); + * => + e.report(sys->sprint("tk: unknown command: %s", s)); + } +} + +# +# Execute tk command and check for error. +# +tkcmd(e: ref Env, s: string): string +{ + if (e != nil && (e.flags & mashlib->EDumping)) + sys->fprint(e.stderr, "+ %s\n", s); + r := tk->cmd(gtop, s); + if (r != nil && r[0] == '!' && e != nil) + sys->fprint(e.stderr, "tk: %s\n\tcommand was %s\n", r[1:], s); + return r; +} + +focus(e: ref Env) +{ + tkcmd(e, "focus .ft.t"); +} + +# +# Serve loop. +# +tkserve(mash: chan of string) +{ + mashlib->reap(); + for (;;) { + cmd := <-mash; + if (mashlib->servechan != nil && len cmd > 1) { + cmd[len cmd - 1] = '\n'; + mashlib->servechan <-= array of byte cmd[1:]; + } + } +} + +notname(e: ref Env, s: string) +{ + e.report(sys->sprint("tk: %s: malformed name", s)); +} + +# +# Define a button, menu or item. +# +define(e: ref Env, l: list of string) +{ + if (l == nil) { + e.usage("tk def definition"); + return; + } + s := hd l; + l = tl l; + case s { + "button" => + if (len l != 2) { + e.usage("tk def button name value"); + return; + } + s = hd l; + if (!ident(s)) { + notname(e, s); + return; + } + i := tkitems.update(s, Svalue, tl l, nil, nil); + i.tag = Cbutton; + "ibutton" => + if (len l != 3) { + e.usage("tk def ibutton name value path"); + return; + } + s = hd l; + if (!ident(s)) { + notname(e, s); + return; + } + i := tkitems.update(s, Svalue, tl l, nil, nil); + i.tag = Cibutton; + "menu" => + if (len l != 1) { + e.usage("tk def menu name"); + return; + } + s = hd l; + if (!ident(s)) { + notname(e, s); + return; + } + i := tkitems.update(s, Svalue, nil, nil, nil); + i.tag = Cmenu; + "item" => + if (len l != 3) { + e.usage("tk def item menu name value"); + return; + } + s = hd l; + i := tkitems.find(s); + if (i == nil || i.tag != Cmenu) { + e.report(s + ": not a menu"); + return; + } + l = tl l; + i.value = updateitem(i.value, hd l, hd tl l); + * => + e.report("tk: " + s + ": unknown command"); + } +} + +# +# Update a menu item. +# +updateitem(l: list of string, c, v: string): list of string +{ + r: list of string; + while (l != nil) { + w := hd l; + l = tl l; + d := hd l; + l = tl l; + if (d == c) { + r = c :: v :: r; + c = nil; + } else + r = d :: w :: r; + } + if (c != nil) + r = c :: v :: r; + return mashlib->revstrs(r); +} + +items(e: ref Env, l: list of string): list of ref Symb +{ + r: list of ref Symb; + while (l != nil) { + i := tkitems.find(hd l); + if (i == nil) { + e.report(hd l + ": not an item"); + return nil; + } + r = i :: r; + l = tl l; + } + return r; +} + +deleteall(e: ref Env, l: list of string) +{ + while (l != nil) { + tkcmd(e, "destroy " + BUTT + hd l); + l = tl l; + } +} + +sendcmd(c: string): string +{ + return tk->quote("send mash " + tk->quote(c)); +} + +addbutton(e: ref Env, w, t, c: string) +{ + tkcmd(e, sys->sprint("button %s%s -%s %s -command %s", BUTT, t, w, t, sendcmd(c))); +} + +addimage(e: ref Env, t, f: string) +{ + r := tkcmd(nil, sys->sprint("image create bitmap %s -file %s.bit -maskfile %s.mask", t, f, f)); + if (r != nil && r[0] == '!') + tkcmd(e, sys->sprint("image create bitmap %s -file %s.bit", t, f)); +} + +additem(e: ref Env, s: ref Symb) +{ + case s.tag { + Cbutton => + addbutton(e, "text", s.name, hd s.value); + Cibutton => + addimage(e, s.name, hd tl s.value); + addbutton(e, "image", s.name, hd s.value); + Cmenu => + t := s.name; + tkcmd(e, sys->sprint("menubutton %s%s -text %s -menu %s%s.menu -underline -1", BUTT, t, t, BUTT,t)); + t += ".menu"; + tkcmd(e, "menu " + BUTT + t); + t = BUTT + t; + l := s.value; + while (l != nil) { + v := sendcmd(hd l); + l = tl l; + c := tk->quote(hd l); + l = tl l; + tkcmd(e, sys->sprint("%s add command -label %s -command %s", t, c, v)); + } + } +} + +pack(e: ref Env, l: list of string) +{ + s := "pack"; + while (l != nil) { + s += sys->sprint(" %s%s", BUTT, hd l); + l = tl l; + } + s += " -side left"; + tkcmd(e, s); +} + +propagate(e: ref Env) +{ + tkcmd(e, "pack propagate . 0"); + tkcmd(e, "update"); +} + +unmark(r: list of ref Symb) +{ + while (r != nil) { + s := hd r; + case s.tag { + Cbutton + Cmark or Cibutton + Cmark or Cmenu + Cmark => + s.tag -= Cmark; + } + r = tl r; + } +} + +# +# Check that the layout tags are unique. +# +unique(e: ref Env, r: list of ref Symb): int +{ + u := 1; +loop: + for (l := r; l != nil; l = tl l) { + s := hd l; + case s.tag { + Cbutton + Cmark or Cibutton + Cmark or Cmenu + Cmark => + e.report(sys->sprint("layout: tag %s repeated", s.name)); + u = 0; + break loop; + Cbutton or Cibutton or Cmenu => + s.tag += Cmark; + } + } + unmark(r); + return u; +} + +# +# Update the button bar layout and the environment. +# Maybe spawn the server. +# +layout(e: ref Env, l: list of string) +{ + r := items(e, l); + if (r == nil && l != nil) + return; + if (!unique(e, r)) + return; + if (tklayout != nil) + deleteall(e, tklayout); + n := len r; + a := array[n] of ref Symb; + while (--n >= 0) { + a[n] = hd r; + r = tl r; + } + n = len a; + for (i := 0; i < n; i++) + additem(e, a[i]); + pack(e, l); + propagate(e); + tklayout = l; + tkenv = e.clone(); + tkenv.flags |= mashlib->ETop; + if (!tkserving) { + tkserving = 1; + mash := chan of string; + tk->namechan(gtop, mash, "mash"); + spawn tkserve(mash); + mashlib->startserve = 1; + } +} + +dumpbutton(out: ref Iobuf, w: string, s: ref Symb) +{ + out.puts(sys->sprint("tk def %s %s %s", w, s.name, mashlib->quote(hd s.value))); + if (s.tag == Cibutton) + out.puts(sys->sprint(" %s", mashlib->quote(hd tl s.value))); + out.puts(";\n"); +} + +# +# Print commands to reconstruct toolbar. +# +dump(e: ref Env, l: list of string) +{ + r: list of ref Symb; + if (l != nil) + r = items(e, l); + else + r = tkitems.all(); + out := e.outfile(); + if (out == nil) + return; + while (r != nil) { + s := hd r; + case s.tag { + Cbutton => + dumpbutton(out, "button", s); + Cibutton => + dumpbutton(out, "ibutton", s); + Cmenu => + t := s.name; + out.puts(sys->sprint("tk def menu %s;\n", t)); + i := s.value; + while (i != nil) { + v := hd i; + i = tl i; + c := hd i; + i = tl i; + out.puts(sys->sprint("tk def item %s %s %s;\n", t, c, mashlib->quote(v))); + } + } + r = tl r; + } + if (l == nil) { + out.puts("tk layout"); + for (l = tklayout; l != nil; l = tl l) { + out.putc(' '); + out.puts(hd l); + } + out.puts(";\n"); + } + out.close(); +} + +clear(e: ref Env) +{ + tkcmd(e, ".ft.t delete 1.0 end; update"); +} + +dofile(e: ref Env, title, dir: string, pats: list of string) +{ + e.output(selectfile->filename(gctxt, gtop.image, title, pats, dir)); +} + +sel(e: ref Env) +{ + sel := tkcmd(e, ".ft.t tag ranges sel"); + if (sel != nil) { + s := tkcmd(e, ".ft.t dump " + sel); + e.output(s); + } +} + +text(e: ref Env) +{ + sel := tkcmd(e, ".ft.t tag ranges sel"); + if (sel != nil) + tkcmd(e, ".ft.t tag remove sel " + sel); + s := tkcmd(e, ".ft.t dump 1.0 end"); + if (sel != nil) + tkcmd(e, ".ft.t tag add sel " + sel); + e.output(s); +} + +notice0 := array[] of +{ + "frame .f -borderwidth 2 -relief groove -padx 3 -pady 3", + "frame .f.f", + "label .f.f.l -bitmap error -foreground red", +}; + +notice1 := array[] of +{ + "button .f.b -text { OK } -command {send cmd done}", + "pack .f.f.l .f.f.m -side left -expand 1 -padx 10 -pady 10", + "pack .f.f .f.b -padx 10 -pady 10", + "pack .f", + "update; cursor -default", +}; + +notice(mesg: string) +{ + x := int tk->cmd(gtop, ". cget -x"); + y := int tk->cmd(gtop, ". cget -y"); + where := sys->sprint("-x %d -y %d", x + 30, y + 30); + t := tk->toplevel(gctxt.screen, where + " -borderwidth 2 -relief raised"); + cmd := chan of string; + tk->namechan(t, cmd, "cmd"); + wmlib->tkcmds(t, notice0); + tk->cmd(t, "label .f.f.m -text '" + mesg); + wmlib->tkcmds(t, notice1); + <- cmd; +} + +dodialog(e: ref Env, l: list of string) +{ + title := hd l; + l = tl l; + msg := hd l; + l = tl l; + x := dialog->prompt(gctxt, gtop.image, nil, title, msg, int hd l, tl l); + e.output(string x); + focus(e); +} diff --git a/appl/cmd/mash/xeq.b b/appl/cmd/mash/xeq.b new file mode 100644 index 00000000..fd2f1e6f --- /dev/null +++ b/appl/cmd/mash/xeq.b @@ -0,0 +1,543 @@ +# +# Command execution. +# + +# +# Entry from parser. +# +Cmd.xeq(c: self ref Cmd, e: ref Env) +{ + if (e.flags & EDumping) { + s := c.text(); + f := e.outfile(); + f.puts(s); + if (s != nil && s[len s - 1] != '&') + f.putc(';'); + f.putc('\n'); + f.close(); + f = nil; + } + if ((e.flags & ENoxeq) == 0) + c.xeqit(e, 1); +} + +# +# Execute a command. Tail recursion. +# +Cmd.xeqit(c: self ref Cmd, e: ref Env, wait: int) +{ +tail: for (;;) { + if (c == nil) + return; + case c.op { + Csimple => + c.simple(e, wait); + Casync => + e = e.clone(); + e.in = e.devnull(); + e.wait = nil; + spawn c.left.xeqit(e, 1); + Cgroup => + if (c.redirs != nil) { + (ok, in, out) := mkredirs(e, c.redirs); + if (!ok) + return; + e = e.copy(); + e.in = in; + e.out = out; + c.left.xeqit(e, 1); + } else { + c = c.left; + continue tail; + } + Csubgroup => + e = e.clone(); + if (c.redirs != nil) { + (ok, in, out) := mkredirs(e, c.redirs); + if (!ok) + return; + e.in = in; + e.out = out; + } + c = c.left; + continue tail; + Cseq => + c.left.xeqit(e, 1); + c = c.right; + continue tail; + Cpipe => + do { + fds := e.pipe(); + if (fds == nil) + return; + n := e.clone(); + n.out = fds[0]; + c.left.xeqit(n, 0); + n = nil; + e = e.clone(); + e.in = fds[1]; + fds = nil; + c = c.right; + } while (c.op == Cpipe); + continue tail; + Cif => + t := c.left.truth(e); + if (c.right.op == Celse) { + if (t) + c.right.left.xeqit(e, wait); + else + c.right.right.xeqit(e, wait); + } else if (t) + c.right.xeqit(e, wait); + Celse => + panic("unexpected else"); + Cwhile => + while (c.left.truth(e)) + c.right.xeqit(e, wait); + Cfor => + (ok, l) := evalw(c.words, e); + if (!ok) + return; + s := c.item.word.text; + c = c.left; + while (l != nil) { + e.let(s, (hd l) :: nil); + c.xeqit(e, 1); + l = tl l; + } + Ccase => + (s1, l1) := c.left.eeval(e); + r := c.right; + while (r != nil) { + l := r.left; + (s2, l2) := l.left.eeval(e); + if (match2(e, s1, l1, s2, l2)) { + c = l.right; + continue tail; + } + r = r.right; + } + Ceq => + c.assign(e, 0); + Cdefeq => + c.assign(e, 1); + Cfn => + (s, nil, nil) := c.item.ieval(e); + if (!ident(s)) { + e.report("bad function name"); + return; + } + e.define(s, c.left); + Crescue => + e.report("rescue not implemented"); + Cdepend => + c.depend(e); + Crule => + c.rule(e); + * => + sys->print("number %d\n", c.op); + } return; } # tail recursion +} + +# +# Execute quote or backquote generator. Return generated item. +# +Cmd.quote(c: self ref Cmd, e: ref Env, back: int): ref Item +{ + e = e.copy(); + fds := e.pipe(); + if (fds == nil) + return nil; + e.out = fds[0]; + in := bufio->fopen(fds[1], Bufio->OREAD); + if (in == nil) + e.couldnot("fopen", "pipe"); + c.xeqit(e, 0); + fds = nil; + e = nil; + if (back) { + l: list of string; + while ((s := in.gets('\n')) != nil) { + (nil, r) := sys->tokenize(s, " \t\r\n"); + l = prepend(l, r); + } + return Item.iteml(revstrs(l)); + } else { + s := in.gets('\n'); + if (s != nil && s[len s - 1] == '\n') + s = s[:len s - 1]; + return Item.itemw(s); + } +} + +# +# Execute serve generator. +# +Cmd.serve(c: self ref Cmd, e: ref Env, write: int): ref Item +{ + e = e.clone(); + fds := e.pipe(); + if (fds == nil) + return nil; + if (write) + e.in = fds[0]; + else + e.out = fds[0]; + s := e.servefd(fds[1], write); + if (s == nil) + return nil; + c.xeqit(e, 0); + return Item.itemw(s); +} + +# +# Expression evaluation, first pass. +# Parse tree is copied and word items are evaluated. +# nil return for error is propagated. +# +Cmd.eeval1(c: self ref Cmd, e: ref Env): ref Cmd +{ + case c.op { + Cword => + l := c.item.ieval1(e); + if (l == nil) + return nil; + return Cmd.cmd1i(Cword, nil, l); + Chd or Ctl or Clen or Cnot => + l := c.left.eeval1(e); + if (l == nil) + return nil; + return Cmd.cmd1(c.op, l); + Ccaret or Ccons or Ceqeq or Cnoteq or Cmatch => + l := c.left.eeval1(e); + r := c.right.eeval1(e); + if (l == nil || r == nil) + return nil; + return Cmd.cmd2(c.op, l, r); + } + panic("expr1: bad op"); + return nil; +} + +# +# Expression evaluation, second pass. +# Returns a tuple (singleton, list, expand flag). +# +Cmd.eeval2(c: self ref Cmd, e: ref Env): (string, list of string, int) +{ + case c.op { + Cword => + return c.item.ieval2(e); + Clist => + return (nil, c.value, 0); + Ccaret => + (s1, l1, x1) := c.left.eeval2(e); + (s2, l2, x2) := c.right.eeval2(e); + return caret(s1, l1, x1, s2, l2, x2); + Chd => + (s, l, x) := c.left.eeval2(e); + if (s != nil) + return (s, nil, x); + if (l != nil) + return (hd l, nil, 0); + Ctl => + (s, l, nil) := c.left.eeval2(e); + if (s != nil) + break; + if (l != nil) + return (nil, tl l, 0); + Clen => + (s, l, nil) := c.left.eeval2(e); + if (s != nil) + return ("1", nil, 0); + return (string len l, nil, 0); + Cnot => + (s, l, nil) := c.left.eeval2(e); + if (s == nil && l == nil) + return (TRUE, nil, 0); + Ccons => + (s1, l1, nil) := c.left.eeval2(e); + (s2, l2, nil) := c.right.eeval2(e); + if (s1 != nil) { + if (s2 != nil) + return (nil, s1 :: s2 :: nil, 0); + if (l2 != nil) + return (nil, s1 :: l2, 0); + return (s1, nil, 0); + } else if (l1 != nil) { + if (s2 != nil) + return (nil, prepend(s2 :: nil, revstrs(l1)), 0); + if (l2 != nil) + return (nil, prepend(l2, revstrs(l1)), 0); + return (nil, l1, 0); + } else + return (s2, l2, 0); + Ceqeq => + if (c.evaleq(e)) + return (TRUE, nil, 0); + Cnoteq => + if (!c.evaleq(e)) + return (TRUE, nil, 0); + Cmatch => + if (c.evalmatch(e)) + return (TRUE, nil, 0); + * => + panic("expr2: bad op"); + } + return (nil, nil, 0); +} + +# +# Evaluate expression. 1st pass, 2nd pass, maybe glob. +# +Cmd.eeval(c: self ref Cmd, e: ref Env): (string, list of string) +{ + c = c.eeval1(e); + if (c == nil) + return (nil, nil); + (s, l, x) := c.eeval2(e); + if (x && s != nil) + (s, l) = glob(e, s); + return (s, l); +} + +# +# Assignment - let or set. +# +Cmd.assign(c: self ref Cmd, e: ref Env, def: int) +{ + i := c.item; + if (i == nil) + return; + (ok, v) := evalw(c.words, e); + if (!ok) + return; + s := c.item.word.text; + if (def) + e.let(s, v); + else + e.set(s, v); +} + +# +# Evaluate command and test for non-empty. +# +Cmd.truth(c: self ref Cmd, e: ref Env): int +{ + (s, l) := c.eeval(e); + return s != nil || l != nil; +} + +# +# Evaluate word. +# +evalw(l: list of ref Item, e: ref Env): (int, list of string) +{ + if (l == nil) + return (1, nil); + w := pass1(e, l); + if (w == nil) + return (0, nil); + return (1, pass2(e, w)); +} + +# +# Evaluate list of items, pass 1 - reverses. +# +pass1(e: ref Env, l: list of ref Item): list of ref Item +{ + r: list of ref Item; + while (l != nil) { + i := (hd l).ieval1(e); + if (i == nil) + return nil; + r = i :: r; + l = tl l; + } + return r; +} + +# +# Evaluate list of items, pass 2 with globbing - reverses (restores order). +# +pass2(e: ref Env, l: list of ref Item): list of string +{ + r: list of string; + while (l != nil) { + (s, t, x) := (hd l).ieval2(e); + if (x && s != nil) + (s, t) = glob(e, s); + if (s != nil) + r = s :: r; + else if (t != nil) + r = prepend(r, revstrs(t)); + l = tl l; + } + return r; +} + +# +# Simple command. Maybe a function. +# +Cmd.simple(c: self ref Cmd, e: ref Env, wait: int) +{ + w := pass1(e, c.words); + if (w == nil) + return; + s := pass2(e, w); + if (s == nil) + return; + if (e.flags & EEcho) + echo(e, s); + (ok, in, out) := mkredirs(e, c.redirs); + if (ok) + e.runit(s, in, out, wait); +} + +# +# Cmd name and arglist. Maybe a function. +# +Env.runit(e: self ref Env, s: list of string, in, out: ref Sys->FD, wait: int) +{ + d := e.func(hd s); + if (d != nil) { + if (e.level >= MAXELEV) { + e.report(hd s + ": function nesting too deep"); + return; + } + e = e.copy(); + e.level++; + e.in = in; + e.out = out; + e.local = Stab.new(); + e.local.assign(ARGS, tl s); + d.xeqit(e, wait); + } else + exec(s, e, in, out, wait); +} + +# +# Item evaluation, first pass. Copy parse tree. Expand variables. +# Call first pass of expression evaluation. Execute generators. +# +Item.ieval1(i: self ref Item, e: ref Env): ref Item +{ + if (i == nil) + return nil; + case i.op { + Icaret or Iicaret => + l := i.left.ieval1(e); + r := i.right.ieval1(e); + if (l == nil || r == nil) + return nil; + return Item.item2(i.op, l, r); + Idollar or Idollarq=> + s := e.dollar(i.word.text); + if (s == nil) { + e.undefined(i.word.text); + return nil; + } + if (s.value == empty) + return Item.itemw(nil); + if (i.op == Idollar) + return Item.iteml(s.value); + else + return Item.itemw(concat(s.value)); + Iword or Imatch => + return i; + Iexpr => + l := i.cmd.eeval1(e); + if (l == nil) + return nil; + return Item.itemc(Iexpr, l); + Ibackq => + return i.cmd.quote(e, 1); + Iquote => + return i.cmd.quote(e, 0); + Iinpipe => + return i.cmd.serve(e, 0); + Ioutpipe => + return i.cmd.serve(e, 1); + } + panic("ieval1: bad op"); + return nil; +} + +# +# Item evaluation, second pass. Outer level carets. Expand matches. +# Call second pass of expression evaluation. +# +Item.ieval2(i: self ref Item, e: ref Env): (string, list of string, int) +{ + case i.op { + Icaret or Iicaret => + return i.caret(e); + Imatch => + return (e.arg(i.word.text), nil, 0); + Idollar or Idollarq => + panic("ieval2: unexpected $"); + Iword => + return (i.word.text, nil, i.word.flags & Wexpand); + Iexpr => + return i.cmd.eeval2(e); + Ibackq or Iinpipe or Ioutpipe => + panic("ieval2: unexpected generator"); + } + panic("ieval2: bad op"); + return (nil, nil, 0); +} + +# +# Item evaluation. +# +Item.ieval(i: self ref Item, e: ref Env): (string, list of string, int) +{ + i = i.ieval1(e); + if (i == nil) + return (nil, nil, 0); + return i.ieval2(e); +} + +# +# Redirection item evaluation. +# +Item.reval(i: self ref Item, e: ref Env): (int, string) +{ + (s, l, nil) := i.ieval(e); + if (s == nil) { + if (l == nil) + e.report("null redirect"); + else + e.report("list for redirect"); + return (0, nil); + } + return (1, s); +} + +# +# Make redirection names. +# +mkrdnames(e: ref Env, l: list of ref Redir): (int, array of string) +{ + f := array[Rcount] of string; + while (l != nil) { + r := hd l; + (ok, s) := r.word.reval(e); + if (!ok) + return (0, nil); + f[r.op] = s; + l = tl l; + } + return (1, f); +} + +# +# Perform redirections. +# +mkredirs(e: ref Env, l: list of ref Redir): (int, ref Sys->FD, ref Sys->FD) +{ + (ok, f) := mkrdnames(e, l); + if (!ok) + return (0, nil, nil); + return redirect(e, f, e.in, e.out); +} diff --git a/appl/cmd/mathcalc.b b/appl/cmd/mathcalc.b new file mode 100644 index 00000000..4f4f475f --- /dev/null +++ b/appl/cmd/mathcalc.b @@ -0,0 +1,79 @@ +implement MathCalc; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "tk.m"; + tk: Tk; + +include "bufio.m"; + bufmod : Bufio; +Iobuf : import bufmod; + +include "../lib/tcl.m"; + +include "tcllib.m"; + + +MathCalc : module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +CALCPATH: con "/dis/lib/tcl_calc.dis"; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + cal := load TclLib CALCPATH; + if (cal==nil){ + sys->print("mathcalc: can't load %s: %r\n", CALCPATH); + exit; + } + bufmod = load Bufio Bufio->PATH; + if (bufmod==nil){ + sys->print("bufmod load %r\n"); + exit; + } + iob := bufmod->fopen(sys->fildes(0),bufmod->OREAD); + if (iob==nil){ + sys->print("mathcalc: cannot open stdin for reading: %r\n"); + return; + } + input : string; + new_inp := "calc%"; + sys->print("%s ", new_inp); + while((input=iob.gets('\n'))!=nil){ + input=input[0:len input -1]; + if (input=="quit") + exit; + arr:=array[] of {input}; + (i,msg):=cal->exec(nil,arr); + if (msg!=nil) + sys->print("%s\n",msg); + sys->print("%s ", new_inp); + } + +} + + +# expr0 : expr1 +# | expr0 '+' expr0 +# | expr0 '-' expr0 +# ; +# +# expr1 : expr2 +# | expr1 '*' expr1 +# | expr1 '/' expr1 +# ; +# +# expr2 : '-' expr2 +# | '+' expr2 +# | expr3 +# ; +# +# expr3 : INT +# | REAL +# ; diff --git a/appl/cmd/mc.b b/appl/cmd/mc.b new file mode 100644 index 00000000..265d548e --- /dev/null +++ b/appl/cmd/mc.b @@ -0,0 +1,2547 @@ +implement Calculator; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; + arg: Arg; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "math.m"; + maths: Math; +include "rand.m"; + rand: Rand; +include "daytime.m"; + daytime: Daytime; + +Calculator: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + arg = load Arg Arg->PATH; + bufio = load Bufio Bufio->PATH; + maths = load Math Math->PATH; + rand = load Rand Rand->PATH; + daytime = load Daytime Daytime->PATH; + + maths->FPcontrol(0, Math->INVAL|Math->ZDIV|Math->OVFL|Math->UNFL|Math->INEX); + + rand->init(daytime->now()); + rand->init(rand->rand(Big)^rand->rand(Big)); + daytime = nil; + + arg->init(args); + while((c := arg->opt()) != 0){ + case(c){ + 'b' => + bits = 1; + 'd' => + debug = 1; + 's' => + strict = 1; + } + } + gargs = args = arg->argv(); + if(args == nil){ + stdin = 1; + bin = bufio->fopen(sys->fildes(0), Sys->OREAD); + } + else if(tl args == nil) + bin = bufio->open(hd args, Sys->OREAD); + + syms = array[Hash] of ref Sym; + + pushscope(); + for(i := 0; keyw[i].t0 != nil; i++) + enter(keyw[i].t0, keyw[i].t1); + for(i = 0; conw[i].t0 != nil; i++) + adddec(conw[i].t0, Ocon, conw[i].t1, 0); + for(i = 0; varw[i].t0 != nil; i++) + adddec(varw[i].t0, Ovar, varw[i].t1, 0); + for(i = 0; funw[i].t0 != nil; i++) + adddec(funw[i].t0, Olfun, real funw[i].t1, funw[i].t2); + + deg = lookup(Deg).dec; + pbase = lookup(Base).dec; + errdec = ref Dec; + + pushscope(); + for(;;){ + e: ref Node; + + { + t := lex(); + if(t == Oeof) + break; + unlex(t); + ls := lexes; + e = stat(1); + ckstat(e, Onothing, 0); + if(ls == lexes){ + t = lex(); + error(nil, sys->sprint("syntax error near %s", opstring(t))); + unlex(t); + } + consume(Onl); + } + exception ex{ + Eeof => + e = nil; + err("premature eof"); + skip(); + "*" => + e = nil; + err(ex); + skip(); + } + if(0 && debug) + prtree(e, 0); + if(e != nil && e.op != Ofn){ + (k, v) := (Onothing, 0.0); + { + (k, v) = estat(e); + } + exception ex{ + "*" => + e = nil; + err(ex); + } + if(pexp(e)) + printnum(v, "\n"); + if(k == Oexit) + exit; + } + } + popscope(); + popscope(); +} + +bits: int; +debug: int; +strict: int; + +None: con -2; +Eof: con -1; +Eeof: con "eof"; + +Hash: con 16; +Big: con 1<<30; +Maxint: con 16r7FFFFFFF; +Nan: con Math->NaN; +Infinity: con Math->Infinity; +Pi: con Math->Pi; +Eps: con 1E-10; +Bigeps: con 1E-2; +Ln2: con 0.6931471805599453; +Ln10: con 2.302585092994046; +Euler: con 2.71828182845904523536; +Gamma: con 0.57721566490153286060; +Phi: con 1.61803398874989484820; + +Oeof, +Ostring, Onum, Oident, Ocon, Ovar, Ofun, Olfun, +Oadd, Osub, Omul, Odiv, Omod, Oidiv, Oexp, Oand, Oor, Oxor, Olsh, Orsh, +Oadde, Osube, Omule, Odive, Omode, Oidive, Oexpe, Oande, Oore, Oxore, Olshe, Orshe, +Oeq, One, Ogt, Olt, Oge, Ole, +Oinc, Opreinc, Opostinc, Odec, Opredec, Opostdec, +Oandand, Ooror, +Oexc, Onot, Ofact, Ocom, +Oas, Odas, +Oplus, Ominus, Oinv, +Ocomma, Oscomma, Oquest, Ocolon, +Onand, Onor, Oimp, Oimpby, Oiff, +Olbr, Orbr, Olcbr, Orcbr, Oscolon, Onl, +Onothing, +Oprint, Oread, +Oif, Oelse, Ofor, Owhile, Odo, Obreak, Ocont, Oexit, Oret, Ofn, Oinclude, +Osigma, Opi, Ocfrac, Oderiv, Ointeg, Osolve, +Olog, Olog10, Olog2, Ologb, Oexpf, Opow, Osqrt, Ocbrt, Ofloor, Oceil, Omin, Omax, Oabs, Ogamma, Osign, Oint, Ofrac, Oround, Oerf, Oatan2, Osin, Ocos, Otan, Oasin, Oacos, Oatan, Osinh, Ocosh, Otanh, Oasinh, Oacosh, Oatanh, Orand, +Olast: con iota; + +Binary: con (1<<8); +Preunary: con (1<<9); +Postunary: con (1<<10); +Assoc: con (1<<11); +Rassoc: con (1<<12); +Prec: con Binary-1; + +opss := array[Olast] of +{ + "eof", + "string", + "number", + "identifier", + "constant", + "variable", + "function", + "library function", + "+", + "-", + "*", + "/", + "%", + "//", + "&", + "|", + "^", + "<<", + ">>", + "+=", + "-=", + "*=", + "/=", + "%=", + "//=", + "&=", + "|=", + "^=", + "<<=", + ">>=", + "==", + "!=", + ">", + "<", + ">=", + "<=", + "++", + "++", + "++", + "--", + "--", + "--", + "**", + "&&", + "||", + "!", + "!", + "!", + "~", + "=", + ":=", + "+", + "-", + "1/", + ",", + ",", + "?", + ":", + "↑", + "↓", + "->", + "<-", + "<->", + "(", + ")", + "{", + "}", + ";", + "\n", + "", +}; + +ops := array[Olast] of +{ + Oeof => 0, + Ostring => 17, + Onum => 17, + Oident => 17, + Ocon => 17, + Ovar => 17, + Ofun => 17, + Olfun => 17, + Oadd => 12|Binary|Assoc|Preunary, + Osub => 12|Binary|Preunary, + Omul => 13|Binary|Assoc, + Odiv => 13|Binary, + Omod => 13|Binary, + Oidiv => 13|Binary, + Oexp => 14|Binary|Rassoc, + Oand => 8|Binary|Assoc, + Oor => 6|Binary|Assoc, + Oxor => 7|Binary|Assoc, + Olsh => 11|Binary, + Orsh => 11|Binary, + Oadde => 2|Binary|Rassoc, + Osube => 2|Binary|Rassoc, + Omule => 2|Binary|Rassoc, + Odive => 2|Binary|Rassoc, + Omode => 2|Binary|Rassoc, + Oidive => 2|Binary|Rassoc, + Oexpe => 2|Binary|Rassoc, + Oande => 2|Binary|Rassoc, + Oore => 2|Binary|Rassoc, + Oxore => 2|Binary|Rassoc, + Olshe => 2|Binary|Rassoc, + Orshe => 2|Binary|Rassoc, + Oeq => 9|Binary, + One => 9|Binary, + Ogt => 10|Binary, + Olt => 10|Binary, + Oge => 10|Binary, + Ole => 10|Binary, + Oinc => 15|Rassoc|Preunary|Postunary, + Opreinc => 15|Rassoc|Preunary, + Opostinc => 15|Rassoc|Postunary, + Odec => 15|Rassoc|Preunary|Postunary, + Opredec => 15|Rassoc|Preunary, + Opostdec => 15|Rassoc|Postunary, + Oandand => 5|Binary|Assoc, + Ooror => 4|Binary|Assoc, + Oexc => 15|Rassoc|Preunary|Postunary, + Onot => 15|Rassoc|Preunary, + Ofact => 15|Rassoc|Postunary, + Ocom => 15|Rassoc|Preunary, + Oas => 2|Binary|Rassoc, + Odas => 2|Binary|Rassoc, + Oplus => 15|Rassoc|Preunary, + Ominus => 15|Rassoc|Preunary, + Oinv => 15|Rassoc|Postunary, + Ocomma => 1|Binary|Assoc, + Oscomma => 1|Binary|Assoc, + Oquest => 3|Binary|Rassoc, + Ocolon => 3|Binary|Rassoc, + Onand => 8|Binary, + Onor => 6|Binary, + Oimp => 9|Binary, + Oimpby => 9|Binary, + Oiff => 10|Binary|Assoc, + Olbr => 16, + Orbr => 16, + Onothing => 0, +}; + +Deg: con "degrees"; +Base: con "printbase"; +Limit: con "solvelimit"; +Step: con "solvestep"; + +keyw := array[] of +{ + ("include", Oinclude), + ("if", Oif), + ("else", Oelse), + ("for", Ofor), + ("while", Owhile), + ("do", Odo), + ("break", Obreak), + ("continue", Ocont), + ("exit", Oexit), + ("return", Oret), + ("print", Oprint), + ("read", Oread), + ("fn", Ofn), + ("", 0), +}; + +conw := array[] of +{ + ("π", Pi), + ("Pi", Pi), + ("e", Euler), + ("γ", Gamma), + ("Gamma", Gamma), + ("φ", Phi), + ("Phi", Phi), + ("∞", Infinity), + ("Infinity", Infinity), + ("NaN", Nan), + ("Nan", Nan), + ("nan", Nan), + ("", 0.0), +}; + +varw := array[] of +{ + (Deg, 0.0), + (Base, 10.0), + (Limit, 100.0), + (Step, 1.0), + ("", 0.0), +}; + +funw := array[] of +{ + ("log", Olog, 1), + ("ln", Olog, 1), + ("log10", Olog10, 1), + ("log2", Olog2, 1), + ("logb", Ologb, 2), + ("exp", Oexpf, 1), + ("pow", Opow, 2), + ("sqrt", Osqrt, 1), + ("cbrt", Ocbrt, 1), + ("floor", Ofloor, 1), + ("ceiling", Oceil, 1), + ("min", Omin, 2), + ("max", Omax, 2), + ("abs", Oabs, 1), + ("Γ", Ogamma, 1), + ("gamma", Ogamma, 1), + ("sign", Osign, 1), + ("int", Oint, 1), + ("frac", Ofrac, 1), + ("round", Oround, 1), + ("erf", Oerf, 1), + ("atan2", Oatan2, 2), + ("sin", Osin, 1), + ("cos", Ocos, 1), + ("tan", Otan, 1), + ("asin", Oasin, 1), + ("acos", Oacos, 1), + ("atan", Oatan, 1), + ("sinh", Osinh, 1), + ("cosh", Ocosh, 1), + ("tanh", Otanh, 1), + ("asinh", Oasinh, 1), + ("acosh", Oacosh, 1), + ("atanh", Oatanh, 1), + ("rand", Orand, 0), + ("Σ", Osigma, 3), + ("sigma", Osigma, 3), + ("Π", Opi, 3), + ("pi", Opi, 3), + ("cfrac", Ocfrac, 3), + ("Δ", Oderiv, 2), + ("differential", Oderiv, 2), + ("∫", Ointeg, 3), + ("integral", Ointeg, 3), + ("solve", Osolve, 1), + ("", 0, 0), +}; + +stdin: int; +bin: ref Iobuf; +lineno: int = 1; +file: string; +iostack: list of (int, int, int, string, ref Iobuf); +geof: int; +garg: string; +gargs: list of string; +bufc: int = None; +buft: int = Olast; +lexes: int; +lexval: real; +lexstr: string; +lexsym: ref Sym; +syms: array of ref Sym; +deg: ref Dec; +pbase: ref Dec; +errdec: ref Dec; +inloop: int; +infn: int; + +Node: adt +{ + op: int; + left: cyclic ref Node; + right: cyclic ref Node; + val: real; + str: string; + dec: cyclic ref Dec; + src: int; +}; + +Dec: adt +{ + kind: int; + scope: int; + sym: cyclic ref Sym; + val: real; + na: int; + code: cyclic ref Node; + old: cyclic ref Dec; + next: cyclic ref Dec; +}; + +Sym: adt +{ + name: string; + kind: int; + dec: cyclic ref Dec; + next: cyclic ref Sym; +}; + +opstring(t: int): string +{ + s := opss[t]; + if(s != nil) + return s; + for(i := 0; keyw[i].t0 != nil; i++) + if(t == keyw[i].t1) + return keyw[i].t0; + for(i = 0; funw[i].t0 != nil; i++) + if(t == funw[i].t1) + return funw[i].t0; + return s; +} + +err(s: string) +{ + sys->print("error: %s\n", s); +} + +error(n: ref Node, s: string) +{ + if(n != nil) + lno := n.src; + else + lno = lineno; + s = sys->sprint("line %d: %s", lno, s); + if(file != nil) + s = sys->sprint("file %s: %s", file, s); + raise s; +} + +fatal(s: string) +{ + sys->print("fatal: %s\n", s); + exit; +} + +stack(s: string, f: ref Iobuf) +{ + iostack = (bufc, buft, lineno, file, bin) :: iostack; + bufc = None; + buft = Olast; + lineno = 1; + file = s; + bin = f; +} + +unstack() +{ + (bufc, buft, lineno, file, bin) = hd iostack; + iostack = tl iostack; +} + +doinclude(s: string) +{ + f := bufio->open(s, Sys->OREAD); + if(f == nil) + error(nil, sys->sprint("cannot open %s", s)); + stack(s, f); +} + +getc(): int +{ + if((c := bufc) != None) + bufc = None; + else if(bin != nil) + c = bin.getc(); + else{ + if(garg == nil){ + if(gargs == nil){ + if(geof == 0){ + geof = 1; + c = '\n'; + } + else + c = Eof; + } + else{ + garg = hd gargs; + gargs = tl gargs; + c = ' '; + } + } + else{ + c = garg[0]; + garg = garg[1: ]; + } + } + if(c == Eof && iostack != nil){ + unstack(); + return getc(); + } + return c; +} + +ungetc(c: int) +{ + bufc = c; +} + +slash(c: int): int +{ + if(c != '\\') + return c; + nc := getc(); + case(nc){ + 'b' => return '\b'; + 'f' => return '\f'; + 'n' => return '\n'; + 'r' => return '\r'; + 't' => return '\t'; + } + return nc; +} + +lexstring(): int +{ + sp := ""; + while((c := getc()) != '"'){ + if(c == Eof) + raise Eeof; + sp[len sp] = slash(c); + } + lexstr = sp; + return Ostring; +} + +lexchar(): int +{ + while((c := getc()) != '\''){ + if(c == Eof) + raise Eeof; + lexval = real slash(c); + } + return Onum; +} + +basev(c: int, base: int): int +{ + if(c >= 'a' && c <= 'z') + c += 10-'a'; + else if(c >= 'A' && c <= 'Z') + c += 10-'A'; + else if(c >= '0' && c <= '9') + c -= '0'; + else + return -1; + if(c >= base) + error(nil, "bad digit"); + return c; +} + +lexe(base: int): int +{ + neg := 0; + v := big 0; + c := getc(); + if(c == '-') + neg = 1; + else + ungetc(c); + for(;;){ + c = getc(); + cc := basev(c, base); + if(cc < 0){ + ungetc(c); + break; + } + v = big base*v+big cc; + } + if(neg) + v = -v; + return int v; +} + +lexnum(): int +{ + base := 10; + exp := 0; + r := f := e := 0; + v := big 0; + c := getc(); + if(c == '0'){ + base = 8; + c = getc(); + if(c == '.'){ + base = 10; + ungetc(c); + } + else if(c == 'x' || c == 'X') + base = 16; + else + ungetc(c); + } + else + ungetc(c); + for(;;){ + c = getc(); + if(!r && (c == 'r' || c == 'R')){ + if(f || e) + error(nil, "bad base"); + r = 1; + base = int v; + if(base < 2 || base > 36) + error(nil, "bad base"); + v = big 0; + continue; + } + if(c == '.'){ + if(f || e) + error(nil, "bad real"); + f = 1; + continue; + } + if(base == 10 && (c == 'e' || c == 'E')){ + if(e) + error(nil, "bad E part"); + e = 1; + exp = lexe(base); + continue; + } + cc := basev(c, base); + if(cc < 0){ + ungetc(c); + break; + } + v = big base*v+big cc; + if(f) + f++; + } + lexval = real v; + if(f) + lexval /= real base**(f-1); + if(exp){ + if(exp > 0) + lexval *= real base**exp; + else + lexval *= maths->pow(real base, real exp); + } + return Onum; +} + +lexid(): int +{ + sp := ""; + for(;;){ + c := getc(); + if(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c >= 'α' && c <= 'ω' || c >= 'Α' && c <= 'Ω' || c == '_') + sp[len sp] = c; + else{ + ungetc(c); + break; + } + } + lexsym = enter(sp, Oident); + return lexsym.kind; +} + +follow(c: int, c1: int, c2: int): int +{ + nc := getc(); + if(nc == c) + return c1; + ungetc(nc); + return c2; +} + +skip() +{ + if((t := buft) != Olast){ + lex(); + if(t == Onl) + return; + } + for(;;){ + c := getc(); + if(c == Eof){ + ungetc(c); + return; + } + if(c == '\n'){ + lineno++; + return; + } + } +} + +lex(): int +{ + lexes++; + if((t := buft) != Olast){ + buft = Olast; + if(t == Onl) + lineno++; + return t; + } + for(;;){ + case(c := getc()){ + Eof => + return Oeof; + '#' => + while((c = getc()) != '\n'){ + if(c == Eof) + raise Eeof; + } + lineno++; + '\n' => + lineno++; + return Onl; + ' ' or + '\t' or + '\r' or + '\v' => + ; + '"' => + return lexstring(); + '\'' => + return lexchar(); + '0' to '9' => + ungetc(c); + return lexnum(); + 'a' to 'z' or + 'A' to 'Z' or + 'α' to 'ω' or + 'Α' to 'Ω' or + '_' => + ungetc(c); + return lexid(); + '+' => + c = getc(); + if(c == '=') + return Oadde; + ungetc(c); + return follow('+', Oinc, Oadd); + '-' => + c = getc(); + if(c == '=') + return Osube; + if(c == '>') + return Oimp; + ungetc(c); + return follow('-', Odec, Osub); + '*' => + c = getc(); + if(c == '=') + return Omule; + if(c == '*') + return follow('=', Oexpe, Oexp); + ungetc(c); + return Omul; + '/' => + c = getc(); + if(c == '=') + return Odive; + if(c == '/') + return follow('=', Oidive, Oidiv); + ungetc(c); + return Odiv; + '%' => + return follow('=', Omode, Omod); + '&' => + c = getc(); + if(c == '=') + return Oande; + ungetc(c); + return follow('&', Oandand, Oand); + '|' => + c = getc(); + if(c == '=') + return Oore; + ungetc(c); + return follow('|', Ooror, Oor); + '^' => + return follow('=', Oxore, Oxor); + '=' => + return follow('=', Oeq, Oas); + '!' => + return follow('=', One, Oexc); + '>' => + c = getc(); + if(c == '=') + return Oge; + if(c == '>') + return follow('=', Orshe, Orsh); + ungetc(c); + return Ogt; + '<' => + c = getc(); + if(c == '=') + return Ole; + if(c == '<') + return follow('=', Olshe, Olsh); + if(c == '-') + return follow('>', Oiff, Oimpby); + ungetc(c); + return Olt; + '(' => + return Olbr; + ')' => + return Orbr; + '{' => + return Olcbr; + '}' => + return Orcbr; + '~' => + return Ocom; + '.' => + ungetc(c); + return lexnum(); + ',' => + return Ocomma; + '?' => + return Oquest; + ':' => + return follow('=', Odas, Ocolon); + ';' => + return Oscolon; + '↑' => + return Onand; + '↓' => + return Onor; + '∞' => + lexval = Infinity; + return Onum; + * => + error(nil, sys->sprint("bad character %c", c)); + } + } +} + +unlex(t: int) +{ + lexes--; + buft = t; + if(t == Onl) + lineno--; +} + +mustbe(t: int) +{ + nt := lex(); + if(nt != t) + error(nil, sys->sprint("expected %s not %s", opstring(t), opstring(nt))); +} + +consume(t: int) +{ + nt := lex(); + if(nt != t) + unlex(nt); +} + +elex(): int +{ + t := lex(); + if(binary(t)) + return t; + if(hexp(t)){ + unlex(t); + return Oscomma; + } + return t; +} + +hexp(o: int): int +{ + return preunary(o) || o == Olbr || atom(o); +} + +atom(o: int): int +{ + return o >= Ostring && o <= Olfun; +} + +asop(o: int): int +{ + return o == Oas || o == Odas || o >= Oadde && o <= Orshe || o >= Oinc && o <= Opostdec; +} + +preunary(o: int): int +{ + return ops[o]&Preunary; +} + +postunary(o: int): int +{ + return ops[o]&Postunary; +} + +binary(o: int): int +{ + return ops[o]&Binary; +} + +prec(o: int): int +{ + return ops[o]&Prec; +} + +assoc(o: int): int +{ + return ops[o]&Assoc; +} + +rassoc(o: int): int +{ + return ops[o]&Rassoc; +} + +preop(o: int): int +{ + case(o){ + Oadd => return Oplus; + Osub => return Ominus; + Oinc => return Opreinc; + Odec => return Opredec; + Oexc => return Onot; + } + return o; +} + +postop(o: int): int +{ + case(o){ + Oinc => return Opostinc; + Odec => return Opostdec; + Oexc => return Ofact; + } + return o; +} + +prtree(p: ref Node, in: int) +{ + if(p == nil) + return; + for(i := 0; i < in; i++) + sys->print(" "); + sys->print("%s ", opstring(p.op)); + case(p.op){ + Ostring => + sys->print("%s", p.str); + Onum => + sys->print("%g", p.val); + Ocon or + Ovar => + sys->print("%s(%g)", p.dec.sym.name, p.dec.val); + Ofun or + Olfun => + sys->print("%s", p.dec.sym.name); + } + sys->print("\n"); + # sys->print(" - %d\n", p.src); + prtree(p.left, in+1); + prtree(p.right, in+1); +} + +tree(o: int, l: ref Node, r: ref Node): ref Node +{ + p := ref Node; + p.op = o; + p.left = l; + p.right = r; + p.src = lineno; + if(asop(o)){ + if(o >= Oadde && o <= Orshe){ + p = tree(Oas, l, p); + p.right.op += Oadd-Oadde; + } + } + return p; +} + +itree(n: int): ref Node +{ + return vtree(real n); +} + +vtree(v: real): ref Node +{ + n := tree(Onum, nil, nil); + n.val = v; + return n; +} + +ltree(s: string, a: ref Node): ref Node +{ + n := tree(Olfun, a, nil); + n.dec = lookup(s).dec; + return n; +} + +ptree(n: ref Node, p: real): ref Node +{ + if(isinteger(p)){ + i := int p; + if(i == 0) + return itree(1); + if(i == 1) + return n; + if(i == -1) + return tree(Oinv, n, nil); + if(i < 0) + return tree(Oinv, tree(Oexp, n, itree(-i)), nil); + } + return tree(Oexp, n, vtree(p)); +} + +iscon(n: ref Node): int +{ + return n.op == Onum || n.op == Ocon; +} + +iszero(n: ref Node): int +{ + return iscon(n) && eval(n) == 0.0; +} + +isone(n: ref Node): int +{ + return iscon(n) && eval(n) == 1.0; +} + +isnan(n: ref Node): int +{ + return iscon(n) && maths->isnan(eval(n)); +} + +isinf(n: ref Node): int +{ + return iscon(n) && (v := eval(n)) == Infinity || v == -Infinity; +} + +stat(scope: int): ref Node +{ + e1, e2, e3, e4: ref Node; + + consume(Onl); + t := lex(); + case(t){ + Olcbr => + if(scope) + pushscope(); + for(;;){ + e2 = stat(1); + if(e1 == nil) + e1 = e2; + else + e1 = tree(Ocomma, e1, e2); + consume(Onl); + t = lex(); + if(t == Oeof) + raise Eeof; + if(t == Orcbr) + break; + unlex(t); + } + if(scope) + popscope(); + return e1; + Oprint or + Oread or + Oret => + if(t == Oret && !infn) + error(nil, "return not in fn"); + e1= tree(t, expr(0, 1), nil); + consume(Oscolon); + if(t == Oread) + allvar(e1.left); + return e1; + Oif => + # mustbe(Olbr); + e1 = expr(0, 1); + # mustbe(Orbr); + e2 = stat(1); + e3 = nil; + consume(Onl); + t = lex(); + if(t == Oelse) + e3 = stat(1); + else + unlex(t); + return tree(Oif, e1, tree(Ocomma, e2, e3)); + Ofor => + inloop++; + mustbe(Olbr); + e1 = expr(0, 1); + mustbe(Oscolon); + e2 = expr(0, 1); + mustbe(Oscolon); + e3 = expr(0, 1); + mustbe(Orbr); + e4 = stat(1); + inloop--; + return tree(Ocomma, e1, tree(Ofor, e2, tree(Ocomma, e4, e3))); + Owhile => + inloop++; + # mustbe(Olbr); + e1 = expr(0, 1); + # mustbe(Orbr); + e2 = stat(1); + inloop--; + return tree(Ofor, e1, tree(Ocomma, e2, nil)); + Odo => + inloop++; + e1 = stat(1); + consume(Onl); + mustbe(Owhile); + # mustbe(Olbr); + e2 = expr(0, 1); + # mustbe(Orbr); + consume(Oscolon); + inloop--; + return tree(Odo, e1, e2); + Obreak or + Ocont or + Oexit => + if((t == Obreak || t == Ocont) && !inloop) + error(nil, "break/continue not in loop"); + consume(Oscolon); + return tree(t, nil, nil); + Ofn => + if(infn) + error(nil, "nested functions not allowed"); + infn++; + mustbe(Oident); + s := lexsym; + d := mkdec(s, Ofun, 1); + d.code = tree(Ofn, nil, nil); + pushscope(); + (d.na, d.code.left) = args(0); + allvar(d.code.left); + pushparams(d.code.left); + d.code.right = stat(0); + popscope(); + infn--; + return d.code; + Oinclude => + e1 = expr(0, 0); + if(e1.op != Ostring) + error(nil, "bad include file"); + consume(Oscolon); + doinclude(e1.str); + return nil; + * => + unlex(t); + e1 = expr(0, 1); + consume(Oscolon); + if(debug) + prnode(e1); + return e1; + } + return nil; +} + +ckstat(n: ref Node, parop: int, pr: int) +{ + if(n == nil) + return; + pr |= n.op == Oprint; + ckstat(n.left, n.op, pr); + ckstat(n.right, n.op, pr); + case(n.op){ + Ostring => + if(!pr || parop != Oprint && parop != Ocomma) + error(n, "illegal string operation"); + } +} + +pexp(e: ref Node): int +{ + if(e == nil) + return 0; + if(e.op == Ocomma) + return pexp(e.right); + return e.op >= Ostring && e.op <= Oiff && !asop(e.op); +} + +expr(p: int, zok: int): ref Node +{ + n := exp(p, zok); + ckexp(n, Onothing); + return n; +} + +exp(p: int, zok: int): ref Node +{ + l := prim(zok); + if(l == nil) + return nil; + while(binary(t := elex()) && (o := prec(t)) >= p){ + if(rassoc(t)) + r := exp(o, 0); + else + r = exp(o+1, 0); + if(t == Oscomma) + t = Ocomma; + l = tree(t, l, r); + } + if(t != Oscomma) + unlex(t); + return l; +} + +prim(zok: int): ref Node +{ + p: ref Node; + na: int; + + t := lex(); + if(preunary(t)){ + t = preop(t); + return tree(t, exp(prec(t), 0), nil); + } + case(t){ + Olbr => + p = exp(0, zok); + mustbe(Orbr); + Ostring => + p = tree(t, nil, nil); + p.str = lexstr; + Onum => + p = tree(t, nil ,nil); + p.val = lexval; + Oident => + s := lexsym; + d := s.dec; + if(d == nil) + d = mkdec(s, Ovar, 0); + case(t = d.kind){ + Ocon or + Ovar => + p = tree(t, nil, nil); + p.dec = d; + Ofun or + Olfun => + p = tree(t, nil, nil); + p.dec = d; + (na, p.left) = args(prec(t)); + if(!(t == Olfun && d.val == real Osolve && na == 2)) + if(na != d.na) + error(p, "wrong number of arguments"); + if(t == Olfun){ + case(int d.val){ + Osigma or + Opi or + Ocfrac or + Ointeg => + if((op := p.left.left.left.op) != Oas && op != Odas) + error(p.left, "expression not an assignment"); + Oderiv => + if((op := p.left.left.op) != Oas && op != Odas) + error(p.left, "expression not an assignment"); + } + } + } + * => + unlex(t); + if(!zok) + error(nil, "missing expression"); + return nil; + } + while(postunary(t = lex())){ + t = postop(t); + p = tree(t, p, nil); + } + unlex(t); + return p; +} + +ckexp(n: ref Node, parop: int) +{ + if(n == nil) + return; + o := n.op; + l := n.left; + r := n.right; + if(asop(o)) + var(l); + case(o){ + Ovar => + s := n.dec.sym; + d := s.dec; + if(d == nil){ + if(strict) + error(n, sys->sprint("%s undefined", s.name)); + d = mkdec(s, Ovar, 1); + } + n.dec = d; + Odas => + ckexp(r, o); + l.dec = mkdec(l.dec.sym, Ovar, 1); + * => + ckexp(l, o); + ckexp(r, o); + if(o == Oquest && r.op != Ocolon) + error(n, "bad '?' operator"); + if(o == Ocolon && parop != Oquest) + error(n, "bad ':' operator"); + } +} + +commas(n: ref Node): int +{ + if(n == nil || n.op == Ofun || n.op == Olfun) + return 0; + c := commas(n.left)+commas(n.right); + if(n.op == Ocomma) + c++; + return c; +} + +allvar(n: ref Node) +{ + if(n == nil) + return; + if(n.op == Ocomma){ + allvar(n.left); + allvar(n.right); + return; + } + var(n); +} + +args(p: int): (int, ref Node) +{ + if(!p) + mustbe(Olbr); + a := exp(p, 1); + if(!p) + mustbe(Orbr); + na := 0; + if(a != nil) + na = commas(a)+1; + return (na, a); +} + +hash(s: string): int +{ + l := len s; + h := 4104; + for(i := 0; i < l; i++) + h = 1729*h ^ s[i]; + if(h < 0) + h = -h; + return h&(Hash-1); +} + +enter(sp: string, k: int): ref Sym +{ + for(s := syms[hash(sp)]; s != nil; s = s.next){ + if(sp == s.name) + return s; + } + s = ref Sym; + s.name = sp; + s.kind = k; + h := hash(sp); + s.next = syms[h]; + syms[h] = s; + return s; +} + +lookup(sp: string): ref Sym +{ + return enter(sp, Oident); +} + +mkdec(s: ref Sym, k: int, dec: int): ref Dec +{ + d := ref Dec; + d.kind = k; + d.val = 0.0; + d.na = 0; + d.sym = s; + d.scope = 0; + if(dec) + pushdec(d); + return d; +} + +adddec(sp: string, k: int, v: real, n: int): ref Dec +{ + d := mkdec(enter(sp, Oident), k, 1); + d.val = v; + d.na = n; + return d; +} + +scope: int; +curscope: ref Dec; +scopes: list of ref Dec; + +pushscope() +{ + scope++; + scopes = curscope :: scopes; + curscope = nil; +} + +popscope() +{ + popdecs(); + curscope = hd scopes; + scopes = tl scopes; + scope--; +} + +pushparams(n: ref Node) +{ + if(n == nil) + return; + if(n.op == Ocomma){ + pushparams(n.left); + pushparams(n.right); + return; + } + n.dec = mkdec(n.dec.sym, Ovar, 1); +} + +pushdec(d: ref Dec) +{ + if(0 && debug) + sys->print("dec %s scope %d\n", d.sym.name, scope); + d.scope = scope; + s := d.sym; + if(s.dec != nil && s.dec.scope == scope) + error(nil, sys->sprint("redeclaration of %s", s.name)); + d.old = s.dec; + s.dec = d; + d.next = curscope; + curscope = d; +} + +popdecs() +{ + nd: ref Dec; + for(d := curscope; d != nil; d = nd){ + d.sym.dec = d.old; + d.old = nil; + nd = d.next; + d.next = nil; + } + curscope = nil; +} + +estat(n: ref Node): (int, real) +{ + k: int; + v: real; + + if(n == nil) + return (Onothing, 0.0); + l := n.left; + r := n.right; + case(n.op){ + Ocomma => + (k, v) = estat(l); + if(k == Oexit || k == Oret || k == Obreak || k == Ocont) + return (k, v); + return estat(r); + Oprint => + v = print(l); + return (Onothing, v); + Oread => + v = read(l); + return (Onothing, v); + Obreak or + Ocont or + Oexit => + return (n.op, 0.0); + Oret => + return (Oret, eval(l)); + Oif => + v = eval(l); + if(int v) + return estat(r.left); + else if(r.right != nil) + return estat(r.right); + else + return (Onothing, v); + Ofor => + for(;;){ + v = eval(l); + if(!int v) + break; + (k, v) = estat(r.left); + if(k == Oexit || k == Oret) + return (k, v); + if(k == Obreak) + break; + if(r.right != nil) + v = eval(r.right); + } + return (Onothing, v); + Odo => + for(;;){ + (k, v) = estat(l); + if(k == Oexit || k == Oret) + return (k, v); + if(k == Obreak) + break; + v = eval(r); + if(!int v) + break; + } + return (Onothing, v); + * => + return (Onothing, eval(n)); + } + return (Onothing, 0.0); +} + +eval(e: ref Node): real +{ + lv, rv: real; + + if(e == nil) + return 1.0; + o := e.op; + l := e.left; + r := e.right; + if(o != Ofun && o != Olfun) + lv = eval(l); + if(o != Oandand && o != Ooror && o != Oquest) + rv = eval(r); + case(o){ + Ostring => + return 0.0; + Onum => + return e.val; + Ocon or + Ovar => + return e.dec.val; + Ofun => + return call(e.dec, l); + Olfun => + return libfun(int e.dec.val, l); + Oadd => + return lv+rv; + Osub => + return lv-rv; + Omul => + return lv*rv; + Odiv => + return lv/rv; + Omod => + return real (big lv%big rv); + Oidiv => + return real (big lv/big rv); + Oand => + return real (big lv&big rv); + Oor => + return real (big lv|big rv); + Oxor => + return real (big lv^big rv); + Olsh => + return real (big lv<<int rv); + Orsh => + return real (big lv>>int rv); + Oeq => + return real (lv == rv); + One => + return real (lv != rv); + Ogt => + return real (lv > rv); + Olt => + return real (lv < rv); + Oge => + return real (lv >= rv); + Ole => + return real (lv <= rv); + Opreinc => + l.dec.val += 1.0; + return l.dec.val; + Opostinc => + l.dec.val += 1.0; + return l.dec.val-1.0; + Opredec => + l.dec.val -= 1.0; + return l.dec.val; + Opostdec => + l.dec.val -= 1.0; + return l.dec.val+1.0; + Oexp => + if(isinteger(rv) && rv >= 0.0) + return lv**int rv; + return maths->pow(lv, rv); + Oandand => + if(!int lv) + return lv; + return eval(r); + Ooror => + if(int lv) + return lv; + return eval(r); + Onot => + return real !int lv; + Ofact => + if(isinteger(lv) && lv >= 0.0){ + n := int lv; + lv = 1.0; + for(i := 2; i <= n; i++) + lv *= real i; + return lv; + } + return gamma(lv+1.0); + Ocom => + return real ~big lv; + Oas or + Odas => + l.dec.val = rv; + return rv; + Oplus => + return lv; + Ominus => + return -lv; + Oinv => + return 1.0/lv; + Ocomma => + return rv; + Oquest => + if(int lv) + return eval(r.left); + else + return eval(r.right); + Onand => + return real !(int lv&int rv); + Onor => + return real !(int lv|int rv); + Oimp => + return real (!int lv|int rv); + Oimpby => + return real (int lv|!int rv); + Oiff => + return real !(int lv^int rv); + * => + fatal(sys->sprint("case %s in eval", opstring(o))); + } + return 0.0; +} + +var(e: ref Node) +{ + if(e == nil || e.op != Ovar || e.dec.kind != Ovar) + error(e, "expected a variable"); +} + +libfun(o: int, a: ref Node): real +{ + a1, a2: real; + + case(o){ + Osolve => + return solve(a); + Osigma or + Opi or + Ocfrac => + return series(o, a); + Oderiv => + return differential(a); + Ointeg => + return integral(a); + } + v := 0.0; + if(a != nil && a.op == Ocomma){ + a1 = eval(a.left); + a2 = eval(a.right); + } + else + a1 = eval(a); + case(o){ + Olog => + v = maths->log(a1); + Olog10 => + v = maths->log10(a1); + Olog2 => + v = maths->log(a1)/maths->log(2.0); + Ologb => + v = maths->log(a1)/maths->log(a2); + Oexpf => + v = maths->exp(a1); + Opow => + v = maths->pow(a1, a2); + Osqrt => + v = maths->sqrt(a1); + Ocbrt => + v = maths->cbrt(a1); + Ofloor => + v = maths->floor(a1); + Oceil => + v = maths->ceil(a1); + Omin => + v = maths->fmin(a1, a2); + Omax => + v = maths->fmax(a1, a2); + Oabs => + v = maths->fabs(a1); + Ogamma => + v = gamma(a1); + Osign => + if(a1 > 0.0) + v = 1.0; + else if(a1 < 0.0) + v = -1.0; + else + v = 0.0; + Oint => + (vi, nil) := maths->modf(a1); + v = real vi; + Ofrac => + (nil, v) = maths->modf(a1); + Oround => + v = maths->rint(a1); + Oerf => + v = maths->erf(a1); + Osin => + v = maths->sin(D2R(a1)); + Ocos => + v = maths->cos(D2R(a1)); + Otan => + v = maths->tan(D2R(a1)); + Oasin => + v = R2D(maths->asin(a1)); + Oacos => + v = R2D(maths->acos(a1)); + Oatan => + v = R2D(maths->atan(a1)); + Oatan2 => + v = R2D(maths->atan2(a1, a2)); + Osinh => + v = maths->sinh(a1); + Ocosh => + v = maths->cosh(a1); + Otanh => + v = maths->tanh(a1); + Oasinh => + v = maths->asinh(a1); + Oacosh => + v = maths->acosh(a1); + Oatanh => + v = maths->atanh(a1); + Orand => + v = real rand->rand(Big)/real Big; + * => + fatal(sys->sprint("case %s in libfun", opstring(o))); + } + return v; +} + +series(o: int, a: ref Node): real +{ + p0, p1, q0, q1: real; + + l := a.left; + r := a.right; + if(o == Osigma) + v := 0.0; + else if(o == Opi) + v = 1.0; + else{ + p0 = q1 = 0.0; + p1 = q0 = 1.0; + v = Infinity; + } + i := l.left.left.dec; + ov := i.val; + i.val = eval(l.left.right); + eq := 0; + for(;;){ + rv := eval(l.right); + if(i.val > rv) + break; + lv := v; + ev := eval(r); + if(o == Osigma) + v += ev; + else if(o == Opi) + v *= ev; + else{ + t := ev*p1+p0; + p0 = p1; + p1 = t; + t = ev*q1+q0; + q0 = q1; + q1 = t; + v = p1/q1; + } + if(v == lv && rv == Infinity){ + eq++; + if(eq > 100) + break; + } + else + eq = 0; + i.val += 1.0; + } + i.val = ov; + return v; +} + +pushe(a: ref Node, l: list of real): list of real +{ + if(a == nil) + return l; + if(a.op == Ocomma){ + l = pushe(a.left, l); + return pushe(a.right, l); + } + l = eval(a) :: l; + return l; +} + +pusha(f: ref Node, l: list of real, nl: list of real): (list of real, list of real) +{ + if(f == nil) + return (l, nl); + if(f.op == Ocomma){ + (l, nl) = pusha(f.left, l, nl); + return pusha(f.right, l, nl); + } + l = f.dec.val :: l; + f.dec.val = hd nl; + return (l, tl nl); +} + +pop(f: ref Node, l: list of real): list of real +{ + if(f == nil) + return l; + if(f.op == Ocomma){ + l = pop(f.left, l); + return pop(f.right, l); + } + f.dec.val = hd l; + return tl l; +} + +rev(l: list of real): list of real +{ + nl: list of real; + + for( ; l != nil; l = tl l) + nl = hd l :: nl; + return nl; +} + +call(d: ref Dec, a: ref Node): real +{ + l: list of real; + + nl := rev(pushe(a, nil)); + (l, nil) = pusha(d.code.left, nil, nl); + l = rev(l); + (k, v) := estat(d.code.right); + l = pop(d.code.left, l); + if(k == Oexit) + exit; + return v; +} + +print(n: ref Node): real +{ + if(n == nil) + return 0.0; + if(n.op == Ocomma){ + print(n.left); + return print(n.right); + } + if(n.op == Ostring){ + sys->print("%s", n.str); + return 0.0; + } + v := eval(n); + printnum(v, ""); + return v; +} + +read(n: ref Node): real +{ + bio: ref Iobuf; + + if(n == nil) + return 0.0; + if(n.op == Ocomma){ + read(n.left); + return read(n.right); + } + sys->print("%s ? ", n.dec.sym.name); + if(!stdin){ + bio = bufio->fopen(sys->fildes(0), Sys->OREAD); + stack(nil, bio); + } + lexnum(); + consume(Onl); + n.dec.val = lexval; + if(!stdin && bin == bio) + unstack(); + return n.dec.val; +} + +isint(v: real): int +{ + return v >= -real Maxint && v <= real Maxint; +} + +isinteger(v: real): int +{ + return v == real int v && isint(v); +} + +split(v: real): (int, real) +{ + # v >= 0.0 + n := int v; + if(real n > v) + n--; + return (n, v-real n); +} + +n2c(n: int): int +{ + if(n < 10) + return n+'0'; + return n-10+'a'; +} + +gamma(v: real): real +{ + (s, lg) := maths->lgamma(v); + return real s*maths->exp(lg); +} + +D2R(a: real): real +{ + if(deg.val != 0.0) + a *= Pi/180.0; + return a; +} + +R2D(a: real): real +{ + if(deg.val != 0.0) + a /= Pi/180.0; + return a; +} + +side(n: ref Node): int +{ + if(n == nil) + return 0; + if(asop(n.op) || n.op == Ofun) + return 1; + return side(n.left) || side(n.right); +} + +sametree(n1: ref Node, n2: ref Node): int +{ + if(n1 == n2) + return 1; + if(n1 == nil || n2 == nil) + return 0; + if(n1.op != n2.op) + return 0; + case(n1.op){ + Ostring => + return n1.str == n2.str; + Onum => + return n1.val == n2.val; + Ocon or + Ovar => + return n1.dec == n2.dec; + Ofun or + Olfun => + return n1.dec == n2.dec && sametree(n1.left, n2.left); + * => + return sametree(n1.left, n2.left) && sametree(n1.right, n2.right); + } + return 0; +} + +simplify(n: ref Node): ref Node +{ + if(n == nil) + return nil; + op := n.op; + l := n.left = simplify(n.left); + r := n.right = simplify(n.right); + if(l != nil && iscon(l) && (r == nil || iscon(r))){ + if(isnan(l)) + return l; + if(r != nil && isnan(r)) + return r; + return vtree(eval(n)); + } + case(op){ + Onum or + Ocon or + Ovar or + Olfun or + Ocomma => + return n; + Oplus => + return l; + Ominus => + if(l.op == Ominus) + return l.left; + Oinv => + if(l.op == Oinv) + return l.left; + Oadd => + if(iszero(l)) + return r; + if(iszero(r)) + return l; + if(sametree(l, r)) + return tree(Omul, itree(2), l); + Osub => + if(iszero(l)) + return simplify(tree(Ominus, r, nil)); + if(iszero(r)) + return l; + if(sametree(l, r)) + return itree(0); + Omul => + if(iszero(l)) + return l; + if(iszero(r)) + return r; + if(isone(l)) + return r; + if(isone(r)) + return l; + if(sametree(l, r)) + return tree(Oexp, l, itree(2)); + Odiv => + if(iszero(l)) + return l; + if(iszero(r)) + return vtree(Infinity); + if(isone(l)) + return ptree(r, -1.0); + if(isone(r)) + return l; + if(sametree(l, r)) + return itree(1); + Oexp => + if(iszero(l)) + return l; + if(iszero(r)) + return itree(1); + if(isone(l)) + return l; + if(isone(r)) + return l; + * => + fatal(sys->sprint("case %s in simplify", opstring(op))); + } + return n; +} + +deriv(n: ref Node, d: ref Dec): ref Node +{ + if(n == nil) + return nil; + op := n.op; + l := n.left; + r := n.right; + case(op){ + Onum or + Ocon => + n = itree(0); + Ovar => + if(d == n.dec) + n = itree(1); + else + n = itree(0); + Olfun => + case(int n.dec.val){ + Olog => + n = ptree(l, -1.0); + Olog10 => + n = ptree(tree(Omul, l, vtree(Ln10)), -1.0); + Olog2 => + n = ptree(tree(Omul, l, vtree(Ln2)), -1.0); + Oexpf => + n = n; + Opow => + return deriv(tree(Oexp, l.left, l.right), d); + Osqrt => + return deriv(tree(Oexp, l, vtree(0.5)), d); + Ocbrt => + return deriv(tree(Oexp, l, vtree(1.0/3.0)), d); + Osin => + n = ltree("cos", l); + Ocos => + n = tree(Ominus, ltree("sin", l), nil); + Otan => + n = ptree(ltree("cos", l), -2.0); + Oasin => + n = ptree(tree(Osub, itree(1), ptree(l, 2.0)), -0.5); + Oacos => + n = tree(Ominus, ptree(tree(Osub, itree(1), ptree(l, 2.0)), -0.5), nil); + Oatan => + n = ptree(tree(Oadd, itree(1), ptree(l, 2.0)), -1.0); + Osinh => + n = ltree("cosh", l); + Ocosh => + n = ltree("sinh", l); + Otanh => + n = ptree(ltree("cosh", l), -2.0); + Oasinh => + n = ptree(tree(Oadd, itree(1), ptree(l, 2.0)), -0.5); + Oacosh => + n = ptree(tree(Osub, ptree(l, 2.0), itree(1)), -0.5); + Oatanh => + n = ptree(tree(Osub, itree(1), ptree(l, 2.0)), -1.0); + * => + return vtree(Nan); + } + return tree(Omul, n, deriv(l, d)); + Oplus or + Ominus => + n = tree(op, deriv(l, d), nil); + Oinv => + n = tree(Omul, tree(Ominus, ptree(l, -2.0), nil), deriv(l, d)); + Oadd or + Osub or + Ocomma => + n = tree(op, deriv(l, d), deriv(r, d)); + Omul => + n = tree(Oadd, tree(Omul, deriv(l, d), r), tree(Omul, l, deriv(r, d))); + Odiv => + n = tree(Osub, tree(Omul, deriv(l, d), r), tree(Omul, l, deriv(r, d))); + n = tree(Odiv, n, ptree(r, 2.0)); + Oexp => + nn := tree(Oadd, tree(Omul, deriv(l, d), tree(Odiv, r, l)), tree(Omul, ltree("log", l), deriv(r, d))); + n = tree(Omul, n, nn); + * => + n = vtree(Nan); + } + return n; +} + +derivative(n: ref Node, d: ref Dec): ref Node +{ + n = simplify(deriv(n, d)); + if(isnan(n)) + error(n, "no derivative"); + if(debug) + prnode(n); + return n; +} + +newton(f: ref Node, e: ref Node, d: ref Dec, v1: real, v2: real): (int, real) +{ + v := (v1+v2)/2.0; + lv := 0.0; + its := 0; + for(;;){ + lv = v; + d.val = v; + v = eval(e); + # if(v < v1 || v > v2) + # return (0, 0.0); + if(maths->isnan(v)) + return (0, 0.0); + if(its > 100 || fabs(v-lv) < Eps) + break; + its++; + } + if(fabs(v-lv) > Bigeps || fabs(eval(f)) > Bigeps) + return (0, 0.0); + return (1, v); +} + +solve(n: ref Node): real +{ + d: ref Dec; + + if(n == nil) + return Nan; + if(n.op == Ocomma){ # solve(..., var) + var(n.right); + d = n.right.dec; + n = n.left; + if(!varmem(n, d)) + error(n, "variable not in equation"); + } + else{ + d = findvar(n, nil); + if(d == nil) + error(n, "variable missing"); + if(d == errdec) + error(n, "one variable only required"); + } + if(n.op == Oeq) + n.op = Osub; + dn := derivative(n, d); + var := tree(Ovar, nil, nil); + var.dec = d; + nr := tree(Osub, var, tree(Odiv, n, dn)); + ov := d.val; + lim := lookup(Limit).dec.val; + step := lookup(Step).dec.val; + rval := Infinity; + d.val = -lim-step; + v1 := 0.0; + v2 := eval(n); + for(v := -lim; v <= lim; v += step){ + d.val = v; + v1 = v2; + v2 = eval(n); + if(maths->isnan(v2)) # v == nan, v <= nan, v >= nan all give 1 + continue; + if(fabs(v2) < Eps){ + if(v >= -lim && v <= lim && v != rval){ + printnum(v, " "); + rval = v; + } + } + else if(v1*v2 <= 0.0){ + (f, rv) := newton(n, nr, var.dec, v-step, v); + if(f && rv >= -lim && rv <= lim && rv != rval){ + printnum(rv, " "); + rval = rv; + } + } + } + d.val = ov; + if(rval == Infinity) + error(n, "no roots found"); + else + sys->print("\n"); + return rval; +} + +differential(n: ref Node): real +{ + x := n.left.left.dec; + ov := x.val; + v := evalx(derivative(n.right, x), x, eval(n.left.right)); + x.val = ov; + return v; +} + +integral(n: ref Node): real +{ + l := n.left; + r := n.right; + x := l.left.left.dec; + ov := x.val; + a := eval(l.left.right); + b := eval(l.right); + h := b-a; + end := evalx(r, x, a) + evalx(r, x, b); + odd := even := 0.0; + oldarea := 0.0; + area := h*end/2.0; + for(i := 1; i < 1<<16; i <<= 1){ + even += odd; + odd = 0.0; + xv := a+h/2.0; + for(j := 0; j < i; j++){ + odd += evalx(r, x, xv); + xv += h; + } + h /= 2.0; + oldarea = area; + area = h*(end+4.0*odd+2.0*even)/3.0; + if(maths->isnan(area)) + error(n, "integral not found"); + if(fabs(area-oldarea) < Eps) + break; + } + if(fabs(area-oldarea) > Bigeps) + error(n, "integral not found"); + x.val = ov; + return area; +} + +evalx(n: ref Node, d: ref Dec, v: real): real +{ + d.val = v; + return eval(n); +} + +findvar(n: ref Node, d: ref Dec): ref Dec +{ + if(n == nil) + return d; + d = findvar(n.left, d); + d = findvar(n.right, d); + if(n.op == Ovar){ + if(d == nil) + d = n.dec; + if(n.dec != d) + d = errdec; + } + return d; +} + +varmem(n: ref Node, d: ref Dec): int +{ + if(n == nil) + return 0; + if(n.op == Ovar) + return d == n.dec; + return varmem(n.left, d) || varmem(n.right, d); +} + +fabs(r: real): real +{ + if(r < 0.0) + return -r; + return r; +} + +cvt(v: real, base: int): string +{ + if(base == 10) + return sys->sprint("%g", v); + neg := 0; + if(v < 0.0){ + neg = 1; + v = -v; + } + if(!isint(v)){ + n := 0; + lg := maths->log(v)/maths->log(real base); + if(lg < 0.0){ + (n, nil) = split(-lg); + v *= real base**n; + n = -n; + } + else{ + (n, nil) = split(lg); + v /= real base**n; + } + s := cvt(v, base) + "E" + string n; + if(neg) + s = "-" + s; + return s; + } + (n, f) := split(v); + s := ""; + do{ + r := n%base; + n /= base; + s[len s] = n2c(r); + }while(n != 0); + ls := len s; + for(i := 0; i < ls/2; i++){ + t := s[i]; + s[i] = s[ls-1-i]; + s[ls-1-i] = t; + } + if(f != 0.0){ + s[len s] = '.'; + for(i = 0; i < 16 && f != 0.0; i++){ + f *= real base; + (n, f) = split(f); + s[len s] = n2c(n); + } + } + s = string base + "r" + s; + if(neg) + s = "-" + s; + return s; +} + +printnum(v: real, s: string) +{ + base := int pbase.val; + if(!isinteger(pbase.val) || base < 2 || base > 36) + base = 10; + sys->print("%s%s", cvt(v, base), s); + if(bits){ + r := array[1] of real; + b := array[8] of byte; + r[0] = v; + maths->export_real(b, r); + for(i := 0; i < 8; i++) + sys->print("%2.2x ", int b[i]); + sys->print("\n"); + } +} + +Left, Right, Pre, Post: con 1<<iota; + +lspace := array[] of { 0, 0, 2, 3, 4, 5, 0, 0, 0, 9, 10, 0, 0, 0, 0, 0, 0, 0 }; +rspace := array[] of { 0, 1, 2, 3, 4, 5, 0, 0, 0, 9, 10, 0, 0, 0, 0, 0, 0, 0 }; + +preced(op1: int, op2: int, s: int): int +{ + br := 0; + p1 := prec(op1); + p2 := prec(op2); + if(p1 > p2) + br = 1; + else if(p1 == p2){ + if(op1 == op2){ + if(rassoc(op1)) + br = s == Left; + else + br = s == Right && !assoc(op1); + } + else{ + if(rassoc(op1)) + br = s == Left; + else + br = s == Right && op1 != Oadd; + if(postunary(op1) && preunary(op2)) + br = 1; + } + } + return br; +} + +prnode(n: ref Node) +{ + pnode(n, Onothing, Pre); + sys->print("\n"); +} + +pnode(n: ref Node, opp: int, s: int) +{ + if(n == nil) + return; + op := n.op; + if(br := preced(opp, op, s)) + sys->print("("); + if(op == Oas && n.right.op >= Oadd && n.right.op <= Orsh && n.left == n.right.left){ + pnode(n.left, op, Left); + sys->print(" %s ", opstring(n.right.op+Oadde-Oadd)); + pnode(n.right.right, op, Right); + } + else if(binary(op)){ + p := prec(op); + pnode(n.left, op, Left); + if(lspace[p]) + sys->print(" "); + sys->print("%s", opstring(op)); + if(rspace[p]) + sys->print(" "); + pnode(n.right, op, Right); + } + else if(op == Oinv){ # cannot print postunary -1 + sys->print("%s", opstring(op)); + pnode(n.left, Odiv, Right); + } + else if(preunary(op)){ + sys->print("%s", opstring(op)); + pnode(n.left, op, Pre); + } + else if(postunary(op)){ + pnode(n.left, op, Post); + sys->print("%s", opstring(op)); + } + else{ + case(op){ + Ostring => + sys->print("%s", n.str); + Onum => + sys->print("%g", n.val); + Ocon or + Ovar => + sys->print("%s", n.dec.sym.name); + Ofun or + Olfun => + sys->print("%s(", n.dec.sym.name); + pnode(n.left, Onothing, Pre); + sys->print(")"); + * => + fatal(sys->sprint("bad op %s in pnode()", opstring(op))); + } + } + if(br) + sys->print(")"); +} diff --git a/appl/cmd/md5sum.b b/appl/cmd/md5sum.b new file mode 100644 index 00000000..399ba354 --- /dev/null +++ b/appl/cmd/md5sum.b @@ -0,0 +1,65 @@ +implement MD5sum; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +MD5sum: module +{ + init: fn(nil : ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + kr = load Keyring Keyring->PATH; + a := tl argv; + err := 0; + if(a != nil){ + for( ; a != nil; a = tl a) { + s := hd a; + fd := sys->open(s, Sys->OREAD); + if (fd == nil) { + sys->fprint(stderr, "md5sum: cannot open %s: %r\n", s); + err = 1; + } else + err |= md5sum(fd, s); + } + } else + err |= md5sum(sys->fildes(0), ""); + if(err) + raise "fail:error"; +} + +md5sum(fd: ref Sys->FD, file: string): int +{ + err := 0; + buf := array[Sys->ATOMICIO] of byte; + state: ref Keyring->DigestState = nil; + nbytes := big 0; + while((nr := sys->read(fd, buf, len buf)) > 0){ + state = kr->md5(buf, nr, nil, state); + nbytes += big nr; + } + if(nr < 0) { + sys->fprint(stderr, "md5sum: error reading %s: %r\n", file); + err = 1; + } + digest := array[Keyring->MD5dlen] of byte; + kr->md5(buf, 0, digest, state); + sum := ""; + for(i:=0; i<len digest; i++) + sum += sys->sprint("%2.2ux", int digest[i]); + if(file != nil) + sys->print("%s\t%s\n", sum, file); + else + sys->print("%s\n", sum); + return err; +} diff --git a/appl/cmd/mdb.b b/appl/cmd/mdb.b new file mode 100644 index 00000000..71938af5 --- /dev/null +++ b/appl/cmd/mdb.b @@ -0,0 +1,335 @@ +implement Mdb; + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; + print, sprint: import sys; + +include "draw.m"; +include "string.m"; + str: String; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +Mdb: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +mfd: ref Sys->FD; +dot := 0; +lastaddr := 0; +count := 1; + +atoi(s: string): int +{ + b := 10; + if(s == nil) + return 0; + if(s[0] == '0') { + b = 8; + s = s[1:]; + if(s == nil) + return 0; + if(s[0] == 'x' || s[0] == 'X') { + b = 16; + s = s[1:]; + } + } + n: int; + (n, nil) = str->toint(s, b); + return n; +} + +eatws(s: string): string +{ + for (i := 0; i < len s; i++) + if (s[i] != ' ' && s[i] != '\t') + return s[i:]; + return nil; +} + +eatnum(s: string): string +{ + if(len s == 0) + return s; + while(gotnum(s) || gotalpha(s)) + s = s[1:]; + return s; +} + +gotnum(s: string): int +{ + if(len s == 0) + return 0; + if(s[0] >= '0' && s[0] <= '9') + return 1; + else + return 0; +} + +gotalpha(s: string): int +{ + if(len s == 0) + return 0; + if((s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z')) + return 1; + else + return 0; +} + +getexpr(s: string): (string, int, int) +{ + ov: int; + v := 0; + op := '+'; + for(;;) { + ov = v; + s = eatws(s); + if(s == nil) + return (nil, 0, 0); + if(s[0] == '.' || s[0] == '+' || s[0] == '^') { + v = dot; + s = s[1:]; + } else if(s[0] == '"') { + v = lastaddr; + s = s[1:]; + } else if(s[0] == '(') { + (s, v, nil) = getexpr(s[1:]); + s = s[1:]; + } else if(gotnum(s)) { + v = atoi(s); + s = eatnum(s); + } else + return (s, 0, 0); + case op { + '+' => v = ov+v; + '-' => v = ov-v; + '*' => v = ov*v; + '%' => v = ov/v; + '&' => v = ov&v; + '|' => v = ov|v; + } + if(s == nil) + return (nil, v, 1); + case s[0] { + '+' or '-' or '*' or '%' or '&' or '|' => + op = s[0]; s = s[1:]; + * => + return (eatws(s), v, 1); + } + } +} + +lastcmd := ""; + +docmd(s: string) +{ + ok: int; + n: int; + s = eatws(s); + (s, n, ok) = getexpr(s); + if(ok) { + dot = n; + lastaddr = n; + } + count = 1; + if(s != nil && s[0] == ',') { + (s, n, ok) = getexpr(s[1:]); + if(ok) + count = n; + } + if(s == nil && (s = lastcmd) == nil) + return; + lastcmd = s; + cmd := s[0]; + case cmd { + '?' or '/' => + case s[1] { + 'w' => + writemem(2, s[2:]); + 'W' => + writemem(4, s[2:]); + * => + dumpmem(s[1:], cmd); + } + '=' => + dumpmem(s[1:], cmd); + * => + sys->fprint(stderr, "invalid cmd: %c\n", cmd); + } +} + +octal(n: int, d: int): string +{ + s: string; + do { + s = string (n%8) + s; + n /= 8; + } while(d-- > 1); + return "0" + s; +} + +printable(c: int): string +{ + case c { + 32 to 126 => + return sprint("%c", c); + '\n' => + return "\\n"; + '\r' => + return "\\r"; + '\b' => + return "\\b"; + '\a' => + return "\\a"; + '\v' => + return "\\v"; + * => + return sprint("\\x%2.2x", c); + } + +} + +dumpmem(s: string, t: int) +{ + n := 0; + c := count; + while(c-- > 0) for(p:=0; p<len s; p++) { + fmt := s[p]; + case fmt { + 'b' or 'c' or 'C' => + n = 1; + 'x' or 'd' or 'u' or 'o' => + n = 2; + 'X' or 'D' or 'U' or 'O' => + n = 4; + 's' or 'S' or 'r' or 'R' => + print("'%c' format not yet supported\n", fmt); + continue; + 'n' => + print("\n"); + continue; + '+' => + dot++; + continue; + '-' => + dot--; + continue; + '^' => + dot -= n; + continue; + * => + print("unknown format '%c'\n", fmt); + continue; + } + b := array[n] of byte; + v: int; + if(t == '=') + v = dot; + else { + sys->seek(mfd, big dot, Sys->SEEKSTART); + sys->read(mfd, b, len b); + v = 0; + for(i := 0; i < n; i++) + v |= int b[i] << (8*i); + } + case fmt { + 'c' => print("%c", v); + 'C' => print("%s", printable(v)); + 'b' => print("%#2.2ux ", v); + 'x' => print("%#4.4ux ", v); + 'X' => print("%#8.8ux ", v); + 'd' => print("%-4d ", v); + 'D' => print("%-8d ", v); + 'u' => print("%-4ud ", v); + 'U' => print("%-8ud ", v); + 'o' => print("%s ", octal(v, 6)); + 'O' => print("%s ", octal(v, 11)); + } + if(t != '=') + dot += n; + } + print("\n"); +} + +writemem(n: int, s: string) +{ + v: int; + ok: int; + s = eatws(s); + sys->seek(mfd, big dot, Sys->SEEKSTART); + for(;;) { + (s, v, ok) = getexpr(s); + if(!ok) + return; + b := array[n] of byte; + for(i := 0; i < n; i++) + b[i] = byte (v >> (8*i)); + if (sys->write(mfd, b, len b) != len b) + sys->fprint(stderr, "mdb: write error: %r\n"); + } +} + +usage() +{ + sys->fprint(stderr, "usage: mdb [-w] file [command]\n"); + raise "fail:usage"; +} + +writeable := 0; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + str = load String String->PATH; + if (str == nil) { + sys->fprint(stderr, "mdb: cannot load %s: %r\n", String->PATH); + raise "fail:bad module"; + } + bufio = load Bufio Bufio->PATH; + if (bufio == nil) { + sys->fprint(stderr, "mdb: cannot load %s: %r\n", Bufio->PATH); + raise "fail:bad module"; + } + + if (len argv < 2) + usage(); + if (argv != nil) + argv = tl argv; + if (argv != nil && len hd argv && (hd argv)[0] == '-') { + if (hd argv != "-w") + usage(); + writeable = 1; + argv = tl argv; + } + if (argv == nil) + usage(); + fname := hd argv; + argv = tl argv; + cmd := ""; + if(argv != nil) + cmd = hd argv; + + oflags := Sys->OREAD; + if (writeable) + oflags = Sys->ORDWR; + mfd = sys->open(fname, oflags); + if(mfd == nil) { + sys->fprint(stderr, "mdb: cannot open %s: %r\n", fname); + raise "fail:cannot open"; + } + + if(cmd != nil) + docmd(cmd); + else { + stdin := bufio->fopen(sys->fildes(0), Sys->OREAD); + while ((s := stdin.gets('\n')) != nil) { + if (s[len s -1] == '\n') + s = s[0:len s - 1]; + docmd(s); + } + } +} diff --git a/appl/cmd/memfs.b b/appl/cmd/memfs.b new file mode 100644 index 00000000..e18388c5 --- /dev/null +++ b/appl/cmd/memfs.b @@ -0,0 +1,648 @@ +implement MemFS; + +include "sys.m"; + sys: Sys; + OTRUNC, ORCLOSE, OREAD, OWRITE: import Sys; +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; +include "styxlib.m"; + styxlib: Styxlib; + Styxserver: import styxlib; +include "draw.m"; +include "arg.m"; + +MemFS: module { + init: fn(ctxt: ref Draw->Context, args: list of string); +}; + + +blksz : con 512; +Efull : con "filesystem full"; + +Memfile : adt { + name : string; + owner : string; + qid : Sys->Qid; + perm : int; + atime : int; + mtime : int; + nopen : int; + data : array of array of byte; # allocated in blks, no holes + length : int; + parent : cyclic ref Memfile; # Dir entry linkage + kids : cyclic ref Memfile; + prev : cyclic ref Memfile; + next : cyclic ref Memfile; + hashnext : cyclic ref Memfile; # Qid hash linkage +}; + +Qidhash : adt { + buckets : array of ref Memfile; + nextqid : int; + new : fn () : ref Qidhash; + add : fn (h : self ref Qidhash, mf : ref Memfile); + remove : fn (h : self ref Qidhash, mf : ref Memfile); + lookup : fn (h : self ref Qidhash, qid : Sys->Qid) : ref Memfile; +}; + +timefd: ref Sys->FD; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + styx = checkload(load Styx Styx->PATH, Styx->PATH); + styxlib = checkload(load Styxlib Styxlib->PATH, Styxlib->PATH); + arg := checkload(load Arg Arg->PATH, Arg->PATH); + + amode := Sys->MREPL; + maxsz := 16r7fffffff; + srv := 0; + mntpt := "/tmp"; + + arg->init(argv); + arg->setusage("memfs [-s] [-rab] [-m size] [mountpoint]"); + while((opt := arg->opt()) != 0) { + case opt{ + 's' => + srv = 1; + 'r' => + amode = Sys->MREPL; + 'a' => + amode = Sys->MAFTER; + 'b' => + amode = Sys->MBEFORE; + 'm' => + maxsz = int arg->earg(); + * => + arg->usage(); + } + } + argv = arg->argv(); + arg = nil; + if (argv != nil) + mntpt = hd argv; + + srvfd: ref Sys->FD; + mntfd: ref Sys->FD; + if (srv) + srvfd = sys->fildes(0); + else { + p := array [2] of ref Sys->FD; + if (sys->pipe(p) == -1) + error(sys->sprint("cannot create pipe: %r")); + mntfd = p[0]; + srvfd = p[1]; + } + styx->init(); + styxlib->init(styx); + timefd = sys->open("/dev/time", sys->OREAD); + + (tc, styxsrv) := Styxserver.new(srvfd); + if (srv) + memfs(maxsz, tc, styxsrv, nil); + else { + sync := chan of int; + spawn memfs(maxsz, tc, styxsrv, sync); + <-sync; + if (sys->mount(mntfd, nil, mntpt, amode | Sys->MCREATE, nil) == -1) + error(sys->sprint("failed to mount onto %s: %r", mntpt)); + } +} + +checkload[T](x: T, p: string): T +{ + if(x == nil) + error(sys->sprint("cannot load %s: %r", p)); + return x; +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + +error(e: string) +{ + sys->fprint(stderr(), "memfs: %s\n", e); + raise "fail:error"; +} + +freeblks: int; + +memfs(maxsz : int, tc : chan of ref Tmsg, srv : ref Styxserver, sync: chan of int) +{ + sys->pctl(Sys->NEWNS, nil); + if (sync != nil) + sync <-= 1; + freeblks = (maxsz / blksz); + qhash := Qidhash.new(); + + # init root + root := newmf(qhash, nil, "memfs", srv.uname, 8r755 | Sys->DMDIR); + root.parent = root; + + while((tmsg := <-tc) != nil) { +# sys->print("%s\n", tmsg.text()); + Msg: + pick tm := tmsg { + Readerror => + break; + Version => + srv.devversion(tm); + Auth => + srv.devauth(tm); + Flush => + srv.reply(ref Rmsg.Flush(tm.tag)); + Walk => + (err, c, mf) := fidtomf(srv, qhash, tm.fid); + if (err != "") { + srv.reply(ref Rmsg.Error(tm.tag, err)); + continue; + } + nc: ref styxlib->Chan; + if (tm.newfid != tm.fid) { + nc = srv.clone(c, tm.newfid); + if (nc == nil) { + srv.reply(ref Rmsg.Error(tm.tag, "fid in use")); + continue; + } + c = nc; + } + qids: array of Sys->Qid; + if (len tm.names > 0) { + oqid := c.qid; + opath := c.path; + qids = array[len tm.names] of Sys->Qid; + wmf := mf; + for (i := 0; i < len tm.names; i++) { + wmf = dirlookup(wmf, tm.names[i]); + if (wmf == nil) { + if (nc == nil) { + c.qid = oqid; + c.path = opath; + } else + srv.chanfree(nc); + if (i == 0) + srv.reply(ref Rmsg.Error(tm.tag, Styxlib->Enotfound)); + else + srv.reply(ref Rmsg.Walk(tm.tag, qids[0:i])); + break Msg; + } + c.qid = wmf.qid; + qids[i] = wmf.qid; + } + } + srv.reply(ref Rmsg.Walk(tm.tag, qids)); + Open => + (err, c, mf) := fidtomf(srv, qhash, tm.fid); + if (err == "" && c.open) + err = Styxlib->Eopen; + if (err == "" && !modeok(tm.mode, mf.perm, c.uname, mf.owner)) + err = Styxlib->Eperm; + if (err == "" && (mf.perm & Sys->DMDIR) && (tm.mode & (OTRUNC|OWRITE|ORCLOSE))) + err = Styxlib->Eperm; + if (err == "" && (tm.mode & ORCLOSE)) { + p := mf.parent; + if (p == nil || !modeok(OWRITE, p.perm, c.uname, p.owner)) + err = Styxlib->Eperm; + } + + if (err != "") { + srv.reply(ref Rmsg.Error(tm.tag, err)); + continue; + } + + c.open = 1; + c.mode = tm.mode; + c.qid.vers = mf.qid.vers; + mf.nopen++; + if (tm.mode & OTRUNC) { + # OTRUNC cannot be set for a directory + # always at least one blk so don't need to check fs limit + freeblks += (len mf.data); + mf.data = nil; + freeblks--; + mf.data = array[1] of {* => array [blksz] of byte}; + mf.length = 0; + mf.mtime = now(); + } + srv.reply(ref Rmsg.Open(tm.tag, mf.qid, Styx->MAXFDATA)); + Create => + (err, c, parent) := fidtomf(srv, qhash, tm.fid); + if (err == "" && c.open) + err = Styxlib->Eopen; + if (err == "" && !(parent.qid.qtype & Sys->QTDIR)) + err = Styxlib->Enotdir; + if (err == "" && !modeok(OWRITE, parent.perm, c.uname, parent.owner)) + err = Styxlib->Eperm; + if (err == "" && (tm.perm & Sys->DMDIR) && (tm.mode & (OTRUNC|OWRITE|ORCLOSE))) + err = Styxlib->Eperm; + if (err == "" && dirlookup(parent, tm.name) != nil) + err = Styxlib->Eexists; + + if (err != "") { + srv.reply(ref Rmsg.Error(tm.tag, err)); + continue; + } + + isdir := tm.perm & Sys->DMDIR; + if (!isdir && freeblks <= 0) { + srv.reply(ref Rmsg.Error(tm.tag, Efull)); + continue; + } + + # modify perms as per Styx specification... + perm : int; + if (isdir) + perm = (tm.perm&~8r777) | (parent.perm&tm.perm&8r777); + else + perm = (tm.perm&(~8r777|8r111)) | (parent.perm&tm.perm& 8r666); + + nmf := newmf(qhash, parent, tm.name, c.uname, perm); + if (!isdir) { + freeblks--; + nmf.data = array[1] of {* => array [blksz] of byte}; + } + + # link in the new MemFile + nmf.next = parent.kids; + if (parent.kids != nil) + parent.kids.prev = nmf; + parent.kids = nmf; + + c.open = 1; + c.mode = tm.mode; + c.qid = nmf.qid; + nmf.nopen = 1; + srv.reply(ref Rmsg.Create(tm.tag, nmf.qid, Styx->MAXFDATA)); + Read => + (err, c, mf) := fidtomf(srv, qhash, tm.fid); + if (err == "" && !c.open) + err = Styxlib->Ebadfid; + + if (err != "") { + srv.reply(ref Rmsg.Error(tm.tag, err)); + continue; + } + data: array of byte = nil; + if (mf.perm & Sys->DMDIR) + data = dirdata(mf, int tm.offset, tm.count); + else + data = filedata(mf, int tm.offset, tm.count); + mf.atime = now(); + srv.reply(ref Rmsg.Read(tm.tag, data)); + Write => + (err, c, mf) := fidtomf(srv, qhash, tm.fid); + if (c != nil && !c.open) + err = Styxlib->Ebadfid; + if (err == nil && (mf.perm & Sys->DMDIR)) + err = Styxlib->Eperm; + if (err == nil) + err = writefile(mf, int tm.offset, tm.data); + if (err != nil) { + srv.reply(ref Rmsg.Error(tm.tag, err)); + continue; + } + srv.reply(ref Rmsg.Write(tm.tag, len tm.data)); + Clunk => + (err, c, mf) := fidtomf(srv, qhash, tm.fid); + if (c != nil) + srv.chanfree(c); + if (err != nil) { + srv.reply(ref Rmsg.Error(tm.tag, err)); + continue; + } + if (c.open) { + if (c.mode & ORCLOSE) + unlink(mf); + mf.nopen--; + freeblks += delfile(qhash, mf); + } + srv.reply(ref Rmsg.Clunk(tm.tag)); + Stat => + (err, c, mf) := fidtomf(srv, qhash, tm.fid); + if (err != nil) { + srv.reply(ref Rmsg.Error(tm.tag, err)); + continue; + } + srv.reply(ref Rmsg.Stat(tm.tag, fileinfo(mf))); + Remove => + (err, c, mf) := fidtomf(srv, qhash, tm.fid); + if (err != nil) { + srv.reply(ref Rmsg.Error(tm.tag, err)); + continue; + } + srv.chanfree(c); + parent := mf.parent; + if (!modeok(OWRITE, parent.perm, c.uname, parent.owner)) + err = Styxlib->Eperm; + if (err == "" && (mf.perm & Sys->DMDIR) && mf.kids != nil) + err = "directory not empty"; + if (err == "" && mf == root) + err = "root directory"; + if (err != nil) { + srv.reply(ref Rmsg.Error(tm.tag, err)); + continue; + } + + unlink(mf); + if (c.open) + mf.nopen--; + freeblks += delfile(qhash, mf); + srv.reply(ref Rmsg.Remove(tm.tag)); + Wstat => + (err, c, mf) := fidtomf(srv, qhash, tm.fid); + stat := tm.stat; + perm := mf.perm & ~Sys->DMDIR; + if (err == nil && stat.name != mf.name) { + parent := mf.parent; + if (!modeok(OWRITE, parent.perm, c.uname, parent.owner)) + err = Styxlib->Eperm; + else if (dirlookup(parent, stat.name) != nil) + err = Styxlib->Eexists; + } + if (err == nil && (stat.mode != mf.perm || stat.mtime != mf.mtime)) { + if (c.uname != mf.owner) + err = Styxlib->Eperm; + } + if (err != nil) { + srv.reply(ref Rmsg.Error(tm.tag, err)); + continue; + } + isdir := mf.perm & Sys->DMDIR; + if(stat.name != nil) + mf.name = stat.name; + if(stat.mode != ~0) + mf.perm = stat.mode | isdir; + if(stat.mtime != ~0) + mf.mtime = stat.mtime; + if(stat.uid != nil) + mf.owner = stat.uid; + t := now(); + mf.atime = t; + mf.parent.mtime = t; + # not supporting group id at the moment + srv.reply(ref Rmsg.Wstat(tm.tag)); + Attach => + c := srv.newchan(tm.fid); + if (c == nil) { + srv.reply(ref Rmsg.Error(tm.tag, Styxlib->Einuse)); + continue; + } + c.uname = tm.uname; + c.qid = root.qid; + srv.reply(ref Rmsg.Attach(tm.tag, c.qid)); + } + } +} + +writefile(mf: ref Memfile, offset: int, data: array of byte): string +{ + if(mf.perm & Sys->DMAPPEND) + offset = mf.length; + startblk := offset/blksz; + nblks := ((len data + offset) - (startblk * blksz))/blksz; + lastblk := startblk + nblks; + need := lastblk + 1 - len mf.data; + if (need > 0) { + if (need > freeblks) + return Efull; + mf.data = (array [lastblk+1] of array of byte)[:] = mf.data; + freeblks -= need; + } + mf.length = max(mf.length, offset + len data); + + # handle (possibly incomplete first block) separately + offset %= blksz; + end := min(blksz-offset, len data); + if (mf.data[startblk] == nil) + mf.data[startblk] = array [blksz] of byte; + mf.data[startblk++][offset:] = data[:end]; + + ix := blksz - offset; + while (ix < len data) { + if (mf.data[startblk] == nil) + mf.data[startblk] = array [blksz] of byte; + end = min(ix+blksz,len data); + mf.data[startblk++][:] = data[ix:end]; + ix += blksz; + } + mf.mtime = now(); + return nil; +} + +filedata(mf: ref Memfile, offset, n: int): array of byte +{ + if (offset +n > mf.length) + n = mf.length - offset; + if (n == 0) + return nil; + + data := array [n] of byte; + startblk := offset/blksz; + offset %= blksz; + rn := min(blksz - offset, n); + data[:] = mf.data[startblk++][offset:offset+rn]; + ix := blksz - offset; + while (ix < n) { + rn = blksz; + if (ix+rn > n) + rn = n - ix; + data[ix:] = mf.data[startblk++][:rn]; + ix += blksz; + } + return data; +} + +QHSIZE: con 256; +QHMASK: con QHSIZE-1; + +Qidhash.new() : ref Qidhash +{ + qh := ref Qidhash; + qh.buckets = array [QHSIZE] of ref Memfile; + qh.nextqid = 0; + return qh; +} + +Qidhash.add(h : self ref Qidhash, mf : ref Memfile) +{ + path := h.nextqid++; + mf.qid = Sys->Qid(big path, 0, Sys->QTFILE); + bix := path & QHMASK; + mf.hashnext = h.buckets[bix]; + h.buckets[bix] = mf; +} + +Qidhash.remove(h : self ref Qidhash, mf : ref Memfile) +{ + + bix := int mf.qid.path & QHMASK; + prev : ref Memfile; + for (cur := h.buckets[bix]; cur != nil; cur = cur.hashnext) { + if (cur == mf) + break; + prev = cur; + } + if (cur != nil) { + if (prev != nil) + prev.hashnext = cur.hashnext; + else + h.buckets[bix] = cur.hashnext; + cur.hashnext = nil; + } +} + +Qidhash.lookup(h : self ref Qidhash, qid : Sys->Qid) : ref Memfile +{ + bix := int qid.path & QHMASK; + for (mf := h.buckets[bix]; mf != nil; mf = mf.hashnext) + if (mf.qid.path == qid.path) + break; + return mf; +} + +newmf(qh : ref Qidhash, parent : ref Memfile, name, owner : string, perm : int) : ref Memfile +{ + # qid gets set by Qidhash.add() + t := now(); + mf := ref Memfile (name, owner, Sys->Qid(big 0,0,Sys->QTFILE), perm, t, t, 0, nil, 0, parent, nil, nil, nil, nil); + qh.add(mf); + if(perm & Sys->DMDIR) + mf.qid.qtype = Sys->QTDIR; + return mf; +} + +fidtomf(srv : ref Styxserver, qh : ref Qidhash, fid : int) : (string, ref Styxlib->Chan, ref Memfile) +{ + c := srv.fidtochan(fid); + if (c == nil) + return (Styxlib->Ebadfid, nil, nil); + mf := qh.lookup(c.qid); + if (mf == nil) + return (Styxlib->Enotfound, c, nil); + return (nil, c, mf); +} + +unlink(mf : ref Memfile) +{ + parent := mf.parent; + if (parent == nil) + return; + if (mf.next != nil) + mf.next.prev = mf.prev; + if (mf.prev != nil) + mf.prev.next = mf.next; + else + mf.parent.kids = mf.next; + mf.parent = nil; + mf.prev = nil; + mf.next = nil; +} + +delfile(qh : ref Qidhash, mf : ref Memfile) : int +{ + if (mf.nopen <= 0 && mf.parent == nil && mf.kids == nil + && mf.prev == nil && mf.next == nil) { + qh.remove(mf); + nblks := len mf.data; + mf.data = nil; + return nblks; + } + return 0; +} + +dirlookup(dir : ref Memfile, name : string) : ref Memfile +{ + if (name == ".") + return dir; + if (name == "..") + return dir.parent; + for (mf := dir.kids; mf != nil; mf = mf.next) { + if (mf.name == name) + break; + } + return mf; +} + +access := array[] of {8r400, 8r200, 8r600, 8r100}; +modeok(mode, perm : int, user, owner : string) : int +{ + if(mode >= (OTRUNC|ORCLOSE|OREAD|OWRITE)) + return 0; + + # not handling groups! + if (user != owner) + perm <<= 6; + + if ((mode & OTRUNC) && !(perm & 8r200)) + return 0; + + a := access[mode &3]; + if ((a & perm) != a) + return 0; + return 1; +} + +dirdata(dir : ref Memfile, start, n : int) : array of byte +{ + data := array[Styx->MAXFDATA] of byte; + for (k := dir.kids; start > 0 && k != nil; k = k.next) { + a := styx->packdir(fileinfo(k)); + start -= len a; + } + r := 0; + for (; r < n && k != nil; k = k.next) { + a := styx->packdir(fileinfo(k)); + if(r+len a > n) + break; + data[r:] = a; + r += len a; + } + return data[0:r]; +} + +fileinfo(f : ref Memfile) : Sys->Dir +{ + dir := sys->zerodir; + dir.name = f.name; + dir.uid = f.owner; + dir.gid = "memfs"; + dir.qid = f.qid; + dir.mode = f.perm; + dir.atime = f.atime; + dir.mtime = f.mtime; + dir.length = big f.length; + dir.dtype = 0; + dir.dev = 0; + return dir; +} + +min(a, b : int) : int +{ + if (a < b) + return a; + return b; +} + +max(a, b : int) : int +{ + if (a > b) + return a; + return b; +} + +now(): int +{ + if (timefd == nil) + return 0; + buf := array[128] of byte; + sys->seek(timefd, big 0, 0); + n := sys->read(timefd, buf, len buf); + if(n < 0) + return 0; + + t := (big string buf[0:n]) / big 1000000; + return int t; +} diff --git a/appl/cmd/metamorph.b b/appl/cmd/metamorph.b new file mode 100644 index 00000000..9b693c85 --- /dev/null +++ b/appl/cmd/metamorph.b @@ -0,0 +1,94 @@ +implement metamorph; + +include "sys.m"; +include "draw.m"; +include "bufio.m"; +include "string.m"; +include "imagefile.m"; + +sys: Sys; +bufio: Bufio; +str: String; +draw: Draw; + +FD: import sys; +Display: import draw; + +stderr: ref FD; + +metamorph: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + str = load String String->PATH; + if (bufio == nil) { + sys->fprint(stderr, "could not load %s: %r\n", Bufio->PATH); + exit; + } + draw = load Draw Draw->PATH; + if (draw == nil) { + sys->fprint(stderr, "could not load %s: %r\n", Draw->PATH); + exit; + } + ri := load RImagefile RImagefile->READGIFPATH; + if (ri == nil) { + sys->fprint(stderr, "could not load %s: %r\n", RImagefile->READGIFPATH); + exit; + } + ir := load Imageremap Imageremap->PATH; + if (ir == nil) { + sys->fprint(stderr, "could not load %s: %r\n", Imageremap->PATH); + exit; + } + + if (len args < 2) { + sys->fprint(stderr, "Metamorph Usage:\n metamorph <# of slides>\n\n"); + return; + } + + infile :string; + + + (numslides, nil) := str->toint((hd (tl args)), 10); + + for (count := 1;count <=numslides; count++) { + + ri->init(bufio); + + if ( count < 10 ) + infile= sys->sprint("img00%d.GIF",count); + if (( count >= 10 ) && ( count < 100)) + infile= sys->sprint("img0%d.GIF",count); + if (count >= 100) + infile= sys->sprint("img%d.GIF",count); + + outfile := sys->sprint("img%d.bit",count); + + inf := bufio->open(infile, Bufio->OREAD); + sys->print ("Reading %s\n",infile); + if (inf == nil) { + sys->fprint(stderr, "could not fopen(0): %r\n"); + exit; + } + (gif, s) := ri->read(inf); + if (gif == nil) { + sys->fprint(stderr, "bad GIF: %s\n", s); + exit; + } + (im, e) := ir->remap(gif, ctxt.display, 1); + if (im == nil) { + sys->fprint(stderr, "bad remap: %s\n", e); + exit; + } + sys->print("Writing %s\n",outfile); + outf := sys->create(outfile, sys->OWRITE,438); + ctxt.display.writeimage(outf, im); + outf = nil; + } +} diff --git a/appl/cmd/mk/ar.m b/appl/cmd/mk/ar.m new file mode 100644 index 00000000..dfa686ae --- /dev/null +++ b/appl/cmd/mk/ar.m @@ -0,0 +1,26 @@ +# +# initially generated by c2l +# + +Ar: module +{ + PATH: con "ar.dis"; + + ARMAG: con "!<arch>\n"; + SARMAG: con 8; + ARFMAG: con "`\n"; + SARNAME: con 16; + + ar_hdr: adt{ + name: array of byte; # SARNAME + date: array of byte; # 12 + uid: array of byte; # 6 + gid: array of byte; # 6 + mode: array of byte; # 8 + size: array of byte; # 10 + fmag: array of byte; # 2 + }; + + SAR_HDR: con 60; + +}; diff --git a/appl/cmd/mk/mk.b b/appl/cmd/mk/mk.b new file mode 100644 index 00000000..49a5c1a2 --- /dev/null +++ b/appl/cmd/mk/mk.b @@ -0,0 +1,4211 @@ +# +# 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 "math.m"; + math: Math; +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; + math = load Math Math->PATH; + regex = load Regex Regex->PATH; + daytime = load Daytime Daytime->PATH; + 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<<iota; +NREGEXP: con 10; + +Arc: adt{ + flag: int; + n: cyclic ref Node; + r: ref Rule; + stem: array of byte; + prog: array of byte; + match: array of array of byte; + next: cyclic ref Arc; +}; + +# Arc.flag +TOGO: con 1; + +Node: adt{ + name: array of byte; + time: int; + flags: int; + prereqs: cyclic ref Arc; + next: cyclic ref Node; # list for a rule +}; + +# Node.flags +VIRTUAL, CYCLE, READY, CANPRETEND, PRETENDING, NOTMADE, BEINGMADE, MADE, PROBABLE, VACUOUS, NORECIPE, DELETE, NOMINUSE: con 1<<iota; + +Job: adt{ + r: ref Rule; # master rule for job + n: ref Node; # list of node targets + stem: array of byte; + match: array of array of byte; + p: ref Word; # prerequistes + np: ref Word; # new prerequistes + t: ref Word; # targets + at: ref Word; # all targets + nproc: int; # slot number + next: cyclic ref Job; +}; + +Symtab: adt{ + space: int; + name: array of byte; + svalue: array of byte; + ivalue: int; + nvalue: ref Node; + rvalue: ref Rule; + wvalue: ref Word; + next: cyclic ref Symtab; +}; + +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_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<<iota; + +MKFILE: con "mkfile"; + +version := array[] of { byte '@', byte '(', byte '#', byte ')', byte 'm', byte 'k', byte ' ', byte 'g', byte 'e', byte 'n', byte 'e', byte 'r', byte 'a', byte 'l', byte ' ', byte 'r', byte 'e', byte 'l', byte 'e', byte 'a', byte 's', byte 'e', byte ' ', byte '4', byte ' ', byte '(', byte 'p', byte 'l', byte 'a', byte 'n', byte ' ', byte '9', byte ')', byte '\0' }; +debug: int; +rules, metarules: ref Rule; +nflag: int = 0; +tflag: int = 0; +iflag: int = 0; +kflag: int = 0; +aflag: int = 0; +uflag: int = 0; +explain: array of byte = nil; +target1: ref Word; +nreps: int = 1; +jobs: ref Job; +bout: ref Iobuf; +patrule: ref Rule; + +main(argc: int, argv: array of array of byte) +{ + w: ref Word; + s, temp: array of byte; + files := array[256] of array of byte; + f: array of array of byte = files; + ff: int; + sflag: int = 1; + i: int; + tfd: ref Sys->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("/appl/cmd/mk/mkbinds", 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 + 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; +} + diff --git a/appl/cmd/mk/mkbinds b/appl/cmd/mk/mkbinds new file mode 100644 index 00000000..a5df28dc --- /dev/null +++ b/appl/cmd/mk/mkbinds @@ -0,0 +1,2 @@ +/appl/cmd/mk/mkconfig /mkconfig +/appl/cmd/mk/mksubdirs /mkfiles/mksubdirs diff --git a/appl/cmd/mk/mkconfig b/appl/cmd/mk/mkconfig new file mode 100644 index 00000000..17d31a93 --- /dev/null +++ b/appl/cmd/mk/mkconfig @@ -0,0 +1,28 @@ +# +# Set the following 4 variables. The host system is the system where +# the software will be built; the target system is where it will run. +# They are almost always the same. + +# On Nt systems, the ROOT path MUST be of the form `drive:/path' +ROOT= + +# +# Except for building kernels, SYSTARG must always be the same as SYSHOST +# +SYSHOST=Plan9 # build system OS type (Hp, Inferno, Irix, Linux, Nt, Plan9, Solaris) +SYSTARG=$SYSHOST # target system OS type (Hp, Inferno, Irix, Linux, Nt, Plan9, Solaris) + +# +# specify the architecture of the target system - Inferno imports it from the +# environment; for other systems it is usually just hard-coded +# +#OBJTYPE=386 # target system object type (s800, mips, 386, arm, sparc) +OBJTYPE=386 + +# +# no changes required beyond this point +# +OBJDIR=$SYSTARG/$OBJTYPE + +<$ROOT/mkfiles/mkhost-$SYSHOST # variables appropriate for host system +<$ROOT/mkfiles/mkfile-$SYSTARG-$OBJTYPE # variables used to build target object type diff --git a/appl/cmd/mk/mkfile b/appl/cmd/mk/mkfile new file mode 100644 index 00000000..51d20245 --- /dev/null +++ b/appl/cmd/mk/mkfile @@ -0,0 +1,19 @@ +<../../../mkconfig + +TARG= mk.dis\ + +MODULES=\ + ar.m\ + +SYSMODULES= \ + bufio.m\ + draw.m\ + math.m\ + sys.m\ + regex.m\ + daytime.m\ + libc0.m\ + +DISBIN=$ROOT/dis + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/mk/mksubdirs b/appl/cmd/mk/mksubdirs new file mode 100644 index 00000000..3fe01c81 --- /dev/null +++ b/appl/cmd/mk/mksubdirs @@ -0,0 +1,16 @@ +all:V: all-$SHELLTYPE +install:V: install-$SHELLTYPE +uninstall:V: uninstall-$SHELLTYPE +nuke:V: nuke-$SHELLTYPE +clean:V: clean-$SHELLTYPE + +%-rc %-nt %-sh:QV: + load std + for j in $DIRS { + if { ftest -d $j } { + echo 'cd' $j '; mk' $MKFLAGS $stem + cd $j; mk $MKFLAGS $stem; cd .. + } else { + ! { ftest -e $j } + } + } diff --git a/appl/cmd/mkdir.b b/appl/cmd/mkdir.b new file mode 100644 index 00000000..21e03ee7 --- /dev/null +++ b/appl/cmd/mkdir.b @@ -0,0 +1,75 @@ +implement Mkdir; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + + +stderr: ref Sys->FD; + +Mkdir: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + if(argv == nil || (argv = tl argv) == nil) + exit; + pflag := 0; + if(hd argv == "-p"){ + pflag = 1; + argv = tl argv; + } + e := ""; + for(; argv != nil; argv = tl argv){ + dir := hd argv; + if(!pflag){ + (ok, d) := sys->stat(dir); + if(ok < 0){ + if(mkdir(dir) < 0) + e = "error"; + }else{ + sys->fprint(stderr, "mkdir: %s already exists\n", dir); + e = "error"; + } + }else if(mkpath(dir) < 0) + e = "error"; + } + if(e != nil) + raise "fail:"+e; +} + +mkpath(dir: string): int +{ + (nil, flds) := sys->tokenize(dir, "/"); + s := ""; + if(dir != "" && dir[0] != '/') + s = "."; + for(; flds != nil; flds = tl flds){ + s += "/"+hd flds; + (ok, d) := sys->stat(s); + if(ok < 0){ + if(mkdir(s) < 0) + return -1; + }else if((d.mode & Sys->DMDIR) == 0){ + sys->fprint(stderr, "mkdir: can't create %s: %s not a directory\n", dir, s); + return -1; + } + } + return 0; +} + +mkdir(dir: string): int +{ + f := sys->create(dir, Sys->OREAD, Sys->DMDIR + 8r777); + if(f == nil) { + sys->fprint(stderr, "mkdir: can't create %s: %r\n", dir); + return -1; + } + return 0; +} diff --git a/appl/cmd/mkfile b/appl/cmd/mkfile new file mode 100644 index 00000000..2bbdb938 --- /dev/null +++ b/appl/cmd/mkfile @@ -0,0 +1,219 @@ +<../../mkconfig + +DIRS=\ + auth\ + auxi\ + avr\ + disk\ + install\ + ip\ + lego\ + limbo\ + mash\ + mk\ + mpc\ + ndb\ + sh\ + spki\ + usb\ + +TARG=\ + 9660srv.dis\ + 9export.dis\ + 9srvfs.dis\ + 9win.dis\ + B.dis\ + archfs.dis\ + auplay.dis\ + auhdr.dis\ + basename.dis\ + bind.dis\ + # bit2gif.dis\ + broke.dis\ + bytes.dis\ + cal.dis\ + cat.dis\ + cd.dis\ + chgrp.dis\ + chmod.dis\ + cleanname.dis\ + cmp.dis\ + comm.dis\ + cook.dis\ + cprof.dis\ + cp.dis\ + cpu.dis\ + crypt.dis\ + date.dis\ + dbfs.dis\ + dd.dis\ + dial.dis\ + diff.dis\ + disdep.dis\ + disdump.dis\ + dossrv.dis\ + du.dis\ + echo.dis\ + ed.dis\ + emuinit.dis\ + env.dis\ + export.dis\ + fc.dis\ + fcp.dis\ + fmt.dis\ + fone.dis\ + fortune.dis\ + freq.dis\ + fs.dis\ + ftest.dis\ + ftpfs.dis\ + getauthinfo.dis\ + gettar.dis\ + # gif2bit.dis\ + grep.dis\ + gunzip.dis\ + gzip.dis\ + idea.dis\ + import.dis\ + iostats.dis\ + itest.dis\ + itreplay.dis\ + kill.dis\ + listen.dis\ + lockfs.dis\ + logfile.dis\ + look.dis\ + lookman.dis\ + lc.dis\ + ls.dis\ + lstar.dis\ + man.dis\ + man2txt.dis\ + mathcalc.dis\ + mc.dis\ + md5sum.dis\ + mdb.dis\ + memfs.dis\ + metamorph.dis\ + mkdir.dis\ + mntgen.dis\ + mount.dis\ + mouse.dis\ + mprof.dis\ + mv.dis\ + netkey.dis\ + netstat.dis\ + newer.dis\ + ns.dis\ + nsbuild.dis\ + os.dis\ + p.dis\ + pause.dis\ + plumb.dis\ + plumber.dis\ + prof.dis\ + ps.dis\ + puttar.dis\ + pwd.dis\ + ramfile.dis\ + randpass.dis\ + raw2iaf.dis\ + rawdbfs.dis\ + rcmd.dis\ + rdp.dis\ + read.dis\ + rioimport.dis\ + rm.dis\ + runas.dis\ + sed.dis\ + sendmail.dis\ + sha1sum.dis\ + shutdown.dis\ + sleep.dis\ + sort.dis\ + src.dis\ + stack.dis\ + stackv.dis\ + stream.dis\ + strings.dis\ + styxchat.dis\ + styxmon.dis\ + styxlisten.dis\ + sum.dis\ + tail.dis\ + tarfs.dis\ + tclsh.dis\ + tcs.dis\ + tee.dis\ + telnet.dis\ + test.dis\ + time.dis\ + timestamp.dis\ + tkcmd.dis\ + touch.dis\ + touchcal.dis\ + tokenize.dis\ + tr.dis\ + tsort.dis\ + unicode.dis\ + units.dis\ + uniq.dis\ + unmount.dis\ + uudecode.dis\ + uuencode.dis\ + wav2iaf.dis\ + wc.dis\ + webgrab.dis\ + wish.dis\ + wmexport.dis\ + wmimport.dis\ + xargs.dis\ + xd.dis\ + xmount.dis\ + yacc.dis\ + zeros.dis\ + +MODULES=\ + +SYSMODULES=\ + bufio.m\ + bundle.m\ + daytime.m\ + draw.m\ + env.m\ + filepat.m\ + filter.m\ + fslib.m\ + ir.m\ + keyring.m\ + man.m\ + newns.m\ + prefab.m\ + readdir.m\ + regex.m\ + security.m\ + sh.m\ + srv.m\ + string.m\ + styx.m\ + styxlib.m\ + sys.m\ + tk.m\ + tkclient.m\ + url.m\ + webget.m\ + workdir.m\ + +DISBIN=$ROOT/dis + +<$ROOT/mkfiles/mkdis +<$ROOT/mkfiles/mksubdirs + +auhdr.dis: auplay.dis + rm -f auhdr.dis && cp auplay.dis auhdr.dis + +dbfs.dis: $MODDIR/styxservers.m +rawdbfs.dis: $MODDIR/styxservers.m +import.dis: $MODDIR/encoding.m $MODDIR/factotum.m +basename.dis: $MODDIR/names.m +cleanname.dis: $MODDIR/names.m diff --git a/appl/cmd/mntgen.b b/appl/cmd/mntgen.b new file mode 100644 index 00000000..3f066425 --- /dev/null +++ b/appl/cmd/mntgen.b @@ -0,0 +1,188 @@ +implement Mntgen; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "styx.m"; + styx: Styx; + Rmsg, Tmsg: import styx; +include "styxservers.m"; + styxservers: Styxservers; + Ebadfid, Enotfound, Eopen, Einuse: import Styxservers; + Styxserver, readbytes, Navigator, Fid: import styxservers; + + nametree: Nametree; + Tree: import nametree; + +Mntgen: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +Qroot: con big 16rfffffff; + +badmodule(p: string) +{ + sys->fprint(sys->fildes(2), "cannot load %s: %r\n", p); + raise "fail:bad module"; +} +DEBUG: con 0; + +Entry: adt { + refcount: int; + path: big; +}; +refcounts := array[10] of Entry; +tree: ref Tree; +nav: ref Navigator; + +uniq: int; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + styx = load Styx Styx->PATH; + if (styx == nil) + badmodule(Styx->PATH); + styx->init(); + styxservers = load Styxservers Styxservers->PATH; + if (styxservers == nil) + badmodule(Styxservers->PATH); + styxservers->init(styx); + + nametree = load Nametree Nametree->PATH; + if (nametree == nil) + badmodule(Nametree->PATH); + nametree->init(); + + navop: chan of ref Styxservers->Navop; + (tree, navop) = nametree->start(); + nav = Navigator.new(navop); + (tchan, srv) := Styxserver.new(sys->fildes(0), nav, Qroot); + + tree.create(Qroot, dir(".", Sys->DMDIR | 8r555, Qroot)); + + for (;;) { + gm := <-tchan; + if (gm == nil) { + tree.quit(); + exit; + } + e := handlemsg(gm, srv, tree); + if (e != nil) + srv.reply(ref Rmsg.Error(gm.tag, e)); + } +} + +walk1(c: ref Fid, name: string): string +{ + if (name == ".."){ + if (c.path != Qroot) + decref(c.path); + c.walk(Sys->Qid(Qroot, 0, Sys->QTDIR)); + } else if (c.path == Qroot) { + (d, err) := nav.walk(c.path, name); + if (d == nil) + d = addentry(name); + else + incref(d.qid.path); + c.walk(d.qid); + } else + return Enotfound; + return nil; +} + +handlemsg(gm: ref Styx->Tmsg, srv: ref Styxserver, nil: ref Tree): string +{ + pick m := gm { + Walk => + c := srv.getfid(m.fid); + if(c == nil) + return Ebadfid; + if(c.isopen) + return Eopen; + if(m.newfid != m.fid){ + nc := srv.newfid(m.newfid); + if(nc == nil) + return Einuse; + c = c.clone(nc); + incref(c.path); + } + qids := array[len m.names] of Sys->Qid; + oldpath := c.path; + oldqtype := c.qtype; + incref(oldpath); + for (i := 0; i < len m.names; i++){ + err := walk1(c, m.names[i]); + if (err != nil){ + if(m.newfid != m.fid){ + decref(c.path); + srv.delfid(c); + } + c.path = oldpath; + c.qtype = oldqtype; + if(i == 0) + return err; + srv.reply(ref Rmsg.Walk(m.tag, qids[0:i])); + return nil; + } + qids[i] = Sys->Qid(c.path, 0, c.qtype); + } + decref(oldpath); + srv.reply(ref Rmsg.Walk(m.tag, qids)); + Clunk => + c := srv.clunk(m); + if (c != nil && c.path != Qroot) + decref(c.path); + * => + srv.default(gm); + } + return nil; +} + +addentry(name: string): ref Sys->Dir +{ + for (i := 0; i < len refcounts; i++) + if (refcounts[i].refcount == 0) + break; + if (i == len refcounts) { + refcounts = (array[len refcounts * 2] of Entry)[0:] = refcounts; + for (j := i; j < len refcounts; j++) + refcounts[j].refcount = 0; + } + d := dir(name, Sys->DMDIR|8r555, big i | (big uniq++ << 32)); + tree.create(Qroot, d); + refcounts[i] = (1, d.qid.path); + return ref d; +} + +incref(q: big) +{ + id := int q; + if (id >= 0 && id < len refcounts){ + refcounts[id].refcount++; + } +} + +decref(q: big) +{ + id := int q; + if (id >= 0 && id < len refcounts){ + if (--refcounts[id].refcount == 0) + tree.remove(refcounts[id].path); + } +} + +Blankdir: Sys->Dir; +dir(name: string, perm: int, qid: big): Sys->Dir +{ + d := Blankdir; + d.name = name; + d.uid = "me"; + d.gid = "me"; + d.qid.path = qid; + if (perm & Sys->DMDIR) + d.qid.qtype = Sys->QTDIR; + else + d.qid.qtype = Sys->QTFILE; + d.mode = perm; + return d; +} diff --git a/appl/cmd/mount.b b/appl/cmd/mount.b new file mode 100644 index 00000000..9eb6e3a9 --- /dev/null +++ b/appl/cmd/mount.b @@ -0,0 +1,348 @@ +implement Mount; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "keyring.m"; +include "security.m"; +include "factotum.m"; +include "styxconv.m"; +include "styxpersist.m"; +include "arg.m"; +include "sh.m"; + +Mount: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +verbose := 0; +doauth := 1; +do9 := 0; +oldstyx := 0; +persist := 0; +showstyx := 0; +quiet := 0; + +alg := "none"; +keyfile: string; +spec: string; +addr: string; + +fail(status, msg: string) +{ + sys->fprint(sys->fildes(2), "mount: %s\n", msg); + raise "fail:"+status; +} + +nomod(mod: string) +{ + fail("load", sys->sprint("can't load %s: %r", mod)); +} + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + + arg->init(args); + arg->setusage("mount [-a|-b] [-coA9] [-C cryptoalg] [-k keyfile] [-q] net!addr|file|{command} mountpoint [spec]"); + flags := 0; + while((o := arg->opt()) != 0){ + case o { + 'a' => + flags |= Sys->MAFTER; + 'b' => + flags |= Sys->MBEFORE; + 'c' => + flags |= Sys->MCREATE; + 'C' => + alg = arg->earg(); + 'k' or + 'f' => + keyfile = arg->earg(); + 'A' => + doauth = 0; + '9' => + doauth = 0; + do9 = 1; + 'o' => + oldstyx = 1; + 'v' => + verbose = 1; + 'P' => + persist = 1; + 'S' => + showstyx = 1; + 'q' => + quiet = 1; + * => + arg->usage(); + } + } + args = arg->argv(); + if(len args != 2){ + if(len args != 3) + arg->usage(); + spec = hd tl tl args; + } + arg = nil; + addr = hd args; + mountpoint := hd tl args; + + if(oldstyx && do9) + fail("usage", "cannot combine -o and -9 options"); + + fd := connect(ctxt, addr); + ok: int; + if(do9){ + fd = styxlog(fd); + factotum := load Factotum Factotum->PATH; + if(factotum == nil) + nomod(Factotum->PATH); + factotum->init(); + ok = factotum->mount(fd, mountpoint, flags, spec, nil).t0; + }else{ + err: string; + if(!persist){ + (fd, err) = authcvt(fd); + if(fd == nil) + fail("error", err); + } + fd = styxlog(fd); + ok = sys->mount(fd, nil, mountpoint, flags, spec); + } + if(ok < 0 && !quiet) + fail("mount failed", sys->sprint("mount failed: %r")); +} + +connect(ctxt: ref Draw->Context, dest: string): ref Sys->FD +{ + if(dest != nil && dest[0] == '{' && dest[len dest - 1] == '}'){ + if(persist) + fail("usage", "cannot persistently mount a command"); + doauth = 0; + return popen(ctxt, dest :: nil); + } + (n, nil) := sys->tokenize(dest, "!"); + if(n == 1){ + fd := sys->open(dest, Sys->ORDWR); + if(fd != nil){ + if(persist) + fail("usage", "cannot persistently mount a file"); + return fd; + } + if(dest[0] == '/') + fail("open failed", sys->sprint("can't open %s: %r", dest)); + } + svc := "styx"; + if(do9) + svc = "9fs"; + dest = netmkaddr(dest, "net", svc); + if(persist){ + styxpersist := load Styxpersist Styxpersist->PATH; + if(styxpersist == nil) + fail("load", sys->sprint("cannot load %s: %r", Styxpersist->PATH)); + sys->pipe(p := array[2] of ref Sys->FD); + (c, err) := styxpersist->init(p[0], do9, nil); + if(c == nil) + fail("error", "styxpersist: "+err); + spawn dialler(c, dest); + return p[1]; + } + (ok, c) := sys->dial(dest, nil); + if(ok < 0) + fail("dial failed", sys->sprint("can't dial %s: %r", dest)); + return c.dfd; +} + +dialler(dialc: chan of chan of ref Sys->FD, dest: string) +{ + while((reply := <-dialc) != nil){ + if(verbose) + sys->print("dialling %s\n", addr); + (ok, c) := sys->dial(dest, nil); + if(ok == -1){ + reply <-= nil; + continue; + } + (fd, err) := authcvt(c.dfd); + if(fd == nil && verbose) + sys->print("%s\n", err); + # XXX could check that user at the other end is still the same. + reply <-= fd; + } +} + +authcvt(fd: ref Sys->FD): (ref Sys->FD, string) +{ + err: string; + if(doauth){ + (fd, err) = authenticate(keyfile, alg, fd, addr); + if(fd == nil) + return (nil, err); + if(verbose) + sys->print("remote username is %s\n", err); + } + if(oldstyx) + return cvstyx(fd); + return (fd, nil); +} + +popen(ctxt: ref Draw->Context, argv: list of string): ref Sys->FD +{ + sh := load Sh Sh->PATH; + if(sh == nil) + nomod(Sh->PATH); + sync := chan of int; + fds := array[2] of ref Sys->FD; + sys->pipe(fds); + spawn runcmd(sh, ctxt, argv, fds[0], sync); + <-sync; + return fds[1]; +} + +runcmd(sh: Sh, ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD, sync: chan of int) +{ + sys->pctl(Sys->FORKFD, nil); + sys->dup(stdin.fd, 0); + stdin = nil; + sync <-= 0; + sh->run(ctxt, argv); +} + +cvstyx(fd: ref Sys->FD): (ref Sys->FD, string) +{ + styxconv := load Styxconv Styxconv->PATH; + if(styxconv == nil) + return (nil, sys->sprint("cannot load %s: %r", Styxconv->PATH)); + styxconv->init(); + p := array[2] of ref Sys->FD; + if(sys->pipe(p) < 0) + return (nil, sys->sprint("can't create pipe: %r")); + pidc := chan of int; + spawn styxconv->styxconv(p[1], fd, pidc); + p[1] = nil; + <-pidc; + return (p[0], nil); +} + +authenticate(keyfile, alg: string, dfd: ref Sys->FD, addr: string): (ref Sys->FD, string) +{ + cert : string; + + kr := load Keyring Keyring->PATH; + if(kr == nil) + return (nil, sys->sprint("cannot load %s: %r", Keyring->PATH)); + + kd := "/usr/" + user() + "/keyring/"; + if(keyfile == nil) { + cert = kd + netmkaddr(addr, "tcp", ""); + (ok, nil) := sys->stat(cert); + if (ok < 0) + cert = kd + "default"; + } + else if(len keyfile > 0 && keyfile[0] != '/') + cert = kd + keyfile; + else + cert = keyfile; + ai := kr->readauthinfo(cert); + if(ai == nil) + return (nil, sys->sprint("cannot read %s: %r", cert)); + + auth := load Auth Auth->PATH; + if(auth == nil) + nomod(Auth->PATH); + + err := auth->init(); + if(err != nil) + return (nil, "cannot init auth: "+err); + + fd: ref Sys->FD; + (fd, err) = auth->client(alg, ai, dfd); + if(fd == nil) + return (nil, "authentication failed: "+err); + return (fd, err); +} + +user(): string +{ + fd := sys->open("/dev/user", sys->OREAD); + if(fd == nil) + return ""; + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return ""; + + return string buf[0:n]; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, nil) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} + +kill(pid: int) +{ + if ((fd := sys->open("#p/" + string pid + "/ctl", Sys->OWRITE)) != nil) + sys->fprint(fd, "kill"); +} + +include "styx.m"; + styx: Styx; + Rmsg, Tmsg: import styx; + +styxlog(fd: ref Sys->FD): ref Sys->FD +{ + if(showstyx){ + sys->pipe(p := array[2] of ref Sys->FD); + styx = load Styx Styx->PATH; + styx->init(); + spawn tmsgreader(p[0], fd, p1 := chan[1] of int, p2 := chan[1] of int); + spawn rmsgreader(fd, p[0], p2, p1); + fd = p[1]; + } + return fd; +} + +tmsgreader(cfd, sfd: ref Sys->FD, p1, p2: chan of int) +{ + p1 <-= sys->pctl(0, nil); + m: ref Tmsg; + do{ + m = Tmsg.read(cfd, 9000); + sys->print("%s\n", m.text()); + d := m.pack(); + if(sys->write(sfd, d, len d) != len d) + sys->print("tmsg write error: %r\n"); + } while(m != nil && tagof(m) != tagof(Tmsg.Readerror)); + kill(<-p2); +} + +rmsgreader(sfd, cfd: ref Sys->FD, p1, p2: chan of int) +{ + p1 <-= sys->pctl(0, nil); + m: ref Rmsg; + do{ + m = Rmsg.read(sfd, 9000); + sys->print("%s\n", m.text()); + d := m.pack(); + if(sys->write(cfd, d, len d) != len d) + sys->print("rmsg write error: %r\n"); + } while(m != nil && tagof(m) != tagof(Tmsg.Readerror)); + kill(<-p2); +} diff --git a/appl/cmd/mouse.b b/appl/cmd/mouse.b new file mode 100644 index 00000000..9e21d61a --- /dev/null +++ b/appl/cmd/mouse.b @@ -0,0 +1,394 @@ +implement mouse; +# ported from plan 9's aux/mouse + +include "sys.m"; + sys: Sys; + sprint, fprint, sleep: import sys; +include "draw.m"; + +stderr: ref Sys->FD; + +mouse: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +Sleep500: con 500; +Sleep1000: con 1000; +Sleep2000: con 2000; +TIMEOUT: con 5000; +fail := "fail:"; +usage() +{ + fprint(stderr, "usage: mouse [type]\n"); + raise fail+"usage"; +} + +write(fd: ref Sys->FD, buf: array of byte, n: int): int +{ + if (debug) { + sys->fprint(stderr, "write(%d) ", fd.fd); + for (i := 0; i < len buf; i++) { + sys->fprint(stderr, "'%c' ", int buf[i]); + } + sys->fprint(stderr, "\n"); + } + return sys->write(fd, buf, n); +} + +speeds := array[] of {"b1200", "b2400", "b4800", "b9600"}; +debug := 0; +can9600 := 0; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + +{ + if (argv == nil) + usage(); + + argv = tl argv; + def := 0; + baud := 0; + while (argv != nil && len (arg := hd argv) > 1 && arg[0] == '-') { + case arg[1] { + 'D' => + debug = 1; + * => + usage(); + } + argv = tl argv; + } + if (len argv > 1) + usage(); + + p: string; + if (argv == nil) + p = mouseprobe(); + else + p = hd argv; + if (p != nil && !isnum(p)) { + mouseconfig(p); + return; + } + if (p == nil) { + serial("0"); + serial("1"); + fprint(stderr, "mouse: no mouse detected\n"); + } else { + err := serial(p); + fprint(stderr, "mouse: %s\n", err); + } +} +exception{ + # this could be taken out so the shell could + # get an indication that the command has failed. + "fail:*" => + ; +} +} + +# probe for a serial mouse on port p; +# return some an error string if not found. +serial(p: string): string +{ + baud := 0; + f := sys->sprint("/dev/eia%sctl", p); + if ((ctl := sys->open(f, Sys->ORDWR)) == nil) + return sprint("can't open %s - %r\n", f); + + f = sys->sprint("/dev/eia%s", p); + if ((data := sys->open(f, Sys->ORDWR)) == nil) + return sprint("can't open %s - %r\n", f); + + if(debug) fprint(stderr, "ctl=%d, data=%d\n", ctl.fd, data.fd); + + if(debug) fprint(stderr, "MorW()\n"); + mtype := MorW(ctl, data); + if (mtype == 0) { + if(debug) return "no mouse detected"; + + if(debug) fprint(stderr, "C()\n"); + mtype = C(ctl, data); + } + if (mtype == 0) + return "no mouse detected on port "+p; + + if(debug)fprint(stderr, "done eia setup\n"); + mt := "serial " + p; + case mtype { + * => + return "unknown mouse type"; + 'C' => + if(debug) fprint(stderr, "Logitech 5 byte mouse\n"); + Cbaud(ctl, data, baud); + 'W' => + if(debug) fprint(stderr, "Type W mouse\n"); + Wbaud(ctl, data, baud); + 'M' => + if(debug) fprint(stderr, "Microsoft compatible mouse\n"); + mt += " M"; + } + mouseconfig(mt); + return nil; +} + +mouseconfig(mt: string) +{ + if ((conf := sys->open("/dev/mousectl", Sys->OWRITE)) == nil) { + fprint(stderr, "mouse: can't open mousectl - %r\n"); + raise fail+"open mousectl"; + } + if(debug) fprint(stderr, "opened mousectl\n"); + if (write(conf, array of byte mt, len array of byte mt) < 0) { + fprint(stderr, "mouse: error setting mouse type - %r\n"); + raise fail+"write conf"; + } + fprint(stderr, "mouse: configured as '%s'\n", mt); +} + +isnum(s: string): int +{ + for (i := 0; i < len s; i++) + if (s[i] < '0' || s[i] > '9') + return 0; + return 1; +} + +mouseprobe(): string +{ + if ((probe := sys->open("/dev/mouseprobe", Sys->OREAD)) == nil) { + fprint(stderr, "mouse: can't open mouseprobe - %r\n"); + return nil; + } + buf := array[64] of byte; + n := sys->read(probe, buf, len buf); + if (n <= 0) + return nil; + if (buf[n - 1] == byte '\n') + n--; + if(debug) fprint(stderr, "mouse probe detected mouse of type '%s'\n", string buf[0:n]); + return string buf[0:n]; +} + +readbyte(fd: ref Sys->FD): int +{ + buf := array[1] of byte; + (n, err) := timedread(fd, buf, 1, 200); + if (n < 0) { + if (err == nil) + return -1; + fprint(stderr, "mouse: readbyte failed - %s\n", err); + raise fail+"read failed"; + } + return int buf[0]; +} + +slowread(fd: ref Sys->FD, buf: array of byte, nbytes: int, msg: string): int +{ + for (i := 0; i < nbytes; i++) { + if ((c := readbyte(fd)) == -1) + break; + buf[i] = byte c; + } + if(debug) dumpbuf(buf[0:i], msg); + return i; +} + +dumpbuf(buf: array of byte, msg: string) +{ + sys->fprint(stderr, "%s", msg); + for (i := 0; i < len buf; i++) + sys->fprint(stderr, "#%ux ", int buf[i]); + sys->fprint(stderr, "\n"); +} + +toggleRTS(fd: ref Sys->FD) +{ + # reset the mouse (toggle RTS) + # must be >100mS + writes(fd, "d0"); + sleep(10); + writes(fd, "r0"); + sleep(Sleep500); + writes(fd, "d1"); + sleep(10); + writes(fd, "r1"); + sleep(Sleep500); +} + +setupeia(fd: ref Sys->FD, baud, bits: string) +{ + # set the speed to 1200/2400/4800/9600 baud, + # 7/8-bit data, one stop bit and no parity + + (abaud, abits) := (array of byte baud, array of byte bits); + if(debug)sys->fprint(stderr, "setupeia(%s,%s)\n", baud, bits); + write(fd, abaud, len abaud); + write(fd, abits, len abits); + writes(fd, "s1"); + writes(fd, "pn"); +} + +# check for types M, M3 & W +# +# we talk to all these mice using 1200 baud + +MorW(ctl, data: ref Sys->FD): int +{ + # set up for type M, V or W + # flush any pending data + + setupeia(ctl, "b1200", "l7"); + toggleRTS(ctl); + if(debug)sys->fprint(stderr, "toggled RTS\n"); + + buf := array[256] of byte; + while (slowread(data, buf, len buf, "flush: ") > 0) + ; + if(debug) sys->fprint(stderr, "done slowread\n"); + toggleRTS(ctl); + + # see if there's any data from the mouse + # (type M, V and W mice) + c := slowread(data, buf, len buf, "check M: "); + + # type M, V and W mice return "M" or "M3" after reset. + # check for type W by sending a 'Send Standard Configuration' + # command, "*?". + if (c > 0 && int buf[0] == 'M') { + writes(data, "*?"); + c = slowread(data, buf, len buf, "check W: "); + # 4 bytes back indicates a type W mouse + if (c == 4) { + if (int buf[1] & (1<<4)) + can9600 = 1; + setupeia(ctl, "b1200", "l8"); + writes(data, "*U"); + slowread(data, buf, len buf, "check W: "); + return 'W'; + } + return 'M'; + } + return 0; +} + +# check for type C by seeing if it responds to the status +# command "s". the mouse is at an unknown speed so we +# have to check all possible speeds. +C(ctl, data: ref Sys->FD): int +{ + buf := array[256] of byte; + for (s := speeds; len s > 0; s = s[1:]) { + if (debug) sys->print("%s\n", s[0]); + setupeia(ctl, s[0], "l8"); + writes(data, "s"); + c := slowread(data, buf, len buf, "check C: "); + if (c >= 1 && (int buf[0] & 16rbf) == 16r0f) { + sleep(100); + writes(data, "*n"); + sleep(100); + setupeia(ctl, "b1200", "l8"); + writes(data, "s"); + c = slowread(data, buf, len buf, "recheck C: "); + if (c >= 1 && (int buf[0] & 16rbf) == 16r0f) { + writes(data, "U"); + return 'C'; + } + } + sleep(100); + } + return 0; +} + +Cbaud(ctl, data: ref Sys->FD, baud: int) +{ + buf := array[2] of byte; + case baud { + 0 or 1200 => + return; + 2400 => + buf[1] = byte 'o'; + 4800 => + buf[1] = byte 'p'; + 9600 => + buf[1] = byte 'q'; + * => + fprint(stderr, "mouse: can't set baud rate, mouse at 1200\n"); + return; + } + buf[0] = byte '*'; + sleep(100); + write(data, buf, 2); + sleep(100); + write(data, buf, 2); + setupeia(ctl, sys->sprint("b%d", baud), "l8"); +} + +Wbaud(ctl, data: ref Sys->FD, baud: int) +{ + case baud { + 0 or 1200 => + return; + * => + if (baud == 9600 && can9600) + break; + fprint(stderr, "mouse: can't set baud rate, mouse at 1200\n"); + return; + } + writes(data, "*q"); + setupeia(ctl, "b9600", "l8"); + slowread(data, array[32] of byte, 32, "setbaud: "); +} + +readproc(fd: ref Sys->FD, buf: array of byte, n: int, + pidch: chan of int, ch: chan of (int, string)) +{ + s: string; + pidch <-= sys->pctl(0, nil); + n = sys->read(fd, buf, n); + if (n < 0) + s = sys->sprint("read: %r"); + ch <-= (n, s); +} + +sleepproc(t: int, pidch: chan of int, ch: chan of (int, string)) +{ + pidch <-= sys->pctl(0, nil); + sys->sleep(t); + ch <-= (-1, nil); +} + +timedread(fd: ref Sys->FD, buf: array of byte, n: int, t: int): (int, string) +{ + pidch := chan of int; + retch := chan of (int, string); + spawn readproc(fd, buf, n, pidch, retch); + wpid := <-pidch; + spawn sleepproc(t, pidch, retch); + spid := <-pidch; + + (nr, err) := <-retch; + if (nr == -1 && err == nil) + kill(wpid); + else + kill(spid); + return (nr, err); +} + +kill(pid: int) +{ + if ((fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE)) == nil) { + fprint(stderr, "couldn't kill %d: %r\n", pid); + return; + } + sys->write(fd, array of byte "kill", 4); +} + +writes(fd: ref Sys->FD, s: string): int +{ + a := array of byte s; + return write(fd, a, len a); +} + diff --git a/appl/cmd/mpc/mkfile b/appl/cmd/mpc/mkfile new file mode 100644 index 00000000..17d7ab37 --- /dev/null +++ b/appl/cmd/mpc/mkfile @@ -0,0 +1,14 @@ +<../../../mkconfig + +TARG=\ + qconfig.dis\ + qflash.dis\ + +SYSMODULES=\ + sys.m\ + draw.m\ + string.m\ + +DISBIN=$ROOT/dis/mpc + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/mpc/qconfig.b b/appl/cmd/mpc/qconfig.b new file mode 100644 index 00000000..dbff19b7 --- /dev/null +++ b/appl/cmd/mpc/qconfig.b @@ -0,0 +1,193 @@ +implement Configflash; + +# +# this isn't a proper config program: it's currently just +# enough to set important parameters such as ethernet address. +# an extension is in the works. +# --chf + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +Configflash: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +Region: adt { + base: int; + limit: int; +}; + +# +# structure of allocation descriptor +# +Fcheck: con 0; +Fbase: con 4; +Flen: con 8; +Ftag: con 11; +Fsig: con 12; +Fasize: con 3*4+3+1; + +Tdead: con byte 0; +Tboot: con byte 16r01; +Tconf: con byte 16r02; +Tnone: con byte 16rFF; + +flashsig := array[] of {byte 16rF1, byte 16rA5, byte 16r5A, byte 16r1F}; +noval := array[] of {0 to 3 =>byte 16rFF}; # + +Ctag, Cscreen, Cconsole, Cbaud, Cether, Cea, Cend: con iota; +config := array[] of { + Ctag => "#plan9.ini\n", # current flag for qboot, don't change + Cscreen => "vgasize=640x480x8\n", + Cconsole => "console=0 lcd\n", + Cbaud => "baud=9600\n", + Cether => "ether0=type=SCC port=2 ", # note missing \n + Cea => "ea=08003e400080\n", + Cend => "\0" # qboot currently requires it but shouldn't +}; + +Param: adt { + name: string; + index: int; +}; + +params := array[] of { + Param("vgasize", Cscreen), + Param("console", Cconsole), + Param("ea", Cea), + Param("baud", Cbaud) +}; + +# could come from file or #F/flash/flashctl +FLASHSEG: con 256*1024; +bootregion := Region(0, FLASHSEG); + +stderr: ref Sys->FD; +prog := "qconfig"; +damaged := 0; +debug := 0; + +usage() +{ + sys->fprint(stderr, "Usage: %s [-D] [-f flash] [-param value ...]\n", prog); + exit; +} + +err(s: string) +{ + sys->fprint(stderr, "%s: %s", prog, s); + if(!damaged) + sys->fprint(stderr, "; flash not modified\n"); + else + sys->fprint(stderr, "; flash might now be invalid\n"); + exit; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + stderr = sys->fildes(2); + if(args != nil){ + prog = hd args; + args = tl args; + } + str = load String String->PATH; + if(str == nil) + err(sys->sprint("can't load %s: %r", String->PATH)); + flash := "#F/flash/flash"; + offset := 0; + region := bootregion; + + for(; args != nil && (hd args)[0] == '-'; args = tl args) + case a := hd args { + "-f" => + (flash, args) = argf(tl args); + "-D" => + debug = 1; + * => + p := lookparam(params, a[1:]); + if(p.index < 0) + err(sys->sprint("unknown config parameter: %s", a)); + v: string; + (v, args) = argf(tl args); + config[p.index] = a[1:]+"="+v+"\n"; # would be nice to check it + } + if(len args > 0) + usage(); + out := sys->open(flash, Sys->ORDWR); + if(out == nil) + err(sys->sprint("can't open %s for read/write: %r", flash)); + # TO DO: hunt for free space and add new entry + plonk(out, FLASHSEG-Fasize, mkdesc(0, 128*1024, Tboot)); + c := flatten(config); + if(debug) + sys->print("%s", c); + bconf := array of byte c; + plonk(out, FLASHSEG-Fasize*2, mkdesc(128*1024, len bconf, Tconf)); + plonk(out, 128*1024, bconf); +} + +argf(args: list of string): (string, list of string) +{ + if(args == nil) + usage(); + return (hd args, args); +} + +lookparam(options: array of Param, s: string): Param +{ + for(i := 0; i < len options; i++) + if(options[i].name == s) + return options[i]; + return Param(nil, -1); +} + +flatten(a: array of string): string +{ + s := ""; + for(i := 0; i < len a; i++) + s += a[i]; + return s; +} + +plonk(out: ref Sys->FD, where: int, val: array of byte) +{ + if(debug){ + sys->print("write #%ux [%d]:", where, len val); + for(i:=0; i<len val; i++) + sys->print(" %.2ux", int val[i]); + sys->print("\n"); + } + sys->seek(out, big where, 0); + if(sys->write(out, val, len val) != len val) + err(sys->sprint("bad flash write: %r")); +} + +cvt(v: int): array of byte +{ + a := array[4] of byte; + a[0] = byte (v>>24); + a[1] = byte (v>>16); + a[2] = byte (v>>8); + a[3] = byte (v & 16rff); + return a; +} + +mkdesc(base: int, length: int, tag: byte): array of byte +{ + a := array[Fasize] of byte; + a[Fcheck:] = noval; + a[Fbase:] = cvt(base); + a[Flen:] = cvt(length)[1:]; # it's three bytes + a[Ftag] = tag; + a[Fsig:] = flashsig; + return a; +} diff --git a/appl/cmd/mpc/qflash.b b/appl/cmd/mpc/qflash.b new file mode 100644 index 00000000..13c77ce8 --- /dev/null +++ b/appl/cmd/mpc/qflash.b @@ -0,0 +1,188 @@ +implement Writeflash; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +Writeflash: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +Region: adt { + base: int; + limit: int; +}; + +# could come from file or #F/flash/flashctl +FLASHSEG: con 256*1024; +kernelregion := Region(FLASHSEG, FLASHSEG+2*FLASHSEG); +bootregion := Region(0, FLASHSEG); + +stderr: ref Sys->FD; +prog := "qflash"; +damaged := 0; + +usage() +{ + sys->fprint(stderr, "Usage: %s [-b] [-o offset] [-f flashdev] file\n", prog); + exit; +} + +err(s: string) +{ + sys->fprint(stderr, "%s: %s", prog, s); + if(!damaged) + sys->fprint(stderr, "; flash not modified\n"); + else + sys->fprint(stderr, "; flash might now be invalid\n"); + exit; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + stderr = sys->fildes(2); + if(args != nil){ + prog = hd args; + args = tl args; + } + str = load String String->PATH; + if(str == nil) + err(sys->sprint("can't load %s: %r", String->PATH)); + region := kernelregion; + flash := "#F/flash/flash"; + offset := 0; + save := 0; + + for(; args != nil && (hd args)[0] == '-'; args = tl args) + case hd args { + "-b" => + region = bootregion; + offset = 16r100 - 8*4; # size of exec header + save = 1; + "-h" => + region.limit += FLASHSEG; + "-f" => + if(tl args == nil) + usage(); + flash = hd args; + args = tl args; + "-o" => + if(tl args == nil) + usage(); + args = tl args; + s := hd args; + v: int; + rs: string; + if(str->prefix("16r", s)) + (v, rs) = str->toint(s[3:], 16); + else if(str->prefix("0x", s)) + (v, rs) = str->toint(s[2:], 16); + else if(str->prefix("0", s)) + (v, rs) = str->toint(s[1:], 8); + else + (v, rs) = str->toint(s, 10); + if(v < 0 || len rs != 0) + err(sys->sprint("bad offset: %s", s)); + offset = v; + "-s" => + save = 1; + * => + usage(); + } + if(args == nil) + usage(); + fname := hd args; + fd := sys->open(fname, Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open %s: %r", fname)); + (r, dir) := sys->fstat(fd); + if(r < 0) + err(sys->sprint("can't stat %s: %r", fname)); + length := int dir.length; + avail := region.limit - (region.base+offset); + if(length > avail) + err(sys->sprint("%s contents %ud bytes, exceeds flash region %ud bytes", fname, length, avail)); + # check fname's contents... + where := region.base+offset; + saved: list of (int, array of byte); + if(save){ + saved = saveflash(flash, region.base, where) :: saved; + saved = saveflash(flash, where+length, region.limit) :: saved; + } + for(i := (region.base+offset)/FLASHSEG; i < region.limit/FLASHSEG; i++) + erase(flash, i); + out := sys->open(flash, Sys->OWRITE); + if(out == nil) + err(sys->sprint("can't open %s for writing: %r", flash)); + if(sys->seek(out, big where, 0) != big where) + err(sys->sprint("can't seek to #%6.6ux on flash: %r", where)); + if(length) + sys->print("writing %ud bytes to %s at #%6.6ux\n", length, flash, where); + buf := array[Sys->ATOMICIO] of byte; + total := 0; + while((n := sys->read(fd, buf, len buf)) > 0) { + if(total+n > avail) + err(sys->sprint("file %s too big for region of %ud bytes", fname, avail)); + r = sys->write(out, buf, n); + damaged = 1; + if(r != n){ + if(r < 0) + err(sys->sprint("error writing %s at byte %ud: %r", flash, total)); + else + err(sys->sprint("short write on %s at byte %ud", flash, total)); + } + total += n; + } + if(n < 0) + err(sys->sprint("error reading %s: %r", fname)); + sys->print("wrote %ud bytes from %s to flash %s (#%6.6ux-#%6.6ux)\n", total, fname, flash, region.base, region.base+total); + for(l := saved; l != nil; l = tl l){ + (addr, data) := hd l; + n = len data; + if(n == 0) + continue; + sys->print("restoring %ud bytes at #%6.6ux\n", n, addr); + if(sys->seek(out, big addr, 0) != big addr) + err(sys->sprint("can't seek to #%6.6ux on %s: %r", addr, flash)); + r = sys->write(out, data, n); + if(r < 0) + err(sys->sprint("error writing %s: %r", flash)); + else if(r != n) + err(sys->sprint("short write on %s at byte %ud/%ud", flash, r, n)); + else + sys->print("restored %ud bytes at #%6.6ux\n", n, addr); + } +} + +erase(flash: string, seg: int) +{ + ctl := sys->open(flash+"ctl", Sys->OWRITE); + if(ctl == nil) + err(sys->sprint("can't open %sctl: %r\n", flash)); + if(sys->fprint(ctl, "erase %ud", seg*FLASHSEG) < 0) + err(sys->sprint("can't erase flash %s segment %d: %r\n", flash, seg)); +} + +saveflash(flash: string, base: int, limit: int): (int, array of byte) +{ + fd := sys->open(flash, Sys->OREAD); + if(fd == nil) + err(sys->sprint("can't open %s for reading: %r", flash)); + nb := limit - base; + if(nb <= 0) + return (base, nil); + if(sys->seek(fd, big base, 0) != big base) + err(sys->sprint("can't seek to #%6.6ux to save flash contents: %r", base)); + saved := array[nb] of byte; + if(sys->read(fd, saved, len saved) != len saved) + err(sys->sprint("can't read flash #%6.6ux to #%6.6ux: %r", base, limit)); + sys->print("saved %ud bytes at #%6.6ux\n", len saved, base); + return (base, saved); +} diff --git a/appl/cmd/mprof.b b/appl/cmd/mprof.b new file mode 100644 index 00000000..1722d50a --- /dev/null +++ b/appl/cmd/mprof.b @@ -0,0 +1,260 @@ +implement Prof; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; + arg: Arg; +include "profile.m"; + profile: Profile; +include "sh.m"; + +stderr: ref Sys->FD; + +Prof: module { + init: fn(nil: ref Draw->Context, argv: list of string); + init0: fn(nil: ref Draw->Context, argv: list of string): Profile->Prof; +}; + +ignored(s: string) +{ + sys->fprint(stderr, "mprof: warning: %s ignored\n", s); +} + +exits(e: string) +{ + if(profile != nil) + profile->end(); + raise "fail:" + e; +} + +pfatal(s: string) +{ + sys->fprint(stderr, "mprof: %s: %s\n", s, profile->lasterror()); + exits("error"); +} + +badmodule(p: string) +{ + sys->fprint(stderr, "mprof: cannot load %s: %r\n", p); + exits("bad module"); +} + +usage(s: string) +{ + sys->fprint(stderr, "mprof: %s\n", s); + sys->fprint(stderr, "usage: mprof [-bcMflnve] [-m modname]... [cmd arg ...]"); + exits("usage"); +} + +init(ctxt: ref Draw->Context, argv: list of string) +{ + init0(ctxt, argv); +} + +init0(ctxt: ref Draw->Context, argv: list of string): Profile->Prof +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + arg = load Arg Arg->PATH; + if(arg == nil) + badmodule(Arg->PATH); + arg->init(argv); + profile = load Profile Profile->PATH; + if(profile == nil) + badmodule(Profile->PATH); + if(profile->init() < 0) + pfatal("cannot initialize profile device"); + + v := 0; + begin := end := 0; + ep := 0; + wm := 0; + mem := 0; + exec, mods: list of string; + while((c := arg->opt()) != 0){ + case c { + 'b' => begin = 1; + 'c' => end = 1; + 'M' => v |= profile->MODULE; + 'f' => v |= profile->FUNCTION; + 'l' => v |= profile->LINE; + 'n' => v |= profile->FULLHDR; + 'v' => v |= profile->VERBOSE; + 'm' => + if((s := arg->arg()) == nil) + usage("missing module name"); + mods = s :: mods; + 'e' => + ep = 1; + 'g' => + wm = 1; + '1' => + mem |= Profile->MAIN; + '2' => + mem |= Profile->HEAP; + '3' => + mem |= Profile->IMAGE; + * => + usage(sys->sprint("unknown option -%c", c)); + } + } + + exec = arg->argv(); + + if(begin && end) + ignored("-e option"); + if((begin || end) && v != 0) + ignored("output format"); + if(begin && exec != nil) + begin = 0; + if(begin == 0 && exec == nil){ + if(mods != nil) + ignored("-m option"); + mods = nil; + } + if(end){ + if(mods != nil) + ignored("-m option"); + if(ep || exec != nil) + ignored("command"); + profile->end(); + exit; + } + + for( ; mods != nil; mods = tl mods) + profile->profile(hd mods); + + if(begin){ + if(profile->memstart(mem) < 0) + pfatal("cannot start profiling"); + exit; + } + r := 0; + if(exec != nil){ + if(ep) + profile->profile(disname(hd exec)); + if(profile->memstart(mem) < 0) + pfatal("cannot start profiling"); + # r = run(ctxt, hd exec, exec); + wfd := openwait(sys->pctl(0, nil)); + ci := chan of int; + spawn execute(ctxt, hd exec, exec, ci); + epid := <- ci; + wait(wfd, epid); + } + if(profile->stop() < 0) + pfatal("cannot stop profiling"); + if(exec == nil || r >= 0){ + modl := profile->memstats(); + if(modl.mods == nil) + pfatal("no profile information"); + if(wm){ + if(exec == nil){ + if(profile->memstart(mem) < 0) + pfatal("cannot restart profiling"); + } + else + profile->end(); + return modl; + } + if(!(v&(profile->MODULE|profile->FUNCTION|profile->LINE))) + v |= profile->MODULE|profile->LINE; + if(profile->memshow(modl, v) < 0) + pfatal("cannot show profile"); + if(exec == nil){ + if(profile->memstart(mem) < 0) + pfatal("cannot restart profiling"); + exit; + } + } + profile->end(); + return (nil, 0, nil); +} + +disname(cmd: string): string +{ + file := cmd; + if(len file<4 || file[len file-4:]!=".dis") + file += ".dis"; + if(exists(file)) + return file; + if(file[0]!='/' && file[0:2]!="./") + file = "/dis/"+file; + # if(exists(file)) + # return file; + return file; +} + +execute(ctxt: ref Draw->Context, cmd : string, argl : list of string, ci: chan of int) +{ + ci <-= sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: stderr.fd :: nil); + file := cmd; + if(len file<4 || file[len file-4:]!=".dis") + file += ".dis"; + c := load Command file; + if(c == nil) { + err := sys->sprint("%r"); + if(file[0]!='/' && file[0:2]!="./"){ + c = load Command "/dis/"+file; + if(c == nil) + err = sys->sprint("%r"); + } + if(c == nil){ + sys->fprint(stderr, "mprof: %s: %s\n", cmd, err); + return; + } + } + c->init(ctxt, argl); +} + +# run(ctxt: ref Draw->Context, cmd : string, argl : list of string): int +# { +# file := cmd; +# if(len file<4 || file[len file-4:]!=".dis") +# file += ".dis"; +# c := load Command file; +# if(c == nil) { +# err := sys->sprint("%r"); +# if(file[0]!='/' && file[0:2]!="./"){ +# c = load Command "/dis/"+file; +# if(c == nil) +# err = sys->sprint("%r"); +# } +# if(c == nil){ +# sys->fprint(stderr, "mprof: %s: %s\n", cmd, err); +# return -1; +# } +# } +# c->init(ctxt, argl); +# return 0; +# } + +openwait(pid : int) : ref Sys->FD +{ + w := sys->sprint("#p/%d/wait", pid); + fd := sys->open(w, Sys->OREAD); + if (fd == nil) + pfatal("fd == nil in wait"); + return fd; +} + +wait(wfd : ref Sys->FD, wpid : int) +{ + n : int; + + buf := array[Sys->WAITLEN] of byte; + status := ""; + for(;;) { + if ((n = sys->read(wfd, buf, len buf)) < 0) + pfatal("bad read in wait"); + status = string buf[0:n]; + if (int status == wpid) + break; + } +} + +exists(f: string): int +{ + return sys->open(f, Sys->OREAD) != nil; +} diff --git a/appl/cmd/mv.b b/appl/cmd/mv.b new file mode 100644 index 00000000..2ca8e671 --- /dev/null +++ b/appl/cmd/mv.b @@ -0,0 +1,184 @@ +implement Mv; + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; + +include "draw.m"; + draw: Draw; + +include "string.m"; + str: String; + + +Mv: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + str = load String String->PATH; + if(str == nil) { + sys->fprint(stderr, "mv: can't load %s: %r\n", String->PATH); + raise "fail:load"; + } + + dirto, dirfrom: Sys->Dir; + todir, toelem: string; + if(len argv<3) { + sys->fprint(stderr, "usage: mv fromfile tofile\n"); + sys->fprint(stderr, " mv fromfile ... todir\n"); + raise "fail:usage"; + } + argv = tl argv; + arr := array[len argv] of string; + for (i:=0; argv!=nil;i++){ + arr[i]= hd argv; + argv = tl argv; + } + (i,dirto)=sys->stat(arr[len arr-1]); + if(i >= 0 && (dirto.mode&Sys->DMDIR)){ + (i,dirfrom)=sys->stat(arr[0]); + if(len arr == 2 && i >= 0 && (dirfrom.mode&Sys->DMDIR)) + (todir,toelem)=split(arr[len arr-1]); + else{ + todir = arr[len arr -1]; + toelem = ""; # toelem will be fromelem + } + }else + (todir,toelem)=split(arr[len arr-1]); + if(len arr > 2 && toelem != nil) { + sys->fprint(stderr, "mv: %s not a directory\n", arr[len arr-1]); + raise "fail:error"; + } + failed := 0; + for(i=0; i < len arr-1; i++) + if (mv(arr[i], todir, toelem) < 0) + failed++; + if(failed) + raise "fail:error"; +} + +mv(from,todir,toelem : string): int +{ + (i,dirb):=sys->stat(from); + if(i != 0) { + sys->fprint(stderr, "mv: can't stat %s: %r\n", from); + return -1; + } + (fromdir,fromelem):=split(from); + fromname:= fromdir+fromelem; + if(toelem == nil){ + if (todir[len todir-1]!='/') + todir[len todir]='/'; + toelem = fromelem; + } + i = len toelem; + if(i==0){ + sys->fprint(stderr, "mv: null last name element moving %s\n", fromname); + return -1; + } + toname:=todir+toelem; + if(samefile(fromdir, todir)){ + if(samefile(fromname, toname)){ + sys->fprint(stderr, "mv: %s and %s are the same\n", fromname, toname); + return -1; + } + (j,dirt):=sys->stat(toname); + if( (j == 0) && (dirb.mode&Sys->DMDIR) ){ + sys->fprint(stderr, "mv: can't rename a directory to an existing name\n"); + return -1; + } + if(j == 0) + hardremove(toname); + dirt = sys->nulldir; + dirt.name=toelem; + if(sys->wstat(fromname,dirt) >= 0) + return 0; + if(dirb.mode&Sys->DMDIR){ + sys->fprint(stderr, "mv: can't rename directory %s: %r\n", fromname); + return -1; + } + } + # Renaming won't work --- have to copy + if(dirb.mode&Sys->DMDIR){ + sys->fprint(stderr, "mv: %s is a directory, not copied to %s\n", fromname, toname); + return -1; + } + fdf := sys->open(fromname, Sys->OREAD); + if(fdf==nil){ + sys->fprint(stderr, "mv: can't open %s: %r\n", fromname); + return -1; + } + (j,dirt):=sys->stat(toname); + fdt := sys->create(toname, Sys->OWRITE, dirb.mode); + if(fdt == nil){ + sys->fprint(stderr, "mv: can't create %s: %r\n", toname); + return -1; + } + if ((stat := copy1(fdf, fdt, fromname, toname)) != -1) + fdf = nil; # temp bug: sometimes can't remove open file + if (sys->remove(fromname) < 0) { + sys->fprint(stderr, "mv: can't remove %s: %r\n", fromname); + return -1; + } + return stat; +} + + +copy1(fdf, fdt : ref Sys->FD,from, fto : string): int +{ + n : int; + buf:=array[Sys->ATOMICIO] of byte; + for(;;) { + n = sys->read(fdf, buf, len buf); + if (n<=0) + break; + n1 := sys->write(fdt, buf, n); + if(n1 != n) { + sys->fprint(stderr, "mv: error writing %s: %r\n", fto); + return -1; + } + } + if(n < 0) { + sys->fprint(stderr, "mv: error reading %s: %r\n", from); + return -1; + } + return 0; +} + +split(name : string): (string,string) +{ + (d,t) := str->splitr(name, "/"); + if(d!=nil) + return(d,t); + else if(name=="..") + return("../","."); + else + return("./",name); +} + +samefile(a,b : string): int +{ + if(a==b) + return 1; + (i,da):=sys->stat(a); + (j,db):=sys->stat(b); + if(i < 0 || j < 0) + return 0; + i= (da.qid.path==db.qid.path && da.qid.vers==db.qid.vers && + da.dev==db.dev && da.dtype==db.dtype); + return i; +} + +hardremove(a: string) +{ + if(sys->remove(a) == -1){ + sys->fprint(stderr, "mv: can't remove %s: %r\n", a); + raise "fail:mv"; + } + do; while(sys->remove(a) != -1); +} diff --git a/appl/cmd/ndb/cs.b b/appl/cmd/ndb/cs.b new file mode 100644 index 00000000..1506e9ba --- /dev/null +++ b/appl/cmd/ndb/cs.b @@ -0,0 +1,676 @@ +implement Cs; + +# +# Connection server translates net!machine!service into +# /net/tcp/clone 135.104.9.53!564 +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "srv.m"; + srv: Srv; + +include "bufio.m"; +include "attrdb.m"; + attrdb: Attrdb; + Attr, Db, Dbentry, Tuples: import attrdb; + +include "ip.m"; + ip: IP; +include "ipattr.m"; + ipattr: IPattr; + +include "arg.m"; + +Cs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +# signature of dial-on-demand module +CSdial: module +{ + init: fn(nil: ref Draw->Context): string; + connect: fn(): string; +}; + +Reply: adt +{ + fid: int; + pid: int; + addrs: list of string; + err: string; +}; + +Cached: adt +{ + expire: int; + query: string; + addrs: list of string; +}; + +Ncache: con 16; +cache:= array[Ncache] of ref Cached; +nextcache := 0; + +rlist: list of ref Reply; + +ndbfile := "/lib/ndb/local"; +ndb: ref Db; +mntpt := "/net"; +myname: string; + +stderr: ref Sys->FD; + +verbose := 0; +dialmod: CSdial; + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + cantload(Attrdb->PATH); + attrdb->init(); + ip = load IP IP->PATH; + if(ip == nil) + cantload(IP->PATH); + ip->init(); + ipattr = load IPattr IPattr->PATH; + if(ipattr == nil) + cantload(IPattr->PATH); + ipattr->init(attrdb, ip); + + svcname := "#scs"; + arg := load Arg Arg->PATH; + if (arg == nil) + cantload(Arg->PATH); + arg->init(args); + arg->setusage("cs [-v] [-x mntpt] [-f database] [-d dialmod]"); + while((c := arg->opt()) != 0) + case c { + 'v' or 'D' => + verbose++; + 'd' => # undocumented hack to replace svc/cs/cs + f := arg->arg(); + if(f != nil){ + dialmod = load CSdial f; + if(dialmod == nil) + cantload(f); + } + 'f' => + ndbfile = arg->earg(); + 'x' => + mntpt = arg->earg(); + svcname = "#scs"+svcpt(mntpt); + * => + arg->usage(); + } + + if(arg->argv() != nil) + arg->usage(); + arg = nil; + + srv = load Srv Srv->PATH; # hosted Inferno only + if(srv != nil) + srv->init(); + + sys->remove(svcname+"/cs"); + sys->unmount(svcname, mntpt); + publish(svcname); + if(sys->bind(svcname, mntpt, Sys->MBEFORE) < 0) + error(sys->sprint("can't bind #s on %s: %r", mntpt)); + file := sys->file2chan(mntpt, "cs"); + if(file == nil) + error(sys->sprint("can't make %s/cs: %r", mntpt)); + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + refresh(); + if(dialmod != nil){ + e := dialmod->init(ctxt); + if(e != nil) + error(sys->sprint("can't initialise dial-on-demand: %s", e)); + } + spawn cs(file); +} + +svcpt(s: string): string +{ + for(i:=0; i<len s; i++) + if(s[i] == '/') + s[i] = '_'; + return s; +} + +publish(dir: string) +{ + d := Sys->nulldir; + d.mode = 8r777; + if(sys->wstat(dir, d) < 0) + sys->fprint(sys->fildes(2), "cs: can't publish %s: %r\n", dir); +} + +cantload(m: string) +{ + error(sys->sprint("cannot load %s: %r", m)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "cs: %s\n", s); + raise "fail:error"; +} + +refresh() +{ + myname = sysname(); + if(ndb == nil){ + ndb2 := Db.open(ndbfile); + if(ndb2 == nil){ + err := sys->sprint("%r"); + ndb2 = Db.open("/lib/ndb/inferno"); # try to get service map at least + if(ndb2 == nil) + sys->fprint(sys->fildes(2), "cs: warning: can't open %s: %s\n", ndbfile, err); # continue without it + } + ndb = Db.open(mntpt+"/ndb"); + if(ndb != nil) + ndb = ndb.append(ndb2); + else + ndb = ndb2; + }else + ndb.reopen(); +} + +sysname(): string +{ + t := rf("/dev/sysname"); + if(t != nil) + return t; + t = rf("#e/sysname"); + if(t == nil){ + s := rf(mntpt+"/ndb"); + if(s != nil){ + db := Db.sopen(t); + if(db != nil){ + (e, nil) := db.find(nil, "sys"); + if(e != nil) + t = e.findfirst("sys"); + } + } + } + if(t != nil){ + fd := sys->open("/dev/sysname", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "%s", t); + } + return t; +} + +rf(name: string): string +{ + fd := sys->open(name, Sys->OREAD); + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return nil; + return string buf[0:n]; +} + +cs(file: ref Sys->FileIO) +{ + pidc := chan of int; + donec := chan of ref Reply; + for (;;) { + alt { + (nil, buf, fid, wc) := <-file.write => + cleanfid(fid); # each write cancels previous requests + if(dialmod != nil){ + e := dialmod->connect(); + if(e != nil){ + if(len e > 5 && e[0:5]=="fail:") + e = e[5:]; + if(e == "") + e = "unknown error"; + wc <-= (0, "cs: dial on demand: "+e); + break; + } + } + if(wc != nil){ + nbytes := len buf; + query := string buf; + if(query == "refresh"){ + refresh(); + wc <-= (nbytes, nil); + break; + } + now := time(); + r := ref Reply; + r.fid = fid; + spawn request(r, query, nbytes, now, wc, pidc, donec); + r.pid = <-pidc; + rlist = r :: rlist; + } + + (off, nbytes, fid, rc) := <-file.read => + if(rc != nil){ + r := findfid(fid); + if(r != nil) + reply(r, off, nbytes, rc); + else + rc <-= (nil, "unknown request"); + } else + ; # cleanfid(fid); # compensate for csendq in file2chan + + r := <-donec => + r.pid = 0; + } + } +} + +findfid(fid: int): ref Reply +{ + for(rl := rlist; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid == fid) + return r; + } + return nil; +} + +cleanfid(fid: int) +{ + rl := rlist; + rlist = nil; + for(; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid != fid) + rlist = r :: rlist; + else + killgrp(r.pid); + } +} + +killgrp(pid: int) +{ + if(pid != 0){ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "killgrp") < 0) + sys->fprint(stderr, "cs: can't killgrp %d: %r\n", pid); + } +} + +request(r: ref Reply, query: string, nbytes: int, now: int, wc: chan of (int, string), pidc: chan of int, donec: chan of ref Reply) +{ + pidc <-= sys->pctl(Sys->NEWPGRP, nil); + if(query != nil && query[0] == '!'){ + # general query + (r.addrs, r.err) = genquery(query[1:]); + }else{ + (r.addrs, r.err) = xlate(query, now); + if(r.addrs == nil && r.err == nil) + r.err = "cs: can't translate address"; + } + if(r.err != nil){ + if(verbose) + sys->fprint(stderr, "cs: %s: %s\n", query, r.err); + wc <-= (0, r.err); + } else + wc <-= (nbytes, nil); + donec <-= r; +} + +reply(r: ref Reply, off: int, nbytes: int, rc: chan of (array of byte, string)) +{ + if(r.err != nil){ + rc <-= (nil, r.err); + return; + } + addr: string = nil; + if(r.addrs != nil){ + addr = hd r.addrs; + r.addrs = tl r.addrs; + } + off = 0; # this version ignores offset + rc <-= reads(addr, off, nbytes); +} + +# +# return the file2chan reply for a read of the given string +# +reads(str: string, off, nbytes: int): (array of byte, string) +{ + bstr := array of byte str; + slen := len bstr; + if(off < 0 || off >= slen) + return (nil, nil); + if(off + nbytes > slen) + nbytes = slen - off; + if(nbytes <= 0) + return (nil, nil); + return (bstr[off:off+nbytes], nil); +} + +lookcache(query: string, now: int): ref Cached +{ + for(i:=0; i<len cache; i++){ + c := cache[i]; + if(c != nil && c.query == query && now < c.expire){ + if(verbose) + sys->print("cache: %s -> %s\n", query, hd c.addrs); + return c; + } + } + return nil; +} + +putcache(query: string, addrs: list of string, now: int) +{ + ce := ref Cached; + ce.expire = now+120; + ce.query = query; + ce.addrs = addrs; + cache[nextcache] = ce; + nextcache = (nextcache+1)%Ncache; +} + +xlate(address: string, now: int): (list of string, string) +{ + n: int; + l, rl, results: list of string; + repl, netw, mach, service: string; + + ce := lookcache(address, now); + if(ce != nil && ce.addrs != nil) + return (ce.addrs, nil); + + (n, l) = sys->tokenize(address, "!\n"); + if(n < 2) + return (nil, "bad format request"); + + netw = hd l; + if(netw == "net") + netw = "tcp"; # TO DO: better (needs lib/ndb) + if(!isnetwork(netw)) + return (nil, "network unavailable "+netw); + l = tl l; + + if(!isipnet(netw)) { + repl = mntpt + "/" + netw + "/clone "; + for(;;){ + repl += hd l; + if((l = tl l) == nil) + break; + repl += "!"; + } + return (repl :: nil, nil); # no need to cache + } + + if(n != 3) + return (nil, "bad format request"); + mach = hd l; + service = hd tl l; + + if(!isnumeric(service)) { + s := xlatesvc(netw, service); + if(s == nil){ + if(srv != nil) + s = srv->ipn2p(netw, service); + if(s == nil) + return (nil, "cs: can't translate service"); + } + service = s; + } + + attr := ipattr->dbattr(mach); + if(mach == "*") + l = "" :: nil; + else if(attr != "ip") { + # Symbolic server == "$SVC" + if(mach[0] == '$' && len mach > 1 && ndb != nil){ + (s, nil) := ipattr->findnetattr(ndb, "sys", myname, mach[1:]); + if(s == nil){ + names := dblook("infernosite", "", mach[1:]); + if(names == nil) + return (nil, "cs: can't translate "+mach); + s = hd names; + } + mach = s; + attr = ipattr->dbattr(mach); + } + if(attr == "sys"){ + results = dblook("sys", mach, "ip"); + if(results != nil) + attr = "ip"; + } + if(attr != "ip"){ + err: string; + (results, err) = querydns(mach, "ip"); + if(err != nil) + return (nil, err); + }else if(results == nil) + results = mach :: nil; + l = results; + if(l == nil){ + if(srv != nil) + l = srv->iph2a(mach); + if(l == nil) + return (nil, "cs: unknown host"); + } + } else + l = mach :: nil; + + while(l != nil) { + s := hd l; + l = tl l; + if(s != "") + s[len s] = '!'; + s += service; + + repl = mntpt+"/"+netw+"/clone "+s; + if(verbose) + sys->fprint(stderr, "cs: %s!%s!%s -> %s\n", netw, mach, service, repl); + + rl = repl :: rl; + } + rl = reverse(rl); + putcache(address, rl, now); + return (rl, nil); +} + +querydns(name: string, rtype: string): (list of string, string) +{ + fd := sys->open(mntpt+"/dns", Sys->ORDWR); + if(fd == nil) + return (nil, nil); + if(sys->fprint(fd, "%s %s", name, rtype) < 0) + return (nil, sys->sprint("%r")); + rl: list of string; + buf := array[256] of byte; + sys->seek(fd, big 0, 0); + while((n := sys->read(fd, buf, len buf)) > 0){ + # name rtype value + (nf, fld) := sys->tokenize(string buf[0:n], " \t"); + if(nf != 3){ + sys->fprint(stderr, "cs: odd result from dns: %s\n", string buf[0:n]); + continue; + } + rl = hd tl tl fld :: rl; + } + return (reverse(rl), nil); +} + +dblook(attr: string, val: string, rattr: string): list of string +{ + rl: list of string; + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = ndb.findbyattr(ptr, attr, val, rattr); + if(e == nil) + break; + for(l := e.findbyattr(attr, val, rattr); l != nil; l = tl l){ + (nil, al) := hd l; + for(; al != nil; al = tl al) + if(!inlist((hd al).val, rl)) + rl = (hd al).val :: rl; + } + } + return reverse(rl); +} + +inlist(s: string, l: list of string): int +{ + for(; l != nil; l = tl l) + if(hd l == s) + return 1; + return 0; +} + +reverse(l: list of string): list of string +{ + t: list of string; + for(; l != nil; l = tl l) + t = hd l :: t; + return t; +} + +isnumeric(a: string): int +{ + i, c: int; + + for(i = 0; i < len a; i++) { + c = a[i]; + if(c < '0' || c > '9') + return 0; + } + return 1; +} + +nets: list of string; + +isnetwork(s: string) : int +{ + if(find(s, nets)) + return 1; + (ok, nil) := sys->stat(mntpt+"/"+s+"/clone"); + if(ok >= 0) { + nets = s :: nets; + return 1; + } + return 0; +} + +find(e: string, l: list of string) : int +{ + for(; l != nil; l = tl l) + if (e == hd l) + return 1; + return 0; +} + +isipnet(s: string) : int +{ + return s == "net" || s == "tcp" || s == "udp" || s == "il"; +} + +xlatesvc(proto: string, s: string): string +{ + if(ndb == nil || s == nil || isnumeric(s)) + return s; + (e, nil) := ndb.findbyattr(nil, proto, s, "port"); + if(e == nil) + return nil; + matches := e.findbyattr(proto, s, "port"); + if(matches == nil) + return nil; + (ts, al) := hd matches; + restricted := ""; + if(ts.hasattr("restricted")) + restricted = "!r"; + if(verbose > 1) + sys->print("%s=%q port=%s%s\n", proto, s, (hd al).val, restricted); + return (hd al).val+restricted; +} + +time(): int +{ + timefd := sys->open("/dev/time", Sys->OREAD); + if(timefd == nil) + return 0; + buf := array[128] of byte; + sys->seek(timefd, big 0, 0); + n := sys->read(timefd, buf, len buf); + if(n < 0) + return 0; + return int ((big string buf[0:n]) / big 1000000); +} + +# +# general query: attr1=val1 attr2=val2 ... finds matching tuple(s) +# where attr1 is the key and val1 can't be * +# +genquery(query: string): (list of string, string) +{ + (tups, err) := attrdb->parseline(query, 0); + if(err != nil) + return (nil, "bad query: "+err); + if(tups == nil) + return (nil, "bad query"); + pairs := tups.pairs; + a0 := (hd pairs).attr; + if(a0 == "ipinfo") + return (nil, "ipinfo not yet supported"); + v0 := (hd pairs).val; + + # if((a0 == "dom" || a0 == "ip") && v0 != nil){ + # query dns ... + # } + + ptr: ref Attrdb->Dbptr; + e: ref Dbentry; + for(;;){ + (e, ptr) = ndb.findpair(ptr, a0, v0); + if(e == nil) + break; + for(l := e.lines; l != nil; l = tl l) + if(qmatch(hd l, tl pairs)){ + ls: list of string; + for(l = e.lines; l != nil; l = tl l) + ls = tuptext(hd l) :: ls; + return (reverse(ls), nil); + } + } + return (nil, "no match"); +} + +# +# see if set of tuples t contains every non-* attr/val pair +# +qmatch(t: ref Tuples, av: list of ref Attr): int +{ +Match: + for(; av != nil; av = tl av){ + a := hd av; + for(pl := t.pairs; pl != nil; pl = tl pl) + if((hd pl).attr == a.attr && + (a.val == "*" || a.val == (hd pl).val)) + continue Match; + return 0; + } + return 1; +} + +tuptext(t: ref Tuples): string +{ + s: string; + for(pl := t.pairs; pl != nil; pl = tl pl){ + p := hd pl; + if(s != nil) + s[len s] = ' '; + s += sys->sprint("%s=%q", p.attr, p.val); + } + return s; +} diff --git a/appl/cmd/ndb/csquery.b b/appl/cmd/ndb/csquery.b new file mode 100644 index 00000000..61690617 --- /dev/null +++ b/appl/cmd/ndb/csquery.b @@ -0,0 +1,97 @@ +implement Csquery; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Csquery: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: csquery [-x /net] [-s server] [address ...]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + cantload(Bufio->PATH); + + net := "/net"; + server: string; + arg := load Arg Arg->PATH; + if(arg == nil) + cantload(Arg->PATH); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'x' => + net = arg->arg(); + if(net == nil) + usage(); + 's' => + server = arg->arg(); + if(server == nil) + usage(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(server == nil) + server = net+"/cs"; + if(args != nil){ + for(; args != nil; args = tl args) + csquery(server, hd args); + }else{ + f := bufio->fopen(sys->fildes(0), Sys->OREAD); + if(f == nil) + exit; + for(;;){ + sys->print("> "); + s := f.gets('\n'); + if(s == nil) + break; + csquery(server, s[0:len s-1]); + } + } +} + +cantload(s: string) +{ + sys->fprint(sys->fildes(2), "csquery: can't load %s: %r\n", s); + raise "fail:load"; +} + +csquery(server: string, addr: string) +{ + cs := sys->open(server, Sys->ORDWR); + if(cs == nil){ + sys->fprint(sys->fildes(2), "csquery: can't open %s: %r\n", server); + raise "fail:open"; + } + stdout := sys->fildes(1); + b := array of byte addr; + if(sys->write(cs, b, len b) > 0){ + sys->seek(cs, big 0, Sys->SEEKSTART); + buf := array[256] of byte; + while((n := sys->read(cs, buf, len buf)) > 0) + sys->print("%s\n", string buf[0:n]); + if(n == 0) + return; + } + sys->print("%s: %r\n", addr); +} diff --git a/appl/cmd/ndb/dns.b b/appl/cmd/ndb/dns.b new file mode 100644 index 00000000..8aa1fc0a --- /dev/null +++ b/appl/cmd/ndb/dns.b @@ -0,0 +1,1860 @@ +implement DNS; + +# +# domain name service +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# +# RFCs: 1034, 1035, 2181, 2308 +# +# TO DO: +# server side: +# database; inmyzone; ptr generation; separate zone transfer +# currently doesn't implement loony rules on case +# limit work +# check data +# Call +# ipv6 +# + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; + +include "draw.m"; + +include "bufio.m"; + +include "srv.m"; + srv: Srv; + +include "ip.m"; + ip: IP; + IPaddrlen, IPaddr, IPv4off, OUdphdrlen: import ip; + +include "arg.m"; + +include "attrdb.m"; + attrdb: Attrdb; + Db, Dbentry, Tuples: import attrdb; + +include "ipattr.m"; + ipattr: IPattr; + dbattr: import ipattr; + +DNS: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Reply: adt +{ + fid: int; + pid: int; + query: string; + attr: string; + addrs: list of string; + err: string; +}; + +rlist: list of ref Reply; + +dnsfile := "/lib/ndb/local"; +myname: string; +mntpt := "/net"; +DNSport: con 53; +debug := 0; +referdns := 0; +usehost := 1; +now: int; + +servers: list of string; + +# domain name from dns/db +domain: string; +dnsdomains: list of string; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + arg := load Arg Arg->PATH; + if(arg == nil) + cantload(Arg->PATH); + arg->init(args); + arg->setusage("dns [-Drh] [-f dnsfile] [-x mntpt]"); + svcname := "#sdns"; + while((c := arg->opt()) != 0) + case c { + 'D' => + debug = 1; + 'f' => + dnsfile = arg->earg(); + 'h' => + usehost = 0; + 'r' => + referdns = 1; + 'x' => + mntpt = arg->earg(); + svcname = "#sdns"+svcpt(mntpt); + * => + arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + + if(usehost){ + srv = load Srv Srv->PATH; # hosted Inferno only + if(srv != nil) + srv->init(); + } + ip = load IP IP->PATH; + if(ip == nil) + cantload(IP->PATH); + ip->init(); + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + cantload(Attrdb->PATH); + attrdb->init(); + ipattr = load IPattr IPattr->PATH; + if(ipattr == nil) + cantload(IPattr->PATH); + ipattr->init(attrdb, ip); + + sys->pctl(Sys->NEWPGRP | Sys->FORKFD, nil); + myname = sysname(); + stderr = sys->fildes(2); + readservers(); + now = time(); + sys->remove(svcname+"/dns"); + sys->unmount(svcname, mntpt); + publish(svcname); + if(sys->bind(svcname, mntpt, Sys->MBEFORE) < 0) + error(sys->sprint("can't bind #s on %s: %r", mntpt)); + file := sys->file2chan(mntpt, "dns"); + if(file == nil) + error(sys->sprint("can't make %s/dns: %r", mntpt)); + sync := chan of int; + spawn dnscache(sync); + <-sync; + spawn dns(file); +} + +publish(dir: string) +{ + d := Sys->nulldir; + d.mode = 8r777; + if(sys->wstat(dir, d) < 0) + sys->fprint(sys->fildes(2), "cs: can't publish %s: %r\n", dir); +} + +svcpt(s: string): string +{ + for(i:=0; i<len s; i++) + if(s[i] == '/') + s[i] = '_'; + return s; +} + +cantload(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(stderr, "dns: %s\n", s); + raise "fail:error"; +} + +dns(file: ref Sys->FileIO) +{ + pidc := chan of int; + donec := chan of ref Reply; + for(;;){ + alt { + (nil, buf, fid, wc) := <-file.write => + now = time(); + cleanfid(fid); # each write cancels previous requests + if(wc != nil){ + r := ref Reply; + r.fid = fid; + spawn request(r, buf, wc, pidc, donec); + r.pid = <-pidc; + rlist = r :: rlist; + } + + (off, nbytes, fid, rc) := <-file.read => + now = time(); + if(rc != nil){ + r := findfid(fid); + if(r != nil) + reply(r, off, nbytes, rc); + else + rc <-= (nil, "unknown request"); + } + + r := <-donec => + now = time(); + r.pid = 0; + if(r.err != nil) + cleanfid(r.fid); + } + } +} + +findfid(fid: int): ref Reply +{ + for(rl := rlist; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid == fid) + return r; + } + return nil; +} + +cleanfid(fid: int) +{ + rl := rlist; + rlist = nil; + for(; rl != nil; rl = tl rl){ + r := hd rl; + if(r.fid != fid) + rlist = r :: rlist; + else + killgrp(r.pid); + } +} + +killgrp(pid: int) +{ + if(pid != 0){ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "killgrp") < 0) + sys->fprint(stderr, "dns: can't killgrp %d: %r\n", pid); + } +} + +request(r: ref Reply, data: array of byte, wc: chan of (int, string), pidc: chan of int, donec: chan of ref Reply) +{ + pidc <-= sys->pctl(Sys->NEWPGRP, nil); + query := string data; + for(i := 0; i < len query; i++) + if(query[i] == ' ') + break; + r.query = query[0:i]; + for(; i < len query && query[i] == ' '; i++) + ; + r.attr = query[i:]; + attr := rrtype(r.attr); + if(attr < 0) + r.err = "unknown type"; + else + (r.addrs, r.err) = dnslookup(r.query, attr); + if(r.addrs == nil && r.err == nil) + r.err = "not found"; + if(r.err != nil){ + if(debug) + sys->fprint(stderr, "dns: %s: %s\n", query, r.err); + wc <-= (0, "dns: "+r.err); + } else + wc <-= (len data, nil); + donec <-= r; +} + +reply(r: ref Reply, off: int, nbytes: int, rc: chan of (array of byte, string)) +{ + if(r.err != nil || r.addrs == nil){ + rc <-= (nil, r.err); + return; + } + addr: string; + if(r.addrs != nil){ + addr = hd r.addrs; + r.addrs = tl r.addrs; + } + off = 0; # this version ignores offsets +# rc <-= reads(r.query+" "+r.attr+" "+addr, off, nbytes); + rc <-= reads(addr, off, nbytes); +} + +# +# return the file2chan reply for a read of the given string +# +reads(str: string, off, nbytes: int): (array of byte, string) +{ + bstr := array of byte str; + slen := len bstr; + if(off < 0 || off >= slen) + return (nil, nil); + if(off + nbytes > slen) + nbytes = slen - off; + if(nbytes <= 0) + return (nil, nil); + return (bstr[off:off+nbytes], nil); +} + +sysname(): string +{ + t := rf("/dev/sysname"); + if(t != nil) + return t; + t = rf("#e/sysname"); + if(t == nil){ + s := rf(mntpt+"/ndb"); + if(s != nil){ + db := Db.sopen(t); + if(db != nil){ + (e, nil) := db.find(nil, "sys"); + if(e != nil) + t = e.findfirst("sys"); + } + } + } + if(t != nil){ + fd := sys->open("/dev/sysname", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "%s", t); + } + return t; +} + +rf(name: string): string +{ + fd := sys->open(name, Sys->OREAD); + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return nil; + return string buf[0:n]; +} + +samefile(d1, d2: Sys->Dir): int +{ + # ``it was black ... it was white! it was dark ... it was light! ah yes, i remember it well...'' + return d1.dev==d2.dev && d1.dtype==d2.dtype && + d1.qid.path==d2.qid.path && d1.qid.vers==d2.qid.vers && + d1.mtime==d2.mtime; +} + +# +# database +# dnsdomain= suffix to add to unqualified unrooted names +# dns= dns server to try +# dom= domain name +# ip= IP address +# ns= name server +# soa= +# soa=delegated +# infernosite= set of site-wide parameters +# + +# +# basic Domain Name Service resolver +# + +laststat := 0; # time last stat'd (to reduce churn) +dnsdb: ref Db; + +readservers(): list of string +{ + if(laststat != 0 && now < laststat+2*60) + return servers; + laststat = now; + if(dnsdb == nil){ + db := Db.open(dnsfile); + if(db == nil){ + sys->fprint(stderr, "dns: can't open %s: %r\n", dnsfile); + return nil; + } + dyndb := Db.open(mntpt+"/ndb"); + if(dyndb != nil) + dnsdb = dyndb.append(db); + else + dnsdb = db; + }else{ + if(!dnsdb.changed()) + return servers; + dnsdb.reopen(); + } + if((l := dblooknet("sys", myname, "dnsdomain")) == nil) + l = dblook("infernosite", "", "dnsdomain"); + dnsdomains = "" :: l; + if((l = dblooknet("sys", myname, "dns")) == nil) + l = dblook("infernosite", "", "dns"); + servers = l; +# zones := dblook("soa", "", "dom"); +#printlist("zones", zones); + if(debug) + printlist("dnsdomains", dnsdomains); + if(debug) + printlist("servers", servers); + return servers; +} + +printlist(w: string, l: list of string) +{ + sys->print("%s:", w); + for(; l != nil; l = tl l) + sys->print(" %q", hd l); + sys->print("\n"); +} + +dblookns(dom: string): list of ref RR +{ + domns := dblook("dom", dom, "ns"); + hosts: list of ref RR; + for(; domns != nil; domns = tl domns){ + s := hd domns; + if(debug) + sys->print("dns db: dom=%s ns=%s\n", dom, s); + ipl: list of ref RR = nil; + addrs := dblook("dom", s, "ip"); + for(; addrs != nil; addrs = tl addrs){ + a := parseip(hd addrs); + if(a != nil){ + ipl = ref RR.A(s, Ta, Cin, now+60, 0, a) :: ipl; + if(debug) + sys->print("dom=%s ip=%s\n", s, hd addrs); + } + } + if(ipl != nil){ + # only use ones for which we've got addresses + cachec <-= (ipl, 0); + hosts = ref RR.Host(dom, Tns, Cin, now+60, 0, s) :: hosts; + } + } + if(hosts == nil){ + if(debug) + sys->print("dns: no ns for dom=%s in db\n", dom); + return nil; + } + cachec <-= (hosts, 0); + cachec <-= Sync; + return hosts; +} + +defaultresolvers(): list of ref NS +{ + resolvers := readservers(); + al: list of ref RR; + for(; resolvers != nil; resolvers = tl resolvers){ + nm := hd resolvers; + a := parseip(nm); + if(a == nil){ + # try looking it up as a domain name with an ip address + for(addrs := dblook("dom", nm, "ip"); addrs != nil; addrs = tl addrs){ + a = parseip(hd addrs); + if(a != nil) + al = ref RR.A("defaultns", Ta, Cin, now+60, 0, a) :: al; + } + }else + al = ref RR.A("defaultns", Ta, Cin, now+60, 0, a) :: al; + } + if(al == nil){ + if(debug) + sys->print("dns: no default resolvers\n"); + return nil; + } + return ref NS("defaultns", al, 1, now+60) :: nil; +} + +dblook(attr: string, val: string, rattr: string): list of string +{ + rl: list of string; + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = dnsdb.findbyattr(ptr, attr, val, rattr); + if(e == nil) + break; + for(l := e.findbyattr(attr, val, rattr); l != nil; l = tl l){ + (nil, al) := hd l; + for(; al != nil; al = tl al) + if(!inlist((hd al).val, rl)) + rl = (hd al).val :: rl; + } + } + return reverse(rl); +} + +# +# starting from the ip= associated with attr=val, search over all +# containing networks for the nearest values of rattr +# +dblooknet(attr: string, val: string, rattr: string): list of string +{ +#sys->print("dblooknet: %s=%s -> %s\n", attr, val, rattr); + (results, nil) := ipattr->findnetattrs(dnsdb, attr, val, rattr::nil); + rl: list of string; + for(; results != nil; results = tl results){ + (nil, nattrs) := hd results; + for(; nattrs != nil; nattrs = tl nattrs){ + na := hd nattrs; + if(na.name == rattr){ + for(pairs := na.pairs; pairs != nil; pairs = tl pairs) + if((s := (hd pairs).val) != nil && !inlist(s, rl)) + rl = s :: rl; + } + } + } + if(rl == nil) + return dblook(attr, val, rattr); + return reverse(rl); +} + +inlist(s: string, l: list of string): int +{ + for(; l != nil; l = tl l) + if(hd l == s) + return 1; + return 0; +} + +reverse[T](l: list of T): list of T +{ + r: list of T; + for(; l != nil; l = tl l) + r = hd l :: r; + return r; +} + +append(h: list of string, s: string): list of string +{ + if(h == nil) + return s :: nil; + return hd h :: append(tl h, s); +} + +# +# subset of RR types +# +Ta: con 1; +Tns: con 2; +Tcname: con 5; +Tsoa: con 6; +Tmb: con 7; +Tptr: con 12; +Thinfo: con 13; +Tmx: con 15; +Tall: con 255; + +# +# classes +# +Cin: con 1; +Call: con 255; + +# +# opcodes +# +Oquery: con 0<<11; # normal query +Oinverse: con 1<<11; # inverse query +Ostatus: con 2<<11; # status request +Omask: con 16rF<<11; # mask for opcode + +# +# response codes +# +Rok: con 0; +Rformat: con 1; # format error +Rserver: con 2; # server failure +Rname: con 3; # bad name +Runimplemented: con 4; # unimplemented operation +Rrefused: con 5; # permission denied, not supported +Rmask: con 16rF; # mask for response + +# +# other flags in opcode +# +Fresp: con 1<<15; # message is a response +Fauth: con 1<<10; # true if an authoritative response +Ftrunc: con 1<<9; # truncated message +Frecurse: con 1<<8; # request recursion +Fcanrecurse: con 1<<7; # server can recurse + +QR: adt { + name: string; + rtype: int; + class: int; + + text: fn(q: self ref QR): string; +}; + +RR: adt { + name: string; + rtype: int; + class: int; + ttl: int; + flags: int; + pick { + Error => + reason: string; # cached negative + Host => + host: string; + Hinfo => + cpu: string; + os: string; + Mx => + pref: int; + host: string; + Soa => + soa: ref SOA; + A or + Other => + rdata: array of byte; + } + + islive: fn(r: self ref RR): int; + outlives: fn(a: self ref RR, b: ref RR): int; + match: fn(a: self ref RR, b: ref RR): int; + text: fn(a: self ref RR): string; +}; + +SOA: adt { + mname: string; + rname: string; + serial: int; + refresh: int; + retry: int; + expire: int; + minttl: int; + + text: fn(nil: self ref SOA): string; +}; + +DNSmsg: adt { + id: int; + flags: int; + qd: list of ref QR; + an: list of ref RR; + ns: list of ref RR; + ar: list of ref RR; + err: string; + + pack: fn(m: self ref DNSmsg, hdrlen: int): array of byte; + unpack: fn(a: array of byte): ref DNSmsg; + text: fn(m: self ref DNSmsg): string; +}; + +NM: adt { + name: string; + rr: list of ref RR; + stats: ref Stats; +}; + +Stats: adt { + rtt: int; +}; + +cachec: chan of (list of ref RR, int); +cache: array of list of ref NM; +Sync: con (nil, 0); # empty list sent to ensure that last cache update done + +hash(s: string): array of list of ref NM +{ + h := 0; + for(i:=0; i<len s; i++){ # hashpjw + c := s[i]; + if(c >= 'A' && c <= 'Z') + c += 'a'-'A'; + h = (h<<4) + c; + if((g := h & int 16rF0000000) != 0) + h ^= ((g>>24) & 16rFF) | g; + } + return cache[(h&~(1<<31))%len cache:]; +} + +lower(s: string): string +{ + for(i := 0; i < len s; i++){ + c := s[i]; + if(c >= 'A' && c <= 'Z'){ + n := s; + for(; i < len n; i++){ + c = n[i]; + if(c >= 'A' && c <= 'Z') + n[i] = c+('a'-'A'); + } + return n; + } + } + return s; +} + +# +# split rrl into a list of those RRs that match rr and a list of those that don't +# +partrrl(rr: ref RR, rrl: list of ref RR): (list of ref RR, list of ref RR) +{ + m: list of ref RR; + nm: list of ref RR; + name := lower(rr.name); + for(; rrl != nil; rrl = tl rrl){ + t := hd rrl; + if(t.rtype == rr.rtype && t.class == rr.class && + (t.name == name || lower(t.name) == name)) + m = t :: m; + else + nm = t :: nm; + } + return (m, nm); +} + +copyrrl(rrl: list of ref RR): list of ref RR +{ + nl: list of ref RR; + for(; rrl != nil; rrl = tl rrl) + nl = ref *hd rrl :: nl; +# return revrrl(rrl); + return rrl; # probably don't care about order +} + +dnscache(sync: chan of int) +{ + cache = array[32] of list of ref NM; + cachec = chan of (list of ref RR, int); + sync <-= sys->pctl(0, nil); + for(;;){ + (rrl, flags) := <-cachec; + #now = time(); + List: + while(rrl != nil){ + rrset: list of ref RR; + (rrset, rrl) = partrrl(hd rrl, rrl); + rr := hd rrset; + rr.flags = flags; + name := lower(rr.name); + hb := hash(name); + for(ces := hb[0]; ces != nil; ces = tl ces){ + ce := hd ces; + if(ce.name == name){ + rr.name = ce.name; # share string + x := ce.rr; + ce.rr = insertrrset(ce.rr, rr, rrset); + if(x != ce.rr && debug) + sys->print("insertrr %s:%s\n", name, rrsettext(rrset)); + continue List; + } + } + if(debug) + sys->print("newrr %s:%s\n", name, rrsettext(rrset)); + hb[0] = ref NM(name, rrset, nil) :: hb[0]; + } + } +} + +lookcache(name: string, rtype: int, rclass: int): (list of ref RR, string) +{ + results: list of ref RR; + name = lower(name); + for(ces := hash(name)[0]; ces != nil; ces = tl ces){ + ce := hd ces; + if(ce.name == name){ + for(zl := ce.rr; zl != nil; zl = tl zl){ + r := hd zl; + if((r.rtype == rtype || r.rtype == Tall || rtype == Tall) && r.class == rclass && r.name == name && r.islive()){ + pick ar := r { + Error => + if(rtype != Tall || ar.reason != "resource does not exist"){ + if(debug) + sys->print("lookcache: %s[%s]: !%s\n", name, rrtypename(rtype), ar.reason); + return (nil, ar.reason); + } + * => + results = ref *r :: results; + } + } + } + } + } + if(debug) + sys->print("lookcache: %s[%s]: %s\n", name, rrtypename(rtype), rrsettext(results)); + return (results, nil); +} + +# +# insert RRset new in existing list of RRsets rrl +# if that's desirable (it's the whole RRset or nothing, see rfc2181) +# +insertrrset(rrl: list of ref RR, rr: ref RR, new: list of ref RR): list of ref RR +{ + # TO DO: expire entries + match := 0; + for(l := rrl; l != nil; l = tl l){ + orr := hd l; + if(orr.rtype == rr.rtype && orr.class == rr.class){ # name already known to match + match = 1; + if(!orr.islive()) + break; # prefer new, unexpired data + if(tagof rr == tagof RR.Error && tagof orr != tagof RR.Error) + return rrl; # prefer unexpired positive + if(rr.flags & Fauth) + break; # prefer newly-arrived authoritative data + if(orr.flags & Fauth) + return rrl; # prefer authoritative data + if(orr.outlives(rr)) + return rrl; # prefer longer-lived data + } + } + if(match){ + # strip out existing RR set + l = rrl; + rrl = nil; + for(; l != nil; l = tl l){ + orr := hd l; + if((orr.rtype != rr.rtype || orr.class != rr.class) && orr.islive()){ + rrl = orr :: rrl;} + } + } + # add new RR set + for(; new != nil; new = tl new){ + nrr := hd new; + nrr.name = rr.name; + rrl = nrr :: rrl; + } + return rrl; +} + +rrsettext(rrl: list of ref RR): string +{ + s := ""; + for(; rrl != nil; rrl = tl rrl) + s += " ["+(hd rrl).text()+"]"; + return s; +} + +QR.text(qr: self ref QR): string +{ + s := sys->sprint("%s %s", qr.name, rrtypename(qr.rtype)); + if(qr.class != Cin) + s += sys->sprint(" [c=%d]", qr.class); + return s; +} + +RR.islive(rr: self ref RR): int +{ + return rr.ttl >= now; +} + +RR.outlives(a: self ref RR, b: ref RR): int +{ + return a.ttl > b.ttl; +} + +RR.match(a: self ref RR, b: ref RR): int +{ + # compare content, not ttl + return a.rtype == b.rtype && a.class == b.class && a.name == b.name; +} + +RR.text(rr: self ref RR): string +{ + s := sys->sprint("%s %s", rr.name, rrtypename(rr.rtype)); + pick ar := rr { + Host => + s += sys->sprint("\t%s", ar.host); + Hinfo => + s += sys->sprint("\t%s %s", ar.cpu, ar.os); + Mx => + s += sys->sprint("\t%ud %s", ar.pref, ar.host); + Soa => + s += sys->sprint("\t%s", ar.soa.text()); + A => + if(len ar.rdata == 4){ + a := ar.rdata; + s += sys->sprint("\t%d.%d.%d.%d", int a[0], int a[1], int a[2], int a[3]); + } + Error => + s += sys->sprint("\t!%s", ar.reason); + } + return s; +} + +SOA.text(soa: self ref SOA): string +{ + return sys->sprint("%s %s %ud %ud %ud %ud %ud", soa.mname, soa.rname, + soa.serial, soa.refresh, soa.retry, soa.expire, soa.minttl); +} + +NS: adt { + name: string; + addr: list of ref RR; + canrecur: int; + ttl: int; +}; + +dnslookup(name: string, attr: int): (list of string, string) +{ + case attr { + Ta => + case dbattr(name) { + "sys" => + # could apply domains + ; + "dom" => + ; + * => + return (nil, "invalid host name"); + } + if(srv != nil){ # try the host's map first + l := srv->iph2a(name); + if(l != nil) + return (fullresult(name, "ip", l), nil); + } + Tptr => + if(srv != nil){ # try host's map first + l := srv->ipa2h(arpa2addr(name)); + if(l != nil) + return (fullresult(name, "ptr", l), nil); + } + } + return dnslookup1(name, attr); +} + +fullresult(name: string, attr: string, l: list of string): list of string +{ + rl: list of string; + for(; l != nil; l = tl l) + rl = sys->sprint("%s %s\t%s", name, attr, hd l) :: rl; + return reverse(rl); +} + +arpa2addr(a: string): string +{ + (nf, flds) := sys->tokenize(a, "."); + rl: list of string; + for(; flds != nil && lower(s := hd flds) != "in-addr"; flds = tl flds) + rl = s :: rl; + dom: string; + for(; rl != nil; rl = tl rl){ + if(dom != nil) + dom[len dom] = '.'; + dom += hd rl; + } + return dom; +} + +dnslookup1(label: string, attr: int): (list of string, string) +{ + (rrl, err) := fulldnsquery(label, attr, 0); + if(err != nil || rrl == nil) + return (nil, err); + r: list of string; + for(; rrl != nil; rrl = tl rrl) + r = (hd rrl).text() :: r; + return (reverse(r), nil); +} + +trimdot(s: string): string +{ + while(s != nil && s[len s - 1] == '.') + s = s[0:len s -1]; + return s; +} + +parent(s: string): string +{ + if(s == "") + return "."; + for(i := 0; i < len s; i++) + if(s[i] == '.') + return s[i+1:]; + return ""; +} + +rootservers(): list of ref NS +{ + slist := ref NS("a.root-servers.net", + ref RR.A("a.root-servers.net", Ta, Cin, 1<<31, 0, + array[] of {byte 198, byte 41, byte 0, byte 4})::nil, 0, 1<<31) :: nil; + return slist; +} + +# +# this broadly follows the algorithm given in RFC 1034 +# as adjusted and qualified by several other RFCs. +# `label' is 1034's SNAME, `attr' is `STYPE' +# +# TO DO: +# keep statistics for name servers + +fulldnsquery(label: string, attr: int, depth: int): (list of ref RR, string) +{ + slist: list of ref NS; + fd: ref Sys->FD; + if(depth > 10) + return (nil, "dns loop"); + ncname := 0; +Step1: + for(tries:=0; tries<10; tries++){ + + # 1. see if in local information, and if so, return it + (x, err) := lookcache(label, attr, Cin); + if(x != nil) + return (x, nil); + if(err != nil) + return (nil, err); + if(attr != Tcname){ + if(++ncname > 10) + return (nil, "cname alias loop"); + (x, err) = lookcache(label, Tcname, Cin); + if(x != nil){ + pick rx := hd x { + Host => + label = rx.host; + continue; + } + } + } + + # 2. find the best servers to ask + slist = nil; + for(d := trimdot(label); d != "."; d = parent(d)){ + nsl: list of ref RR; + (nsl, err) = lookcache(d, Tns, Cin); + if(nsl == nil) + nsl = dblookns(d); + # add each to slist; put ones with known addresses first + known: list of ref NS = nil; + for(; nsl != nil; nsl = tl nsl){ + pick ns := hd nsl { + Host => + (addrs, err2) := lookcache(ns.host, Ta, Cin); + if(addrs != nil) + known = ref NS(ns.host, addrs, 0, 1<<31) :: known; + else if(err2 == nil) + slist = ref NS(ns.host, nil, 0, 1<<31) :: slist; + } + + } + for(; known != nil; known = tl known) + slist = hd known :: slist; + if(slist != nil) + break; + } + # if no servers, resort to safety belt + if(slist == nil){ + slist = defaultresolvers(); + if(slist == nil){ + slist = rootservers(); + if(slist == nil) + return (nil, "no dns servers configured"); + } + } + (id, query, err1) := mkquery(attr, Cin, label); + if(err1 != nil){ + sys->fprint(stderr, "dns: %s\n", err1); + return (nil, err1); + } + + if(debug) + printnslist(sys->sprint("ns for %s: ", d), slist); + + # 3. send them queries until one returns a response + for(qset := slist; qset != nil; qset = tl qset){ + ns := hd qset; + if(ns.addr == nil){ + if(debug) + sys->print("recursive[%d] query for %s address\n", depth+1, ns.name); + (ns.addr, nil) = fulldnsquery(ns.name, Ta, depth+1); + if(ns.addr == nil) + continue; + } + if(fd == nil){ + fd = udpport(); + if(fd == nil) + return (nil, sys->sprint("%r")); + } + (dm, err2) := udpquery(fd, id, query, ns.name, hd ns.addr); + if(dm == nil){ + sys->fprint(stderr, "dns: %s: %s\n", ns.name, err2); + # TO DO: remove from slist + continue; + } + # 4. analyse the response + # a. answers the question or has Rname, cache it and return to client + # b. delegation to other NS? cache and goto step 2. + # c. if response is CNAME and QTYPE!=CNAME change SNAME to the + # canonical name (data) of the CNAME RR and goto step 1. + # d. if response is server failure or otherwise odd, delete server from SLIST + # and goto step 3. + auth := (dm.flags & Fauth) != 0; + soa: ref RR.Soa; + (soa, dm.ns) = soaof(dm.ns); + if((dm.flags & Rmask) != Rok){ + # don't repeat the request on an error + # TO DO: should return `best error' + if(tl qset != nil && ((dm.flags & Rmask) != Rname || !auth)) + continue; + cause := reason(dm.flags & Rmask); + if(auth && soa != nil){ + # rfc2038 says to cache soa with cached negatives, and the + # negative to be retrieved for all attributes if name does not exist + if((ttl := soa.soa.minttl) > 0) + ttl += now; + else + ttl = now+10*60; + a := attr; + if((dm.flags & Rmask) == Rname) + a = Tall; + cachec <-= (ref RR.Error(label, a, Cin, ttl, auth, cause)::soa::nil, auth); + } + return (nil, cause); + } + if(dm.an != nil){ + if(1 && dm.ns != nil) + cachec <-= (dm.ns, 0); + if(1 && dm.ar != nil) + cachec <-= (dm.ar, 0); + cachec <-= (dm.an, auth); + cachec <-= Sync; + if(isresponse(dm, attr)) + return (dm.an, nil); + if(attr != Tcname && (cn := cnameof(dm)) != nil){ + if(++ncname > 10) + return (nil, "cname alias loop"); + label = cn; + continue Step1; + } + } + if(auth){ + if(soa != nil && (ttl := soa.soa.minttl) > 0) + ttl += now; + else + ttl = now+10*60; + cachec <-= (ref RR.Error(label, attr, Cin, ttl, auth, "resource does not exist")::soa::nil, auth); + return (nil, "resource does not exist"); + } + if(isdelegation(dm)){ + # cache valid name servers and hints + cachec <-= (dm.ns, 0); + if(dm.ar != nil) + cachec <-= (dm.ar, 0); + cachec <-= Sync; + continue Step1; + } + } + } + return (nil, "server failed"); +} + +isresponse(dn: ref DNSmsg, attr: int): int +{ + if(dn == nil || dn.an == nil) + return 0; + return (hd dn.an).rtype == attr; +} + +cnameof(dn: ref DNSmsg): string +{ + if(dn != nil && dn.an != nil && (rr := hd dn.an).rtype == Tcname) + pick ar := rr { + Host => + return ar.host; + } + return nil; +} + +soaof(rrl: list of ref RR): (ref RR.Soa, list of ref RR) +{ + for(l := rrl; l != nil; l = tl l) + pick rr := hd l { + Soa => + rest := tl l; + for(; rrl != l; rrl = tl rrl) + if(tagof hd rrl != tagof RR.Soa) # (just in case) + rest = hd rrl :: rest; + return (rr, rest); + } + return (nil, rrl); +} + +isdelegation(dn: ref DNSmsg): int +{ + if(dn.an != nil) + return 0; + for(al := dn.ns; al != nil; al = tl al) + if((hd al).rtype == Tns) + return 1; + return 0; +} + +printnslist(prefix: string, nsl: list of ref NS) +{ + s := prefix; + for(; nsl != nil; nsl = tl nsl){ + ns := hd nsl; + s += sys->sprint(" [%s %s]", ns.name, rrsettext(ns.addr)); + } + sys->print("%s\n", s); +} + +# +# DNS message format +# + +Udpdnslim: con 512; + +Labels: adt { + names: list of (string, int); + + new: fn(): ref Labels; + look: fn(labs: self ref Labels, s: string): int; + install: fn(labs: self ref Labels, s: string, o: int); +}; + +Labels.new(): ref Labels +{ + return ref Labels; +} + +Labels.look(labs: self ref Labels, s: string): int +{ + for(nl := labs.names; nl != nil; nl = tl nl){ + (t, o) := hd nl; + if(s == t) + return 16rC000 | o; + } + return 0; +} + +Labels.install(labs: self ref Labels, s: string, off: int) +{ + labs.names = (s, off) :: labs.names; +} + +put2(a: array of byte, o: int, val: int): int +{ + if(o < 0) + return o; + if(o + 2 > len a) + return -o; + a[o] = byte (val>>8); + a[o+1] = byte val; + return o+2; +} + +put4(a: array of byte, o: int, val: int): int +{ + if(o < 0) + return o; + if(o + 4 > len a) + return -o; + a[o] = byte (val>>24); + a[o+1] = byte (val>>16); + a[o+2] = byte (val>>8); + a[o+3] = byte val; + return o+4; +} + +puta(a: array of byte, o: int, b: array of byte): int +{ + if(o < 0) + return o; + l := len b; + if(l > 255 || o+l+1 > len a) + return -(o+l+1); + a[o++] = byte l; + a[o:] = b; + return o+len b; +} + +puts(a: array of byte, o: int, s: string): int +{ + return puta(a, o, array of byte s); +} + +get2(a: array of byte, o: int): (int, int) +{ + if(o < 0) + return (0, o); + if(o + 2 > len a) + return (0, -o); + val := (int a[o] << 8) | int a[o+1]; + return (val, o+2); +} + +get4(a: array of byte, o: int): (int, int) +{ + if(o < 0) + return (0, o); + if(o + 4 > len a) + return (0, -o); + val := (((((int a[o] << 8)| int a[o+1]) << 8) | int a[o+2]) << 8) | int a[o+3]; + return (val, o+4); +} + +gets(a: array of byte, o: int): (string, int) +{ + if(o < 0) + return (nil, o); + if(o+1 > len a) + return (nil, -o); + l := int a[o++]; + if(o+l > len a) + return (nil, -o); + return (string a[o:o+l], o+l); +} + +putdn(a: array of byte, o: int, name: string, labs: ref Labels): int +{ + if(o < 0) + return o; + o0 := o; + while(name != "") { + n := labs.look(name); + if(n != 0){ + o = put2(a, o, n); + if(o < 0) + return -o0; + return o; + } + for(l := 0; l < len name && name[l] != '.'; l++) + ; + if(o+l+1 > len a) + return -o0; + labs.install(name, o); + a[o++] = byte l; + for(i := 0; i < l; i++) + a[o++] = byte name[i]; + for(; l < len name && name[l] == '.'; l++) + ; + name = name[l:]; + } + if(o >= len a) + return -o0; + a[o++] = byte 0; + return o; +} + +getdn(a: array of byte, o: int, depth: int): (string, int) +{ + if(depth > 30) + return (nil, -o); + if(o < 0) + return (nil, o); + name := ""; + while(o < len a && (l := int a[o++]) != 0) { + if((l & 16rC0) == 16rC0) { # pointer + if(o >= len a) + return (nil, -o); + po := ((l & 16r3F)<<8) | int a[o]; + if(po >= len a) + return ("", -o); + o++; + pname: string; + (pname, po) = getdn(a, po, depth+1); + if(po < 1) + return (nil, -o); + name += pname; + break; + } + if((l & 16rC0) != 0) + return (nil, -o); # format error + if(o + l > len a) + return (nil, -o); + name += string a[o:o+l]; + o += l; + if(o < len a && a[o] != byte 0) + name += "."; + } + return (lower(name), o); +} + +putqrl(a: array of byte, o: int, qrl: list of ref QR, labs: ref Labels): int +{ + for(; qrl != nil && o >= 0; qrl = tl qrl){ + q := hd qrl; + o = putdn(a, o, q.name, labs); + o = put2(a, o, q.rtype); + o = put2(a, o, q.class); + } + return o; +} + +getqrl(nq: int, a: array of byte, o: int): (list of ref QR, int) +{ + if(o < 0) + return (nil, o); + qrl: list of ref QR; + for(i := 0; i < nq; i++) { + qd := ref QR; + (qd.name, o) = getdn(a, o, 0); + (qd.rtype, o) = get2(a, o); + (qd.class, o) = get2(a, o); + if(o < 1) + break; + qrl = qd :: qrl; + } + q: list of ref QR; + for(; qrl != nil; qrl = tl qrl) + q = hd qrl :: q; + return (q, o); +} + +putrrl(a: array of byte, o: int, rrl: list of ref RR, labs: ref Labels): int +{ + if(o < 0) + return o; + for(; rrl != nil; rrl = tl rrl){ + rr := hd rrl; + o0 := o; + o = putdn(a, o, rr.name, labs); + o = put2(a, o, rr.rtype); + o = put2(a, o, rr.class); + o = put4(a, o, rr.ttl); + pick ar := rr { + Host => + o = putdn(a, o, ar.host, labs); + Hinfo => + o = puts(a, o, ar.cpu); + o = puts(a, o, ar.os); + Mx => + o = put2(a, o, ar.pref); + o = putdn(a, o, ar.host, labs); + Soa => + soa := ar.soa; + o = putdn(a, o, soa.mname, labs); + o = putdn(a, o, soa.rname, labs); + o = put4(a, o, soa.serial); + o = put4(a, o, soa.refresh); + o = put4(a, o, soa.retry); + o = put4(a, o, soa.expire); + o = put4(a, o, soa.minttl); + A or + Other => + dlen := len ar.rdata; + o = put2(a, o, dlen); + if(o < 1) + return -o0; + if(o + dlen > len a) + return -o0; + a[o:] = ar.rdata; + o += dlen; + } + } + return o; +} + +getrrl(nr: int, a: array of byte, o: int): (list of ref RR, int) +{ + if(o < 0) + return (nil, o); + rrl: list of ref RR; + for(i := 0; i < nr; i++) { + name: string; + rtype, rclass, ttl: int; + (name, o) = getdn(a, o, 0); + (rtype, o) = get2(a, o); + (rclass, o) = get2(a, o); + (ttl, o) = get4(a, o); + if(ttl <= 0) + ttl = 0; + #ttl = 1*60; + ttl += now; + dlen: int; + (dlen, o) = get2(a, o); + if(o < 1) + return (rrl, o); + if(o+dlen > len a) + return (rrl, -(o+dlen)); + rr: ref RR; + dname: string; + case rtype { + Tsoa => + soa := ref SOA; + (soa.mname, o) = getdn(a, o, 0); + (soa.rname, o) = getdn(a, o, 0); + (soa.serial, o) = get4(a, o); + (soa.refresh, o) = get4(a, o); + (soa.retry, o) = get4(a, o); + (soa.expire, o) = get4(a, o); + (soa.minttl, o) = get4(a, o); + rr = ref RR.Soa(name, rtype, rclass, ttl, 0, soa); + Thinfo => + cpu, os: string; + (cpu, o) = gets(a, o); + (os, o) = gets(a, o); + rr = ref RR.Hinfo(name, rtype, rclass, ttl, 0, cpu, os); + Tmx => + pref: int; + host: string; + (pref, o) = get2(a, o); + (host, o) = getdn(a, o, 0); + rr = ref RR.Mx(name, rtype, rclass, ttl, 0, pref, host); + Tcname or + Tns or + Tptr => + (dname, o) = getdn(a, o, 0); + rr = ref RR.Host(name, rtype, rclass, ttl, 0, dname); + Ta => + rdata := array[dlen] of byte; + rdata[0:] = a[o:o+dlen]; + rr = ref RR.A(name, rtype, rclass, ttl, 0, rdata); + o += dlen; + * => + rdata := array[dlen] of byte; + rdata[0:] = a[o:o+dlen]; + rr = ref RR.Other(name, rtype, rclass, ttl, 0, rdata); + o += dlen; + } + rrl = rr :: rrl; + } + r: list of ref RR; + for(; rrl != nil; rrl = tl rrl) + r = (hd rrl) :: r; + return (r, o); +} + +DNSmsg.pack(msg: self ref DNSmsg, hdrlen: int): array of byte +{ + a := array[Udpdnslim+hdrlen] of byte; + + l := hdrlen; + l = put2(a, l, msg.id); + l = put2(a, l, msg.flags); + l = put2(a, l, len msg.qd); + l = put2(a, l, len msg.an); + l = put2(a, l, len msg.ns); + l = put2(a, l, len msg.ar); + labs := Labels.new(); + l = putqrl(a, l, msg.qd, labs); + l = putrrl(a, l, msg.an, labs); + l = putrrl(a, l, msg.ns, labs); + l = putrrl(a, l, msg.ar, labs); + if(l < 1) + return nil; + return a[0:l]; +} + +DNSmsg.unpack(a: array of byte): ref DNSmsg +{ + msg := ref DNSmsg; + msg.flags = Rformat; + l := 0; + (msg.id, l) = get2(a, l); + (msg.flags, l) = get2(a, l); + if(l < 0 || l > len a){ + msg.err = "length error"; + return msg; + } + if(l >= len a) + return msg; + + nqd, nan, nns, nar: int; + (nqd, l) = get2(a, l); + (nan, l) = get2(a, l); + (nns, l) = get2(a, l); + (nar, l) = get2(a, l); + if(l >= len a) + return msg; + (msg.qd, l) = getqrl(nqd, a, l); + (msg.an, l) = getrrl(nan, a, l); + (msg.ns, l) = getrrl(nns, a, l); + (msg.ar, l) = getrrl(nar, a, l); + if(l < 1){ + sys->fprint(stderr, "l=%d format error\n", l); + msg.err = "format error"; + return msg; + } + return msg; +} + +DNSmsg.text(msg: self ref DNSmsg): string +{ + s := sys->sprint("id=%ud flags=#%ux[%s]\n", msg.id, msg.flags, flagtext(msg.flags)); + s += " QR:\n"; + for(x := msg.qd; x != nil; x = tl x) + s += "\t"+(hd x).text()+"\n"; + s += " AN:\n"; + for(l := msg.an; l != nil; l = tl l) + s += "\t"+(hd l).text()+"\n"; + s += " NS:\n"; + for(l = msg.ns; l != nil; l = tl l) + s += "\t"+(hd l).text()+"\n"; + s += " AR:\n"; + for(l = msg.ar; l != nil; l = tl l) + s += "\t"+(hd l).text()+"\n"; + return s; +} + +flagtext(f: int): string +{ + s := ""; + if(f & Fresp) + s += "R"; + if(f & Fauth) + s += "A"; + if(f & Ftrunc) + s += "T"; + if(f & Frecurse) + s += "r"; + if(f & Fcanrecurse) + s += "c"; + if((f & Fresp) == 0) + return s; + if(s != "") + s += ","; + return s+reason(f & Rmask); +} + +rcodes := array[] of { + Rok => "no error", + Rformat => "format error", + Rserver => "server failure", + Rname => "name does not exist", + Runimplemented => "unimplemented", + Rrefused => "refused", +}; + +reason(n: int): string +{ + if(n < 0 || n > len rcodes) + return sys->sprint("error %d", n); + return rcodes[n]; +} + +rrtype(s: string): int +{ + case s { + "ip" => return Ta; + "ns" => return Tns; + "cname" => return Tcname; + "soa" => return Tsoa; + "ptr" => return Tptr; + "mx" => return Tmx; + "hinfo" => return Thinfo; + "all" or "any" => return Tall; + * => return -1; + } +} + +rrtypename(t: int): string +{ + case t { + Ta => return "ip"; + Tns => return "ns"; + Tcname => return "cname"; + Tsoa => return "soa"; + Tptr => return "ptr"; + Tmx => return "mx"; + Tall => return "all"; + Thinfo => return "hinfo"; + * => return string t; + } +} + +# +# format of UDP head read and written in `oldheaders' mode +# +Udphdrsize: con OUdphdrlen; +Udpraddr: con 0; +Udpladdr: con IPaddrlen; +Udprport: con 2*IPaddrlen; +Udplport: con 2*IPaddrlen+2; +dnsid := 1; + +mkquery(qtype: int, qclass: int, name: string): (int, array of byte, string) +{ + qd := ref QR(name, qtype, qclass); + dm := ref DNSmsg; + dm.id = dnsid++; # doesn't matter if two different procs use it + dm.flags = Oquery; + if(referdns || !debug) + dm.flags |= Frecurse; + dm.qd = qd :: nil; + a: array of byte; + a = dm.pack(Udphdrsize); + if(a == nil) + return (0, nil, "dns: bad query message"); # should only happen if a name is ridiculous + for(i:=0; i<Udphdrsize; i++) + a[i] = byte 0; + a[Udprport] = byte (DNSport>>8); + a[Udprport+1] = byte DNSport; + return (dm.id, a, nil); +} + +udpquery(fd: ref Sys->FD, id: int, query: array of byte, sname: string, addr: ref RR): (ref DNSmsg, string) +{ + # TO DO: check address and ports? + + if(debug) + sys->print("udp query %s\n", sname); + pick ar := addr { + A => + query[Udpraddr:] = ip->v4prefix[0:IPv4off]; + query[Udpraddr+IPv4off:] = ar.rdata[0:4]; + * => + return (nil, "not A resource"); + } + dm: ref DNSmsg; + pidc := chan of int; + c := chan of array of byte; + spawn reader(fd, c, pidc); + rpid := <-pidc; + spawn timer(c, pidc); + tpid := <-pidc; + for(ntries := 0; ntries < 8; ntries++){ + if(debug){ + ipa := query[Udpraddr+IPv4off:]; + sys->print("send udp!%d.%d.%d.%d!%d [%d] %d\n", int ipa[0], int ipa[1], + int ipa[2], int ipa[3], get2(query, Udprport).t0, ntries, len query); + } + n := sys->write(fd, query, len query); + if(n != len query) + return (nil, sys->sprint("udp write err: %r")); + buf := <-c; + if(buf != nil){ + buf = buf[Udphdrsize:]; + dm = DNSmsg.unpack(buf); + if(dm == nil){ + kill(tpid); + kill(rpid); + return (nil, "bad udp reply message"); + } + if(dm.flags & Fresp && dm.id == id){ + if(dm.flags & Ftrunc && dm.ns == nil){ + if(debug) + sys->print("id=%d was truncated\n", dm.id); + }else + break; + }else if(debug) + sys->print("id=%d got flags #%ux id %d\n", id, dm.flags, dm.id); + }else if(debug) + sys->print("timeout\n"); + } + kill(tpid); + kill(rpid); + if(dm == nil) + return (nil, "no reply"); + if(dm.err != nil){ + sys->fprint(stderr, "bad reply: %s\n", dm.err); + return (nil, dm.err); + } + if(debug) + sys->print("reply: %s\n", dm.text()); + return (dm, nil); +} + +reader(fd: ref Sys->FD, c: chan of array of byte, pidc: chan of int) +{ + pidc <-= sys->pctl(0, nil); + for(;;){ + buf := array[4096+Udphdrsize] of byte; + n := sys->read(fd, buf, len buf); + if(n > 0){ + if(debug) + sys->print("rcvd %d\n", n); + c <-= buf[0:n]; + }else + c <-= nil; + } +} + +timer(c: chan of array of byte, pidc: chan of int) +{ + pidc <-= sys->pctl(0, nil); + for(;;){ + sys->sleep(5*1000); + c <-= nil; + } +} + +kill(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +udpport(): ref Sys->FD +{ + (ok, conn) := sys->announce(mntpt+"/udp!*!0"); + if(ok < 0) + return nil; + if(sys->fprint(conn.cfd, "headers") < 0){ + sys->fprint(stderr, "dns: can't set headers mode: %r\n"); + return nil; + } + sys->fprint(conn.cfd, "oldheaders"); # plan 9 interface + conn.dfd = sys->open(conn.dir+"/data", Sys->ORDWR); + if(conn.dfd == nil){ + sys->fprint(stderr, "dns: can't open %s/data: %r\n", conn.dir); + return nil; + } + return conn.dfd; +} + +# +# TCP/IP can be used to get the whole of a truncated message +# +tcpquery(query: array of byte): (ref DNSmsg, string) +{ + # TO DO: check request id, ports etc. + + ipa := query[Udpraddr+IPv4off:]; + addr := sys->sprint("tcp!%d.%d.%d.%d!%d", int ipa[0], int ipa[1], int ipa[2], int ipa[3], DNSport); + (ok, conn) := sys->dial(addr, nil); + if(ok < 0) + return (nil, sys->sprint("can't dial %s: %r", addr)); + query = query[Udphdrsize-2:]; + put2(query, 0, len query-2); # replace UDP header by message length + n := sys->write(conn.dfd, query[Udphdrsize:], len query); + if(n != len query) + return (nil, sys->sprint("dns: %s: write err: %r", addr)); + buf := readn(conn.dfd, 2); # TCP/DNS record header + (mlen, nil) := get2(buf, 0); + if(mlen < 2 || mlen > 16384) + return (nil, sys->sprint("dns: %s: bad reply msg length=%d", addr, mlen)); + buf = readn(conn.dfd, mlen); + if(buf == nil) + return (nil, sys->sprint("dns: %s: read err: %r", addr)); + dm := DNSmsg.unpack(buf); + if(dm == nil) + return (nil, "dns: bad reply message"); + if(dm.err != nil){ + sys->fprint(stderr, "dns: %s: bad reply: %s\n", addr, dm.err); + return (nil, dm.err); + } + return (dm, nil); +} + +readn(fd: ref Sys->FD, nb: int): array of byte +{ + buf:= array[nb] of byte; + for(n:=0; n<nb;){ + m := sys->read(fd, buf[n:], nb-n); + if(m <= 0) + return nil; + n += m; + } + return buf; +} + +timefd: ref Sys->FD; + +time(): int +{ + if(timefd == nil){ + timefd = sys->open("/dev/time", Sys->OREAD); + if(timefd == nil) + return 0; + } + buf := array[128] of byte; + sys->seek(timefd, big 0, 0); + n := sys->read(timefd, buf, len buf); + if(n < 0) + return 0; + return int ((big string buf[0:n]) / big 1000000); +} + +parseip(s: string): array of byte +{ + (ok, a) := IPaddr.parse(s); + if(ok < 0 || !a.isv4()) + return nil; + return a.v4(); +} diff --git a/appl/cmd/ndb/dnsquery.b b/appl/cmd/ndb/dnsquery.b new file mode 100644 index 00000000..194c08a2 --- /dev/null +++ b/appl/cmd/ndb/dnsquery.b @@ -0,0 +1,177 @@ +implement Dnsquery; + +# +# Copyright © 2003 Vita Nuova Holdings LImited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Dnsquery: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: dnsquery [-x /net] [-s server] [address ...]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + cantload(Bufio->PATH); + + net := "/net"; + server: string; + arg := load Arg Arg->PATH; + if(arg == nil) + cantload(Arg->PATH); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'x' => + net = arg->arg(); + if(net == nil) + usage(); + 's' => + server = arg->arg(); + if(server == nil) + usage(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(server == nil) + server = net+"/dns"; + if(args != nil){ + for(; args != nil; args = tl args) + dnsquery(server, hd args); + }else{ + f := bufio->fopen(sys->fildes(0), Sys->OREAD); + if(f == nil) + exit; + for(;;){ + sys->print("> "); + s := f.gets('\n'); + if(s == nil) + break; + dnsquery(server, s[0:len s-1]); + } + } +} + +cantload(s: string) +{ + sys->fprint(sys->fildes(2), "dnsquery: can't load %s: %r\n", s); + raise "fail:load"; +} + +dnsquery(server: string, query: string) +{ + dns := sys->open(server, Sys->ORDWR); + if(dns == nil){ + sys->fprint(sys->fildes(2), "dnsquery: can't open %s: %r\n", server); + raise "fail:open"; + } + stdout := sys->fildes(1); + for(i := len query; --i >= 0 && query[i] != ' ';) + {} + if(i < 0){ + i = len query; + case dbattr(query) { + "ip" => + query += " ptr"; + * => + query += " ip"; + } + } + if(query[i+1:] == "ptr"){ + while(i > 0 && query[i-1] == ' ') + i--; + if(!hastail(query[0:i], ".in-addr.arpa") && !hastail(query[0:i], ".IN-ADDR.ARPA")) + query = addr2arpa(query[0:i])+" ptr"; + } + b := array of byte query; + if(sys->write(dns, b, len b) > 0){ + sys->seek(dns, big 0, Sys->SEEKSTART); + buf := array[256] of byte; + while((n := sys->read(dns, buf, len buf)) > 0) + sys->print("%s\n", string buf[0:n]); + if(n == 0) + return; + } + sys->print("!%r\n"); +} + +hastail(s: string, t: string): int +{ + if(len s >= len t && s[len s - len t:] == t) + return 1; + return 0; +} + +addr2arpa(a: string): string +{ + (nf, flds) := sys->tokenize(a, "."); + rl: list of string; + for(; flds != nil; flds = tl flds) + rl = hd flds :: rl; + addr: string; + for(; rl != nil; rl = tl rl){ + if(addr != nil) + addr[len addr] = '.'; + addr += hd rl; + } + return addr+".in-addr.arpa"; +} + +dbattr(s: string): string +{ + digit := 0; + dot := 0; + alpha := 0; + hex := 0; + colon := 0; + for(i := 0; i < len s; i++){ + case c := s[i] { + '0' to '9' => + digit = 1; + 'a' to 'f' or 'A' to 'F' => + hex = 1; + '.' => + dot = 1; + ':' => + colon = 1; + * => + if(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '-' || c == '&') + alpha = 1; + } + } + if(alpha){ + if(dot) + return "dom"; + return "sys"; + } + if(colon) + return "ip"; + if(dot){ + if(!hex) + return "ip"; + return "dom"; + } + return "sys"; +} diff --git a/appl/cmd/ndb/mkfile b/appl/cmd/ndb/mkfile new file mode 100644 index 00000000..fb1a7074 --- /dev/null +++ b/appl/cmd/ndb/mkfile @@ -0,0 +1,28 @@ +<../../../mkconfig + +TARG=\ + cs.dis\ + csquery.dis\ + dns.dis\ + dnsquery.dis\ + mkhash.dis\ + query.dis\ + registry.dis\ + regquery.dis\ + +SYSMODULES=\ + sys.m\ + draw.m\ + bufio.m\ + arg.m\ + attrdb.m\ + ip.m\ + ipattr.m\ + styx.m\ + styxservers.m\ + +MODULES=\ + +DISBIN=$ROOT/dis/ndb + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/ndb/mkhash.b b/appl/cmd/ndb/mkhash.b new file mode 100644 index 00000000..f1876355 --- /dev/null +++ b/appl/cmd/ndb/mkhash.b @@ -0,0 +1,119 @@ +implement Mkhash; + +# +# for compatibility, this is closely modelled on Plan 9's ndb/mkhash +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; +include "attrdb.m"; + attrdb: Attrdb; + Db, Dbf, Dbentry, Tuples, Attr: import attrdb; + attrhash: Attrhash; + NDBPLEN, NDBHLEN, NDBCHAIN, NDBNAP: import Attrhash; + +Mkhash: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + error(sys->sprint("can't load %s: %r", Attrdb->PATH)); + attrdb->init(); + attrhash = load Attrhash Attrhash->PATH; + if(attrhash == nil) + error(sys->sprint("can't load %s: %r", Attrhash->PATH)); + + if(len args != 3) + error("usage: mkhash file attr"); + args = tl args; + dbname := hd args; + args = tl args; + attr := hd args; + dbf := Dbf.open(dbname); + if(dbf == nil) + error(sys->sprint("can't open %s: %r", dbname)); + offset := 0; + n := 0; + for(;;){ + (e, nil, next) := dbf.readentry(offset, nil, nil, 0); + if(e == nil) + break; + m := len e.find(attr); + if(0 && m != 0) + sys->fprint(sys->fildes(2), "%ud [%d]\n", offset, m); + n += m; + offset = next; + } + hlen := 2*n+1; + chains := n*2*NDBPLEN; + file := array[NDBHLEN + hlen*NDBPLEN + chains] of byte; + tab := file[NDBHLEN:]; + for(i:=0; i<len tab; i+=NDBPLEN) + put3(tab[i:], NDBNAP); + offset = 0; + chain := hlen*NDBPLEN; + for(;;){ + (e, nil, next) := dbf.readentry(offset, nil, nil, 0); + if(e == nil) + break; + for(l := e.find(attr); l != nil; l = tl l) + for((nil, al) := hd l; al != nil; al = tl al) + chain = enter(tab, hd al, hlen, chain, offset); + offset = next; + } + hashfile := dbname+"."+attr; + hfd := sys->create(hashfile, Sys->OWRITE, 8r666); + if(hfd == nil) + error(sys->sprint("can't create %s: %r", hashfile)); + mtime := 0; + if(dbf.dir != nil) + mtime = dbf.dir.mtime; + put4(file, mtime); + put4(file[4:], hlen); + if(sys->write(hfd, file, NDBHLEN+chain) != NDBHLEN+chain) + error(sys->sprint("error writing %s: %r", hashfile)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "mkhash: %s\n", s); + raise "fail:error"; +} + +enter(tab: array of byte, a: ref Attr, hlen: int, chain: int, offset: int): int +{ + o := attrhash->hash(a.val, hlen)*NDBPLEN; + for(; (p := attrhash->get3(tab[o:])) != NDBNAP; o = p & ~NDBCHAIN) + if((p & NDBCHAIN) == 0){ + put3(tab[o:], chain | NDBCHAIN); + put3(tab[chain:], p); + put3(tab[chain+NDBPLEN:], offset); + return chain+2*NDBPLEN; + } + put3(tab[o:], offset); + return chain; +} + +put3(a: array of byte, v: int) +{ + a[0] = byte v; + a[1] = byte (v>>8); + a[2] = byte (v>>16); +} + +put4(a: array of byte, v: int) +{ + a[0] = byte v; + a[1] = byte (v>>8); + a[2] = byte (v>>16); + a[3] = byte (v>>24); +} diff --git a/appl/cmd/ndb/query.b b/appl/cmd/ndb/query.b new file mode 100644 index 00000000..b636492d --- /dev/null +++ b/appl/cmd/ndb/query.b @@ -0,0 +1,135 @@ +implement Query; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + +include "attrdb.m"; + attrdb: Attrdb; + Attr, Tuples, Dbentry, Db: import attrdb; + +include "arg.m"; + +Query: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: query attr [value [rattr]]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + dbfile := "/lib/ndb/local"; + arg := load Arg Arg->PATH; + if(arg == nil) + badload(Arg->PATH); + arg->init(args); + arg->setusage("query [-a] [-f dbfile] attr [value [rattr]]"); + all := 0; + while((o := arg->opt()) != 0) + case o { + 'f' => dbfile = arg->earg(); + 'a' => all = 1; + * => arg->usage(); + } + args = arg->argv(); + if(args == nil) + arg->usage(); + attr := hd args; + args = tl args; + value, rattr: string; + vflag := 0; + if(args != nil){ + vflag = 1; + value = hd args; + args = tl args; + if(args != nil) + rattr = hd args; + } + arg = nil; + + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + badload(Attrdb->PATH); + err := attrdb->init(); + if(err != nil) + error(sys->sprint("can't init Attrdb: %s", err)); + + db := Db.open(dbfile); + if(db == nil) + error(sys->sprint("can't open %s: %r", dbfile)); + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + if(rattr != nil) + (e, ptr) = db.findbyattr(ptr, attr, value, rattr); + else if(vflag) + (e, ptr) = db.findpair(ptr, attr, value); + else + (e, ptr) = db.find(ptr, attr); + if(e == nil) + break; + if(rattr != nil){ + matches: list of (ref Tuples, list of ref Attr); + if(rattr != nil) + matches = e.findbyattr(attr, value, rattr); + else + matches = e.find(attr); + for(; matches != nil; matches = tl matches){ + (line, attrs) := hd matches; + if(attrs != nil) + printvals(attrs, all); + if(!all) + exit; + } + }else + printentry(e); + if(!all) + exit; + } +} + +badload(s: string) +{ + error(sys->sprint("can't load %s: %r", s)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "query: %s\n", s); + raise "fail:error"; +} + +printentry(e: ref Dbentry) +{ + s := ""; + for(lines := e.lines; lines != nil; lines = tl lines){ + line := hd lines; + for(al := line.pairs; al != nil; al = tl al){ + a := hd al; + s += sys->sprint(" %q=%q", a.attr, a.val); + } + } + if(s != "") + s = s[1:]; + sys->print("%s\n", s); +} + +printvals(al: list of ref Attr, all: int) +{ + for(; al != nil; al = tl al){ + a := hd al; + sys->print("%q\n", a.val); + if(!all) + break; + } +} diff --git a/appl/cmd/ndb/registry.b b/appl/cmd/ndb/registry.b new file mode 100644 index 00000000..f720781f --- /dev/null +++ b/appl/cmd/ndb/registry.b @@ -0,0 +1,671 @@ +implement Registry; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "string.m"; + str: String; +include "daytime.m"; + daytime: Daytime; +include "bufio.m"; +include "attrdb.m"; + attrdb: Attrdb; + Db, Dbf, Dbentry: import attrdb; +include "styx.m"; + styx: Styx; + Rmsg, Tmsg: import styx; +include "styxservers.m"; + styxservers: Styxservers; + Styxserver, Fid, Navigator, Navop: import styxservers; + Enotdir, Enotfound: import Styxservers; +include "arg.m"; + +# files: +# 'new' +# write name of new service; (and possibly attribute column names) +# entry appears in directory of that name +# can then write attributes/values +# 'index' +# read to get info on all services and their attributes. +# 'find' +# write to set filter. +# read to get info on all services with matching attributes +# 'event' (not needed initially) +# read to block until changes happen. +# servicename +# write to change attributes (only by owner) +# remove to unregister service. + +Registry: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +Qroot, +Qnew, +Qindex, +Qevent, +Qfind, +Qsvc: con iota; + + +Shift: con 4; +Mask: con 2r1111; + +Egreg: con "buggy program!"; +Maxreplyidle: con 3; + +Service: adt { + id: int; + slot: int; + owner: string; + name: string; + atime: int; + mtime: int; + vers: int; + fid: int; # fid that created it (NOFID if static) + attrs: list of (string, string); + + new: fn(owner: string): ref Service; + find: fn(id: int): ref Service; + remove: fn(svc: self ref Service); + set: fn(svc: self ref Service, attr, val: string); + get: fn(svc: self ref Service, attr: string): string; +}; + +Filter: adt { + id: int; # filter ID (it's a fid) + attrs: array of (string, string); + + new: fn(id: int): ref Filter; + find: fn(id: int): ref Filter; + set: fn(f: self ref Filter, a: array of (string, string)); + match: fn(f: self ref Filter, attrs: list of (string, string)): int; + remove: fn(f: self ref Filter); +}; + +filters: list of ref Filter; + + +services := array[9] of ref Service; +nservices := 0; +idseq := 0; +rootvers := 0; +now: int; +startdate: int; +dbfile: string; + +srv: ref Styxserver; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + if(str == nil) + loaderr(String->PATH); + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + loaderr(Daytime->PATH); + styx = load Styx Styx->PATH; + if (styx == nil) + loaderr(Styx->PATH); + styx->init(); + styxservers = load Styxservers Styxservers->PATH; + if (styxservers == nil) + loaderr(Styxservers->PATH); + styxservers->init(styx); + + arg := load Arg Arg->PATH; + if(arg == nil) + loaderr(Arg->PATH); + arg->init(args); + arg->setusage("ndb/registry [-f initdb]"); + while((o := arg->opt()) != 0) + case o { + 'f' => dbfile = arg->earg(); + * => arg->usage(); + } + args = arg->argv(); + if(args != nil) + arg->usage(); + arg = nil; + + sys->pctl(Sys->FORKNS|Sys->NEWFD, 0::1::2::nil); + startdate = now = daytime->now(); + if(dbfile != nil){ + attrdb = load Attrdb Attrdb->PATH; + if(attrdb == nil) + loaderr(Attrdb->PATH); + attrdb->init(); + db := Db.open(dbfile); + if(db == nil) + error(sys->sprint("can't open %s: %r", dbfile)); + dbload(db); + db = nil; # for now assume it's static + } + navops := chan of ref Navop; + spawn navigator(navops); + tchan: chan of ref Tmsg; + (tchan, srv) = Styxserver.new(sys->fildes(0), Navigator.new(navops), big Qroot); + spawn serve(tchan, navops); +} + +loaderr(p: string) +{ + error(sys->sprint("can't load %s: %r", p)); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "registry: %s\n", s); + raise "fail:error"; +} + +serve(tchan: chan of ref Tmsg, navops: chan of ref Navop) +{ +Serve: + while((gm := <-tchan) != nil){ + now = daytime->now(); + err := ""; + pick m := gm { + Readerror => + error(sys->sprint("fatal read error: %s\n", m.error)); + break Serve; + Open => + (fid, mode, d, e) := srv.canopen(m); + if((err = e) != nil) + break; + if(fid.qtype & Sys->QTDIR) + srv.default(m); + else + open(m, fid); + Read => + (fid, e) := srv.canread(m); + if((err = e) != nil) + break; + if(fid.qtype & Sys->QTDIR) + srv.read(m); + else + err = read(m, fid); + Write => + (fid, e) := srv.canwrite(m); + if((err = e) != nil) + break; + err = write(m, fid); + if(err == nil) + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + Clunk => + clunk(srv.clunk(m)); + Remove => + (fid, nil, e) := srv.canremove(m); + srv.delfid(fid); # always clunked even on error + if((err = e) != nil) + break; + err = remove(fid); + if(err == nil) + srv.reply(ref Rmsg.Remove(m.tag)); + * => + srv.default(gm); + } + if(err != "") + srv.reply(ref Rmsg.Error(gm.tag, err)); + } + navops <-= nil; +} + +open(m: ref Tmsg.Open, fid: ref Fid) +{ + path := int fid.path; + case path & Mask { + Qnew => + svc := Service.new(fid.uname); + svc.fid = fid.fid; + fid.open(m.mode, (big ((svc.id << Shift)|Qsvc), 0, Sys->QTFILE)); + * => + fid.open(m.mode, (fid.path, 0, fid.qtype)); + } + srv.reply(ref Rmsg.Open(m.tag, (fid.path, 0, fid.qtype), 0)); +} + +read(m: ref Tmsg.Read, fid: ref Fid): string +{ + path := int fid.path; + case path & Mask { + Qindex => + if(fid.data == nil || m.offset == big 0) + fid.data = getindexdata(-1, Styx->NOFID); + srv.reply(styxservers->readbytes(m, fid.data)); + Qfind => + if(fid.data == nil || m.offset == big 0) + fid.data = getindexdata(-1, fid.fid); + srv.reply(styxservers->readbytes(m, fid.data)); + Qsvc => + if(fid.data == nil || m.offset == big 0){ + svc := Service.find(path >> Shift); + if(svc != nil) + svc.atime = now; + fid.data = getindexdata(path >> Shift, Styx->NOFID); + } + srv.reply(styxservers->readbytes(m, fid.data)); + Qevent => + return "not implemented yet"; + * => + return Egreg; + } + return nil; +} + +write(m: ref Tmsg.Write, fid: ref Fid): string +{ + path := int fid.path; + case path & Mask { + Qsvc => + svc := Service.find(path >> Shift); + if(svc == nil) + return Egreg; + s := string m.data; + toks := str->unquoted(s); + if(toks == nil) + return "bad syntax"; + # first write names the service (possibly with attributes) + if(svc.name == nil){ + if((e := svcnameok(hd toks)) != nil) + return "bad service name"; + svc.name = hd toks; + toks = tl toks; + } + if(len toks % 2 != 0) + return "odd attribute/value pairs"; + svc.mtime = now; + svc.vers++; + for(; toks != nil; toks = tl tl toks) + svc.set(hd toks, hd tl toks); + Qfind => + s := string m.data; + toks := str->unquoted(s); + n := len toks; + if(n % 2 != 0) + return "odd attribute/value pairs"; + f := Filter.find(fid.fid); + if(n != 0){ + a := array[n/2] of (string, string); + for(n=0; toks != nil; n++){ + a[n] = (hd toks, hd tl toks); + toks = tl tl toks; + } + if(f == nil) + f = Filter.new(fid.fid); + f.set(a); + }else{ + if(f != nil) + f.remove(); + } + * => + return Egreg; + } + return nil; +} + +clunk(fid: ref Fid) +{ + path := int fid.path; + case path & Mask { + Qsvc => + svc := Service.find(path >> Shift); + if(svc != nil && svc.fid == fid.fid && int svc.get("persist") == 0) + svc.remove(); + Qevent => + ; # remove queued events? + Qfind => + if((f := Filter.find(fid.fid)) != nil) + f.remove(); + } +} + +remove(fid: ref Fid): string +{ + path := int fid.path; + if((path & Mask) == Qsvc){ + svc := Service.find(path >> Shift); + if(fid.uname == svc.owner){ + svc.remove(); + return nil; + } + } + return "permission denied"; +} + +svcnameok(s: string): string +{ + # could require that a service name contains at least one (or two) '!' characters. + for(i := 0; i < len s; i++){ + c := s[i]; + if(c <= 32 || c == '/' || c == 16r7f) + return "bad character in service name"; + } + case s { + "new" or + "event" or + "find" or + "index" => + return "bad service name"; + } + for(i = 0; i < nservices; i++) + if(services[i].name == s) + return "duplicate service name"; + return nil; +} + +getindexdata(id: int, filterid: int): array of byte +{ + f: ref Filter; + if(filterid != Styx->NOFID) + f = Filter.find(filterid); + s := ""; + for(i := 0; i < nservices; i++){ + svc := services[i]; + if(svc == nil || svc.name == nil) + continue; + if(id == -1){ + if(f != nil && !f.match(svc.attrs)) + continue; + }else if(svc.id != id) + continue; + s += sys->sprint("%q", services[i].name); + for(a := svc.attrs; a != nil; a = tl a){ + (attr, val) := hd a; + s += sys->sprint(" %q %q", attr, val); + } + s[len s] = '\n'; + } + return array of byte s; +} + +navigator(navops: chan of ref Navop) +{ + while((m := <-navops) != nil){ + path := int m.path; + pick n := m { + Stat => + n.reply <-= dirgen(int n.path); + Walk => + name := n.name; + case path & Mask { + Qroot => + case name{ + ".." => + ; # nop + "new" => + path = Qnew; + "index" => + path = Qindex; + "event" => + path = Qevent; + "find" => + path = Qfind; + * => + for(i := 0; i < nservices; i++) + if(services[i].name == name){ + path = (services[i].id << Shift) | Qsvc; + break; + } + if(i == nservices){ + n.reply <-= (nil, Enotfound); + continue; + } + } + * => + if(name == ".."){ + path = Qroot; + break; + } + n.reply <-= (nil, Enotdir); + continue; + } + n.reply <-= dirgen(path); + Readdir => + d: array of int; + case path & Mask { + Qroot => + Nstatic: con 3; + d = array[Nstatic + nservices] of int; + d[0] = Qnew; + d[1] = Qindex; + d[2] = Qfind; +# d[3] = Qevent; + for(i := 0; i < nservices; i++) + if(services[i].name != nil) + d[i + Nstatic] = (services[i].id<<Shift) | Qsvc; + } + if(d == nil){ + n.reply <-= (nil, Enotdir); + break; + } + for (i := n.offset; i < len d; i++) + n.reply <-= dirgen(d[i]); + n.reply <-= (nil, nil); + } + } +} + +dirgen(path: int): (ref Sys->Dir, string) +{ + name: string; + perm: int; + svc: ref Service; + case path & Mask { + Qroot => + name = "."; + perm = 8r777|Sys->DMDIR; + Qnew => + name = "new"; + perm = 8r666; + Qindex => + name = "index"; + perm = 8r444; + Qevent => + name = "event"; + perm = 8r444; + Qfind => + name = "find"; + perm = 8r666; + Qsvc => + id := path >> Shift; + for(i := 0; i < nservices; i++) + if(services[i].id == id) + break; + if(i >= nservices) + return (nil, Enotfound); + svc = services[i]; + name = svc.name; + perm = 8r644; + * => + return (nil, Enotfound); + } + return (dir(path, name, perm, svc), nil); +} + +dir(path: int, name: string, perm: int, svc: ref Service): ref Sys->Dir +{ + d := ref sys->zerodir; + d.qid.path = big path; + if(perm & Sys->DMDIR) + d.qid.qtype = Sys->QTDIR; + d.mode = perm; + d.name = name; + if(svc != nil){ + d.uid = svc.owner; + d.gid = svc.owner; + d.atime = svc.atime; + d.mtime = svc.mtime; + d.qid.vers = svc.vers; + }else{ + d.uid = "registry"; + d.gid = "registry"; + d.atime = startdate; + d.mtime = startdate; + if(path == Qroot) + d.qid.vers = rootvers; + } + return d; +} + +blanksvc: Service; +Service.new(owner: string): ref Service +{ + if(nservices == len services){ + s := array[nservices * 3 / 2] of ref Service; + s[0:] = services; + services = s; + } + svc := ref blanksvc; + svc.id = idseq++; + svc.owner = owner; + svc.atime = now; + svc.mtime = now; + + services[nservices] = svc; + svc.slot = nservices; + nservices++; + rootvers++; + return svc; +} + +Service.find(id: int): ref Service +{ + for(i := 0; i < nservices; i++) + if(services[i].id == id) + return services[i]; + return nil; +} + +Service.remove(svc: self ref Service) +{ + slot := svc.slot; + services[slot] = nil; + nservices--; + rootvers++; + if(slot != nservices){ + services[slot] = services[nservices]; + services[slot].slot = slot; + services[nservices] = nil; + } +} + +Service.get(svc: self ref Service, attr: string): string +{ + for(a := svc.attrs; a != nil; a = tl a) + if((hd a).t0 == attr) + return (hd a).t1; + return nil; +} + +Service.set(svc: self ref Service, attr, val: string) +{ + for(a := svc.attrs; a != nil; a = tl a) + if((hd a).t0 == attr) + break; + if(a == nil){ + svc.attrs = (attr, val) :: svc.attrs; + return; + } + attrs := (attr, val) :: tl a; + for(a = svc.attrs; a != nil; a = tl a){ + if((hd a).t0 == attr) + break; + attrs = hd a :: attrs; + } + svc.attrs = attrs; +} + +Filter.new(id: int): ref Filter +{ + f := ref Filter(id, nil); + filters = f :: filters; + return f; +} + +Filter.find(id: int): ref Filter +{ + if(id != Styx->NOFID) + for(fl := filters; fl != nil; fl = tl fl) + if((hd fl).id == id) + return hd fl; + return nil; +} + +Filter.set(f: self ref Filter, a: array of (string, string)) +{ + f.attrs = a; +} + +Filter.remove(f: self ref Filter) +{ + rl: list of ref Filter; + for(l := filters; l != nil; l = tl l) + if((hd l).id != f.id) + rl = hd l :: rl; + filters = rl; +} + +Filter.match(f: self ref Filter, attrs: list of (string, string)): int +{ + for(i := 0; i < len f.attrs; i++){ + (qn, qv) := f.attrs[i]; + for(al := attrs; al != nil; al = tl al){ + (n, v) := hd al; + if(n == qn && (qv == "*" || v == qv)) + break; + } + if(al == nil) + break; + } + return i == len f.attrs; +} + +dbload(db: ref Db) +{ + ptr: ref Attrdb->Dbptr; + for(;;){ + e: ref Dbentry; + (e, ptr) = db.find(ptr, "service"); + if(e == nil) + break; + svcname := e.findfirst("service"); + if(svcname == nil || svcnameok(svcname) != nil) + continue; + svc := Service.new("registry"); # TO DO: read user's name + svc.name = svcname; + svc.fid = Styx->NOFID; + for(l := e.lines; l != nil; l = tl l){ + for(al := (hd l).pairs; al != nil; al = tl al){ + a := hd al; + if(a.attr != "service") + svc.set(a.attr, a.val); + } + } + } +} + +# return index i >= start such that +# s[i-1] == eoc, or len s if no such index exists. +# eoc shouldn't be ' +qsplit(s: string, start: int, eoc: int): int +{ + inq := 0; + for(i := start; i < len s;){ + c := s[i++]; + if(inq){ + if(c == '\'' && i < len s){ + if(s[i] == '\'') + i++; + else + inq = 0; + } + }else{ + if(c == eoc) + return i; + if(c == '\'') + inq = 1; + } + } + return i; +} diff --git a/appl/cmd/ndb/regquery.b b/appl/cmd/ndb/regquery.b new file mode 100644 index 00000000..f7f32462 --- /dev/null +++ b/appl/cmd/ndb/regquery.b @@ -0,0 +1,104 @@ +implement Regquery; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "arg.m"; + +Regquery: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + cantload(Bufio->PATH); + str = load String String->PATH; + if(str == nil) + cantload(String->PATH); + + mntpt := "/mnt/registry"; + arg := load Arg Arg->PATH; + if(arg == nil) + cantload(Arg->PATH); + arg->init(args); + arg->setusage("regquery [-m mntpt] [-n] [attr val attr val ...]"); + namesonly := 0; + while((c := arg->opt()) != 0) + case c { + 'm' => mntpt = arg->earg(); + 'n' => namesonly = 1; + * => arg->usage(); + } + args = arg->argv(); + arg = nil; + + finder := mntpt+"/find"; + if(args != nil){ + s := ""; + for(; args != nil; args = tl args) + s += sys->sprint(" %q", hd args); + if(s != nil) + s = s[1:]; + regquery(finder, s, namesonly); + }else{ + f := bufio->fopen(sys->fildes(0), Sys->OREAD); + if(f == nil) + exit; + for(;;){ + sys->print("> "); + s := f.gets('\n'); + if(s == nil) + break; + regquery(finder, s[0:len s-1], namesonly); + } + } +} + +cantload(s: string) +{ + sys->fprint(sys->fildes(2), "regquery: can't load %s: %r\n", s); + raise "fail:load"; +} + +regquery(server: string, addr: string, namesonly: int) +{ + fd := sys->open(server, Sys->ORDWR); + if(fd == nil){ + sys->fprint(sys->fildes(2), "regquery: can't open %s: %r\n", server); + raise "fail:open"; + } + stdout := sys->fildes(1); + b := array of byte addr; + if(sys->write(fd, b, len b) >= 0){ + sys->seek(fd, big 0, Sys->SEEKSTART); + if(namesonly){ + bio := bufio->fopen(fd, Bufio->OREAD); + while((s := bio.gets('\n')) != nil){ + l := str->unquoted(s); + if(l != nil) + sys->print("%s\n", hd l); + } + return; + }else{ + buf := array[Sys->ATOMICIO] of byte; + while((n := sys->read(fd, buf, len buf)) > 0) + sys->print("%s", string buf[0:n]); + if(n == 0) + return; + } + } + sys->fprint(sys->fildes(2), "regquery: %r\n"); +} diff --git a/appl/cmd/netkey.b b/appl/cmd/netkey.b new file mode 100644 index 00000000..fc68c22f --- /dev/null +++ b/appl/cmd/netkey.b @@ -0,0 +1,166 @@ +implement Netkey; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + keyring: Keyring; + +Netkey: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +ANAMELEN: con 28; +DESKEYLEN: con 7; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + keyring = load Keyring Keyring->PATH; + + if(len args > 1){ + sys->fprint(sys->fildes(2), "usage: netkey\n"); + raise "fail:usage"; + } + (pw, err) := readconsline("Password: ", 1); + if(err != nil){ + sys->fprint(sys->fildes(2), "netkey: %s\n", err); + raise "fail:error"; + } + if(pw != nil) + while((chal := readconsline("challenge: ", 0).t0) != nil) + sys->print("response: %s\n", netcrypt(passtokey(pw), string int chal)); +} + +readconsline(prompt: string, raw: int): (string, string) +{ + fd := sys->open("/dev/cons", Sys->ORDWR); + if(fd == nil) + return (nil, sys->sprint("can't open cons: %r")); + sys->fprint(fd, "%s", prompt); + fdctl: ref Sys->FD; + if(raw){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->fprint(fdctl, "rawon") < 0) + return (nil, sys->sprint("can't open consctl: %r")); + } + line := array[256] of byte; + o := 0; + err: string; + buf := array[1] of byte; + Read: + while((r := sys->read(fd, buf, len buf)) > 0){ + c := int buf[0]; + case c { + 16r7F => + err = "interrupt"; + break Read; + '\b' => + if(o > 0) + o--; + '\n' or '\r' or 16r4 => + break Read; + * => + if(o > len line){ + err = "line too long"; + break Read; + } + line[o++] = byte c; + } + } + if(r < 0) + err = sys->sprint("can't read cons: %r"); + if(raw){ + sys->fprint(fdctl, "rawoff"); + sys->fprint(fd, "\n"); + } + if(err != nil) + return (nil, err); + return (string line[0:o], err); +} + +# +# duplicates auth9 but keeps this self-contained +# + +netcrypt(key: array of byte, chal: string): string +{ + buf := array[8] of {* => byte 0}; + a := array of byte chal; + if(len a > 7) + a = a[0:7]; + buf[0:] = a; + encrypt(key, buf, len buf); + return sys->sprint("%.2ux%.2ux%.2ux%.2ux", int buf[0], int buf[1], int buf[2], int buf[3]); +} + +passtokey(p: string): array of byte +{ + a := array of byte p; + n := len a; + if(n >= ANAMELEN) + n = ANAMELEN-1; + buf := array[ANAMELEN] of {* => byte ' '}; + buf[0:] = a[0:n]; + buf[n] = byte 0; + key := array[DESKEYLEN] of {* => byte 0}; + t := 0; + for(;;){ + for(i := 0; i < DESKEYLEN; i++) + key[i] = byte ((int buf[t+i] >> i) + (int buf[t+i+1] << (8 - (i+1)))); + if(n <= 8) + return key; + n -= 8; + t += 8; + if(n < 8){ + t -= 8 - n; + n = 8; + } + encrypt(key, buf[t:], 8); + } +} + +parity := array[] of { + byte 16r01, byte 16r02, byte 16r04, byte 16r07, byte 16r08, byte 16r0b, byte 16r0d, byte 16r0e, + byte 16r10, byte 16r13, byte 16r15, byte 16r16, byte 16r19, byte 16r1a, byte 16r1c, byte 16r1f, + byte 16r20, byte 16r23, byte 16r25, byte 16r26, byte 16r29, byte 16r2a, byte 16r2c, byte 16r2f, + byte 16r31, byte 16r32, byte 16r34, byte 16r37, byte 16r38, byte 16r3b, byte 16r3d, byte 16r3e, + byte 16r40, byte 16r43, byte 16r45, byte 16r46, byte 16r49, byte 16r4a, byte 16r4c, byte 16r4f, + byte 16r51, byte 16r52, byte 16r54, byte 16r57, byte 16r58, byte 16r5b, byte 16r5d, byte 16r5e, + byte 16r61, byte 16r62, byte 16r64, byte 16r67, byte 16r68, byte 16r6b, byte 16r6d, byte 16r6e, + byte 16r70, byte 16r73, byte 16r75, byte 16r76, byte 16r79, byte 16r7a, byte 16r7c, byte 16r7f, + byte 16r80, byte 16r83, byte 16r85, byte 16r86, byte 16r89, byte 16r8a, byte 16r8c, byte 16r8f, + byte 16r91, byte 16r92, byte 16r94, byte 16r97, byte 16r98, byte 16r9b, byte 16r9d, byte 16r9e, + byte 16ra1, byte 16ra2, byte 16ra4, byte 16ra7, byte 16ra8, byte 16rab, byte 16rad, byte 16rae, + byte 16rb0, byte 16rb3, byte 16rb5, byte 16rb6, byte 16rb9, byte 16rba, byte 16rbc, byte 16rbf, + byte 16rc1, byte 16rc2, byte 16rc4, byte 16rc7, byte 16rc8, byte 16rcb, byte 16rcd, byte 16rce, + byte 16rd0, byte 16rd3, byte 16rd5, byte 16rd6, byte 16rd9, byte 16rda, byte 16rdc, byte 16rdf, + byte 16re0, byte 16re3, byte 16re5, byte 16re6, byte 16re9, byte 16rea, byte 16rec, byte 16ref, + byte 16rf1, byte 16rf2, byte 16rf4, byte 16rf7, byte 16rf8, byte 16rfb, byte 16rfd, byte 16rfe, +}; + +des56to64(k56: array of byte): array of byte +{ + k64 := array[8] of byte; + hi := (int k56[0]<<24)|(int k56[1]<<16)|(int k56[2]<<8)|int k56[3]; + lo := (int k56[4]<<24)|(int k56[5]<<16)|(int k56[6]<<8); + + k64[0] = parity[(hi>>25)&16r7f]; + k64[1] = parity[(hi>>18)&16r7f]; + k64[2] = parity[(hi>>11)&16r7f]; + k64[3] = parity[(hi>>4)&16r7f]; + k64[4] = parity[((hi<<3)|int ((big lo & big 16rFFFFFFFF)>>29))&16r7f]; # watch the sign extension + k64[5] = parity[(lo>>22)&16r7f]; + k64[6] = parity[(lo>>15)&16r7f]; + k64[7] = parity[(lo>>8)&16r7f]; + return k64; +} + +encrypt(key: array of byte, data: array of byte, n: int) +{ + ds := keyring->dessetup(des56to64(key), nil); + keyring->desecb(ds, data, n, Keyring->Encrypt); +} diff --git a/appl/cmd/netstat.b b/appl/cmd/netstat.b new file mode 100644 index 00000000..c9ce2216 --- /dev/null +++ b/appl/cmd/netstat.b @@ -0,0 +1,91 @@ +implement Netstat; + +include "sys.m"; +sys: Sys; +FD, Dir: import sys; +fildes, open, fstat, read, dirread, fprint, print, tokenize: import sys; + +include "draw.m"; +Context: import Draw; + +Netstat: module +{ + init: fn(ctxt: ref Context, argv: list of string); +}; + +stderr: ref FD; + +init(nil: ref Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + + stderr = fildes(2); + + nstat("/net/tcp", 1); + nstat("/net/udp", 1); + nstat("/net/il", 0); +} + +nstat(file: string, whine: int) +{ + dir: Dir; + i, ok: int; + + fd := open(file, sys->OREAD); + if(fd == nil) { + if(whine) + fprint(stderr, "netstat: %s: %r\n", file); + return; + } + + (ok, dir) = fstat(fd); + if(ok == -1) { + fprint(stderr, "netstat: fstat %s: %r\n", file); + fd = nil; + return; + } + if((dir.mode&Sys->DMDIR) == 0) { + fprint(stderr, "netstat: not a protocol directory: %s\n", file); + return; + } + for(;;) { + (n, d) := dirread(fd); + if(n <= 0) + break; + for(i = 0; i < n; i++) + if(d[i].name[0] <= '9') + nsprint(file+"/"+d[i].name, d[i].uid); + } +} + +fc(file: string): string +{ + fd := open(file, sys->OREAD); + if(fd == nil) + return "??"; + + buf := array[64] of byte; + n := read(fd, buf, len buf); + if(n <= 1) + return "??"; + if(int buf[n-1] == '\n') + n--; + + return string buf[0:n]; +} + +nsprint(name, user: string) +{ + n: int; + s: list of string; + + sr := fc(name+"/status"); + (n, s) = tokenize(sr, " "); + + print("%-10s %-10s %-12s %-20s %s\n", + name[5:], + user, + hd s, + fc(name+"/local"), + fc(name+"/remote")); +} diff --git a/appl/cmd/newer.b b/appl/cmd/newer.b new file mode 100644 index 00000000..ce0f743d --- /dev/null +++ b/appl/cmd/newer.b @@ -0,0 +1,36 @@ +implement Newer; + +# +# test if a file is up to date +# + +include "sys.m"; + +include "draw.m"; + +Newer: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys := load Sys Sys->PATH; + if(len args != 3){ + sys->fprint(sys->fildes(2), "usage: newer newfile oldfile\n"); + raise "fail:usage"; + } + args = tl args; + (ok1, d1) := sys->stat(hd args); + if(ok1 < 0) + raise sys->sprint("fail:new:%r"); + if(d1.mode & Sys->DMDIR) + raise "fail:new:directory"; + (ok2, d2) := sys->stat(hd tl args); + if(ok2 < 0) + raise sys->sprint("fail:old:%r"); + if(d2.mode & Sys->DMDIR) + raise "fail:old:directory"; + if(d2.mtime > d1.mtime) + raise "fail:older"; +} diff --git a/appl/cmd/ns.b b/appl/cmd/ns.b new file mode 100644 index 00000000..38bb86af --- /dev/null +++ b/appl/cmd/ns.b @@ -0,0 +1,157 @@ +# ns - display the construction of the current namespace (loosely based on plan 9's ns) +implement Ns; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "arg.m"; + +Ns: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +SHELLMETA: con "' \t\\$#"; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: ns [-r] [pid]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + arg := load Arg Arg->PATH; + if(arg == nil){ + sys->fprint(sys->fildes(2), "ns: can't load %s: %r\n", Arg->PATH); + raise "fail:load"; + } + arg->init(args); + pid := sys->pctl(0, nil); + raw := 0; + while((o := arg->opt()) != 0) + case o { + 'r' => + raw = 1; + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(len args > 1) + usage(); + if(len args > 0) + pid = int hd args; + + nsname := sys->sprint("/prog/%d/ns", pid); + nsfd := sys->open(nsname, Sys->OREAD); + if(nsfd == nil) { + sys->fprint(sys->fildes(2), "ns: can't open %s: %r\n", nsname); + raise "fail:open"; + } + + buf := array[2048] of byte; + while((l := sys->read(nsfd, buf, len buf)) > 0){ + (nstr, lstr) := sys->tokenize(string buf[0:l], " \n"); + if(nstr < 2) + continue; + cmd := hd lstr; + lstr = tl lstr; + if(cmd == "cd" && lstr != nil){ + sys->print("%s %s\n", cmd, quoted(hd lstr)); + continue; + } + + sflag := ""; + if((hd lstr)[0] == '-') { + sflag = hd lstr + " "; + lstr = tl lstr; + } + if(len lstr < 2) + continue; + + src := hd lstr; + lstr = tl lstr; + if(len src >= 3 && (src[0:2] == "#/" || src[0:2] == "#U")) # remove unnecesary #/'s and #U's + src = src[2:]; + + # remove "#." from beginning of destination path + dest := hd lstr; + if(dest == "#M") { + dest = dest[2:]; + if(dest == "") + dest = "/"; + } + + if(cmd == "mount" && !raw) + src = netaddr(src); # optionally rewrite network files to network address + + # quote arguments if "#" found + sys->print("%s %s%s %s\n", cmd, sflag, quoted(src), quoted(dest)); + } + if(l < 0) + sys->fprint(sys->fildes(2), "ns: error reading %s: %r\n", nsname); +} + +netaddr(f: string): string +{ + if(len f < 1 || f[0] != '/') + return f; + (nf, flds) := sys->tokenize(f, "/"); # expect /net[.alt]/proto/2/data + if(nf < 4) + return f; + netdir := hd flds; + if(netdir != "net" && netdir != "net.alt") + return f; + proto := hd tl flds; + d := hd tl tl flds; + if(hd tl tl tl flds != "data") + return f; + fd := sys->open(sys->sprint("/%s/%s/%s/remote", hd flds, proto, d), Sys->OREAD); + if(fd == nil) + return f; + buf := array[256] of byte; + n := sys->read(fd, buf, len buf); + if(n <= 0) + return f; + if(buf[n-1] == byte '\n') + n--; + if(netdir != "net") + proto = "/"+netdir+"/"+proto; + return sys->sprint("%s!%s", proto, string buf[0:n]); +} + +any(c: int, t: string): int +{ + for(j := 0; j < len t; j++) + if(c == t[j]) + return 1; + return 0; +} + +contains(s: string, t: string): int +{ + for(i := 0; i<len s; i++) + if(any(s[i], t)) + return 1; + return 0; +} + +quoted(s: string): string +{ + if(!contains(s, SHELLMETA)) + return s; + r := "'"; + for(i := 0; i < len s; i++){ + if(s[i] == '\'') + r[len r] = '\''; + r[len r] = s[i]; + } + r[len r] = '\''; + return r; +} diff --git a/appl/cmd/nsbuild.b b/appl/cmd/nsbuild.b new file mode 100644 index 00000000..36c5b86a --- /dev/null +++ b/appl/cmd/nsbuild.b @@ -0,0 +1,41 @@ +implement Nsbuild; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "newns.m"; + +stderr: ref Sys->FD; + +Nsbuild: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + ns := load Newns "/dis/lib/newns.dis"; + if(ns == nil) { + sys->fprint(stderr, "nsbuild: can't load %s: %r", Newns->PATH); + raise "fail:load"; + } + + if(len argv > 2) { + sys->fprint(stderr, "Usage: nsbuild [nsfile]\n"); + raise "fail:usage"; + } + + nsfile := "namespace"; + if(len argv == 2) + nsfile = hd tl argv; + + e := ns->newns(nil, nsfile); + if(e != ""){ + sys->fprint(stderr, "nsbuild: error building namespace: %s\n", e); + raise "fail:newns"; + } +} diff --git a/appl/cmd/os.b b/appl/cmd/os.b new file mode 100644 index 00000000..c51faa2a --- /dev/null +++ b/appl/cmd/os.b @@ -0,0 +1,155 @@ +implement Os; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "arg.m"; + +Os: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + if(str == nil) + fail(sys->sprint("cannot load %s: %r", String->PATH)); + arg := load Arg Arg->PATH; + if(arg == nil) + fail(sys->sprint("cannot load %s: %r", Arg->PATH)); + + arg->init(args); + arg->setusage("os [-d dir] [-n] command [arg...]"); + + nice := 0; + nicearg: string; + workdir := ""; + mntpoint := ""; + while((opt := arg->opt()) != 0) { + case opt { + 'd' => + workdir = arg->earg(); + 'm' => + mntpoint = arg->earg(); + 'n' => + nice = 1; + 'N' => + nice = 1; + nicearg = sys->sprint(" %q", arg->earg()); + * => + arg->usage(); + } + } + args = arg->argv(); + if (args == nil) + arg->usage(); + arg = nil; + + sys->pctl(Sys->FORKNS, nil); + sys->bind("#p", "/prog", Sys->MREPL); # don't worry if it fails + if(mntpoint == nil){ + mntpoint = "/cmd"; + if(sys->stat(mntpoint+"/clone").t0 == -1) + if(sys->bind("#C", "/", Sys->MBEFORE) < 0) + fail(sys->sprint("bind #C /: %r")); + } + + cfd := sys->open(mntpoint+"/clone", sys->ORDWR); + if(cfd == nil) + fail(sys->sprint("cannot open /cmd/clone: %r")); + + buf := array[32] of byte; + if((n := sys->read(cfd, buf, len buf)) <= 0) + fail(sys->sprint("cannot read /cmd/clone: %r")); + + dir := mntpoint+"/"+string buf[0:n]; + + wfd := sys->open(dir+"/wait", Sys->OREAD); + if(nice && sys->fprint(cfd, "nice%s", nicearg) < 0) + sys->fprint(sys->fildes(2), "os: warning: can't set nice priority: %r\n"); + + if(workdir != nil && sys->fprint(cfd, "dir %s", workdir) < 0) + fail(sys->sprint("cannot set cwd %q: %r", workdir)); + + if(sys->fprint(cfd, "killonclose") < 0) + sys->fprint(sys->fildes(2), "os: warning: cannot write killonclose: %r\n"); + + if(sys->fprint(cfd, "exec %s", str->quoted(args)) < 0) + fail(sys->sprint("cannot exec: %r")); + + if((tocmd := sys->open(dir+"/data", sys->OWRITE)) == nil) + fail(sys->sprint("canot open %s/data for writing: %r", dir)); + + if((fromcmd := sys->open(dir+"/data", sys->OREAD)) == nil) + fail(sys->sprint("cannot open %s/data for reading: %r", dir)); + + spawn copy(sync := chan of int, nil, sys->fildes(0), tocmd); + pid := <-sync; + sync = nil; + tocmd = nil; + + spawn copy(nil, done := chan of int, fromcmd, sys->fildes(1)); + + # cfd is still open, so if we're killgrp'ed and we're on a platform + # (e.g. windows) where the fromcmd read is uninterruptible, + # cfd will be closed, so the command will be killed (due to killonclose), and + # the fromcmd read should complete, allowing that process to be killed. + + <-done; + kill(pid); + + if(wfd != nil){ + status := array[1024] of byte; + n = sys->read(wfd, status, len status); + if(n < 0) + fail(sys->sprint("wait error: %r")); + s := string status[0:n]; + if(s != nil){ + # pid user sys real status + flds := str->unquoted(s); + if(len flds < 5) + fail(sys->sprint("wait error: odd status: %q", s)); + s = hd tl tl tl tl flds; + if(0) + sys->fprint(sys->fildes(2), "WAIT: %q\n", s); + if(s != nil) + raise "fail:host: "+s; + } + } +} + +copy(sync, done: chan of int, f, t: ref Sys->FD) +{ + if(sync != nil) + sync <-= sys->pctl(0, nil); + buf := array[8192] of byte; + for(;;) { + r := sys->read(f, buf, len buf); + if(r <= 0) + break; + w := sys->write(t, buf, r); + if(w != r) + break; + } + if(done != nil) + done <-= 1; +} + +kill(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", sys->OWRITE); + sys->fprint(fd, "kill"); +} + +fail(msg: string) +{ + sys->fprint(sys->fildes(2), "os: %s\n", msg); + raise "fail:"+msg; +} diff --git a/appl/cmd/p.b b/appl/cmd/p.b new file mode 100644 index 00000000..519797a9 --- /dev/null +++ b/appl/cmd/p.b @@ -0,0 +1,141 @@ +implement P; +# Original by Steve Arons, based on Plan 9 p + +include "sys.m"; + sys: Sys; + FD: import Sys; +include "draw.m"; +include "string.m"; + str: String; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "sh.m"; + +stderr: ref FD; +outb, cons: ref Iobuf; +drawctxt: ref Draw->Context; + +nlines := 22; # 1/3rd 66-line nroff page (!) +progname := "p"; + +P: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(stderr, "Usage: p [-number] [file...]\n"); + raise "fail:usage"; +} + +init(ctxt: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + nomod(Bufio->PATH); + str = load String String->PATH; + if(str == nil) + nomod(String->PATH); + sys->pctl(Sys->FORKFD, nil); + drawctxt = ctxt; + + stderr = sys->fildes(2); + + if((stdout := sys->fildes(1)) != nil) + outb = bufio->fopen(stdout, bufio->OWRITE); + if(outb == nil){ + sys->fprint(stderr, "p: can't open stdout: %r\n"); + raise "fail:stdout"; + } + cons = bufio->open("/dev/cons", bufio->OREAD); + if(cons == nil){ + sys->fprint(stderr, "p: can't open /dev/cons: %r\n"); + raise "fail:cons"; + } + + if(argv != nil){ + progname = hd argv; + argv = tl argv; + if(argv != nil){ + s := hd argv; + if(len s > 1 && s[0] == '-'){ + (x, y) := str->toint(s[1:],10); + if(y == "" && x > 0) + nlines = x; + else + usage(); + argv = tl argv; + } + } + } + if(argv == nil) + argv = "-" :: nil; + for(; argv != nil; argv = tl argv){ + file := hd argv; + fd: ref Sys->FD; + if(file == "-"){ + file = "stdin"; + fd = sys->fildes(0); + }else + fd = sys->open(file, Sys->OREAD); + if(fd == nil){ + sys->fprint(stderr, "%s: can't open %s: %r\n", progname, file); + continue; + } + page(fd); + fd = nil; + } +} + +nomod(m: string) +{ + sys->fprint(sys->fildes(2), "%s: can't load %s: %r\n", progname, m); + raise "fail:load"; +} + +page(fd: ref Sys->FD) +{ + inb := bufio->fopen(fd, bufio->OREAD); + nl := nlines; + while((line := inb.gets('\n')) != nil){ + outb.puts(line); + if(--nl == 0){ + outb.flush(); + nl = nlines; + pause(); + } + } + outb.flush(); +} + +pause() +{ + for(;;){ + cmdline := cons.gets('\n'); + if(cmdline == nil || cmdline[0] == 'q') # catch ^d + exit; + else if(cmdline[0] == '!') { + done := chan of int; + spawn command(cmdline[1:], done); + <-done; + }else + break; + } +} + +command(cmdline: string, done: chan of int) +{ + sh := load Sh Sh->PATH; + if(sh == nil) { + sys->fprint(stderr, "%s: can't load %s: %r\n", progname, Sh->PATH); + done <-= 0; + return; + } + sys->pctl(Sys->FORKFD, nil); + sys->dup(cons.fd.fd, 0); + sh->system(drawctxt, cmdline); + done <-= 1; +} diff --git a/appl/cmd/palm/connex.b b/appl/cmd/palm/connex.b new file mode 100644 index 00000000..2cd66fd8 --- /dev/null +++ b/appl/cmd/palm/connex.b @@ -0,0 +1,124 @@ +implement Connex; + +# +# temporary test program for palmsrv development +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "palm.m"; + palm: Palm; + Record: import palm; + palmdb: Palmdb; + DB, PDB, PRC: import palmdb; + +include "desklink.m"; + desklink: Desklink; + SysInfo: import desklink; + +Connex: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + palm = load Palm Palm->PATH; + if(palm == nil) + error(sys->sprint("can't load %s: %r", palm->PATH)); + desklink = load Desklink Desklink->PATH1; + if(desklink == nil) + error(sys->sprint("can't load Desklink: %r")); + + palm->init(); + + err: string; + (palmdb, err) = desklink->connect("/chan/palmsrv"); + if(palmdb == nil) + error(sys->sprint("can't init Desklink: %s", err)); + desklink->init(palm); + sysinfo := desklink->ReadSysInfo(); + if(sysinfo == nil) + error(sys->sprint("can't read sys Info: %r")); + sys->print("ROM: %8.8ux locale: %8.8ux product: '%s'\n", sysinfo.romversion, sysinfo.locale, sysinfo.product); + user := desklink->ReadUserInfo(); + if(user == nil) + error(sys->sprint("can't read user info")); + sys->print("userid: %d viewerid: %d lastsyncpc: %d succsync: %8.8ux lastsync: %8.8ux uname: '%s' password: %s\n", + user.userid, user.viewerid, user.lastsyncpc, user.succsynctime, user.lastsynctime, user.username, ba(user.password)); + sys->print("Storage:\n"); + for(cno:=0;;){ + (cards, more, err) := desklink->ReadStorageInfo(cno); + for(i:=0; i<len cards; i++){ + sys->print("%2d v=%d c=%d romsize=%d ramsize=%d ramfree=%d name='%s' maker='%s'\n", + cards[i].cardno, cards[i].version, cards[i].creation, cards[i].romsize, cards[i].ramsize, + cards[i].ramfree, cards[i].name, cards[i].maker); + cno = cards[i].cardno+1; + } + if(!more) + break; + } + sys->print("ROM DBs:\n"); + listdbs(Desklink->DBListROM); + sys->print("RAM DBs:\n"); + listdbs(Desklink->DBListRAM); + + (db, ee) := DB.open("AddressDB", Palmdb->OREAD); + if(db == nil){ + sys->print("error: AddressDB: %s\n", ee); + exit; + } + pdb := db.records(); + if(pdb == nil){ + sys->print("error: AddressDB: %r\n"); + exit; + } + dumpfd := sys->create("dump", Sys->OWRITE, 8r600); + for(i:=0; (r := pdb.read(i)) != nil; i++) + sys->write(dumpfd, r.data, len r.data); +# desklink->EndOfSync(Desklink->SyncNormal); + desklink->hangup(); +} + +listdbs(sort: int) +{ + index := 0; + for(;;){ + (dbs, more, e) := desklink->ReadDBList(0, sort, index); + if(dbs == nil){ + if(e != nil) + sys->print("ReadDBList: %s\n", e); + break; + } + for(i := 0; i < len dbs; i++){ + sys->print("#%4.4ux '%s'\n", dbs[i].index, dbs[i].name); + index = dbs[i].index+1; + } + if(!more) + break; + } +} + +ba(a: array of byte): string +{ + s := ""; + for(i := 0; i < len a; i++) + s += sys->sprint("%2.2ux", int a[i]); + return s; +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "tconn: %s\n", s); + fd := sys->open("/prog/"+string sys->pctl(0,nil)+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "killgrp"); + raise "fail:error"; +} diff --git a/appl/cmd/palm/desklink.b b/appl/cmd/palm/desklink.b new file mode 100644 index 00000000..23dafaa7 --- /dev/null +++ b/appl/cmd/palm/desklink.b @@ -0,0 +1,843 @@ +implement Palmdb, Desklink; + +# +# Palm Desk Link Protocol (DLP) +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# +# Request and response formats were extracted from +# include/Core/System/DLCommon.h in the PalmOS SDK-5 +# + +include "sys.m"; + sys: Sys; + +include "daytime.m"; + daytime: Daytime; + Tm: import daytime; + +include "palm.m"; + palm: Palm; + DBInfo, Record, Resource, id2s, s2id, get2, put2, get4, put4, gets, argsize, packargs, unpackargs: import palm; + +include "timers.m"; + +include "desklink.m"; + +Maxrecbytes: con 16rFFFF; + +# operations defined by Palm + +T_ReadUserInfo, T_WriteUserInfo, T_ReadSysInfo, T_GetSysDateTime, +T_SetSysDateTime, T_ReadStorageInfo, T_ReadDBList, T_OpenDB, T_CreateDB, +T_CloseDB, T_DeleteDB, T_ReadAppBlock, T_WriteAppBlock, T_ReadSortBlock, +T_WriteSortBlock, T_ReadNextModifiedRec, T_ReadRecord, T_WriteRecord, +T_DeleteRecord, T_ReadResource, T_WriteResource, T_DeleteResource, +T_CleanUpDatabase, T_ResetSyncFlags, T_CallApplication, T_ResetSystem, +T_AddSyncLogEntry, T_ReadOpenDBInfo, T_MoveCategory, T_ProcessRPC, +T_OpenConduit, T_EndOfSync, T_ResetDBIndex, T_ReadRecordIDList, +# DLP 1.1 functions +T_ReadNextRecInCategory, T_ReadNextModifiedRecInCategory, +T_ReadAppPreference, T_WriteAppPreference, T_ReadNetSyncInfo, +T_WriteNetSyncInfo, T_ReadFeature, +# DLP 1.2 functions +T_FindDB, T_SetDBInfo, +# DLP 1.3 functions +T_LoopBackTest, T_ExpSlotEnumerate, T_ExpCardPresent, T_ExpCardInfo: con 16r10+iota; +# then there's a group of VFS requests that we don't currently use + +Response: con 16r80; + +Maxname: con 32; + +A1, A2: con Palm->ArgIDbase+iota; # argument IDs have request-specific interpretation (most have only one ID) + +Timeout: con 30; # seconds time out used by Palm's headers +srvfd: ref Sys->FD; +selfdb: Palmdb; + +errorlist := array [] of { + "no error", + "general Pilot system error", + "unknown request", + "out of dynamic memory on device", + "invalid parameter", + "not found", + "no open databases", + "database already open", + "too many open databases", + "database already exists", + "cannot open database", + "record previously deleted", + "record busy", + "operation not supported", + "unexpected error (ErrUnused1)", + "read only object", + "not enough space", + "size limit exceeded", + "sync cancelled", + "bad arg wrapper", + "argument missing", + "bad argument size", +}; + +Eshort: con "desklink protocol: response too short"; + +debug := 0; + +connect(srvfile: string): (Palmdb, string) +{ + sys = load Sys Sys->PATH; + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + return (nil, sys->sprint("can't load %s: %r", Daytime->PATH)); + srvfd = sys->open(srvfile, Sys->ORDWR); + if(srvfd == nil) + return (nil, sys->sprint("can't open %s: %r", srvfile)); + selfdb = load Palmdb "$self"; + if(selfdb == nil) + return (nil, sys->sprint("can't load self as Palmdb: %r")); + return (selfdb, nil); +} + +hangup(): int +{ + srvfd = nil; + return 0; +} + +# +# set the system error string +# +e(s: string): string +{ + if(s != nil){ + s = "palm: "+s; + sys->werrstr(s); + } + return s; +} + +# +# sent before each conduit is opened by the desktop, +# apparently to detect a pending cancel request (on the device) +# +OpenConduit(): int +{ + return nexec(T_OpenConduit, A1, nil); +} + +# +# end of sync on desktop +# +EndOfSync(status: int): int +{ + req := array[2] of byte; + put2(req, status); + return nexec(T_EndOfSync, A1, req); +} + +ReadSysInfo(): ref SysInfo +{ + if((reply := dexec(T_ReadSysInfo, A1, nil, 14)) == nil) + return nil; + s := ref SysInfo; + s.romversion = get4(reply); + s.locale = get4(reply[4:]); + l := int reply[9]; # should be at most 4 apparently? + s.product = gets(reply[10:10+l]); + return s; +} + +ReadSysInfoVer(): (int, int, int) +{ + req := array[4] of byte; + put2(req, 1); # major version + put2(req, 2); # minor version + if((reply := dexec(T_ReadSysInfo, A2, req, 12)) == nil) + return (0, 0, 0); + return (get4(reply), get4(reply[4:]), get4(reply[8:])); +} + +ReadUserInfo(): ref User +{ + if((reply := dexec(T_ReadUserInfo, 0, nil, 30)) == nil) + return nil; + u := ref User; + u.userid = get4(reply); + u.viewerid = get4(reply[4:]); + u.lastsyncpc = get4(reply[8:]); + u.succsynctime = getdate(reply[12:]); + u.lastsynctime = getdate(reply[20:]); + userlen := int reply[28]; + pwlen := int reply[29]; + u.username = gets(reply[30:30+userlen]); + u.password = array[pwlen] of byte; + u.password[0:] = reply[30+userlen:30+userlen+pwlen]; + return u; +} + +WriteUserInfo(u: ref User, flags: int): int +{ + req := array[22+Maxname] of byte; + put4(req, u.userid); + put4(req[4:], u.viewerid); + put4(req[8:], u.lastsyncpc); + putdate(req[12:], u.lastsynctime); + req[20] = byte flags; + l := puts(req[22:], u.username); + req[21] = byte l; + return nexec(T_WriteUserInfo, A1, req[0:22+l]); +} + +GetSysDateTime(): int +{ + if((reply := dexec(T_GetSysDateTime, A1, nil, 8)) == nil) + return -1; + return getdate(reply); +} + +SetSysDateTime(time: int): int +{ + return nexec(T_SetSysDateTime, A1, putdate(array[8] of byte, time)); +} + +ReadStorageInfo(cardno: int): (array of ref CardInfo, int, string) +{ + req := array[2] of byte; + req[0] = byte cardno; + req[1] = byte 0; + (reply, err) := rexec(T_ReadStorageInfo, A1, req, 30); + if(reply == nil) + return (nil, 0, err); + nc := int reply[3]; + if(nc <= 0) + return (nil, 0, nil); + more := int reply[1] != 0; + a := array[nc] of ref CardInfo; + p := 4; + for(i:=0; i<nc; i++){ + nb: int; + (a[i], nb) = unpackcard(reply[p:]); + p += nb; + } + return (a, more, nil); +} + +unpackcard(a: array of byte): (ref CardInfo, int) +{ + nb := int a[0]; # total size of this card's info + c := ref CardInfo; + c.cardno = int a[1]; + c.version = get2(a[2:]); + c.creation = getdate(a[4:]); + c.romsize = get4(a[12:]); + c.ramsize = get4(a[16:]); + c.ramfree = get4(a[20:]); + l1 := int a[24] + 26; + l2 := int a[25]; + c.name = gets(a[26:l1]); + c.maker = gets(a[l1:l1+l2]); + return (c, nb); +} + +ReadDBCount(cardno: int): (int, int) +{ + req := array[2] of byte; + req[0] = byte cardno; + req[1] = byte 0; + if((reply := dexec(T_ReadStorageInfo, A2, req, 20)) == nil) + return (-1, -1); + return (get2(req[0:]), get2(req[2:])); +} + +unpackdbinfo(a: array of byte): (ref DBInfo, int) +{ + size := int a[0]; + misc := int a[1]; + info := ref DBInfo; + info.attr = get2(a[2:]); + info.dtype = id2s(get4(a[4:])); + info.creator = id2s(get4(a[8:])); + info.version = get2(a[12:]); + info.modno = get4(a[14:]); + info.ctime = getdate(a[18:]); + info.mtime = getdate(a[26:]); + info.btime = getdate(a[34:]); + info.index = get2(a[42:]); + if(size > len a) + size = len a; + info.name = gets(a[44:size]); + return (info, size); +} + +ReadDBList(cardno: int, flags: int, start: int): (array of ref DBInfo, int, string) +{ + req := array[4] of byte; + req[0] = byte (flags | DBListMultiple); + req[1] = byte cardno; + put2(req[2:], start); + (reply, err) := rexec(T_ReadDBList, A1, req, 48); + if(reply == nil || int reply[3] == 0) + return (nil, 0, err); + # lastindex[2] flags[1] actcount[1] + # flags is 16r80 => more to list + more := (reply[2] & byte 16r80) != byte 0; + dbs := array[int reply[3]] of ref DBInfo; +#sys->print("ndb=%d more=%d lastindex=#%4.4ux\n", len dbs, more, get2(reply)); + a := reply[4:]; + for(i := 0; i < len dbs; i++){ + (db, n) := unpackdbinfo(a); + dbs[i] = db; + a = a[n:]; + } + return (dbs, more, nil); +} + +matchdb(cardno: int, flag: int, start: int, dbname: string, dtype: string, creator: string): (ref DBInfo, int) +{ + for(;;){ + (dbs, more, err) := ReadDBList(cardno, flag, start); + if(dbs == nil) + break; + for(i := 0; i < len dbs; i++){ + info := dbs[i]; + if((dbname == nil || info.name == dbname) && + (dtype == nil || info.dtype == dtype) && + (creator == nil || info.creator == creator)) + return (info, info.index); + start = info.index+1; + } + } + return (nil, 0); +} + + +FindDBInfo(cardno: int, start: int, dbname: string, dtype: string, creator: string): ref DBInfo +{ + if(start < 16r1000) { + (info, i) := matchdb(cardno, 16r80, start, dbname, dtype, creator); + if(info != nil) + return info; + } + (info, i) := matchdb(cardno, 16r40, start&~16r1000, dbname, dtype, creator); + if(info != nil) + info.index |= 16r1000; + return info; +} + +DeleteDB(name: string): int +{ + (cardno, dbname) := parsedb(name); + req := array[2+Maxname] of byte; + req[0] = byte cardno; + req[1] = byte 0; + n := puts(req[2:], dbname); + return nexec(T_DeleteDB, A1, req[0:2+n]); +} + +ResetSystem(): int +{ + return nexec(T_ResetSystem, 0, nil); +} + +CloseDB_All(): int +{ + return nexec(T_CloseDB, A2, nil); +} + +AddSyncLogEntry(entry: string): int +{ + req := array[256] of byte; + n := puts(req, entry); + return nexec(T_AddSyncLogEntry, A1, req[0:n]); +} + +# +# this implements a Palmdb->DB directly accessed using the desklink protocol +# + +init(m: Palm): string +{ + palm = m; + return nil; +} + +# +# syntax is [cardno/]dbname +# where cardno defaults to 0 +# +parsedb(name: string): (int, string) +{ + (nf, flds) := sys->tokenize(name, "/"); + if(nf > 1) + return (int hd flds, hd tl flds); + return (0, name); +} + +DB.open(name: string, mode: int): (ref DB, string) +{ + (cardno, dbname) := parsedb(name); + req := array[2+Maxname] of byte; + req[0] = byte cardno; + req[1] = byte mode; + n := puts(req[2:], dbname); + (reply, err) := rexec(T_OpenDB, A1, req[0:2+n], 1); + if(reply == nil) + return (nil, err); + db := ref DB; + db.x = int reply[0]; + inf := db.stat(); + if(inf == nil) + return (nil, sys->sprint("can't get DBInfo: %r")); + db.attr = inf.attr; # mainly need to know whether it's Fresource or not + return (db, nil); +} + +DB.create(name: string, nil: int, nil: int, inf: ref DBInfo): (ref DB, string) +{ + (cardno, dbname) := parsedb(name); + req := array[14+Maxname] of byte; + put4(req, s2id(inf.creator)); + put4(req[4:], s2id(inf.dtype)); + req[8] = byte cardno; + req[9] = byte 0; + put2(req[10:], inf.attr); + put2(req[12:], inf.version); + n := puts(req[14:], dbname); + (reply, err) := rexec(T_CreateDB, A1, req[0:14+n], 1); + if(reply == nil) + return (nil, err); + db := ref DB; + db.x = int reply[0]; + db.attr = inf.attr; + return (db, nil); +} + +DB.stat(db: self ref DB): ref DBInfo +{ + (reply, err) := rexec(T_FindDB, A2, array[] of {byte 16r80, byte db.x}, 54); + if(err != nil) + return nil; + return unpackdbinfo(reply[10:]).t0; +} + +DB.wstat(db: self ref DB, inf: ref DBInfo, flags: int) +{ + # TO DO +} + +DB.close(db: self ref DB): string +{ + return rexec(T_CloseDB, A1, array[] of {byte db.x}, 0).t1; +} + +DB.records(db: self ref DB): ref PDB +{ + if(db.attr & Palm->Fresource){ + sys->werrstr("not a database file"); + return nil; + } + return ref PDB(db); +} + +DB.resources(db: self ref DB): ref PRC +{ + if((db.attr & Palm->Fresource) == 0){ + sys->werrstr("not a resource file"); + return nil; + } + return ref PRC(db); +} + +DB.readidlist(db: self ref DB, sort: int): array of int +{ + req := array[6] of byte; + req[0] = byte db.x; + if(sort) + req[1] = byte 16r80; + else + req[1] = byte 0; + put2(req[2:], 0); + put2(req[4:], -1); + p := dexec(T_ReadRecordIDList, A1, req, 2); + if(p == nil) + return nil; + ret := get2(p); + ids := array[ret] of int; + p = p[8:]; + for (i := 0; i < ret; p = p[4:]) + ids[i++] = get4(p); + return ids; +} + +DB.nentries(db: self ref DB): int +{ + if((reply := dexec(T_ReadOpenDBInfo, A1, array[] of {byte db.x}, 2)) == nil) + return -1; + return get2(reply); +} + +DB.rdappinfo(db: self ref DB): (array of byte, string) +{ + req := array[6] of byte; + req[0] = byte db.x; + req[1] = byte 0; + put2(req[2:], 0); # offset + put2(req[4:], -1); # to end + (reply, err) := rexec(T_ReadAppBlock, A1, req, 2); + if(reply == nil) + return (nil, err); + if(get2(reply) < len reply-2) + return (nil, "short reply"); + return (reply[2:], nil); +} + +DB.wrappinfo(db: self ref DB, data: array of byte): string +{ + req := array[4 + len data] of byte; + req[0] = byte db.x; + req[1] = byte 0; + put2(req[2:], len data); + req[4:] = data; + return rexec(T_WriteAppBlock, A1, req, 0).t1; +} + +DB.rdsortinfo(db: self ref DB): (array of int, string) +{ + req := array[6] of byte; + req[0] = byte db.x; + req[1] = byte 0; + put2(req[2:], 0); + put2(req[4:], -1); + (reply, err) := rexec(T_ReadSortBlock, A1, req, 2); + if(reply == nil) + return (nil, err); + n := len reply; + a := reply[2:n]; + n = (n-2)/2; + s := array[n] of int; + for(i := 0; i < n; i++) + s[i] = get2(a[i*2:]); + return (s, nil); +} + +DB.wrsortinfo(db: self ref DB, s: array of int): string +{ + n := len s; + req := array[4+2*n] of byte; + req[0] = byte db.x; + req[1] = byte 0; + put2(req[2:], 2*n); + for(i := 0; i < n; i++) + put2(req[2+i*2:], s[i]); + return rexec(T_WriteSortBlock, A1, req, 0).t1; +} + +PDB.purge(db: self ref PDB): string +{ + return rexec(T_CleanUpDatabase, A1, array[] of {byte db.db.x}, 0).t1; +} + +DB.resetsyncflags(db: self ref DB): string +{ + return rexec(T_ResetSyncFlags, A1, array[] of {byte db.x}, 0).t1; +} + +# +# .pdb and other data base files +# + +PDB.read(db: self ref PDB, index: int): ref Record +{ + req := array[8] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put2(req[2:], index); + put2(req[4:], 0); # offset + put2(req[6:], Maxrecbytes); + return unpackrec(dexec(T_ReadRecord, A2, req, 10)).t0; +} + +PDB.readid(db: self ref PDB, id: int): (ref Record, int) +{ + req := array[10] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], id); + put2(req[6:], 0); # offset + put2(req[8:], Maxrecbytes); + return unpackrec(dexec(T_ReadRecord, A1, req, 10)); +} + +PDB.write(db: self ref PDB, r: ref Record): string +{ + req := array[8+len r.data] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], r.id); + req[6] = byte (r.attr & Palm->Rsecret); + req[7] = byte r.cat; + req[8:] = r.data; + (reply, err) := rexec(T_WriteRecord, A1, req, 4); + if(reply == nil) + return err; + if(r.id == 0) + r.id = get4(reply); + return nil; +} + +PDB.movecat(db: self ref PDB, from: int, tox: int): string +{ + req := array[4] of byte; + req[0] = byte db.db.x; + req[1] = byte from; + req[2] = byte tox; + req[3] = byte 0; + return rexec(T_MoveCategory, A1, req, 0).t1; +} + +PDB.resetnext(db: self ref PDB): int +{ + return nexec(T_ResetDBIndex, A1, array[] of {byte db.db.x}); +} + +PDB.readnextmod(db: self ref PDB): (ref Record, int) +{ + return unpackrec(dexec(T_ReadNextModifiedRec, A1, array[] of {byte db.db.x}, 10)); +} + +PDB.delete(db: self ref PDB, id: int): string +{ + req := array[6] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], id); + return rexec(T_DeleteRecord, A1, req, 0).t1; +} + +PDB.deletecat(db: self ref PDB, cat: int): string +{ + return rexec(T_DeleteRecord, A1, array[] of {byte db.db.x, byte 16r40, 2 to 6 => byte 0, 7=>byte cat}, 0).t1; +} + +PDB.truncate(db: self ref PDB): string +{ + return rexec(T_DeleteRecord, A1, array[] of {byte db.db.x, byte 16r80, 2 to 7 => byte 0}, 0).t1; +} + +# +# .prc resource files +# + +PRC.write(db: self ref PRC, r: ref Resource): string +{ + req := array[8+len r.data] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], r.name); + put2(req[6:], r.id); + put2(req[8:], len r.data); + return rexec(T_WriteResource, A1, req, 0).t1; +} + +PRC.delete(db: self ref PRC, name: int, id: int): string +{ + req := array[8] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], name); + put4(req[6:], id); + return rexec(T_DeleteResource, A1, req, 0).t1; +} + +PRC.readtype(db: self ref PRC, name: int, id: int): (ref Resource, int) +{ + req := array[12] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put4(req[2:], name); + put2(req[6:], id); + put2(req[8:], 0); # Offset into record + put2(req[10:], Maxrecbytes); + return unpackresource(dexec(T_ReadResource, A2, req, 10)); +} + +PRC.truncate(db: self ref PRC): string +{ + return rexec(T_DeleteResource, A1, array[] of {byte db.db.x, byte 16r80, 2 to 7 => byte 0}, 0).t1; +} + +PRC.read(db: self ref PRC, index: int): ref Resource +{ + req := array[8] of byte; + req[0] = byte db.db.x; + req[1] = byte 0; + put2(req[2:], index); + put2(req[4:], 0); # offset + put2(req[6:], Maxrecbytes); + return unpackresource(dexec(T_ReadResource, A1, req, 12)).t0; +} + +# +# DL protocol +# +# request +# id: byte # operation +# argc: byte # arg count +# args: byte[] +# +# response +# id: byte # cmd|16r80 +# argc: byte # argc response arguments follow header +# error: byte[2] # error code +# args: byte[] +# +# args wrapped by Palm->packargs etc. +# + +# +# RPC exchange with device +# +rpc(req: array of byte): (array of (int, array of byte), string) +{ + if(sys->write(srvfd, req, len req) != len req) + return (nil, sys->sprint("link: %r")); + reply := array[65536] of byte; + nb := sys->read(srvfd, reply, len reply); + if(nb == 0) + return (nil, "link: hangup"); + if(nb < 0) + return (nil, sys->sprint("link: %r")); + r := int reply[0]; + if((r & Response) == 0) + return (nil, e(sys->sprint("received request #%2.2x not response", r))); + if(r != (Response|int req[0])) + return (nil, e(sys->sprint("wrong response #%x", r))); + if(nb < 4) + return (nil, e(Eshort)); + rc := get2(reply[2:]); + if(rc != 0){ + if(rc < 0 || rc >= len errorlist) + return (nil, e(sys->sprint("unknown error %d", rc))); + return (nil, e(errorlist[rc])); + } + argc := int reply[1]; # count of following arguments + if(argc == 0) + return (nil, nil); + return unpackargs(argc, reply[4:nb]); +} + +rexec(cmd: int, argid: int, arg: array of byte, minlen: int): (array of byte, string) +{ + args: array of (int, array of byte); + if(arg != nil) + args = array[] of {(argid, arg)}; + req := array[2+argsize(args)] of byte; + req[0] = byte cmd; + req[1] = byte len args; + packargs(req[2:], args); + (replies, err) := rpc(req); + if(replies == nil){ + if(err != nil) + return (nil, err); + if(minlen > 0) + return (nil, e(Eshort)); + return (nil, nil); + } + (nil, reply) := replies[0]; + if(len reply < minlen) + return (nil, e(Eshort)); + return (reply, nil); +} + +dexec(cmd: int, argid: int, msg: array of byte, minlen: int): array of byte +{ + (reply, nil) := rexec(cmd, argid, msg, minlen); + return reply; +} + +nexec(cmd: int, argid: int, msg: array of byte): int +{ + (nil, err) := rexec(cmd, argid, msg, 0); + if(err != nil) + return -1; + return 0; +} + +unpackresource(a: array of byte): (ref Resource, int) +{ + nb := len a; + if(nb < 10) + return (nil, -1); + size := get2(a[8:]); + if(nb-10 < size) + return (nil, -1); + r := Resource.new(get4(a), get2(a[4:]), size); + r.data[0:] = a[10:10+size]; + return (r, get2(a[6:])); +} + +unpackrec(a: array of byte): (ref Record, int) +{ + nb := len a; + if(nb < 10) + return (nil, -1); + size := get2(a[6:]); + if(nb-10 < size) + return (nil, -1); + r := Record.new(get4(a), int a[8], int a[9], size); + r.data[0:] = a[10:10+size]; + return (r, get2(a[4:])); +} + +# +# pack string (must be Latin1) as zero-terminated array of byte +# +puts(a: array of byte, s: string): int +{ + for(i := 0; i < len s && i < len a-1; i++) + a[i] = byte s[i]; + a[i++] = byte 0; + return i; +} + +# +# the conversion via local time might be wrong, +# since the computers might be in different time zones, +# but is hard to avoid +# + +getdate(data: array of byte): int +{ + yr := (int data[0] << 8) | int data[1]; + if(yr == 0) + return 0; # unspecified + t := ref Tm; + t.sec = int data[6]; + t.min = int data[5]; + t.hour = int data[4]; + t.mday = int data[3]; + t.mon = int data[2] - 1; + t.year = yr - 1900; + t.wday = 0; + t.yday = 0; + return daytime->tm2epoch(t); +} + +putdate(data: array of byte, time: int): array of byte +{ + t := daytime->local(time); + y := t.year + 1900; + if(time == 0) + y = 0; # `unchanged' + data[7] = byte 0; # pad + data[6] = byte t.sec; + data[5] = byte t.min; + data[4] = byte t.hour; + data[3] = byte t.mday; + data[2] = byte (t.mon + 1); + data[0] = byte ((y >> 8) & 16rff); + data[1] = byte (y & 16rff); + return data; +} diff --git a/appl/cmd/palm/desklink.m b/appl/cmd/palm/desklink.m new file mode 100644 index 00000000..cc8d69c4 --- /dev/null +++ b/appl/cmd/palm/desklink.m @@ -0,0 +1,90 @@ + +# +# desktop/Pilot link protocol +# + +Desklink: module { + + PATH1: con "/dis/palm/desklink.dis"; + + User: adt { + userid: int; + viewerid: int; + lastsyncpc: int; + succsynctime: int; + lastsynctime: int; + username: string; + password: array of byte; + }; + + SysInfo: adt { + romversion: int; + locale: int; + product: string; + }; + + CardInfo: adt { + cardno: int; + version: int; + creation: int; + romsize: int; + ramsize: int; + ramfree: int; + name: string; + maker: string; + }; + + connect: fn(srvfile: string): (Palmdb, string); + hangup: fn(): int; + + # + # Desk Link Protocol functions (usually with the same names as in PalmOS) + # + + ReadUserInfo: fn(): ref User; + WriteUserInfo: fn(u: ref User, flags: int): int; + + # WriteUserInfo update flags + UserInfoModUserID: con 16r80; + UserInfoModSyncPC: con 16r40; + UserInfoModSyncDate: con 16r20; + UserInfoModName: con 16r10; + UserInfoModViewerID: con 16r08; + + ReadSysInfo: fn(): ref SysInfo; + ReadSysInfoVer: fn(): (int, int, int); # DLP 1.2 + + GetSysDateTime: fn(): int; + SetSysDateTime: fn(nil: int): int; + + ReadStorageInfo: fn(cardno: int): (array of ref CardInfo, int, string); + ReadDBCount: fn(cardno: int): (int, int); + + ReadDBList: fn(cardno: int, flags: int, start: int): (array of ref Palm->DBInfo, int, string); # flags must contain DBListRAM and/or DBListROM + FindDBInfo: fn(cardno: int, start: int, name: string, dtype, creator: string): ref Palm->DBInfo; + + # list location and options + DBListRAM: con 16r80; + DBListROM: con 16r40; + DBListMultiple: con 16r20; # ok to return multiple entries + + # OpenDB, CreateDB, ReadAppBlock, ... ResetSyncFlags, ReadOpenDBInfo, MoveCategory are functions in DB + CloseDB_All: fn(): int; + DeleteDB: fn(name: string): int; + + ResetSystem: fn(): int; + + OpenConduit: fn(): int; + EndOfSync: fn(status: int): int; + + # EndOfSync status parameter + SyncNormal, SyncOutOfMemory, SyncCancelled, SyncError, SyncIncompatible: con iota; + + AddSyncLogEntry: fn(entry: string): int; + + # + # Palmdb implementation + # + + init: fn(m: Palm): string; +}; diff --git a/appl/cmd/palm/mkfile b/appl/cmd/palm/mkfile new file mode 100644 index 00000000..2b0cb209 --- /dev/null +++ b/appl/cmd/palm/mkfile @@ -0,0 +1,16 @@ +<../../../mkconfig + +TARG=\ + palmsrv.dis\ + desklink.dis\ + connex.dis\ + +MODULES=\ + desklink.m\ + +SYSMODULES=\ + palm.m\ + +DISBIN=$ROOT/dis/palm + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/palm/palmsrv.b b/appl/cmd/palm/palmsrv.b new file mode 100644 index 00000000..f878be03 --- /dev/null +++ b/appl/cmd/palm/palmsrv.b @@ -0,0 +1,901 @@ +implement Palmsrv; + +# +# serve up a Palm using SLP and PADP +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# +# forsyth@vitanuova.com +# +# TO DO +# USB and possibly other transports +# tickle + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "timers.m"; + timers: Timers; + Timer, Sec: import timers; + +include "palm.m"; + +include "arg.m"; + +Palmsrv: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +debug := 0; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: palm/palmsrv [-d /dev/eia0] [-s 57600]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil); + + device, speed: string; + + arg := load Arg Arg->PATH; + if(arg == nil) + error(sys->sprint("can't load %s: %r", Arg->PATH)); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'D' => + debug++; + 'd' => + device = arg->arg(); + 's' => + speed = arg->arg(); + * => + usage(); + } + args = arg->argv(); + arg = nil; + + if(device == nil) + device = "/dev/eia0"; + if(speed == nil) + speed = "57600"; + + dfd := sys->open(device, Sys->ORDWR); + if(dfd == nil) + error(sys->sprint("can't open %s: %r", device)); + cfd := sys->open(device+"ctl", Sys->OWRITE); + + timers = load Timers Timers->PATH; + if(timers == nil) + error(sys->sprint("can't load %s: %r", Timers->PATH)); + srvio := sys->file2chan("/chan", "palmsrv"); + if(srvio == nil) + error(sys->sprint("can't create channel /chan/palmsrv: %r")); + timers->init(Sec/100); + p := Pchan.init(dfd, cfd); + spawn server(srvio, p); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "palmsrv: %s\n", s); + raise "fail:error"; +} + +Xact: adt +{ + fid: int; + reply: array of byte; + error: string; +}; + +server(srv: ref Sys->FileIO, p: ref Pchan) +{ + actions: list of ref Xact; + nuser := 0; + for(;;)alt{ + (nil, nbytes, fid, rc) := <-srv.read => + if(rc == nil){ + actions = delact(actions, fid); + break; + } + act := findact(actions, fid); + if(act == nil){ + rc <-= (nil, "no transaction in progress"); + break; + } + actions = delact(actions, fid); + if(p.shutdown) + rc <-= (nil, "link shut down"); + else if(act.error != nil) + rc <-= (nil, act.error); + else if(act.reply != nil) + rc <-= (act.reply, nil); + else + rc <-= (nil, "no reply"); # probably shouldn't happen + + (nil, data, fid, wc) := <-srv.write => + actions = delact(actions, fid); # discard result of any previous transaction + if(wc == nil){ + if(--nuser <= 0){ + nuser = 0; + p.stop(); + } + break; + } + if(len data == 4 && string data == "exit"){ + p.close(); + wc <-= (len data, nil); + exit; + } + if(p.shutdown){ + wc <-= (0, "link shut down"); # must close then reopen + break; + } + if(!p.started){ + err := p.start(); + if(err != nil){ + wc <-= (0, sys->sprint("can't start protocol: %s", err)); + break; + } + nuser++; + } + (result, err) := p.padp_xchg(data, 20*1000); + if(err != nil){ + wc <-= (0, err); + break; + } + actions = ref Xact(fid, result, err) :: actions; + wc <-= (len data, nil); + } +} + +findact(l: list of ref Xact, fid: int): ref Xact +{ + for(; l != nil; l = tl l) + if((a := hd l).fid == fid) + return a; + return nil; +} + +delact(l: list of ref Xact, fid: int): list of ref Xact +{ + ol := l; + l = nil; + for(; ol != nil; ol = tl ol) + if((a := hd ol).fid != fid) + l = a :: l; + return l; +} + +killpid(pid: int) +{ + if(pid != 0){ + fd := sys->open("/prog/"+string pid+"/ctl", sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); + } +} + +# +# protocol implementation +# Serial Link Protocol (framing) +# Connection Management Protocol (wakeup, negotiation) +# Packet Assembly/Disassembly Protocol (reliable delivery fragmented datagram) +# + +DATALIM: con 1024; + +# SLP packet types +SLP_System, SLP_Unused, SLP_PAD, SLP_Loop: con iota; + +# SLP block content, without framing +Sblock: adt { + src: int; # socket ID + dst: int; # socket ID + proto: int; # packet type + xid: int; # transaction ID + data: array of byte; + + new: fn(): ref Sblock; + print: fn(sb: self ref Sblock, dir: string); +}; + +# +# Palm channel +# +Pchan: adt { + started: int; + shutdown: int; + + protocol: int; + lport: byte; + rport: byte; + + fd: ref Sys->FD; + cfd: ref Sys->FD; + baud: int; + + rpid: int; + lastid: int; + rd: chan of ref Sblock; + reply: ref Sblock; # data replacing lost ack + + init: fn(dfd: ref Sys->FD, cfd: ref Sys->FD): ref Pchan; + start: fn(p: self ref Pchan): string; + stop: fn(p: self ref Pchan); + close: fn(p: self ref Pchan): int; + slp_read: fn(p: self ref Pchan, nil: int): (ref Sblock, string); + slp_write: fn(p: self ref Pchan, xid: int, nil: array of byte): string; + + setbaud: fn(p: self ref Pchan, nil: int); + + padp_read: fn(p: self ref Pchan, xid: int, timeout: int): (array of byte, string); + padp_write: fn(p: self ref Pchan, msg: array of byte, xid: int): string; + padp_xchg: fn(p: self ref Pchan, msg: array of byte, timeout: int): (array of byte, string); + tickle: fn(p: self ref Pchan); + + connect: fn(p: self ref Pchan): string; + accept: fn(p: self ref Pchan, baud: int): string; + + nextseq: fn(p: self ref Pchan): int; +}; + +Pchan.init(dfd: ref Sys->FD, cfd: ref Sys->FD): ref Pchan +{ + p := ref Pchan; + p.fd = dfd; + p.cfd = cfd; + p.baud = InitBaud; + p.protocol = SLP_PAD; + p.rport = byte 3; + p.lport = byte 3; + p.rd = chan of ref Sblock; + p.lastid = 0; + p.rpid = 0; + p.started = 0; + p.shutdown = 0; + return p; +} + +Pchan.start(p: self ref Pchan): string +{ + if(p.started) + return nil; + p.shutdown = 0; + p.baud = InitBaud; + p.reply = nil; + ctl(p, "f"); + ctl(p, "d1"); + ctl(p, "r1"); + ctl(p, "i8"); + ctl(p, "q8192"); + ctl(p, sys->sprint("b%d", InitBaud)); + pidc := chan of int; + spawn slp_recv(p, pidc); + p.started = 1; + p.rpid = <-pidc; + err := p.accept(57600); + if(err != nil) + p.stop(); + return err; +} + +ctl(p: ref Pchan, s: string) +{ + if(p.cfd != nil) + sys->fprint(p.cfd, "%s", s); +} + +Pchan.setbaud(p: self ref Pchan, baud: int) +{ + if(p.baud != baud){ + p.baud = baud; + ctl(p, sys->sprint("b%d", baud)); + sys->sleep(200); + } +} + +Pchan.stop(p: self ref Pchan) +{ + p.shutdown = 0; + if(!p.started) + return; + killpid(p.rpid); + p.rpid = 0; + p.reply = nil; +# ctl(p, "f"); +# ctl(p, "d0"); +# ctl(p, "r0"); +# ctl(p, sys->sprint("b%d", InitBaud)); + p.started = 0; +} + +Pchan.close(p: self ref Pchan): int +{ + if(p.started) + p.stop(); + p.reply = nil; + p.cfd = nil; + p.fd = nil; + timers->shutdown(); + return 0; +} + +# CMP protocol for connection management +# See include/Core/System/CMCommon.h, Palm SDK +# There are two major versions: the original V1, still always used in wakeup messsages; +# and V2, which is completely different (similar structure to Desklink) and used by newer devices, but the headers +# are the same length. Start off in V1 announcing version 2.x, then switch to that. +# My device supports only V1, so I use that. + +CMPHDRLEN: con 10; # V1: type[1] flags[1] vermajor[1] verminor[1] mbz[2] baud[4] + # V2: type[1] cmd[1] error[2] argc[1] mbz[1] mbz[4] + +# CMP V1 +Cmajor: con 1; +Cminor: con 2; + +InitBaud: con 9600; + +# type +Cwake, Cinit, Cabort, Cextended: con 1+iota; + +# Cinit flags +ChangeBaud: con 16r80; +RcvTimeout1: con 16r40; # tell Palm to set receive timeout to 1 minute (CMP v1.1) +RcvTimeout2: con 16r20; # tell Palm to set receive timeout to 2 minutes (v1.1) + +# Cinit and Cwake flag +LongPacketEnable: con 16r10; # enable long packet support (v1.2) + +# Cabort flags +WrongVersion: con 16r80; # incompatible com versions + +# CMP V2 +Carg1: con Palm->ArgIDbase; +Cresponse: con 16r80; +Cxchgprefs, Chandshake: con 16r10+iota; + +Pchan.connect(p: self ref Pchan): string +{ + (nil, e1) := cmp_write(p, Cwake, 0, Cmajor, Cminor, 57600); + if(e1 != nil) + return e1; + (op, flag, nil, nil, baud, e2) := cmp_read(p, 0); + if(e2 != nil) + return e2; + case op { + Cinit=> + if(flag & ChangeBaud) + p.setbaud(baud); + return nil; + + Cabort=> + return "Palm rejected connect"; + + * => + return sys->sprint("Palm connect: reply %d", op); + } + return nil; +} + +Pchan.accept(p: self ref Pchan, maxbaud: int): string +{ + (op, nil, major, minor, baud, err) := cmp_read(p, 0); + if(err != nil) + return err; + if(major != 1){ + sys->fprint(sys->fildes(2), "palmsrv: comm version mismatch: %d.%d\n", major, minor); + cmp_write(p, Cabort, WrongVersion, Cmajor, 0, 0); + return sys->sprint("comm version mismatch: %d.%d", major, minor); + } + if(baud > maxbaud) + baud = maxbaud; + flag := 0; + if(baud != InitBaud) + flag = ChangeBaud; + (nil, err) = cmp_write(p, Cinit, flag, Cmajor, Cminor, baud); + if(err != nil) + return err; + p.setbaud(baud); + return nil; +} + +cmp_write(p: ref Pchan, op: int, flag: int, major: int, minor: int, baud: int): (int, string) +{ + cmpbuf := array[CMPHDRLEN] of byte; + cmpbuf[0] = byte op; + cmpbuf[1] = byte flag; + cmpbuf[2] = byte major; + cmpbuf[3] = byte minor; + cmpbuf[4] = byte 0; + cmpbuf[5] = byte 0; + put4(cmpbuf[6:], baud); + + if(op == Cwake) + return (16rFF, p.padp_write(cmpbuf, 16rFF)); + xid := p.nextseq(); + return (xid, p.padp_write(cmpbuf, xid)); +} + +cmp_read(p: ref Pchan, xid: int): (int, int, int, int, int, string) +{ + (c, err) := p.padp_read(xid, 20*Sec); + if(err != nil) + return (0, 0, 0, 0, 0, err); + if(len c != CMPHDRLEN) + return (0, 0, 0, 0, 0, "CMP: bad response"); + return (int c[0], int c[1], int c[2], int c[3], get4(c[6:]), nil); +} + +# +# Palm PADP protocol +# ``The Packet Assembly/Disassembly Protocol'' in +# Developing Palm OS Communications, US Robotics, 1996, pp. 53-68. +# +# forsyth@caldo.demon.co.uk, 1997 +# + +FIRST: con 16r80; +LAST: con 16r40; +MEMERROR: con 16r20; + +# packet types +Pdata: con 1; +Pack: con 2; +Ptickle: con 4; +Pabort: con 8; + +PADPHDRLEN: con 4; # type[1] flags[1] size[2] + +RetryInterval: con 4*Sec; +MaxRetries: con 14; # they say 14 `seconds', but later state they might need 20 for heap mgmt, so i'll assume 14 attempts (at 4sec ea) + +Pchan.padp_xchg(p: self ref Pchan, msg: array of byte, timeout: int): (array of byte, string) +{ + xid := p.nextseq(); + err := p.padp_write(msg, xid); + if(err != nil) + return (nil, err); + return p.padp_read(xid, timeout); +} + +# +# PADP header +# type[1] flags[2] size[2], high byte first for size +# +# max block size is 2^16-1 +# must ack within 2 seconds +# wait at most 10 seconds for next chunk +# 10 retries +# + +Pchan.padp_write(p: self ref Pchan, buf: array of byte, xid: int): string +{ + count := len buf; + if(count >= 1<<16) + return "padp: write too big"; + p.reply = nil; + flags := FIRST; + mem := buf[0:]; + offset := 0; + while(count > 0){ + n := count; + if(n > DATALIM) + n = DATALIM; + else + flags |= LAST; + ob := array[PADPHDRLEN+n] of byte; + ob[0] = byte Pdata; + ob[1] = byte flags; + l: int; + if(flags & FIRST) + l = count; # total size in first segment + else + l = offset; # offset in rest + put2(ob[2:], l); + ob[PADPHDRLEN:] = mem[0:n]; + if(debug) + padp_dump(ob, "Tx"); + p.slp_write(xid, ob); + retries := 0; + for(;;){ + (ib, nil) := p.slp_read(RetryInterval); + if(ib == nil){ + sys->print("padp write: ack timeout\n"); + retries++; + if(retries > MaxRetries){ + # USR says not to give up if (flags&LAST)!=0; giving up seems safer + sys->print("padp write: give up\n"); + return "PADP: no response"; + } + p.slp_write(xid, ob); + continue; + } + if(ib.proto != SLP_PAD || len ib.data < PADPHDRLEN || ib.xid != xid && ib.xid != 16rFF){ + sys->print("padp write: ack wrong type(%d) or xid(%d,%d), or len %d\n", ib.proto, ib.xid, xid, len ib.data); + continue; + } + if(ib.xid == 16rFF){ # connection management + if(int ib.data[0] == Ptickle) + continue; + if(int ib.data[0] == Pabort){ + sys->print("padp write: device abort\n"); + p.shutdown = 1; + return "device cancelled operation"; + } + } + if(int ib.data[0] != Pack){ + if(int ib.data[0] == Ptickle) + continue; + # right transaction ... if it's acceptable data, USR says to save it & treat as ack + sys->print("padp write: type %d, not ack\n", int ib.data[0]); + if(int ib.data[0] == Pdata && flags & LAST && int ib.data[1] & FIRST){ + p.reply = ib; + break; + } + continue; + } + if(int ib.data[1] & MEMERROR) + return "padp: pilot out of memory"; + if((flags&(FIRST|LAST)) != (int ib.data[1]&(FIRST|LAST)) || + get2(ib.data[2:]) != get2(ob[2:])){ + sys->print("padp write: ack, wrong flags (#%x,#%x) or offset (%d,%d)\n", int ib.data[1], flags, get2(ib.data[2:]), get2(ob[2:])); + continue; + } + if(debug) + sys->print("padp write: ack %d %d\n", xid, get2(ob[2:])); + break; + } + mem = mem[n:]; + count -= n; + offset += n; + flags &= ~FIRST; + } + return nil; +} + +Pchan.padp_read(p: self ref Pchan, xid, timeout: int): (array of byte, string) +{ + buf, mem: array of byte; + + offset := 0; + ready := 0; + retries := 0; + ack := array[PADPHDRLEN] of byte; + for(;;){ + b := p.reply; + if(b == nil){ + err: string; + (b, err) = p.slp_read(timeout); + if(b == nil){ + sys->print("padp read: timeout %d\n", retries); + if(++retries <= 5) + continue; + sys->print("padp read: gave up\n"); + return (nil, err); + } + retries = 0; + } else + p.reply = nil; + if(debug) + padp_dump(b.data, "Rx"); + if(len b.data < PADPHDRLEN){ + sys->print("padp read: length\n"); + continue; + } + if(b.proto != SLP_PAD){ + sys->print("padp read: bad proto (%d)\n", b.proto); + continue; + } + if(int b.data[0] == Pabort && b.xid == 16rFF){ + p.shutdown = 1; + return (nil, "device cancelled transaction"); + } + if(int b.data[0] != Pdata || xid != 0 && b.xid != xid){ + sys->print("padp read mismatch: type (%d) or xid(%d::%d)\n", int b.data[0], b.xid, xid); + continue; + } + f := int b.data[1]; + o := get2(b.data[2:]); + if(f & FIRST){ + buf = array[o] of byte; + ready = 1; + offset = 0; + o = 0; + mem = buf; + timeout = 4*Sec; + } + if(!ready || o != offset){ + sys->print("padp read: offset %d, expected %d\n", o, offset); + continue; + } + n := len b.data - PADPHDRLEN; + if(n > len mem){ + sys->print("padp read: record too long (%d/%d)\n", n, len mem); + # it's probably fatal, but retrying does no harm + continue; + } + mem[0:] = b.data[PADPHDRLEN:PADPHDRLEN+n]; + mem = mem[n:]; + offset += n; + ack[0:] = b.data[0:PADPHDRLEN]; + ack[0] = byte Pack; + p.slp_write(xid, ack); + if(f & LAST) + break; + } + if(offset != len buf) + return (buf[0:offset], nil); + return (buf, nil); +} + +Pchan.nextseq(p: self ref Pchan): int +{ + n := p.lastid + 1; + if(n >= 16rFF) + n = 1; + p.lastid = n; + return n; +} + +Pchan.tickle(p: self ref Pchan) +{ + xid := p.nextseq(); + data := array[PADPHDRLEN] of byte; + data[0] = byte Ptickle; + data[1] = byte (FIRST|LAST); + put2(data[2:], 0); + if(debug) + sys->print("PADP: tickle\n"); + p.slp_write(xid, data); +} + +padp_dump(data: array of byte, dir: string) +{ + stype: string; + + case int data[0] { + Pdata => stype = "Data"; + Pack => stype = "Ack"; + Ptickle => stype = "Tickle"; + Pabort => stype = "Abort"; + * => stype = sys->sprint("#%x", int data[0]); + } + + sys->print("PADP %s %s flags=#%x len=%d\n", stype, dir, int data[1], get2(data[2:])); + + if(debug > 1 && (data[0] != byte Pack || len data > 4)){ + data = data[4:]; + for(i := 0; i < len data;){ + sys->print(" %.2x", int data[i]); + if(++i%16 == 0) + sys->print("\n"); + } + sys->print("\n"); + } +} + +# +# Palm's Serial Link Protocol +# See include/Core/System/SerialLinkMgr.h in Palm SDK +# and the description in the USR document mentioned above. +# + +SLPHDRLEN: con 10; # BE[1] EF[1] ED[1] dest[1] src[1] type[1] size[2] xid[1] check[1] body[size] crc[2] +SLP_MTU: con SLPHDRLEN+PADPHDRLEN+DATALIM; + +Sblock.new(): ref Sblock +{ + return ref Sblock(0, 0, 0, 16rFF, nil); +} + +# +# format and write an SLP frame +# +Pchan.slp_write(p: self ref Pchan, xid: int, b: array of byte): string +{ + d := array[SLPHDRLEN] of byte; + cb := array[2] of byte; + + nb := len b; + d[0] = byte 16rBE; + d[1] = byte 16rEF; + d[2] = byte 16rED; + d[3] = byte p.rport; + d[4] = byte p.lport; + d[5] = byte p.protocol; + d[6] = byte (nb >> 8); + d[7] = byte (nb & 16rFF); + d[8] = byte xid; + d[9] = byte 0; + n := 0; + for(i:=0; i<len d; i++) + n += int d[i]; + d[9] = byte (n & 16rFF); + if(debug) + printbytes(d, "SLP Tx hdr"); + crc := crc16(d, 0); + put2(cb, crc16(b, crc)); + + if(sys->write(p.fd, d, SLPHDRLEN) != SLPHDRLEN || + sys->write(p.fd, b, nb) != len b || + sys->write(p.fd, cb, 2) != 2) + return sys->sprint("%r"); + return nil; +} + +Pchan.slp_read(p: self ref Pchan, timeout: int): (ref Sblock, string) +{ + clock := Timer.start(timeout); + alt { + <-clock.timeout => + if(debug) + sys->print("SLP: timeout\n"); + return (nil, "SLP: timeout"); + b := <-p.rd => + clock.stop(); + return (b, nil); + } +} + +slp_recv(p: ref Pchan, pidc: chan of int) +{ + n: int; + + pidc <-= sys->pctl(0, nil); + buf := array[2*SLP_MTU] of byte; + sb := Sblock.new(); + rd := wr := 0; +Work: + for(;;){ + + if(wr != rd){ + # data already in buffer might start a new frame + if(rd != 0){ + buf[0:] = buf[rd:wr]; + wr -= rd; + rd = 0; + } + }else + rd = wr = 0; + + # header + while(wr < SLPHDRLEN){ + n = sys->read(p.fd, buf[wr:], SLPHDRLEN-wr); + if(n <= 0) + break Work; + wr += n; + } +# {for(i:=0; i<wr;i++)sys->print("%.2x", int buf[i]);sys->print("\n");} + if(buf[0] != byte 16rBE || buf[1] != byte 16rEF || buf[2] != byte 16rED){ + rd++; + continue; + } + if(debug) + printbytes(buf[0:wr], "SLP Rx hdr"); + n = 0; + for(i:=0; i<SLPHDRLEN-1; i++) + n += int buf[i]; + if((n & 16rFF) != int buf[9]){ + rd += 3; + continue; + } + hdr := buf[0:SLPHDRLEN]; + sb.dst = int hdr[3]; + sb.src = int hdr[4]; + sb.proto = int hdr[5]; + size := (int hdr[6]<<8) | int hdr[7]; + sb.xid = int hdr[8]; + sb.data = array[size] of byte; + crc := crc16(hdr, 0); + rd += SLPHDRLEN; + if(rd == wr) + rd = wr = 0; + + # data and CRC + while(wr-rd < size+2){ + n = sys->read(p.fd, buf[wr:], size+2-(wr-rd)); + if(n <= 0) + break Work; + wr += n; + } + crc = crc16(buf[rd:rd+size], crc); + if(crc != get2(buf[rd+size:])){ + if(debug) + sys->print("CRC error: local=#%.4ux pilot=#%.4ux\n", crc, get2(buf[rd+size:])); + for(; rd < wr && buf[rd] != byte 16rBE; rd++) + ; # hunt for next header + continue; + } + if(sb.proto != SLP_Loop){ + sb.data[0:] = buf[rd:rd+size]; + if(debug) + sb.print("Rx"); + rd += size+2; + p.rd <-= sb; + sb = Sblock.new(); + } else { + # should we reflect these? + if(debug) + sb.print("Loop"); + rd += size+2; + } + } + p.rd <-= nil; +} + +Sblock.print(b: self ref Sblock, dir: string) +{ + sys->print("SLP %s %d->%d len=%d proto=%d xid=#%.2x\n", + dir, int b.src, int b.dst, len b.data, int b.proto, int b.xid); +} + +printbytes(d: array of byte, what: string) +{ + buf := sys->sprint("%s[", what); + for(i:=0; i<len d; i++) + buf += sys->sprint(" #%.2x", int d[i]); + buf += "]"; + sys->print("%s\n", buf); +} + +get4(p: array of byte): int +{ + return (int p[0]<<24) | (int p[1]<<16) | (int p[2]<<8) | int p[3]; +} + +get3(p: array of byte): int +{ + return (int p[1]<<16) | (int p[2]<<8) | int p[3]; +} + +get2(p: array of byte): int +{ + return (int p[0]<<8) | int p[1]; +} + +put4(p: array of byte, v: int) +{ + p[0] = byte (v>>24); + p[1] = byte (v>>16); + p[2] = byte (v>>8); + p[3] = byte (v & 16rFF); +} + +put3(p: array of byte, v: int) +{ + p[0] = byte (v>>16); + p[1] = byte (v>>8); + p[2] = byte (v & 16rFF); +} + +put2(p: array of byte, v: int) +{ + p[0] = byte (v>>8); + p[1] = byte (v & 16rFF); +} + +# this will be done by table look up; +# polynomial is xⁱ⁶+xⁱ+x⁵+1 + +crc16(buf: array of byte, crc: int): int +{ + for(j := 0; j < len buf; j++){ + crc = crc ^ (int buf[j]) << 8; + for(i := 0; i < 8; i++) + if(crc & 16r8000) + crc = (crc << 1) ^ 16r1021; + else + crc = crc << 1; + } + return crc & 16rffff; +} diff --git a/appl/cmd/pause.b b/appl/cmd/pause.b new file mode 100644 index 00000000..ab8cfbe4 --- /dev/null +++ b/appl/cmd/pause.b @@ -0,0 +1,17 @@ +implement Pause; +# +# init program to do nothing but pause +# + +include "sys.m"; +include "draw.m"; + +Pause: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, nil: list of string) +{ + <-chan of int; +} diff --git a/appl/cmd/plumb.b b/appl/cmd/plumb.b new file mode 100644 index 00000000..88879715 --- /dev/null +++ b/appl/cmd/plumb.b @@ -0,0 +1,115 @@ +implement Plumb; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "arg.m"; + arg: Arg; + +include "plumbmsg.m"; + plumbmsg: Plumbmsg; + Msg, Attr: import plumbmsg; + +include "workdir.m"; + workdir: Workdir; + +Plumb: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +usage() +{ + sys->fprint(stderr(), "Usage: plumb [-s src] [-d dest] [-D dir] [-k kind] [-a name val] ... data ...\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + arg = load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + plumbmsg = load Plumbmsg Plumbmsg->PATH; + if(plumbmsg == nil) + nomod(Plumbmsg->PATH); + workdir = load Workdir Workdir->PATH; + if(workdir == nil) + nomod(Workdir->PATH); + + if(plumbmsg->init(1, nil, 0) < 0) + err(sys->sprint("can't connect to plumb: %r")); + + attrs: list of ref Attr; + m := ref Msg("plumb", nil, workdir->init(), "text", nil, nil); + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 's' => + m.src = use(arg->arg(), c); + 'd' => + m.dst = use(arg->arg(), c); + 'D' => + m.dir = use(arg->arg(), c); + 'k' => + m.kind = use(arg->arg(), c); + 'a' => + name := use(arg->arg(), c); + val := use(arg->arg(), c); + attrs = tack(attrs, ref Attr(name, val)); + * => + usage(); + } + args = arg->argv(); + if(args == nil) + usage(); + nb := 0; + for(a := args; a != nil; a = tl a) + nb += len array of byte hd a; + nb += len args; + buf := array[nb] of byte; + nb = 0; + for(a = args; a != nil; a = tl a){ + b := array of byte hd a; + buf[nb++] = byte ' '; + buf[nb:] = b; + nb += len b; + } + m.data = buf[1:]; + m.attr = plumbmsg->attrs2string(attrs); + if(m.send() < 0) + err(sys->sprint("can't plumb message: %r")); +} + +tack(l: list of ref Attr, v: ref Attr): list of ref Attr +{ + if(l == nil) + return v :: nil; + return hd l :: tack(tl l, v); +} + +use(s: string, c: int): string +{ + if(s == nil) + err(sys->sprint("missing value for -%c", c)); + return s; +} + +nomod(m: string) +{ + err(sys->sprint("can't load %s: %r\n", m)); +} + +err(s: string) +{ + sys->fprint(stderr(), "plumb: %s\n", s); + raise "fail:error"; +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + diff --git a/appl/cmd/plumber.b b/appl/cmd/plumber.b new file mode 100644 index 00000000..016eb623 --- /dev/null +++ b/appl/cmd/plumber.b @@ -0,0 +1,766 @@ +implement Plumber; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + +include "sh.m"; + +include "regex.m"; + regex: Regex; + +include "string.m"; + str: String; + +include "../lib/plumbing.m"; + plumbing: Plumbing; + Pattern, Rule: import plumbing; + +include "plumbmsg.m"; + plumbmsg: Plumbmsg; + Msg, Attr: import plumbmsg; + +include "arg.m"; + +Plumber: module +{ + init: fn(ctxt: ref Draw->Context, argl: list of string); +}; + +Input: adt +{ + inc: chan of ref Inmesg; + resc: chan of int; + io: ref Sys->FileIO; +}; + +Output: adt +{ + name: string; + outc: chan of string; + io: ref Sys->FileIO; + queue: list of array of byte; + started: int; + startup: string; + waiting: int; +}; + +Port: adt +{ + name: string; + startup: string; + alwaysstart: int; +}; + +Match: adt +{ + p0, p1: int; +}; + +Inmesg: adt +{ + msg: ref Msg; + text: string; # if kind is text + p0,p1: int; + match: array of Match; + port: int; + startup: string; + args: list of string; + attrs: list of ref Attr; + clearclick: int; + set: int; + # $ arguments + _n: array of string; + _dir: string; + _file: string; +}; + +# Message status after processing +HANDLED: con -1; +UNKNOWN: con -2; +NOTSTARTED: con -3; + +output: array of ref Output; + +input: ref Input; + +stderr: ref Sys->FD; +pgrp: int; +rules: list of ref Rule; +titlectl: chan of string; +ports: list of ref Port; +wmstartup := 0; +wmchan := "/chan/wm"; +verbose := 0; + +context: ref Draw->Context; + +usage() +{ + sys->fprint(stderr, "Usage: plumb [-vw] [-c wmchan] [initfile ...]\n"); + raise "fail:usage"; +} + +init(ctxt: ref Draw->Context, args: list of string) +{ + context = ctxt; + + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + stderr = sys->fildes(2); + + regex = load Regex Regex->PATH; + plumbing = load Plumbing Plumbing->PATH; + str = load String String->PATH; + + err: string; + nogrp := 0; + + arg := load Arg Arg->PATH; + arg->init(args); + while ((opt := arg->opt()) != 0) { + case opt { + 'w' => + wmstartup = 1; + 'c' => + if ((wmchan = arg->arg()) == nil) + usage(); + 'v' => + verbose = 1; + 'n' => + nogrp = 1; + * => + usage(); + } + } + args = arg->argv(); + arg = nil; + + (rules, err) = plumbing->init(regex, args); + if(err != nil){ + sys->fprint(stderr, "plumb: %s\n", err); + raise "fail:init"; + } + + plumbmsg = load Plumbmsg Plumbmsg->PATH; + plumbmsg->init(0, nil, 0); + + if(nogrp) + pgrp = sys->pctl(0, nil); + else + pgrp = sys->pctl(sys->NEWPGRP, nil); + + r := rules; + for(i:=0; i<len rules; i++){ + rule := hd r; + r = tl r; + for(j:=0; j<len rule.action; j++) + if(rule.action[j].pred == "to" || rule.action[j].pred == "alwaysstart"){ + p := findport(rule.action[j].arg); + if(p == nil){ + p = ref Port(rule.action[j].arg, nil, rule.action[j].pred == "alwaysstart"); + ports = p :: ports; + } + for(k:=0; k<len rule.action; k++) + if(rule.action[k].pred == "start") + p.startup = rule.action[k].arg; + break; + } + } + + input = ref Input; + input.io = makefile("plumb.input"); + if(input.io == nil) + shutdown(); + input.inc = chan of ref Inmesg; + input.resc = chan of int; + spawn receiver(input); + + output = array[len ports] of ref Output; + + pp := ports; + for(i=0; i<len output; i++){ + p := hd pp; + pp = tl pp; + output[i] = ref Output; + output[i].name = p.name; + output[i].io = makefile("plumb."+p.name); + if(output[i].io == nil) + shutdown(); + output[i].outc = chan of string; + output[i].started = 0; + output[i].startup = p.startup; + output[i].waiting = 0; + } + + # spawn so we return without needing to run plumb in background + spawn sender(input, output); +} + +findport(name: string): ref Port +{ + for(p:=ports; p!=nil; p=tl p) + if((hd p).name == name) + return hd p; + return nil; +} + +makefile(file: string): ref Sys->FileIO +{ + io := sys->file2chan("/chan", file); + if(io == nil){ + sys->fprint(stderr, "plumb: can't establish /chan/%s: %r\n", file); + return nil; + } + return io; +} + +receiver(input: ref Input) +{ + + for(;;){ + (nil, msg, nil, wc) := <-input.io.write; + if(wc == nil) + ; # not interested in EOF; leave channel open + else{ + input.inc <-= parse(msg); + res := <- input.resc; + err := ""; + if(res == UNKNOWN) + err = "no matching plumb rule"; + wc <-= (len msg, err); + } + } +} + +sender(input: ref Input, output: array of ref Output) +{ + outputc := array[len output] of chan of (int, int, int, Sys->Rread); + + for(;;){ + alt{ + in := <-input.inc => + if(in == nil){ + input.resc <-= HANDLED; + break; + } + (j, msg) := process(in); + case j { + HANDLED => + break; + UNKNOWN => + if(in.msg.src != "acme") + sys->fprint(stderr, "plumb: don't know who message goes to\n"); + NOTSTARTED => + sys->fprint(stderr, "plumb: can't start application\n"); + * => + output[j].queue = append(output[j].queue, msg); + outputc[j] = output[j].io.read; + } + input.resc <-= j; + + (j, tmp) := <-outputc => + (nil, nbytes, nil, rc) := tmp; + if(rc == nil) # no interest in EOF + break; + msg := hd output[j].queue; + if(nbytes < len msg){ + rc <-= (nil, "buffer too short for message"); + break; + } + output[j].queue = tl output[j].queue; + if(output[j].queue == nil) + outputc[j] = nil; + rc <-= (msg, nil); + } + } +} + +parse(a: array of byte): ref Inmesg +{ + msg := Msg.unpack(a); + if(msg == nil) + return nil; + i := ref Inmesg; + i.msg = msg; + if(msg.dst != nil){ + if(control(i)) + return nil; + toport(i, msg.dst); + }else + i.port = -1; + i.match = array[10] of { * => Match(-1, -1)}; + i._n = array[10] of string; + i.attrs = plumbmsg->string2attrs(i.msg.attr); + return i; +} + +append(l: list of array of byte, a: array of byte): list of array of byte +{ + if(l == nil) + return a :: nil; + return hd l :: append(tl l, a); +} + +shutdown() +{ + fname := sys->sprint("#p/%d/ctl", pgrp); + if((fdesc := sys->open(fname, sys->OWRITE)) != nil) + sys->write(fdesc, array of byte "killgrp\n", 8); + raise "fail:error"; +} + +# Handle control messages +control(in: ref Inmesg): int +{ + msg := in.msg; + if(msg.kind!="text" || msg.dst!="plumb") + return 0; + text := string msg.data; + case text { + "start" => + start(msg.src, 1); + "stop" => + start(msg.src, -1); + * => + sys->fprint(stderr, "plumb: unrecognized control message from %s: %s\n", msg.src, text); + } + return 1; +} + +start(port: string, startstop: int) +{ + for(i:=0; i<len output; i++) + if(port == output[i].name){ + output[i].waiting = 0; + output[i].started += startstop; + return; + } + sys->fprint(stderr, "plumb: \"start\" message from unrecognized port %s\n", port); +} + +startup(dir, prog: string, args: list of string, wait: chan of int) +{ + if(wmstartup){ + fd := sys->open(wmchan, Sys->OWRITE); + if(fd != nil){ + sys->fprint(fd, "s %s", str->quoted(dir :: prog :: args)); + wait <-= 1; + return; + } + } + + sys->pctl(Sys->NEWFD|Sys->NEWPGRP|Sys->FORKNS, list of {0, 1, 2}); + wait <-= 1; + wait = nil; + mod := load Command prog; + if(mod == nil){ + sys->fprint(stderr, "plumb: can't load %s: %r\n", prog); + return; + } + sys->chdir(dir); + mod->init(context, prog :: args); +} + +# See if messages should be queued while waiting for program to connect +shouldqueue(out: ref Output): int +{ + p := findport(out.name); + if(p == nil){ + sys->fprint(stderr, "plumb: can't happen in shouldqueue\n"); + return 0; + } + if(p.alwaysstart) + return 0; + return out.waiting; +} + +# Determine destination of input message, reformat for output +process(in: ref Inmesg): (int, array of byte) +{ + if(!clarify(in)) + return (UNKNOWN, nil); + if(in.port < 0) + return (UNKNOWN, nil); + a := in.msg.pack(); + j := in.port; + if(a == nil) + j = UNKNOWN; + else if(output[j].started==0 && !shouldqueue(output[j])){ + path: string; + args: list of string; + if(in.startup!=nil){ + path = macro(in, in.startup); + args = expand(in, in.args); + }else if(output[j].startup != nil){ + path = output[j].startup; + args = in.text :: nil; + }else + return (NOTSTARTED, nil); + log(sys->sprint("start %s port %s\n", path, output[j].name)); + wait := chan of int; + output[j].waiting = 1; + spawn startup(in.msg.dir, path, args, wait); + <-wait; + return (HANDLED, nil); + }else{ + if(in.msg.kind != "text") + text := sys->sprint("message of type %s", in.msg.kind); + else{ + text = in.text; + for(i:=0; i<len text; i++){ + if(text[i]=='\n'){ + text = text[0:i]; + break; + } + if(i > 50) { + text = text[0:i]+"..."; + break; + } + } + } + log(sys->sprint("send \"%s\" to %s", text, output[j].name)); + } + return (j, a); +} + +# expand $arguments +expand(in: ref Inmesg, args: list of string): list of string +{ + a: list of string; + while(args != nil){ + a = macro(in, hd args) :: a; + args = tl args; + } + while(a != nil){ + args = hd a :: args; + a = tl a; + } + return args; +} + +# resolve all ambiguities, fill in any missing fields +clarify(in: ref Inmesg): int +{ + in.clearclick = 0; + in.set = 0; + msg := in.msg; + if(msg.kind != "text") + return 0; + in.text = string msg.data; + if(msg.dst != "") + return 1; + return dorules(in, rules); +} + +dorules(in: ref Inmesg, rules: list of ref Rule): int +{ + if (verbose) + log("msg: " + inmesg2s(in)); + for(r:=rules; r!=nil; r=tl r) { + if(matchrule(in, hd r)){ + applyrule(in, hd r); + if (verbose) + log("yes"); + return 1; + } else if (verbose) + log("no"); + } + return 0; +} + +inmesg2s(in: ref Inmesg): string +{ + m := in.msg; + s := sys->sprint("src=%s; dst=%s; dir=%s; kind=%s; attr='%s'", + m.src, m.dst, m.dir, m.kind, m.attr); + if (m.kind == "text") + s += "; data='" + string m.data + "'"; + return s; +} + +matchrule(in: ref Inmesg, r: ref Rule): int +{ + pats := r.pattern; + for(i:=0; i<len in.match; i++) + in.match[i] = (-1,-1); + # no rules at all implies success, so return if any fail + for(i=0; i<len pats; i++) + if(matchpattern(in, pats[i]) == 0) + return 0; + return 1; +} + +applyrule(in: ref Inmesg, r: ref Rule) +{ + acts := r.action; + for(i:=0; i<len acts; i++) + applypattern(in, acts[i]); + if(in.clearclick){ + al: list of ref Attr; + for(l:=in.attrs; l!=nil; l=tl l) + if((hd l).name != "click") + al = hd l :: al; + in.attrs = al; + in.msg.attr = plumbmsg->attrs2string(al); + if(in.set){ + in.text = macro(in, "$0"); + in.msg.data = array of byte in.text; + } + } +} + +matchpattern(in: ref Inmesg, p: ref Pattern): int +{ + msg := in.msg; + text: string; + case p.field { + "src" => text = msg.src; + "dst" => text = msg.dst; + "dir" => text = msg.dir; + "kind" => text = msg.kind; + "attr" => text = msg.attr; + "data" => text = in.text; + * => + sys->fprint(stderr, "plumb: don't recognize pattern field %s\n", p.field); + return 0; + } + if (verbose) + log(sys->sprint("'%s' %s '%s'\n", text, p.pred, p.arg)); + case p.pred { + "is" => + return text == p.arg; + "isfile" or "isdir" => + text = p.arg; + if(p.expand) + text = macro(in, text); + if(len text == 0) + return 0; + if(len in.msg.dir!=0 && text[0] != '/' && text[0]!='#') + text = in.msg.dir+"/"+text; + text = cleanname(text); + (ok, dir) := sys->stat(text); + if(ok < 0) + return 0; + if(p.pred=="isfile" && (dir.mode&Sys->DMDIR)==0){ + in._file = text; + return 1; + } + if(p.pred=="isdir" && (dir.mode&Sys->DMDIR)!=0){ + in._dir = text; + return 1; + } + return 0; + "matches" => + (clickspecified, val) := plumbmsg->lookup(in.attrs, "click"); + if(p.field != "data") + clickspecified = 0; + if(!clickspecified){ + # easy case. must match whole string + matches := regex->execute(p.regex, text); + if(matches == nil) + return 0; + (p0, p1) := matches[0]; + if(p0!=0 || p1!=len text) + return 0; + in.match = matches; + setvars(in, text); + return 1; + } + matches := clickmatch(p.regex, text, int val); + if(matches == nil) + return 0; + (p0, p1) := matches[0]; + # assumes all matches are in same sequence + if(in.match[0].p0 != -1) + return p0==in.match[0].p0 && p1==in.match[0].p1; + in.match = matches; + setvars(in, text); + in.clearclick = 1; + in.set = 1; + return 1; + "set" => + text = p.arg; + if(p.expand) + text = macro(in, text); + case p.field { + "src" => msg.src = text; + "dst" => msg.dst = text; + "dir" => msg.dir = text; + "kind" => msg.kind = text; + "attr" => msg.attr = text; + "data" => in.text = text; + msg.data = array of byte text; + msg.kind = "text"; + in.set = 0; + } + return 1; + * => + sys->fprint(stderr, "plumb: don't recognize pattern predicate %s\n", p.pred); + } + return 0; +} + +applypattern(in: ref Inmesg, p: ref Pattern): int +{ + if(p.field != "plumb"){ + sys->fprint(stderr, "plumb: don't recognize action field %s\n", p.field); + return 0; + } + case p.pred { + "to" or "alwaysstart" => + if(in.port >= 0) # already specified + return 1; + toport(in, p.arg); + "start" => + in.startup = p.arg; + in.args = p.extra; + * => + sys->fprint(stderr, "plumb: don't recognize action %s\n", p.pred); + } + return 1; +} + +toport(in: ref Inmesg, name: string): int +{ + for(i:=0; i<len output; i++) + if(name == output[i].name){ + in.msg.dst = name; + in.port = i; + return i; + } + in.port = -1; + sys->fprint(stderr, "plumb: unrecognized port %s\n", name); + return -1; +} + +# simple heuristic: look for leftmost match that reaches click position +clickmatch(re: ref Regex->Arena, text: string, click: int): array of Match +{ + for(i:=0; i<=click && i < len text; i++){ + matches := regex->executese(re, text, (i, -1), i == 0, 1); + if(matches == nil) + continue; + (p0, p1) := matches[0]; + + if(p0>=i && p1>=click) + return matches; + } + return nil; +} + +setvars(in: ref Inmesg, text: string) +{ + for(i:=0; i<len in.match && in.match[i].p0>=0; i++) + in._n[i] = text[in.match[i].p0:in.match[i].p1]; + for(; i<len in._n; i++) + in._n[i] = ""; +} + +macro(in: ref Inmesg, text: string): string +{ + word := ""; + i := 0; + j := 0; + for(;;){ + if(i == len text) + break; + if(text[i++] != '$') + continue; + if(i == len text) + break; + word += text[j:i-1]; + (res, skip) := dollar(in, text[i:]); + word += res; + i += skip; + j = i; + } + if(j < len text) + word += text[j:]; + return word; +} + +dollar(in: ref Inmesg, text: string): (string, int) +{ + if(text[0] == '$') + return ("$", 1); + if('0'<=text[0] && text[0]<='9') + return (in._n[text[0]-'0'], 1); + if(len text < 3) + return ("$", 0); + case text[0:3] { + "src" => return (in.msg.src, 3); + "dst" => return (in.msg.dst, 3); + "dir" => return (in._dir, 3); + } + if(len text< 4) + return ("$", 0); + case text[0:4] { + "attr" => return (in.msg.attr, 4); + "data" => return (in.text, 4); + "file" => return (in._file, 4); + "kind" => return (in.msg.kind, 4); + } + return ("$", 0); +} + +# compress ../ references and do other cleanups +cleanname(name: string): string +{ + # compress multiple slashes + n := len name; + for(i:=0; i<n-1; i++) + if(name[i]=='/' && name[i+1]=='/'){ + name = name[0:i]+name[i+1:]; + --i; + n--; + } + # eliminate ./ + for(i=0; i<n-1; i++) + if(name[i]=='.' && name[i+1]=='/' && (i==0 || name[i-1]=='/')){ + name = name[0:i]+name[i+2:]; + --i; + n -= 2; + } + found: int; + do{ + # compress xx/.. + found = 0; + for(i=1; i<=n-3; i++) + if(name[i:i+3] == "/.."){ + if(i==n-3 || name[i+3]=='/'){ + found = 1; + break; + } + } + if(found) + for(j:=i-1; j>=0; --j) + if(j==0 || name[j-1]=='/'){ + i += 3; # character beyond .. + if(i<n && name[i]=='/') + ++i; + name = name[0:j]+name[i:]; + n -= (i-j); + break; + } + }while(found); + # eliminate trailing . + if(n>=2 && name[n-2]=='/' && name[n-1]=='.') + --n; + if(n == 0) + return "."; + if(n != len name) + name = name[0:n]; + return name; +} + +log(s: string) +{ + if(len s == 0) + return; + if(s[len s-1] != '\n') + s[len s] = '\n'; + sys->print("plumb: %s", s); +} diff --git a/appl/cmd/prof.b b/appl/cmd/prof.b new file mode 100644 index 00000000..55c676c5 --- /dev/null +++ b/appl/cmd/prof.b @@ -0,0 +1,243 @@ +implement Prof; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; + arg: Arg; +include "profile.m"; + profile: Profile; +include "sh.m"; + +stderr: ref Sys->FD; + +Prof: module { + init: fn(nil: ref Draw->Context, argv: list of string); + init0: fn(nil: ref Draw->Context, argv: list of string): Profile->Prof; +}; + +ignored(s: string) +{ + sys->fprint(stderr, "prof: warning: %s ignored\n", s); +} + +exits(e: string) +{ + if(profile != nil) + profile->end(); + raise "fail:" + e; +} + +pfatal(s: string) +{ + sys->fprint(stderr, "prof: %s: %s\n", s, profile->lasterror()); + exits("error"); +} + +badmodule(p: string) +{ + sys->fprint(stderr, "prof: cannot load %s: %r\n", p); + exits("bad module"); +} + +usage(s: string) +{ + sys->fprint(stderr, "prof: %s\n", s); + sys->fprint(stderr, "usage: prof [-bflnv] [-m modname]... [-s rate] [cmd arg ...]"); + exits("usage"); +} + +init(ctxt: ref Draw->Context, argv: list of string) +{ + init0(ctxt, argv); +} + +init0(ctxt: ref Draw->Context, argv: list of string): Profile->Prof +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + arg = load Arg Arg->PATH; + if(arg == nil) + badmodule(Arg->PATH); + arg->init(argv); + profile = load Profile Profile->PATH; + if(profile == nil) + badmodule(Profile->PATH); + if(profile->init() < 0) + pfatal("cannot initialize profile device"); + + v := 0; + begin := 0; + rate := 0; + ep := 0; + wm := 0; + exec, mods: list of string; + while((c := arg->opt()) != 0){ + case c { + 'b' => begin = 1; + 'f' => v |= profile->FUNCTION; + 'l' => v |= profile->LINE; + 'n' => v |= profile->FULLHDR; + 'v' => v |= profile->VERBOSE; + 's' => + if((s := arg->arg()) == nil) + usage("missing sample rate"); + rate = int s; + if(rate <= 0) + usage("bad sample rate: '" + s + "'"); + 'm' => + if((s := arg->arg()) == nil) + usage("missing module name"); + mods = s :: mods; + 'e' => + ep = 1; + 'g' => + wm = 1; + * => + usage(sys->sprint("unknown option -%c", c)); + } + } + + exec = arg->argv(); + + if(begin && v != 0) + ignored("output format"); + if(begin && exec != nil) + begin = 0; + if(begin == 0 && exec == nil){ + if(mods != nil) + ignored("-m option"); + if(rate > 0) + ignored("-s option"); + mods = nil; + rate = 0; + } + + if(rate > 0) + profile->sample(rate); + for( ; mods != nil; mods = tl mods) + profile->profile(hd mods); + + if(begin){ + if(profile->start() < 0) + pfatal("cannot start profiling"); + exit; + } + r := 0; + if(exec != nil){ + if(ep) + profile->profile(disname(hd exec)); + if(profile->start() < 0) + pfatal("cannot start profiling"); + # r = run(ctxt, hd exec, exec); + wfd := openwait(sys->pctl(0, nil)); + ci := chan of int; + spawn execute(ctxt, hd exec, exec, ci); + epid := <- ci; + wait(wfd, epid); + } + if(profile->stop() < 0) + pfatal("cannot stop profiling"); + if(exec == nil || r >= 0){ + modl := profile->stats(); + if(modl.mods == nil) + pfatal("no profile information"); + if(wm){ + profile->end(); + return modl; + } + if(!(v&(profile->FUNCTION|profile->LINE))) + v |= profile->LINE; + if(profile->show(modl, v) < 0) + pfatal("cannot show profile"); + } + profile->end(); + return (nil, 0, nil); +} + +disname(cmd: string): string +{ + file := cmd; + if(len file<4 || file[len file-4:]!=".dis") + file += ".dis"; + if(exists(file)) + return file; + if(file[0]!='/' && file[0:2]!="./") + file = "/dis/"+file; + # if(exists(file)) + # return file; + return file; +} + +execute(ctxt: ref Draw->Context, cmd : string, argl : list of string, ci: chan of int) +{ + ci <-= sys->pctl(Sys->FORKNS|Sys->NEWFD|Sys->NEWPGRP, 0 :: 1 :: 2 :: stderr.fd :: nil); + file := cmd; + if(len file<4 || file[len file-4:]!=".dis") + file += ".dis"; + c := load Command file; + if(c == nil) { + err := sys->sprint("%r"); + if(file[0]!='/' && file[0:2]!="./"){ + c = load Command "/dis/"+file; + if(c == nil) + err = sys->sprint("%r"); + } + if(c == nil){ + sys->fprint(stderr, "prof: %s: %s\n", cmd, err); + return; + } + } + c->init(ctxt, argl); +} + +# run(ctxt: ref Draw->Context, cmd : string, argl : list of string): int +# { +# file := cmd; +# if(len file<4 || file[len file-4:]!=".dis") +# file += ".dis"; +# c := load Command file; +# if(c == nil) { +# err := sys->sprint("%r"); +# if(file[0]!='/' && file[0:2]!="./"){ +# c = load Command "/dis/"+file; +# if(c == nil) +# err = sys->sprint("%r"); +# } +# if(c == nil){ +# sys->fprint(stderr, "prof: %s: %s\n", cmd, err); +# return -1; +# } +# } +# c->init(ctxt, argl); +# return 0; +# } + +openwait(pid : int) : ref Sys->FD +{ + w := sys->sprint("#p/%d/wait", pid); + fd := sys->open(w, Sys->OREAD); + if (fd == nil) + pfatal("fd == nil in wait"); + return fd; +} + +wait(wfd : ref Sys->FD, wpid : int) +{ + n : int; + + buf := array[Sys->WAITLEN] of byte; + status := ""; + for(;;) { + if ((n = sys->read(wfd, buf, len buf)) < 0) + pfatal("bad read in wait"); + status = string buf[0:n]; + if (int status == wpid) + break; + } +} + +exists(f: string): int +{ + return sys->open(f, Sys->OREAD) != nil; +} diff --git a/appl/cmd/promptstring.b b/appl/cmd/promptstring.b new file mode 100644 index 00000000..2d648c55 --- /dev/null +++ b/appl/cmd/promptstring.b @@ -0,0 +1,66 @@ +RAWON_STR := "*"; + +RAWON : con 0; +RAWOFF : con 1; + +promptstring(prompt, def: string, mode: int): string +{ + if(mode == RAWON || def == nil || def == "") + sys->fprint(stdout, "%s: ", prompt); + else + sys->fprint(stdout, "%s [%s]: ", prompt, def); + (eof, resp) := readline(stdin, mode); + if(eof) + exit; + if(resp == "") + resp = def; + return resp; +} + +readline(fd: ref Sys->FD, mode: int): (int, string) +{ + i: int; + eof: int; + fdctl: ref Sys->FD; + + eof = 0; + buf := array[128] of byte; + tmp := array[128] of byte; + + if(mode == RAWON){ + fdctl = sys->open("/dev/consctl", sys->OWRITE); + if(fdctl == nil || sys->write(fdctl,array of byte "rawon",5) != 5){ + sys->fprint(stderr, "unable to change console mode"); + return (1,nil); + } + } + + for(sofar := 0; sofar < 128; sofar += i){ + i = sys->read(fd, tmp, 128 - sofar); + if(i <= 0){ + eof = 1; + break; + } + if(tmp[i-1] == byte '\n'){ + for(j := 0; j < i-1; j++){ + buf[sofar+j] = tmp[j]; + if(mode == RAWON && RAWON_STR != nil) + sys->write(stdout,array of byte RAWON_STR,1); + } + sofar += j; + if(mode == RAWON) + sys->write(stdout,array of byte "\n",1); + break; + } + else { + for(j := 0; j < i; j++){ + buf[sofar+j] = tmp[j]; + if(mode == RAWON && RAWON_STR != nil) + sys->write(stdout,array of byte RAWON_STR,1); + } + } + } + if(mode == RAWON) + sys->write(fdctl,array of byte "rawoff",6); + return (eof, string buf[0:sofar]); +} diff --git a/appl/cmd/ps.b b/appl/cmd/ps.b new file mode 100644 index 00000000..06c51a2f --- /dev/null +++ b/appl/cmd/ps.b @@ -0,0 +1,61 @@ +implement Ps; + +include "sys.m"; +include "draw.m"; + +FD, Dir: import Sys; +Context: import Draw; + +Ps: module +{ + init: fn(ctxt: ref Context, argv: list of string); +}; + +sys: Sys; +stderr: ref FD; + +init(nil: ref Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + + stderr = sys->fildes(2); + + sys->pctl(Sys->FORKNS, nil); + if(sys->chdir("/prog") < 0){ + sys->fprint(stderr, "ps: can't chdir to /prog: %r\n"); + raise "fail:no /prog"; + } + fd := sys->open(".", sys->OREAD); + if(fd == nil) { + sys->fprint(stderr, "ps: cannot open /prog: %r\n"); + raise "fail:no /prog"; + } + + for(;;) { + (n, d) := sys->dirread(fd); + if(n <= 0){ + if(n < 0) { + sys->fprint(stderr, "ps: error reading /prog: %r\n"); + raise "fail:error on /prog"; + } + break; + } + for(i := 0; i < n; i++) + if(d[i].name[0] >= '0' && d[i].name[0] <= '9') + ps(int d[i].name); + } +} + +ps(pid: int) +{ + proc := string pid+"/status"; + fd := sys->open(proc, sys->OREAD); + if(fd == nil) { # process must have died + # sys->fprint(stderr, "ps: /prog/%s: %r\n", proc); + return; + } + buf := array[128] of byte; + n := sys->read(fd, buf, len buf); + if(n > 0) + sys->print("%s\n", string buf[0:n]); +} diff --git a/appl/cmd/puttar.b b/appl/cmd/puttar.b new file mode 100644 index 00000000..de67105f --- /dev/null +++ b/appl/cmd/puttar.b @@ -0,0 +1,183 @@ +# read list of pathnames on stdin, write POSIX.1 tar on stdout +# Copyright(c)1996 Lucent Technologies. All Rights Reserved. +# 22 Dec 1996 ehg@bell-labs.com + +implement puttar; +include "sys.m"; + sys: Sys; + print, sprint, fprint: import sys; + stdout, stderr: ref sys->FD; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +puttar: module{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Warning(mess: string) +{ + fprint(stderr,"warning: puttar: %s: %r\n",mess); +} + +Error(mess: string){ + fprint(stderr,"puttar: %s: %r\n",mess); + exit; +} + +TBLOCK: con 512; # tar logical blocksize +NBLOCK: con 20; # blocking factor for efficient write +tarbuf := array[NBLOCK*TBLOCK] of byte; # for output +nblock := 0; # how many blocks of data are in tarbuf + +flushblocks(){ + if(nblock<=0) return; + if(nblock<NBLOCK){ + for(i:=(nblock+1)*TBLOCK;i<NBLOCK*TBLOCK;i++) + tarbuf[i] = byte 0; + } + i := sys->write(stdout,tarbuf,NBLOCK*TBLOCK); + if(i!=NBLOCK*TBLOCK) + Error("write error"); + nblock = 0; +} + +putblock(data:array of byte){ + # all writes are done through here, so we can guarantee + # 10kbyte blocks if writing to tape device + if(len data!=TBLOCK) + Error("putblock wants TBLOCK chunks"); + tarbuf[nblock*TBLOCK:] = data; + nblock++; + if(nblock>=NBLOCK) + flushblocks(); +} + +packname(hdr:array of byte, name:string){ + utf := array of byte name; + n := len utf; + if(n<=100){ + hdr[0:] = utf; + return; + } + for(i:=n-101; i<n && int utf[i] != '/'; i++){} + if(i==n) Error(sprint("%s > 100 bytes",name)); + if(i>155) Error(sprint("%s too long\n",name)); + hdr[0:] = utf[i+1:n]; + hdr[345:] = utf[0:i]; # tar supplies implicit slash +} + +octal(width:int, val:int):array of byte{ + octal := array of byte "01234567"; + a := array[width] of byte; + for(i:=width-1; i>=0; i--){ + a[i] = octal[val&7]; + val >>= 3; + } + return a; +} + +chksum(hdr: array of byte):int{ + sum := 0; + for(i:=0; i<len hdr; i++) + sum += int hdr[i]; + return sum; +} + +hdr, zeros, ibuf : array of byte; + +tar(file : string) +{ + ifile: ref sys->FD; + + (rc,stat) := sys->stat(file); + if(rc<0){ Warning(sprint("cannot stat %s",file)); return; }; + ifile = sys->open(file,sys->OREAD); + if(ifile==nil) Error(sprint("cannot open %s",file)); + hdr[0:] = zeros; + packname(hdr,file); + hdr[100:] = octal(7,stat.mode&8r777); + hdr[108:] = octal(7,1); + hdr[116:] = octal(7,1); + hdr[124:] = octal(11,int stat.length); + hdr[136:] = octal(11,stat.mtime); + hdr[148:] = array of byte " "; # for chksum + hdr[156] = byte '0'; + if(stat.mode&Sys->DMDIR) hdr[156] = byte '5'; + hdr[257:] = array of byte "ustar"; + hdr[263:] = array of byte "00"; + hdr[265:] = array of byte stat.uid; # assumes len uid<=32 + hdr[297:] = array of byte stat.gid; + hdr[329:] = octal(8,stat.dev); + hdr[337:] = octal(8,int stat.qid.path); + hdr[148:] = octal(7,chksum(hdr)); + hdr[155] = byte 0; + putblock(hdr); + for(bytes := int stat.length; bytes>0;){ + n := len ibuf; if(n>bytes) n = bytes; # min + if(sys->read(ifile,ibuf,n)!=n) + Error(sprint("read error on %s",file)); + nb := (n+TBLOCK-1)/TBLOCK; + fill := nb*TBLOCK; + for(i:=n; i<fill; i++) ibuf[i] = byte 0; + for(i=0; i<nb; i++) + putblock(ibuf[i*TBLOCK:(i+1)*TBLOCK]); + bytes -= n; + } + ifile = nil; +} + +rtar(file : string) +{ + tar(file); + # recurse if directory + (ok, dir) := sys->stat(file); + if (ok < 0){ + Warning(sprint("cannot stat %s", file)); + return; + } + if (dir.mode & Sys->DMDIR) { + fd := sys->open(file, sys->OREAD); + if (fd == nil) + Error(sprint("cannot open %s", file)); + for (;;) { + (n, d) := sys->dirread(fd); + if (n <= 0) + break; + for (i := 0; i < n; i++) { + if (file[len file - 1] == '/') + rtar(file + d[i].name); + else + rtar(file + "/" + d[i].name); + } + } + } +} + +init(nil: ref Draw->Context, args: list of string){ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + hdr = array[TBLOCK] of byte; + zeros = array[TBLOCK] of {* => byte 0}; + ibuf = array[len tarbuf] of byte; + + if (tl args == nil) { + stdin := bufio->fopen(sys->fildes(0),bufio->OREAD); + if(stdin==nil) Error("can't fopen stdin"); + while((file := stdin.gets('\n'))!=nil){ + if(file[len file-1]=='\n') file = file[0:len file-1]; + tar(file); + } + } + else { + for (args = tl args; args != nil; args = tl args) + rtar(hd args); + } + putblock(zeros); +# putblock(zeros); # XXX is this necessary? + flushblocks(); +} diff --git a/appl/cmd/pwd.b b/appl/cmd/pwd.b new file mode 100644 index 00000000..28fd4d24 --- /dev/null +++ b/appl/cmd/pwd.b @@ -0,0 +1,28 @@ +implement Pwd; + +include "sys.m"; +include "draw.m"; +include "workdir.m"; + +Pwd: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys := load Sys Sys->PATH; + stderr := sys->fildes(2); + gwd := load Workdir Workdir->PATH; + if (gwd == nil) { + sys->fprint(stderr, "pwd: cannot load %s: %r\n", Workdir->PATH); + raise "fail:bad module"; + } + + wd := gwd->init(); + if(wd == nil) { + sys->fprint(stderr, "pwd: %r\n"); + raise "fail:error"; + } + sys->print("%s\n", wd); +} diff --git a/appl/cmd/ramfile.b b/appl/cmd/ramfile.b new file mode 100644 index 00000000..677bf18b --- /dev/null +++ b/appl/cmd/ramfile.b @@ -0,0 +1,97 @@ +implement Ramfile; +include "sys.m"; + sys: Sys; +include "draw.m"; + +# synthesise a file that can be treated just like any other +# file. limitations of file2chan mean that it's not possible +# to know when an open should have truncated the file, so +# we do the only possible thing, and truncate it when we get +# a write at offset 0. thus it can be edited with an editor, +# but can't be used to store seekable, writable data records +# (unless the first record is never written) + +# there should be some way to determine when the file should +# go away - file2chan sends a nil channel whenever the file +# is closed by anyone, which is not good enough. + +stderr: ref Sys->FD; + +Ramfile: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + if (len argv < 2 || len argv > 3) { + sys->fprint(stderr, "usage: ramfile path [data]\n"); + return; + } + path := hd tl argv; + (dir, f) := pathsplit(path); + + if (sys->bind("#s", dir, Sys->MBEFORE|Sys->MCREATE) == -1) { + sys->fprint(stderr, "ramfile: %r\n"); + return; + } + fio := sys->file2chan(dir, f); + if (fio == nil) { + sys->fprint(stderr, "ramfile: file2chan failed: %r\n"); + return; + } + data := array[0] of byte; + if (tl tl argv != nil) + data = array of byte hd tl tl argv; + + spawn server(fio, data); + data = nil; +} + +server(fio: ref Sys->FileIO, data: array of byte) +{ + for (;;) alt { + (offset, count, fid, rc) := <-fio.read => + if (rc != nil) { + if (offset > len data) + rc <-= (nil, nil); + else { + end := offset + count; + if (end > len data) + end = len data; + rc <-= (data[offset:end], nil); + } + } + (offset, d, fid, wc) := <-fio.write => + if (wc != nil) { + if (offset == 0) + data = array[0] of byte; + end := offset + len d; + if (end > len data) { + ndata := array[end] of byte; + ndata[0:] = data; + data = ndata; + ndata = nil; + } + data[offset:] = d; + wc <-= (len d, nil); + } + } +} + +pathsplit(p: string): (string, string) +{ + for (i := len p - 1; i >= 0; i--) + if (p[i] != '/') + break; + if (i < 0) + return (p, nil); + p = p[0:i+1]; + for (i = len p - 1; i >=0; i--) + if (p[i] == '/') + break; + if (i < 0) + return (".", p); + return (p[0:i+1], p[i+1:]); +} diff --git a/appl/cmd/randpass.b b/appl/cmd/randpass.b new file mode 100644 index 00000000..074b21ac --- /dev/null +++ b/appl/cmd/randpass.b @@ -0,0 +1,45 @@ +implement Randpass; + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; + +include "draw.m"; + +include "keyring.m"; + kr : Keyring; + IPint: import kr; + +Randpass: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + + if(args != nil) + args = tl args; + pwlen := 16; + if(args != nil){ + if(!isnumeric(hd args) || (pwlen = int hd args) <= 8 || pwlen > 256){ + sys->fprint(sys->fildes(2), "Usage: randpass [password-length(<256, default=16)]\n"); + raise "fail:usage"; + } + } + + rbig := IPint.random(pwlen*8, pwlen*16); + rstr := rbig.iptob64(); + + sys->print("%s\n", rstr[0:pwlen]); +} + +isnumeric(s: string): int +{ + for(i := 0; i < len s; i++) + if(!(s[i]>='0' && s[i]<='9')) + return 0; + return i > 0; +} diff --git a/appl/cmd/raw2iaf.b b/appl/cmd/raw2iaf.b new file mode 100644 index 00000000..11c29e00 --- /dev/null +++ b/appl/cmd/raw2iaf.b @@ -0,0 +1,122 @@ +implement Raw2Iaf; + +include "sys.m"; +include "draw.m"; + +sys: Sys; +FD: import sys; +stderr: ref FD; + +rateK: con "rate"; +rateV: string = "44100"; +chanK: con "chans"; +chanV: string = "2"; +bitsK: con "bits"; +bitsV: string = "16"; +encK: con "enc"; +encV: string = "pcm"; + +progV: string; +inV: string = nil; +outV: string = nil; +inf: ref FD; +outf: ref FD; + +pad := array[] of { " ", " ", "", " " }; + +Raw2Iaf: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(stderr, "usage: %s -8124 -ms -bw -aup -o out in\n", progV); + exit; +} + +options(s: string) +{ + for (i := 0; i < len s; i++) { + case s[i] { + '8' => rateV = "8000"; + '1' => rateV = "11025"; + '2' => rateV = "22050"; + '4' => rateV = "44100"; + 'm' => chanV = "1"; + 's' => chanV = "2"; + 'b' => bitsV = "8"; + 'w' => bitsV = "16"; + 'a' => encV = "alaw"; + 'u' => encV = "ulaw"; + 'p' => encV = "pcm"; + * => usage(); + } + } +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + progV = hd argv; + v := tl argv; + + while (v != nil) { + a := hd v; + v = tl v; + if (len a == 0) + continue; + if (a[0] == '-') { + if (len a == 1) { + if (inV == nil) + inV = "-"; + else + usage(); + } + else if (a[1] == 'o') { + if (outV != nil) + usage(); + if (len a > 2) + outV = a[2:len a]; + else if (v == nil) + usage(); + else { + outV = hd v; + v = tl v; + } + } + else + options(a[1:len a]); + } + else if (inV == nil) + inV = a; + else + usage(); + } + if (inV == nil || inV == "-") + inf = sys->fildes(0); + else { + inf = sys->open(inV, Sys->OREAD); + if (inf == nil) { + sys->fprint(stderr, "%s: could not open %s: %r\n", progV, inV); + exit; + } + } + if (outV == nil || outV == "-") + outf = sys->fildes(1); + else { + outf = sys->create(outV, Sys->OWRITE, 8r666); + if (outf == nil) { + sys->fprint(stderr, "%s: could not create %s: %r\n", progV, outV); + exit; + } + } + s := rateK + "\t" + rateV + "\n" + + chanK + "\t" + chanV + "\n" + + bitsK + "\t" + bitsV + "\n" + + encK + "\t" + encV; + sys->fprint(outf, "%s%s\n\n", s, pad[len s % 4]); + if (sys->stream(inf, outf, Sys->ATOMICIO) < 0) + sys->fprint(stderr, "%s: data copy error: %r\n", progV); +} diff --git a/appl/cmd/rawdbfs.b b/appl/cmd/rawdbfs.b new file mode 100644 index 00000000..cb2daf2c --- /dev/null +++ b/appl/cmd/rawdbfs.b @@ -0,0 +1,813 @@ +implement Dbfs; + +# +# Copyright © 1999, 2002 Vita Nuova Limited. All rights reserved. +# + +# Enhanced to include record locking, index field generation and update notification + +# TO DO: +# make writing & reading more like real files; don't ignore offsets. +# open with OTRUNC should work. +# provide some way of compacting a dbfs file. + +include "sys.m"; + sys: Sys; + Qid: import Sys; + +include "draw.m"; + +include "arg.m"; + +include "styx.m"; + styx: Styx; + Rmsg, Tmsg: import styx; + +include "styxservers.m"; + styxservers: Styxservers; + Styxserver, Fid, Navigator, Navop: import styxservers; + Enotfound, Eperm, Ebadfid, Ebadarg: import styxservers; + +include "string.m"; + str: String; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "sh.m"; + sh: Sh; + +Record: adt { + id: int; # file number in directory (if block is allocated) + offset: int; # start of data + count: int; # length of block (excluding header) + datalen: int; # length of data (-1 if block is free) + vers: int; # version + + new: fn(offset: int, length: int): ref Record; + qid: fn(r: self ref Record): Sys->Qid; +}; + +# Record lock +Lock: adt { + qpath: big; + fid: int; +}; + +HEADLEN: con 10; +MINSIZE: con 20; + +Database: adt { + file: ref Iobuf; + records: array of ref Record; + maxid: int; + locking: int; + locklist: list of Lock; + indexing: int; + stats: int; + index: int; + s_reads: int; + s_writes: int; + s_creates: int; + s_removes: int; + updcmd: string; + vers: int; + + build: fn(f: ref Iobuf, locking, indexing: int, stats: int, updcmd: string): (ref Database, string); + write: fn(db: self ref Database, n: int, data: array of byte): int; + read: fn(db: self ref Database, n: int): array of byte; + remove: fn(db: self ref Database, n: int); + create: fn(db: self ref Database, data: array of byte): ref Record; + updated: fn(db: self ref Database); + lock: fn(db: self ref Database, c: ref Styxservers->Fid): int; + unlock: fn(db: self ref Database, c: ref Styxservers->Fid); + ownlock: fn(db: self ref Database, c: ref Styxservers->Fid): int; +}; + +Dbfs: module +{ + init: fn(ctxt: ref Draw->Context, nil: list of string); +}; + +Qdir, Qnew, Qdata, Qindex, Qstats: con iota; + +stderr: ref Sys->FD; +database: ref Database; +context: ref Draw->Context; +user: string; +Eremoved: con "file removed"; +Egreg: con "thermal problems"; +Elocked: con "open/create -- file is locked"; + +usage() +{ + sys->fprint(stderr, "Usage: dbfs [-abcelrxD][-u cmd] file mountpoint\n"); + raise "fail:usage"; +} + +nomod(s: string) +{ + sys->fprint(stderr, "dbfs: can't load %s: %r\n", s); + raise "fail:load"; +} + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + context = ctxt; + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + styx = load Styx Styx->PATH; + if(styx == nil) + nomod(Styx->PATH); + styx->init(); + styxservers = load Styxservers Styxservers->PATH; + if(styxservers == nil) + nomod(Styxservers->PATH); + styxservers->init(styx); + str = load String String->PATH; + if(str == nil) + nomod(String->PATH); + bufio = load Bufio Bufio->PATH; + if(bufio == nil) + nomod(Bufio->PATH); + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + arg->init(args); + flags := Sys->MREPL; + copt := 0; + empty := 0; + locking := 0; + stats := 0; + indexing := 0; + updcmd := ""; + while((o := arg->opt()) != 0) + case o { + 'a' => flags = Sys->MAFTER; + 'b' => flags = Sys->MBEFORE; + 'r' => flags = Sys->MREPL; + 'c' => copt = 1; + 'e' => empty = 1; + 'l' => locking = 1; + 'u' => updcmd = arg->arg(); + if(updcmd == nil) + usage(); + 'x' => indexing = 1; + stats = 1; + 'D' => styxservers->traceset(1); + * => usage(); + } + args = arg->argv(); + arg = nil; + + if(len args != 2) + usage(); + if(copt) + flags |= Sys->MCREATE; + file := hd args; + args = tl args; + mountpt := hd args; + + if(updcmd != nil){ + sh = load Sh Sh->PATH; + if(sh == nil) + nomod(Sh->PATH); + } + + df := bufio->open(file, Sys->ORDWR); + if(df == nil && empty){ + (rc, d) := sys->stat(file); + if(rc < 0) + df = bufio->create(file, Sys->ORDWR, 8r600); + } + if(df == nil){ + sys->fprint(stderr, "dbfs: can't open %s: %r\n", file); + raise "fail:cannot open file"; + } + (db, err) := Database.build(df, locking, indexing, stats, updcmd); + if(db == nil){ + sys->fprint(stderr, "dbfs: can't read %s: %s\n", file, err); + raise "fail:cannot read db"; + } + database = db; + + sys->pctl(Sys->FORKFD, nil); + + user = rf("/dev/user"); + if(user == nil) + user = "inferno"; + + fds := array[2] of ref Sys->FD; + if(sys->pipe(fds) < 0){ + sys->fprint(stderr, "dbfs: can't create pipe: %r\n"); + raise "fail:pipe"; + } + + navops := chan of ref Navop; + spawn navigator(navops); + + (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big Qdir); + fds[0] = nil; + + pidc := chan of int; + spawn serveloop(tchan, srv, pidc, navops); + <-pidc; + + if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) { + sys->fprint(stderr, "dbfs: mount failed: %r\n"); + raise "fail:bad mount"; + } +} + +rf(f: string): string +{ + fd := sys->open(f, Sys->OREAD); + if(fd == nil) + return nil; + b := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, b, len b); + if(n < 0) + return nil; + return string b[0:n]; +} + +serveloop(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop) +{ + pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, stderr.fd::1::2::database.file.fd.fd::srv.fd.fd::nil); +# stderr = sys->fildes(stderr.fd); + database.file.fd = sys->fildes(database.file.fd.fd); +Serve: + while((gm := <-tchan) != nil){ + pick m := gm { + Readerror => + sys->fprint(stderr, "dbfs: fatal read error: %s\n", m.error); + break Serve; + Open => + c := srv.getfid(m.fid); + open(srv, m); + Read => + (c, err) := srv.canread(m); + if(c == nil) { + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + if(c.qtype & Sys->QTDIR){ + srv.read(m); + break; + } + case TYPE(c.path) { + Qindex => + if(database.index < 0) { + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + if (m.offset > big 0) { + srv.reply(ref Rmsg.Read(m.tag, nil)); + break; + } + reply := array of byte string ++database.index; + if(m.count < len reply) + reply = reply[:m.count]; + srv.reply(ref Rmsg.Read(m.tag, reply)); + Qstats => + if (m.offset > big 0) { + srv.reply(ref Rmsg.Read(m.tag, nil)); + break; + } + reply := array of byte sys->sprint("%d %d %d %d", database.s_reads, database.s_writes, + database.s_creates, database.s_removes); + if(m.count < len reply) reply = reply[:m.count]; + srv.reply(ref Rmsg.Read(m.tag, reply)); + Qdata => + recno := id2recno(FILENO(c.path)); + if(recno == -1) + srv.reply(ref Rmsg.Error(m.tag, Eremoved)); + else + srv.reply(styxservers->readbytes(m, database.read(recno))); + * => + srv.reply(ref Rmsg.Error(m.tag, Egreg)); + } + Write => + (c, err) := srv.canwrite(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + if(!database.ownlock(c)) { + # shouldn't happen: open checks + srv.reply(ref Rmsg.Error(m.tag, Elocked)); + break; + } + case TYPE(c.path) { + Qindex => + if(database.index >= 0) { + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + break; + } + database.index = int string m.data; + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + Qdata => + recno := id2recno(FILENO(c.path)); + if(recno == -1) + srv.reply(ref Rmsg.Error(m.tag, "phase error")); + else { + changed := 1; + if(database.updcmd != nil){ + oldrec := database.read(recno); + changed = !eqbytes(m.data, oldrec); + } + if(changed && database.write(recno, m.data) == -1){ + srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); + break; + } + if(changed) + database.updated(); # run the command before reply + srv.reply(ref Rmsg.Write(m.tag, len m.data)); + } + * => + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + } + Clunk => + c := srv.getfid(m.fid); + if(c != nil) + database.unlock(c); + srv.clunk(m); + Remove => + c := srv.getfid(m.fid); + database.unlock(c); + if(c == nil || c.qtype & Sys->QTDIR || TYPE(c.path) != Qdata){ + # let it diagnose all the errors + srv.remove(m); + break; + } + recno := id2recno(FILENO(c.path)); + if(recno == -1) + srv.reply(ref Rmsg.Error(m.tag, "phase error")); + else { + database.remove(recno); + database.updated(); + srv.reply(ref Rmsg.Remove(m.tag)); + } + srv.delfid(c); + * => + srv.default(gm); + } + } + navops <-= nil; # shut down navigator +} + +eqbytes(a, b: array of byte): int +{ + if(len a != len b) + return 0; + for(i := 0; i < len a; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +id2recno(id: int): int +{ + recs := database.records; + for(i := 0; i < len recs; i++) + if(recs[i].datalen >= 0 && recs[i].id == id) + return i; + return -1; +} + +open(srv: ref Styxserver, m: ref Tmsg.Open): ref Fid +{ + (c, mode, d, err) := srv.canopen(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + return nil; + } + if(TYPE(c.path) == Qnew){ + # generate new file + if(c.uname != user){ + srv.reply(ref Rmsg.Error(m.tag, Eperm)); + return nil; + } + r := database.create(array[0] of byte); + if(r == nil) { + srv.reply(ref Rmsg.Error(m.tag, "create -- i/o error")); + return nil; + } + (d, nil) = dirgen(QPATH(r.id, Qdata)); + } + if(m.mode & Sys->OTRUNC) { + # TO DO + } + c.open(mode, d.qid); + if(database.locking && TYPE(c.path) == Qdata && (m.mode & (Sys->OWRITE|Sys->ORDWR))) { + if(!database.lock(c)) { + srv.reply(ref Rmsg.Error(m.tag, Elocked)); + return nil; + } + } + srv.reply(ref Rmsg.Open(m.tag, d.qid, srv.iounit())); + return c; +} + +dirslot(n: int): int +{ + for(i := 0; i < len database.records; i++){ + r := database.records[i]; + if(r != nil && r.datalen >= 0){ + if(n == 0) + return i; + n--; + } + } + return -1; +} + +dir(qid: Sys->Qid, name: string, length: big, uid: string, perm: int): ref Sys->Dir +{ + d := ref sys->zerodir; + d.qid = qid; + if(qid.qtype & Sys->QTDIR) + perm |= Sys->DMDIR; + d.mode = perm; + d.name = name; + d.uid = uid; + d.gid = uid; + d.length = length; + return d; +} + +dirgen(p: big): (ref Sys->Dir, string) +{ + case TYPE(p) { + Qdir => + return (dir(Qid(QPATH(0, Qdir),database.vers,Sys->QTDIR), "/", big 0, user, 8r700), nil); + Qnew => + return (dir(Qid(QPATH(0, Qnew),0,Sys->QTFILE), "new", big 0, user, 8r600), nil); + Qindex => + return (dir(Qid(QPATH(0, Qindex),0,Sys->QTFILE), "index", big 0, user, 8r600), nil); + Qstats => + return (dir(Qid(QPATH(0, Qstats),0,Sys->QTFILE), "stats", big 0, user, 8r400), nil); + * => + n := id2recno(FILENO(p)); + if(n < 0 || n >= len database.records) + return (nil, nil); + r := database.records[n]; + if(r == nil || r.datalen < 0) + return (nil, Enotfound); + l := r.datalen; + if(l < 0) + l = 0; + return (dir(r.qid(), sys->sprint("%d", r.id), big l, user, 8r600), nil); + } +} + +navigator(navops: chan of ref Navop) +{ + while((m := <-navops) != nil){ + pick n := m { + Stat => + n.reply <-= dirgen(n.path); + Walk => + if(int n.path != Qdir){ + n.reply <-= (nil, "not a directory"); + break; + } + case n.name { + ".." => + ; # nop + "new" => + n.path = QPATH(0, Qnew); + "stats" => + if(!database.indexing){ + n.reply <-= (nil, Enotfound); + continue; + } + n.path = QPATH(0, Qstats); + "index" => + if(!database.indexing){ + n.reply <-= (nil, Enotfound); + continue; + } + n.path = QPATH(0, Qindex); + * => + if(len n.name < 1 || !(n.name[0]>='0' && n.name[0]<='9')){ # weak test for now + n.reply <-= (nil, Enotfound); + continue; + } + n.path = QPATH(int n.name, Qdata); + } + n.reply <-= dirgen(n.path); + Readdir => + if(int m.path != Qdir){ + n.reply <-= (nil, "not a directory"); + break; + } + o := 1; # Qnew; + stats := -1; + indexing := -1; + if(database.indexing) + indexing = o++; + if(database.stats) + stats = o++; + Dread: + for(i := n.offset; --n.count >= 0; i++){ + case i { + 0 => + n.reply <-= dirgen(QPATH(0,Qnew)); + * => + if(i == indexing) + n.reply <-= dirgen(QPATH(0, Qindex)); + if(i == stats) + n.reply <-= dirgen(QPATH(0, Qstats)); + j := dirslot(i-o); # n² but fine if the file will be small + if(j < 0) + break Dread; + r := database.records[j]; + n.reply <-= dirgen(QPATH(r.id,Qdata)); + } + } + n.reply <-= (nil, nil); + } + } +} + +QPATH(w, q: int): big +{ + return big ((w<<8)|q); +} + +TYPE(path: big): int +{ + return int path & 16rFF; +} + +FILENO(path: big): int +{ + return (int path >> 8) & 16rFFFFFF; +} + +Database.build(f: ref Iobuf, locking, indexing, stats: int, updcmd: string): (ref Database, string) +{ + rl: list of ref Record; + offset := 0; + maxid := 0; + for(;;) { + d := array[HEADLEN] of byte; + n := f.read(d, HEADLEN); + if(n < HEADLEN) + break; + orig := s := string d; + if(len s != HEADLEN) + return (nil, "found bad header"); + r := ref Record; + r.vers = 0; + (r.count, s) = str->toint(s, 10); + (r.datalen, s) = str->toint(s, 10); + if(s != "\n") + return (nil, sys->sprint("found bad header '%s'\n", orig)); + r.offset = offset + HEADLEN; + offset += r.count + HEADLEN; + f.seek(big offset, Bufio->SEEKSTART); + r.id = maxid++; + rl = r :: rl; + } + db := ref Database(f, array[maxid] of ref Record, maxid, locking, nil, indexing, stats, -1, 0, 0, 0, 0, updcmd, 0); + for(i := len db.records - 1; i >= 0; i--) { + db.records[i] = hd rl; + rl = tl rl; + } + return (db, nil); +} + +Database.write(db: self ref Database, recno: int, data: array of byte): int +{ + db.s_writes++; + r := db.records[recno]; + r.vers++; + if(len data <= r.count) { + if(r.count - len data >= HEADLEN + MINSIZE) + splitrec(db, recno, len data); + writerec(db, recno, data); + db.file.flush(); + } else { + freerec(db, recno); + n := allocrec(db, len data); + if(n == -1) + return -1; # BUG: we lose the original data in this case. + db.records[n].id = r.id; + db.write(n, data); + } + return 0; +} + +Database.create(db: self ref Database, data: array of byte): ref Record +{ + db.s_creates++; + db.vers++; + n := allocrec(db, len data); + if(n < 0) + return nil; + if(db.write(n, data) < 0){ + freerec(db, n); + return nil; + } + r := db.records[n]; + r.id = db.maxid++; + return r; +} + +Database.read(db: self ref Database, recno: int): array of byte +{ + db.s_reads++; + r := db.records[recno]; + if(r.datalen <= 0) + return nil; + db.file.seek(big r.offset, Bufio->SEEKSTART); + d := array[r.datalen] of byte; + n := db.file.read(d, r.datalen); + if(n != r.datalen) { + sys->fprint(stderr, "dbfs: only read %d bytes (expected %d)\n", n, r.datalen); + return nil; + } + return d; +} + +Database.remove(db: self ref Database, recno: int) +{ + db.s_removes++; + db.vers++; + freerec(db, recno); + db.file.flush(); +} + +Database.updated(db: self ref Database) +{ + if(db.updcmd != nil) + sh->system(context, db.updcmd); +} + +# Locking - try to lock a record + +Database.lock(db: self ref Database, c: ref Styxservers->Fid): int +{ + if(TYPE(c.path) != Qdata || !db.locking) + return 1; + for(ll := db.locklist; ll != nil; ll = tl ll) { + lock := hd ll; + if(lock.qpath == c.path) + return lock.fid == c.fid; + } + db.locklist = (c.path, c.fid) :: db.locklist; + return 1; +} + + +# Locking - unlock a record + +Database.unlock(db: self ref Database, c: ref Styxservers->Fid) +{ + if(TYPE(c.path) != Qdata || !db.locking) + return; + ll := db.locklist; + db.locklist = nil; + for(; ll != nil; ll = tl ll){ + lock := hd ll; + if(lock.qpath == c.path && lock.fid == c.fid){ + # not replaced on list + }else + db.locklist = hd ll :: db.locklist; + } +} + + +# Locking - check if Fid c has the lock on its record + +Database.ownlock(db: self ref Database, c: ref Styxservers->Fid): int +{ + if(TYPE(c.path) != Qdata || !db.locking) + return 1; + for(ll := db.locklist; ll != nil; ll = tl ll) { + lock := hd ll; + if(lock.qpath == c.path) + return lock.fid == c.fid; + } + return 0; +} + +Record.new(offset: int, length: int): ref Record +{ + return ref Record(-1, offset, length, -1, 0); +} + +Record.qid(r: self ref Record): Qid +{ + return Qid(QPATH(r.id,Qdata), r.vers, Sys->QTFILE); +} + +freerec(db: ref Database, recno: int) +{ + nr := len db.records; + db.records[recno].datalen = -1; + for(i := recno; i >= 0; i--) + if(db.records[i].datalen != -1) + break; + f := i + 1; + nb := 0; + for(i = f; i < nr; i++) { + if(db.records[i].datalen != -1) + break; + nb += db.records[i].count + HEADLEN; + } + db.records[f].count = nb - HEADLEN; + writeheader(db.file, db.records[f]); + # could blank out freed entries here if we cared. + if(i < nr && f < i) + db.records[f+1:] = db.records[i:]; + db.records = db.records[0:nr - (i - f - 1)]; +} + +splitrec(db: ref Database, recno: int, pos: int) +{ + a := array[len db.records + 1] of ref Record; + a[0:] = db.records[0:recno+1]; + if(recno < len db.records - 1) + a[recno+2:] = db.records[recno+1:]; + db.records = a; + r := a[recno]; + a[recno+1] = Record.new(r.offset + pos + HEADLEN, r.count - HEADLEN - pos); + r.count = pos; + writeheader(db.file, a[recno+1]); +} + +writerec(db: ref Database, recno: int, data: array of byte): int +{ + db.records[recno].datalen = len data; + if(writeheader(db.file, db.records[recno]) == -1) + return -1; + if(db.file.write(data, len data) == Bufio->ERROR) + return -1; + return 0; +} + +writeheader(f: ref Iobuf, r: ref Record): int +{ + f.seek(big r.offset - big HEADLEN, Bufio->SEEKSTART); + if(f.puts(sys->sprint("%4d %4d\n", r.count, r.datalen)) == Bufio->ERROR) { + sys->fprint(stderr, "dbfs: error writing header (id %d, offset %d, count %d, datalen %d): %r\n", + r.id, r.offset, r.count, r.datalen); + return -1; + } + return 0; +} + +# finds or creates a record of the requisite size; does not mark it as allocated. +allocrec(db: ref Database, nb: int): int +{ + if(nb < MINSIZE) + nb = MINSIZE; + best := -1; + n := -1; + for(i := 0; i < len db.records; i++) { + r := db.records[i]; + if(r.datalen == -1) { + avail := r.count - nb; + if(avail >= 0 && (n == -1 || avail < best)) { + best = avail; + n = i; + } + } + } + if(n != -1) + return n; + nr := len db.records; + a := array[nr + 1] of ref Record; + a[0:] = db.records[0:]; + offset := 0; + if(nr > 0) + offset = a[nr-1].offset + a[nr-1].count; + db.file.seek(big offset, Bufio->SEEKSTART); + if(db.file.write(array[nb + HEADLEN] of {* => byte(0)}, nb + HEADLEN) == Bufio->ERROR + || db.file.flush() == Bufio->ERROR) { + sys->fprint(stderr, "dbfs: write of new entry failed: %r\n"); + return -1; + } + a[nr] = Record.new(offset + HEADLEN, nb); + db.records = a; + return nr; +} + +now(fd: ref Sys->FD): int +{ + if(fd == nil) + return 0; + buf := array[128] of byte; + sys->seek(fd, big 0, 0); + n := sys->read(fd, buf, len buf); + if(n < 0) + return 0; + t := (big string buf[0:n]) / big 1000000; + return int t; +} diff --git a/appl/cmd/rcmd.b b/appl/cmd/rcmd.b new file mode 100644 index 00000000..43fe9078 --- /dev/null +++ b/appl/cmd/rcmd.b @@ -0,0 +1,170 @@ +implement Rcmd; + +include "sys.m"; +include "draw.m"; +include "arg.m"; +include "keyring.m"; +include "security.m"; + +Rcmd: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +DEFAULTALG := "none"; +sys: Sys; +auth: Auth; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + arg := load Arg Arg->PATH; + if(arg == nil) + badmodule(Arg->PATH); + arg->init(argv); + alg: string; + doauth := 1; + exportpath := "/"; + keyfile: string; + arg->setusage("rcmd [-A] [-f keyfile] [-a alg] [-e exportpath] tcp!mach cmd"); + while((o := arg->opt()) != 0) + case o { + 'a' => + alg = arg->earg(); + 'A' => + doauth = 0; + 'e' => + exportpath = arg->earg(); + (n, nil) := sys->stat(exportpath); + if (n == -1 || exportpath == nil) + arg->usage(); + 'f' => + keyfile = arg->earg(); + if (! (keyfile[0] == '/' || (len keyfile > 2 && keyfile[0:2] == "./"))) + keyfile = "/usr/" + user() + "/keyring/" + keyfile; + * => + arg->usage(); + } + + argv = arg->argv(); + if(argv == nil) + arg->usage(); + arg = nil; + + if (doauth && alg == nil) + alg = DEFAULTALG; + + addr := hd argv; + argv = tl argv; + + args := ""; + while(argv != nil){ + args += " " + hd argv; + argv = tl argv; + } + if(args == "") + args = "sh"; + + kr: Keyring; + au: Auth; + if (doauth) { + kr = load Keyring Keyring->PATH; + if(kr == nil) + badmodule(Keyring->PATH); + au = load Auth Auth->PATH; + if(au == nil) + badmodule(Auth->PATH); + if (keyfile == nil) + keyfile = "/usr/" + user() + "/keyring/default"; + } + + (ok, c) := sys->dial(netmkaddr(addr, "tcp", "rstyx"), nil); + if(ok < 0) + error(sys->sprint("dial server failed: %r")); + + fd := c.dfd; + if (doauth) { + ai := kr->readauthinfo(keyfile); + # + # let auth->client handle nil ai + # if(ai == nil){ + # sys->fprint(stderr(), "rcmd: certificate for %s not found\n", addr); + # raise "fail:no certificate"; + # } + # + + err := au->init(); + if(err != nil) + error(err); + + (fd, err) = au->client(alg, ai, c.dfd); + if(fd == nil){ + sys->fprint(stderr(), "rcmd: authentication failed: %s\n", err); + raise "fail:auth failed"; + } + } + t := array of byte sys->sprint("%d\n%s\n", len (array of byte args)+1, args); + if(sys->write(fd, t, len t) != len t){ + sys->fprint(stderr(), "rcmd: cannot write arguments: %r\n"); + raise "fail:bad arg write"; + } + + if(sys->export(fd, exportpath, sys->EXPWAIT) < 0) { + sys->fprint(stderr(), "rcmd: export: %r\n"); + raise "fail:export failed"; + } +} + +exists(f: string): int +{ + (ok, nil) := sys->stat(f); + return ok >= 0; +} + +user(): string +{ + sys = load Sys Sys->PATH; + + 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]; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + +badmodule(p: string) +{ + sys->fprint(stderr(), "rcmd: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +error(e: string) +{ + sys->fprint(stderr(), "rcmd: %s\n", e); + raise "fail:errors"; +} diff --git a/appl/cmd/rdp.b b/appl/cmd/rdp.b new file mode 100644 index 00000000..80e25f20 --- /dev/null +++ b/appl/cmd/rdp.b @@ -0,0 +1,1230 @@ +implement Rdp; +include "sys.m"; + sys: Sys; + print, sprint: import sys; +include "draw.m"; +include "string.m"; + str: String; + +df_port: con "/dev/eia0"; +df_bps: con 38400; + +Rdp: module +{ + init: fn(nil: ref Draw->Context, arg: list of string); +}; + +dfd: ref sys->FD; +cfd: ref sys->FD; +ifd: ref sys->FD; +pifd: ref sys->FD; +p_isopen := 0; + +R_R15: con 15; +R_PC: con 16; +R_CPSR: con 17; +R_SPSR: con 18; +NREG: con 19; + +debug := 0; +nocr := 0; +tmode := 0; +# echar := 16r1c; # ctrl-\ +echar := 16r1d; # ctrl-] (because Tk grabs the ctrl-\ ) + +bint(x: int): array of byte +{ + b := array[4] of byte; + b[0] = byte x; + b[1] = byte (x>>8); + b[2] = byte (x>>16); + b[3] = byte (x>>24); + return b; +} + +intb(b: array of byte): int +{ + return int b[0] | (int b[1] << 8) + | (int b[2] << 16) | (int b[3] << 24); +} + + +statusmsg(n: int): string +{ + m: string; + case n { + 0 => m = nil; + 1 => m = "Reset"; + 2 => m = "Undefined instruction"; + 3 => m = "Software interrupt"; + 4 => m = "Prefetch abort"; + 5 => m = "Data abort"; + 6 => m = "Address exception"; + 7 => m = "IRQ"; + 8 => m = "FIQ"; + 9 => m = "Error"; + 10 => m = "Branch Through 0"; + 253 => m = "Insufficient privilege"; + 254 => m = "Unimplemented message"; + 255 => m = "Undefined message"; + * => m = sprint("Status %d", n); + } + return m; +} + +sdc: chan of (array of byte, int); +scc: chan of int; + +serinp() +{ + b: array of byte = nil; + save: array of byte = nil; + x := 0; + for(;;) { + m := <- scc; + if(m == 0) { + save = b[0:x]; + continue; + } + b = nil; + t: int; + do { + alt { + m = <- scc => + if(m == 0) + print("<strange error>\n"); + b = nil; + * => + ; + } + if(b == nil) { + if(m >= 0) + t = m; + else + t = -m; + x = 0; + b = array[t] of byte; + } + if(save != nil) { + r := len save; + if(r > (t-x)) + r = t-x; + b[x:] = save[0:r]; + save = save[r:]; + if(len save == 0) + save = nil; + x += r; + continue; + } + r := sys->read(dfd, b[x:], t-x); + if(r < 0) + sdc <-= (array of byte sprint("fail:%r"), -1); + if(r == 0) + sdc <-= (array of byte "fail:hangup", -1); + if(debug) { + if(r == 1) + print("<%ux>", int b[x]); + else + print("<%ux,%ux...(%d)>", int b[x], int b[x+1], r); + } + x += r; + } while(m >= 0 && x < t); + sdc <-= (b, x); + } +} + + +sreadn(n: int): array of byte +{ + b: array of byte; + if(n == 0) + return array[0] of byte; + scc <-= n; + (b, n) = <- sdc; + if(n < 0) + raise string b; + return b[0:n]; +} + + +# yes, it's kind of a hack... +fds := array[32] of ref Sys->FD; + +oscmd() +{ + arg := array[4] of int; + buf := array[4] of array of byte; + b := sreadn(5); + op := intb(b[:4]); + argd := int b[4]; + for(i := 0; i<4; i++) { + t := (argd >> (i*2))&3; + case t { + 0 => ; + 1 => + arg[i] = int sreadn(1)[0]; + 2 => + arg[i] = intb(sreadn(4)); + 3 => + c := int sreadn(1)[0]; + if(c < 255) { + buf[i] = array[c] of byte; + if(c <= 32) { + buf[i][0:] = sreadn(c); + } else + arg[i] = intb(sreadn(4)); + } else { + b: array of byte; + b = sreadn(8); + c = intb(b[:4]); + arg[i] = intb(b[4:8]); + buf[i] = array[c] of byte; + } + } + } + for(i = 0; i<4; i++) + if(buf[i] != nil && len buf[i] > 32) + rdi_read(arg[i], buf[i], len buf[i]); + + r := 0; + case op { + 0 or 2 => ; + * => + out(""); + } + case op { + 0 => + if(debug) + print("SWI_WriteC(%d)\n", arg[0]); + out(string byte arg[0]); + 2 => + if(debug) + print("SWI_Write0(<%d>)\n", len buf[0]); + out(string buf[0]); + 4 => + if(debug) + print("SWI_ReadC()\n"); + sys->read(ifd, b, 1); + r = int b[0]; + 16r66 => + fname := string buf[0]; + if(debug) + print("SWI_Open(%s, %d)\n", fname, arg[1]); + fd: ref Sys->FD; + case arg[1] { + 0 or 1 => + fd = sys->open(fname, Sys->OREAD); + 2 or 3 => + fd = sys->open(fname, Sys->ORDWR); + 4 or 5 => + fd = sys->open(fname, Sys->OWRITE); + if(fd == nil) + fd = sys->create(fname, Sys->OWRITE, 8r666); + 6 or 7 => + fd = sys->open(fname, Sys->OWRITE|Sys->OTRUNC); + if(fd == nil) + fd = sys->create(fname, Sys->OWRITE, 8r666); + 8 or 9 => + fd = sys->open(fname, Sys->OWRITE); + if(fd == nil) + fd = sys->create(fname, Sys->OWRITE, 8r666); + else + sys->seek(fd, big 0, Sys->SEEKEND); + 10 or 11 => + fd = sys->open(fname, Sys->ORDWR); + if(fd == nil) + fd = sys->create(fname, Sys->ORDWR, 8r666); + else + sys->seek(fd, big 0, Sys->SEEKEND); + } + if(fd != nil) { + r = fd.fd; + if(r >= len fds) { + print("<fd %d out of range 1-%d>\n", r, len fds); + r = 0; + } else + fds[r] = fd; + } + 16r68 => + if(debug) + print("SWI_Close(%d)\n", arg[0]); + if(arg[0] <= 0 || arg[0] >= len fds) + r = -1; + else { + if(fds[arg[0]] != nil) + fds[arg[0]] = nil; + else + r = -1; + } + 16r69 => + if(debug) + print("SWI_Write(%d, <%d>)\n", arg[0], len buf[1]); + if(arg[0] <= 0 || arg[0] >= len fds) + r = -1; + else + r = sys->write(fds[arg[0]], buf[1], len buf[1]); + r = arg[2]-r; + 16r6a => + if(debug) + print("SWI_Read(%d, 0x%ux, %d)\n", arg[0], arg[1], arg[2]); + if(arg[0] <= 0 || arg[0] >= len fds) + r = -1; + else { + d := array[arg[2]] of byte; + r = sys->read(fds[arg[0]], d, arg[2]); + if(r > 0) + rdi_write(d, arg[1], r); + } + r = arg[2]-r; + 16r6b => + if(debug) + print("SWI_Seek(%d, %d)\n", arg[0], arg[1]); + if(arg[0] <= 0 || arg[0] >= len fds) + r = -1; + else + r = int sys->seek(fds[arg[0]], big arg[1], 0); + 16r6c => + if(debug) + print("SWI_Flen(%d)\n", arg[0]); + if(arg[0] <= 0 || arg[0] >= len fds) + r = -1; + else { + d: Sys->Dir; + (r, d) = sys->fstat(fds[arg[0]]); + if(r >= 0) + r = int d.length; + } + 16r6e => + if(debug) + print("SWI_IsTTY(%d)\n", arg[0]); + r = 0; # how can we detect if it's a TTY? + * => + print("unsupported: SWI 0x%ux\n", op); + } + b = array[6] of byte; + b[0] = byte 16r13; + if(debug) + print("r0=%d\n", r); + if(r >= 0 && r <= 16rff) { + b[1] = byte 1; + b[2] = byte r; + sys->write(dfd, b, 3); + } else { + b[1] = byte 2; + b[2:] = bint(r); + sys->write(dfd, b, 6); + } +} + + +terminal() +{ + b := array[1024] of byte; + c := 3; # num of invalid chars before resetting + tmode = 1; + for(;;) { + n: int; + b: array of byte; + alt { + scc <-= -8192 => + (b, n) = <- sdc; + (b, n) = <- sdc => + ; + } + if(n < 0) + raise string b; + c -= out(string b[:n]); + if(c < 0) { + scc <-= 0; + raise "rdp:tmode"; + } + if(!tmode) { + return; + } + } +} + +getreply(n: int): (array of byte, int) +{ + loop: for(;;) { + c := int sreadn(1)[0]; + case c { + 16r21 => + oscmd(); + 16r7f => + raise "rdp:reset"; + 16r5f => + break loop; + * => + print("<%ux?>", c); + scc <-= 0; + raise "rdp:tmode"; + } + } + b := sreadn(n+1); + s := int b[n]; + if(s != 0) { + out(""); + print("[%s]\n", statusmsg(s)); + } + return (b[:n], s); +} + +outstr: string; +tpid: int; + +timeout(t: int, c: chan of int) +{ + tpid = sys->pctl(0, nil); + if(t > 0) + sys->sleep(t); + c <-= 0; + tpid = 0; +} + +bsc: chan of string; + +bufout() +{ + buf := ""; + tc := chan of int; + n: int; + s: string; + for(;;) { + alt { + n = <- tc => + print("%s", buf); + buf = ""; + s = <- bsc => + #if(tpid) { + # kill(tpid); + # tpid = 0; + #} + if((len buf+len s) >= 1024) { + print("%s", buf); + buf = s; + } + if(s == "" || debug) { + print("%s", buf); + buf = ""; + } else { + buf += s; + if(tpid == 0) + spawn timeout(300, tc); + } + } + } +} + +out(s: string): int +{ + if(bsc == nil) { + bsc = chan of string; + spawn bufout(); + } + c := 0; + if(nocr || tmode) { + n := ""; + for(i:=0; i<len s; i++) { + if(!(nocr && s[i] == '\r')) + n[len n] = s[i]; + if(s[i] >= 16r7f) + c++; + } + bsc <-= n; + } else + bsc <-= s; + return c; +} + +reset(r: int) +{ + out(""); + if(debug) + print("reset(%d)\n", r); + p_isopen = 0; + b := array of byte sprint("b9600"); + sys->write(cfd, b, len b); + if(r) { + b[0] = byte 127; + sys->write(dfd, b, 1); + print("<sending reset>"); + } + ok := 0; + s := ""; + for(;;) { + n: int; + b: array of byte; + scc <-= -8192; + (b, n) = <- sdc; + if(n < 0) + raise string b; + for(i := 0; i<n; i++) { + if(b[i] == byte 127) { + if(!ok) + print("\n"); + ok = 1; + s = ""; + continue; + } + if(b[i] == byte 0) { + if(ok && i == n-1) { + out(s); + out(""); + return; + } else { + s = ""; + continue; + } + } + if(b[i] < byte 127) + s += string b[i:i+1]; + else + ok = 0; + } + } +} + +sa1100_reset() +{ + rdi_write(bint(1), int 16r90030000, 4); +} + +setbps(bps: int) +{ + # for older Emu's using setserial hacks... + if(bps > 38400) + sys->write(cfd, array of byte "b38400", 6); + + out(""); + print("<bps=%d>\n", bps); + b := array of byte sprint("b%d", bps); + if(sys->write(cfd, b, len b) != len b) + print("setbps failed: %r\n"); +} + +rdi_open(bps: int) +{ + if(debug) + print("rdi_open(%d)\n", bps); + b := array[7] of byte; + usehack := 0; + if(!p_isopen) { + b[0] = byte 0; + b[1] = byte (0 | (1<<1)); + b[2:] = bint(0); + case bps { + 9600 => b[6] = byte 1; + 19200 => b[6] = byte 2; + 38400 => b[6] = byte 3; + # 57600 => b[6] = byte 4; + # 115200 => b[6] = byte 5; + # 230400 => b[6] = byte 6; + * => + b[6] = byte 1; + usehack = 1; + } + sys->write(dfd, b, 7); + getreply(0); + p_isopen = 1; + if(usehack) + sa1100_setbps(bps); + else + setbps(bps); + } +} + +rdi_close() +{ + if(debug) + print("rdi_close()\n"); + b := array[1] of byte; + if(p_isopen) { + b[0] = byte 1; + sys->write(dfd, b, 1); + getreply(0); + p_isopen = 0; + } +} + +rdi_cpuread(reg: array of int, mask: int) +{ + if(debug) + print("rdi_cpuread(..., 0x%ux)\n", mask); + n := 0; + for(i := 0; i<NREG; i++) + if(mask&(1<<i)) + n += 4; + b := array[6+n] of byte; + b[0] = byte 4; + b[1] = byte 255; # current mode + b[2:] = bint(mask); + sys->write(dfd, b, 6); + (b, nil) = getreply(n); + n = 0; + for(i = 0; i<NREG; i++) + if(mask&(1<<i)) { + reg[i] = intb(b[n:n+4]); + n += 4; + } +} + +rdi_cpuwrite(reg: array of int, mask: int) +{ + if(debug) + print("rdi_cpuwrite(..., 0x%ux)\n", mask); + n := 0; + for(i := 0; i<32; i++) + if(mask&(1<<i)) + n += 4; + b := array[6+n] of byte; + b[0] = byte 5; + b[1] = byte 255; # current mode + b[2:] = bint(mask); + n = 6; + for(i = 0; i<32; i++) + if(mask&(1<<i)) { + b[n:] = bint(reg[i]); + n += 4; + } + sys->write(dfd, b, n); + getreply(0); +} + +dump(b: array of byte, n: int) +{ + for(i := 0; i<n; i++) + print(" %d: %2.2ux\n", i, int b[i]); +} + +rdi_read(addr: int, b: array of byte, n: int): int +{ + if(debug) + print("rdi_read(0x%ux, ..., 0x%ux)\n", addr, n); + if(n == 0) + return 0; + sb := array[9] of byte; + sb[0] = byte 2; + sb[1:] = bint(addr); + sb[5:] = bint(n); + sys->write(dfd, sb, 9); + (b[0:], nil) = getreply(n); + # if error, need to read count of bytes transferred + return n; +} + +rdi_write(b: array of byte, addr: int, n: int): int +{ + if(debug) + print("rdi_write(..., 0x%ux, 0x%ux)\n", addr, n); + if(n == 0) + return 0; + sb := array[9+n] of byte; + sb[0] = byte 3; + sb[1:] = bint(addr); + sb[5:] = bint(n); + sb[9:] = b[:n]; + sys->write(dfd, sb, 9); + x := 0; + while(n) { + q := n; + if(q > 8192) + q = 8192; + r := sys->write(dfd, b[x:], q); + if(debug) + print("rdi_write: r=%d ofs=%d n=%d\n", r, x, n); + if(r < 0) + raise "fail:hangup"; + x += r; + n -= r; + } + getreply(0); + return n; +} + +rdi_execute() +{ + if(debug) + print("rdi_execute()\n"); + sb := array[2] of byte; + sb[0] = byte 16r10; + sb[1] = byte 0; + sys->write(dfd, sb, 2); + getreply(0); + out(""); +} + +rdi_info(n: int, arg: int) +{ + sb := array[9] of byte; + sb[0] = byte 16r12; + sb[1:] = bint(n); + sb[5:] = bint(arg); + sys->write(dfd, sb, 9); + getreply(0); +} + + +regdump() +{ + out(""); + reg := array[NREG] of int; + # rdi_cpuread(reg, 16rffff|(1<<R_PC)|(1<<R_CPSR)|(1<<R_SPSR)); + rdi_cpuread(reg, 16rffff|(1<<R_PC)|(1<<R_CPSR)); + for(i := 0; i < 16; i += 4) + print(" r%-2d=%8.8ux r%-2d=%8.8ux r%-2d=%8.8ux r%-2d=%8.8ux\n", + i, reg[i], i+1, reg[i+1], + i+2, reg[i+2], i+3, reg[i+3]); + print(" pc=%8.8ux psr=%8.8ux\n", + reg[R_PC], reg[R_CPSR]); +} + +printable(b: array of byte): string +{ + s := ""; + for(i := 0; i < len b; i++) + if(b[i] >= byte ' ' && b[i] <= byte 126) + s += string b[i:i+1]; + else + s += "."; + return s; +} + +examine(a: int, n: int) +{ + b := array[4] of byte; + for(i := 0; i<n; i++) { + rdi_read(a, b, 4); + print("0x%8.8ux: 0x%8.8ux \"%s\"\n", a, intb(b), printable(b)); + a += 4; + } +} + +atoi(s: string): int +{ + b := 10; + if(len s < 1) + return 0; + if(s[0] == '0') { + b = 8; + s = s[1:]; + if(len s < 1) + return 0; + if(s[0] == 'x' || s[0] == 'X') { + b = 16; + s = s[1:]; + } + } + n: int; + (n, nil) = str->toint(s, b); + return n; +} + +regnum(s: string): int +{ + if(len s < 2) + return -1; + if(s[0] == 'r' && s[1] >= '0' && s[1] <= '9') + return atoi(s[1:]); + case s { + "pc" => return R_PC; + "cpsr" or "psr" => return R_CPSR; + "spsr" => return R_SPSR; + * => return -1; + } +} + +cmdhelp() +{ + print(" e <addr> [<count>] - examine memory\n"); + print(" d <addr> [<value>...] - deposit values in memory\n"); + print(" get <file> <addr> - read file into memory at addr\n"); + print(" load <file> - load AIF file and set the PC\n"); + print(" r - print all registers\n"); + print(" <reg>=<val> - set register value\n"); + print(" sb - run builtin sboot (pc=0x40; g)\n"); + print(" reset - trigger SA1100 software reset\n"); + print(" bps <speed> - change bps rate (SA1100 only)\n"); + print(" q - quit\n"); +} + +cmdmode() +{ + b := array[1024] of byte; + for(;;) { + print("rdp: "); + r := sys->read(ifd, b, len b); + if(r < 0) + raise sprint("fail:%r"); + if(r == 0 || (r == 1 && b[0] == byte 4)) + break; + n: int; + a: list of string; + (n, a) = sys->tokenize(string b[0:r], " \t\n="); + if(n < 1) + continue; + case hd a { + "sb" => + sbmode(); + rdi_execute(); + "q" or "quit" => + return; + "r" or "reg" => + regdump(); + "get" or "getfile" or "l" or "load" => + { + if((hd a)[0] == 'l') + aifload(hd tl a, -1); + else + aifload(hd tl a, atoi(hd tl tl a)); + }exception e{ + "fail:*" => + print("error: %s\n", e[5:]); + continue; + } + "g" or "go" => + rdi_execute(); + "reset" => + sa1100_reset(); + "e" => + a = tl a; + x := atoi(hd a); + n = 1; + a = tl a; + if(a != nil) + n = atoi(hd a); + examine(x, n); + "d" => + a = tl a; + x := atoi(hd a); + for(i := 2; i<n; i++) { + a = tl a; + rdi_write(bint(atoi(hd a)), x, 4); + x += 4; + } + "info" => + a = tl a; + rdi_info(16r180, atoi(hd a)); + "bps" => + sa1100_setbps(atoi(hd tl a)); + "help" or "?" => + cmdhelp(); + * => + if((rn := regnum(hd a)) > -1) { + reg := array[NREG] of int; + reg[rn] = atoi(hd tl a); + rdi_cpuwrite(reg, 1<<rn); + } else + print("?\n"); + } + } +} + +sbmode() +{ + if(debug) + print("sbmode()\n"); + reg := array[NREG] of int; + reg[R_PC] = 16r40; + rdi_cpuwrite(reg, 1<<R_PC); +} + +sbmodeofs(ofs: int) +{ + if(debug) + print("sbmode(0x%ux)\n", ofs); + reg := array[NREG] of int; + reg[0] = ofs; + reg[R_PC] = 16r48; + rdi_cpuwrite(reg, (1<<0)|(1<<R_PC)); +} + +inp: string = ""; + +help: con "(q)uit, (i)nt, (b)reak, !c(r), !(l)ine, !(t)erminal, (s<bps>), (.)cont, (!cmd)\n"; + +menu(fi: ref Sys->FD) +{ + w := israw; + if(israw) + raw(0); +mloop: for(;;) { + out(""); + print("rdp> "); + b := array[256] of byte; + r := sys->read(fi, b, len b); + case int b[0] { + 'q' => + killgrp(); + exit; + 'i' => + b[0] = byte 16r18; + sys->write(dfd, b[0:1], 1); + break mloop; + 'b' => + sys->write(cfd, array of byte "k", 1); + break mloop; + '!' => + cmd := string b[1:r-1]; + print("!%s\n", cmd); + # system(cmd) + print("!\n"); + break mloop; + 'l' => + w = !w; + break mloop; + 'r' => + nocr = !nocr; + break mloop; + 'd' => + debug = !debug; + break mloop; + 't' => + sys->write(pifd, array[] of { byte 4 }, 1); + sdc <-= (array of byte "rdp:tmode", -1); + break mloop; + '.' => + break mloop; + 's' => + bps := atoi(string b[1:r-1]); + setbps(bps); + * => + print(help); + continue; + } + } + if(israw != w) + raw(w); +} + + +input() +{ + fi := sys->fildes(0); + b := array[1024] of byte; +iloop: for(;;) { + r := sys->read(fi, b, len b); + if(r < 0) { + print("stdin: %r"); + killgrp(); + exit; + } + for(i:=0; i<r; i++) { + if(b[i] == byte echar) { + menu(fi); + continue iloop; + } + } + if(r == 0) { + b[0] = byte 4; # ctrl-d + r = 1; + } + if(tmode) + sys->write(dfd, b, r); + else + sys->write(pifd, b, r); + } +} + +ccfd: ref Sys->FD; +israw := 0; + +raw(on: int) +{ + if(ccfd == nil) { + ccfd = sys->open("/dev/consctl", Sys->OWRITE); + if(ccfd == nil) { + print("/dev/consctl: %r\n"); + return; + } + } + if(on) + sys->fprint(ccfd, "rawon"); + else + sys->fprint(ccfd, "rawoff"); + israw = on; +} + +killgrp() +{ + pid := sys->pctl(0, nil); + f := "/prog/"+string pid+"/ctl"; + fd := sys->open(f, Sys->OWRITE); + if(fd == nil) + print("%s: %r\n", f); + else + sys->fprint(fd, "killgrp"); +} + +kill(pid: int) +{ + f := "/prog/"+string pid+"/ctl"; + fd := sys->open(f, Sys->OWRITE); + if(fd == nil) + print("%s: %r\n", f); + else + sys->fprint(fd, "kill"); +} + + +# Code for switching to previously unsupported bps rates: + +##define UTCR1 0x4 +##define UTCR2 0x8 +##define UTCR3 0xc +##define UTDR 0x14 +##define UTSR0 0x1c +##define UTSR1 0x20 +# +#TEXT _startup(SB), $-4 +# MOVW $0x80000000,R2 +# ORR $0x00050000,R2 +# +# MOVW $0, R1 +# MOVW R1, UTDR(R2) /* send ack */ +# +#wait: +# MOVW UTSR1(R2), R1 +# TST $1, R1 /* TBY */ +# BNE wait +# +# MOVW $0x90000000,R3 +# ORR $0x00000010,R3 +# MOVW (R4),R1 +# ADD $0x5a000,R1 /* 100 ms */ +#delay1: +# MOVW (R3),R1 +# SUB.S $0x5a000, R1 /* 100 ms */ +# BLO delay1 +# +# MOVW UTCR3(R2), R5 /* save utcr3 */ +# MOVW $0, R1 +# MOVW R1, UTCR3(R2) /* disable xmt/rcv */ +# +# MOVW R0, R1 +# AND $0xff, R1 +# MOVW R1, UTCR2(R2) +# MOVW R0 >> 8, R1 +# MOVW R1, UTCR1(R2) +# +# MOVW $0xff, R1 +# MOVW R1, UTSR0(R2) /* clear sticky bits */ +# +# MOVW $3, R1 +# MOVW R1, UTCR3(R2) /* enable xmt/rcv */ +# +# MOVW $0, R0 +#sync: +# MOVW R0, UTDR(R2) /* send sync char */ +#syncwait: +# MOVW UTSR1(R2), R1 +# TST $1, R1 /* TBY */ +# BNE syncwait +# TST $2, R1 /* RNE */ +# BEQ sync +# MOVW UTDR(R2), R0 +# MOVW R0, UTDR(R2) /* echo rcvd char */ +# +# MOVW $0xff, R1 +# MOVW R1, UTSR0(R2) /* clear sticky bits */ +# MOVW R5, UTCR3(R2) /* re-enable xmt/rcv and interrupts */ +# +# WORD $0xef000011 /* exit */ + + +bpscode := array[] of { + 16re3a22102, 16re3822805, 16re3a11000, 16re5821014, + 16re5921020, 16re3110001, big 16r1afffffc, 16re3a33209, + 16re3833010, 16re5941000, 16re2811a5a, 16re5931000, + 16re2511a5a, big 16r3afffffc, 16re592500c, 16re3a11000, + 16re582100c, 16re1a11000, 16re20110ff, 16re5821008, + 16re1a11420, 16re5821004, 16re3a110ff, 16re582101c, + 16re3a11003, 16re582100c, 16re3a00000, 16re5820014, + 16re5921020, 16re3110001, big 16r1afffffc, 16re3110002, + big 16r0afffff9, 16re5920014, 16re5820014, 16re3a110ff, + 16re582101c, 16re582500c, 16ref000011, +}; + +sa1100_setbps(bps: int) +{ + print("<sa1100_setbps %d>", bps); + nb := len bpscode*4; + b := array[nb] of byte; + for(i := 0; i < len bpscode; i++) + b[i*4:] = bint(int bpscode[i]); + rdi_write(b, 16r8080, nb); + reg := array[NREG] of int; + d := (3686400/(bps*16))-1; + reg[0] = d; + reg[R_PC] = 16r8080; + rdi_cpuwrite(reg, (1<<0)|(1<<R_PC)); + sb := array[2] of byte; + sb[0] = byte 16r10; + sb[1] = byte 0; + sys->write(dfd, sb, 2); + rb := sreadn(1); + setbps(bps); + do rb = sreadn(1); + while(rb[0] != byte 0); + sb[0] = byte 16rff; + sys->write(dfd, sb, 1); + do rb = sreadn(1); + while(rb[0] != sb[0]); + getreply(0); +} + +aifload(fname: string, adr: int) +{ + out(""); + if(adr < 0) + print("<aifload %s>\n", fname); + fd := sys->open(fname, Sys->OREAD); + if(fd == nil) + raise sprint("fail:%s:%r", fname); + d: Sys->Dir; + (nil, d) = sys->fstat(fd); + b := array[int d.length] of byte; + sys->read(fd, b, len b); + if(adr < 0) { + if(len b < 128) + raise sprint("fail:%s:not aif", fname); + tsize := intb(b[20:24]); + dsize := intb(b[24:28]); + bsize := intb(b[32:36]); + tbase := intb(b[40:44]); + dbase := intb(b[52:56]); + print("%ux/%ux: %ux+%ux+%ux\n", tbase, dbase, tsize, dsize, bsize); + rdi_write(b, tbase, tsize+dsize); + reg := array[NREG] of int; + reg[R_PC] = tbase+8; + rdi_cpuwrite(reg, 1<<R_PC); + } else + rdi_write(b, adr, int d.length); +} + + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + + sys->pctl(Sys->NEWPGRP, nil); + + port := df_port; + bps := df_bps; + usecmdmode := 0; + ofs := -1; + prog: string = nil; + + argv = tl argv; + while(argv != nil) { + a := hd argv; + argv = tl argv; + if(len a >= 2 && a[0] == '-') + case a[1] { + 'c' => + usecmdmode = 1; + 'O' => + ofs = atoi(a[2:]); + 'd' => + debug = 1; + 'p' => + port = a[2:]; + 's' => + bps = atoi(a[2:]); + 'r' => + nocr = 1; + 'l' => + raw(1); + 'e' => + if(a[2] == '^') + echar = a[3]&16r1f; + else + echar = a[2]; + 't' => + tmode = 1; + 'h' => + print("usage: rdp [-crdlht] [-e<c>] [-O<ofs>] [-p<port>] [-s<bps>] [prog]\n"); + return; + * => + print("invalid option: %s\n", a); + return; + } + else + prog = a; + } + + print("rdp 0.17 (port=%s, bps=%d)\n", port, bps); + dfd = sys->open(port, Sys->ORDWR); + if(dfd == nil) { + sys->print("open %s failed: %r\n", port); + return; + } + cfd = sys->open(port+"ctl", Sys->OWRITE); + if(cfd == nil) + sys->print("warning: open %s failed: %r\n", port+"ctl"); + + pfd := array[2] of ref Sys->FD; + sys->pipe(pfd); + ifd = pfd[1]; + pifd = pfd[0]; + (scc, sdc) = (chan of int, chan of (array of byte, int)); + spawn serinp(); + spawn input(); + r := 1; + { + if(tmode) + terminal(); + reset(r); + if(!p_isopen) { + rdi_open(bps); + rdi_info(16r180, (1<<0)|(1<<1)|(1<<3)|(1<<4)|(1<<5)|(1<<6)|(1<<7)|(1<<8)); + } + # print("\n<connection established>\n"); + print("\n<contact has been made>\n"); + if(usecmdmode) { + cmdmode(); + } else { + if(prog != nil) + aifload(prog, -1); + else if(ofs != -1) + sbmodeofs(ofs); + else + sbmode(); + reg := array[NREG] of int; + # rdi_cpuread(reg, (1<<R_PC)|(1<<R_CPSR)); + # print("<execute at %ux; cpsr=%ux>\n", reg[R_PC], reg[R_CPSR]); + rdi_cpuread(reg, (1<<R_PC)); + print("<execute at %ux>\n", reg[R_PC]); + rdi_execute(); + } + rdi_close(); + + # Warning: this will make Linux emu crash... + killgrp(); + }exception e{ + "fail:*" => + if(israw) + raw(0); + killgrp(); + raise e; + "rdp:*" => + out(""); + if(debug) + print("<exception: %s>\n", e); + case e { + "rdp:error" => ; + "rdp:tmode" => + tmode = !tmode; + if(tmode) + print("<terminal mode>\n"); + else + print("<rdp mode>\n"); + "rdp:reset" => + r = 0; + * => + r = 1; + } + } +} + diff --git a/appl/cmd/read.b b/appl/cmd/read.b new file mode 100644 index 00000000..a4a008a9 --- /dev/null +++ b/appl/cmd/read.b @@ -0,0 +1,62 @@ +implement Read; +include "sys.m"; + sys: Sys; +include "draw.m"; + +Read: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: read [-[ero] offset] count\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + # usage: read [-[ero] offset] count + count := Sys->ATOMICIO; + offset := big 0; + seeking := -1; + if (argv != nil) + argv = tl argv; + if (argv != nil && hd argv != nil && (hd argv)[0] == '-') { + if (tl argv == nil) + usage(); + case hd argv { + "-o" => + seeking = Sys->SEEKSTART; + "-e" => + seeking = Sys->SEEKEND; + "-r" => + seeking = Sys->SEEKRELA; + * => + usage(); + } + offset = big hd tl argv; + argv = tl tl argv; + } + if (argv != nil) { + if (tl argv != nil) + usage(); + count = int hd argv; + } + fd := sys->fildes(0); + if (seeking != -1) + sys->seek(fd, offset, seeking); + if (count == 0) + return; + buf := array[count] of byte; + n := sys->read(fd, buf, len buf); + if (n > 0) + sys->write(sys->fildes(1), buf, n); + else { + if (n == -1) { + sys->fprint(sys->fildes(2), "read: read error: %r\n"); + raise "fail:error"; + } + raise "fail:eof"; + } +} diff --git a/appl/cmd/rioimport.b b/appl/cmd/rioimport.b new file mode 100644 index 00000000..49980000 --- /dev/null +++ b/appl/cmd/rioimport.b @@ -0,0 +1,620 @@ +implement Rioimport; +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Image, Point, Rect, Display, Screen: import draw; +include "wmsrv.m"; + wmsrv: Wmsrv; +include "sh.m"; + sh: Sh; +include "string.m"; + str: String; + +Rioimport: module{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +Client: adt{ + ptrstarted: int; + kbdstarted: int; + state: int; # Hidden|Current + req: chan of (array of byte, Sys->Rwrite); + resize: chan of ref Riowin; + ptr: chan of ref Draw->Pointer; + riowctl: chan of (ref Riowin, int); + wins: list of ref Riowin; + winfd: ref Sys->FD; + sc: ref Wmsrv->Client; +}; + +Riowin: adt { + tag: string; + img: ref Image; + dir: string; + state: int; + ptrpid: int; + kbdpid: int; + ctlpid: int; + ptrfd: ref Sys->FD; + ctlfd: ref Sys->FD; +}; + +Hidden, Current: con 1<<iota; +Ptrsize: con 1+4*12; # 'm' plus 4 12-byte decimal integers +P9PATH: con "/n/local"; +Borderwidth: con 4; # defined in /sys/include/draw.h + +display: ref Display; +wsysseq := 0; +screenr := Rect((0, 0), (640, 480)); # no way of getting this reliably from rio + +Minwinsize: con Point(100, 42); + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + sh = load Sh Sh->PATH; + sh->initialise(); + str = load String String->PATH; + wmsrv = load Wmsrv Wmsrv->PATH; + + wc := chan of (ref Draw->Context, string); + spawn rioproxy(wc); + (ctxt, err) := <-wc; + if(err != nil){ + sys->fprint(sys->fildes(2), "rioimport: %s\n", err); + raise "fail:no display"; + } + sh->run(ctxt, tl argv); +} + +ebind(a, b: string, flag: int) +{ + if(sys->bind(a, b, flag) == -1){ + sys->fprint(sys->fildes(2), "rioimport: cannot bind %q onto %q: %r\n", a, b); + raise "fail:error"; + } +} + +rioproxy(wc: chan of (ref Draw->Context, string)) +{ + { + rioproxy1(wc); + } exception e { + "fail:*" => + wc <-= (nil, e[5:]); + } +} + +rioproxy1(wc: chan of (ref Draw->Context, string)) +{ + sys->pctl(Sys->NEWFD, 0 :: 1 :: 2 :: nil); + + ebind("#U*", P9PATH, Sys->MREPL); + display = Display.allocate(P9PATH + "/dev"); + if(display == nil) + raise sys->sprint("fail:cannot allocate display: %r"); + + + (wm, join, req) := wmsrv->init(); + if(wm == nil){ + wc <-= (nil, sys->sprint("%r")); + return; + } + readscreenr(); + wc <-= (ref Draw->Context(display, nil, wm), nil); + + sys->pctl(Sys->FORKNS, nil); + ebind("#₪", "/srv", Sys->MREPL|Sys->MCREATE); + if(sys->bind(P9PATH+"/dev/draw", "/dev/draw", Sys->MREPL) == -1) + ebind(P9PATH+"/dev", "/dev", Sys->MAFTER); + sh->run(nil, "mount" :: "{mntgen}" :: "/mnt" :: nil); + + clients: array of ref Client; + nc := 0; + for(;;) alt{ + (sc, rc) := <-join => + if(nc != 0) + rc <-= "only one client available"; + sync := chan of (ref Client, string); + spawn clientproc(sc,sync); + (c, err) := <-sync; + rc <-= err; + if(c != nil){ + if(sc.id >= len clients) + clients = (array[sc.id + 1] of ref Client)[0:] = clients; + clients[sc.id] = c; + } + (sc, data, rc) := <-req => + clients[sc.id].req <-= (data, rc); + if(rc == nil) + clients[sc.id] = nil; + } +} +zclient: Client; +clientproc(sc: ref Wmsrv->Client, rc: chan of (ref Client, string)) +{ + c := ref zclient; + c.req = chan of (array of byte, Sys->Rwrite); + c.resize = chan of ref Riowin; + c.ptr = chan of ref Draw->Pointer; + c.riowctl = chan of (ref Riowin, int); + c.sc = sc; + rc <-= (c, nil); + +loop: + for(;;) alt{ + (data, drc) := <-c.req => + if(drc == nil) + break loop; + err := handlerequest(c, data); + n := len data; + if(err != nil) + n = -1; + alt{ + drc <-= (n, err) =>; + * =>; + } + p := <-c.ptr => + sc.ptr <-= p; + w := <-c.resize => + if((c.state & Hidden) == 0) + sc.ctl <-= sys->sprint("!reshape %q -1 0 0 0 0 getwin", w.tag); + (w, state) := <-c.riowctl => + if((c.state^state)&Current) + sc.ctl <-= "haskbdfocus " + string ((state & Current)!=0); + if((c.state^state)&Hidden){ + s := "unhide"; + if(state&Hidden) + s = "hide"; + for(wl := c.wins; wl != nil; wl = tl wl){ + if(hd wl != w) + rioctl(hd wl, s); + if(c.state&Hidden) + sc.ctl <-= sys->sprint("!reshape %q -1 0 0 0 0 getwin", (hd wl).tag); + } + } + c.state = state; + w.state = state; + } + sc.stop <-= 1; + for(wl := c.wins; wl != nil; wl = tl wl) + delwin(hd wl); +} + +handlerequest(c: ref Client, data: array of byte): string +{ + req := string data; +#sys->print("%d: %s\n", c.sc.id, req); + if(req == nil) + return "no request"; + args := str->unquoted(req); + n := len args; + case hd args { + "key" => + return "permission denied"; + "ptr" => + # ptr x y + if(n != 3) + return "bad arg count"; + if(c.ptrstarted == 0) + return "pointer not active"; + for(w := c.wins; w != nil; w = tl w){ + if((hd w).ptrfd != nil){ + sys->fprint((hd w).ptrfd, "m%11d %11d", int hd tl args, int hd tl tl args); + return nil; + } + } + return "no windows"; + "start" => + if(n != 2) + return "bad arg count"; + case hd tl args { + "ptr" or + "mouse" => + if(c.ptrstarted == -1) + return "already started"; + sync := chan of int; + for(w := c.wins; w != nil; w = tl w){ + spawn ptrproc(hd w, c.ptr, c.resize, sync); + (hd w).ptrpid = <-sync; + } + c.ptrstarted = 1; + return nil; + "kbd" => + if(c.kbdstarted == -1) + return "already started"; + sync := chan of int; + for(w := c.wins; w != nil; w = tl w){ + spawn kbdproc(hd w, c.sc.kbd, sync); + (hd w).kbdpid = <-sync; + } + return nil; + * => + return "unknown input source"; + } + "!reshape" => + # reshape tag reqid rect [how] + # XXX allow "how" to specify that the origin of the window is never + # changed - a new window will be created instead. + if(n < 7) + return "bad arg count"; + args = tl args; + tag := hd args; args = tl args; + args = tl args; # skip reqid + r: Rect; + r.min.x = int hd args; args = tl args; + r.min.y = int hd args; args = tl args; + r.max.x = int hd args; args = tl args; + r.max.y = int hd args; args = tl args; + if(r.dx() < Minwinsize.x) + r.max.x = r.min.x + Minwinsize.x; + if(r.dy() < Minwinsize.y) + r.max.y = r.min.y + Minwinsize.y; + + spec := ""; + if(args != nil){ + case hd args{ + "onscreen" => + r = fitrect(r, screenr).inset(-Borderwidth); + spec = "-r " + r2s(r); + "place" => + r = fitrect(r, screenr).inset(-Borderwidth); + spec = "-dx " + string r.dx() + " -dy " + string r.dy(); + "exact" => + spec = "-r " + r2s(r.inset(-Borderwidth)); + "max" => + r = screenr; # XXX don't obscure toolbar? + spec = "-r " + r2s(r.inset(Borderwidth)); + "getwin" => + ; # just get the new image + * => + return "unkown placement method"; + } + }else + spec = "-r " + r2s(r.inset(-Borderwidth)); + return reshape(c, tag, spec); + "delete" => + # delete tag + if(tl args == nil) + return "tag required"; + tag := hd tl args; + nw: list of ref Riowin; + for(w := c.wins; w != nil; w = tl w){ + if((hd w).tag == tag){ + delwin(hd w); + wmsrv->c.sc.setimage(tag, nil); + }else + nw = hd w :: nw; + } + c.wins = nil; + for(; nw != nil; nw = tl nw) + c.wins = hd nw :: c.wins; + "label" => + if(n != 2) + return "bad arg count"; + for(w := c.wins; w != nil; w = tl w) + setlabel(hd w, hd tl args); + "raise" => + for(w := c.wins; w != nil; w = tl w){ + rioctl(hd w, "top"); + if(tl w == nil) + rioctl(hd w, "current"); + } + "lower" => + for(w := c.wins; w != nil; w = tl w) + rioctl(hd w, "bottom"); + "task" => + if(n != 2) + return "bad arg count"; + c.state |= Hidden; + for(w := c.wins; w != nil; w = tl w){ + setlabel(hd w, hd tl args); + rioctl(hd w, "hide"); + } + "untask" => + wins: list of ref Riowin; + for(w := c.wins; w != nil; w = tl w) + wins = hd w :: wins; + for(; wins != nil; wins = tl wins) + rioctl(hd wins, "unhide"); + "!move" => + # !move tag reqid startx starty + if(n != 5) + return "bad arg count"; + args = tl args; + tag := hd args; args = tl args; + args = tl args; + w := wmsrv->c.sc.window(tag); + if(w == nil) + return "no such tag"; + return dragwin(c.ptr, c, w, Point(int hd args, int hd tl args)); + "!size" => + return "nope"; + "kbdfocus" => + if(n != 2) + return "bad arg count"; + if(int hd tl args){ + if(c.wins != nil) + return rioctl(hd c.wins, "current"); + } + return nil; + * => + return "unknown request"; + } + return nil; +} + +dragwin(ptr: chan of ref Draw->Pointer, c: ref Client, w: ref Wmsrv->Window, click: Point): string +{ +# if(buttons == 0) +# return "too late"; + p: ref Draw->Pointer; + img := w.img.screen.image; + r := img.r; + off := click.sub(r.min); + do{ + p = <-ptr; + img.origin(r.min, p.xy.sub(off)); + } while (p.buttons != 0); + c.sc.ptr <-= p; +# buttons = 0; + nr: Rect; + nr.min = p.xy.sub(off); + nr.max = nr.min.add(r.size()); + if(nr.eq(r)) + return "not moved"; + reshape(c, w.tag, "-r " + r2s(nr)); + return nil; +} + +rioctl(w: ref Riowin, req: string): string +{ + if(sys->fprint(w.ctlfd, "%s", req) == -1){ +#sys->print("rioctl fail %s: %s: %r\n", w.dir, req); + return sys->sprint("%r"); +} +#sys->print("rioctl %s: %s\n", w.dir, req); + return nil; +} + +reshape(c: ref Client, tag: string, spec: string): string +{ + for(wl := c.wins; wl != nil; wl = tl wl) + if((hd wl).tag == tag) + break; + if(wl == nil){ + (w, e) := newwin(c, tag, spec); + if(w == nil){ +sys->print("can't make new win (spec %q): %s\n", spec, e); + return e; + } + c.wins = w :: c.wins; + wmsrv->c.sc.setimage(tag, w.img); + sync := chan of int; + if(c.kbdstarted){ + spawn kbdproc(w, c.sc.kbd, sync); + w.kbdpid = <-sync; + } + if(c.ptrstarted){ + spawn ptrproc(w, c.ptr, c.resize, sync); + w.ptrpid = <-sync; + } + return nil; + } + w := hd wl; + if(spec != nil){ + e := rioctl(w, "resize " + spec); + if(e != nil) + return e; + } + getwin(w); + if(w.img == nil) + return "getwin failed"; + wmsrv->c.sc.setimage(tag, w.img); + return nil; +} + +zriowin: Riowin; +newwin(c: ref Client, tag, spec: string): (ref Riowin, string) +{ + wsys := readfile(P9PATH + "/env/wsys"); + if(wsys == nil) + return (nil, "no $wsys"); + + d := "/mnt/"+string wsysseq++; + fd := sys->open(wsys, Sys->ORDWR); + if(fd == nil) + return (nil, sys->sprint("cannot open %q: %r\n", wsys)); + # XXX this won't multiplex properly - srv9 should export attach files (actually that's what plan 9 should do) + if(sys->mount(fd, nil, d, Sys->MREPL, "new "+spec) == -1) + return (nil, sys->sprint("mount %q failed: %r", wsys)); + (ok, nil) := sys->stat(d + "/winname"); + if(ok == -1) + return (nil, "could not make window"); + w := ref zriowin; + w.tag = tag; + w.dir = d; + getwin(w); + w.ctlfd = sys->open(d + "/wctl", Sys->ORDWR); + setlabel(w, "inferno "+string sys->pctl(0, nil)+"."+tag); + sync := chan of int; + spawn ctlproc(w, c.riowctl, sync); + w.ctlpid = <-sync; + return (w, nil); +} + +setlabel(w: ref Riowin, s: string) +{ + fd := sys->open(w.dir + "/label", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "%s", s); +} + +ctlproc(w: ref Riowin, wctl: chan of (ref Riowin, int), sync: chan of int) +{ + sync <-= sys->pctl(0, nil); + buf := array[1024] of byte; + for(;;){ + n := sys->read(w.ctlfd, buf, len buf); + if(n <= 0) + break; + if(n > 4*12){ + state := 0; + (nil, toks) := sys->tokenize(string buf[4*12:], " "); + if(hd toks == "current") + state |= Current; + if(hd tl toks == "hidden") + state |= Hidden; + wctl <-= (w, state); + } + } +#sys->print("riowctl eof\n"); +} + +delwin(w: ref Riowin) +{ + sys->unmount(nil, w.dir); + kill(w.ptrpid, "kill"); + kill(w.kbdpid, "kill"); + kill(w.ctlpid, "kill"); +} + +getwin(w: ref Riowin): int +{ + s := readfile(w.dir + "/winname"); +#sys->print("getwin %s\n", s); + i := display.namedimage(s); + if(i == nil) + return -1; + scr := Screen.allocate(i, display.white, 0); + if(scr == nil) + return -1; + wi := scr.newwindow(i.r.inset(Borderwidth), Draw->Refnone, Draw->Nofill); + if(wi == nil) + return -1; + w.img = wi; + return 0; +} + +kbdproc(w: ref Riowin, keys: chan of int, sync: chan of int) +{ + sys->pctl(Sys->NEWFD, nil); + cctl := sys->open(w.dir + "/consctl", Sys->OWRITE); + sys->fprint(cctl, "rawon"); + fd := sys->open(w.dir + "/cons", Sys->OREAD); + if(fd == nil){ + sync <-= -1; + return; + } + sync <-= sys->pctl(0, nil); + buf := array[12] of byte; + while((n := sys->read(fd, buf, len buf)) > 0){ + s := string buf[0:n]; + for(j := 0; j < len s; j++) + keys <-= int s[j]; + } +#sys->print("eof on kbdproc\n"); +} + +# fit a window rectangle to the available space. +# try to preserve requested location if possible. +# make sure that the window is no bigger than +# the screen, and that its top and left-hand edges +# will be visible at least. +fitrect(w, r: Rect): Rect +{ + if(w.dx() > r.dx()) + w.max.x = w.min.x + r.dx(); + if(w.dy() > r.dy()) + w.max.y = w.min.y + r.dy(); + size := w.size(); + if (w.max.x > r.max.x) + (w.min.x, w.max.x) = (r.min.x - size.x, r.max.x - size.x); + if (w.max.y > r.max.y) + (w.min.y, w.max.y) = (r.min.y - size.y, r.max.y - size.y); + if (w.min.x < r.min.x) + (w.min.x, w.max.x) = (r.min.x, r.min.x + size.x); + if (w.min.y < r.min.y) + (w.min.y, w.max.y) = (r.min.y, r.min.y + size.y); + return w; +} + +ptrproc(w: ref Riowin, ptr: chan of ref Draw->Pointer, resize: chan of ref Riowin, sync: chan of int) +{ + w.ptrfd = sys->open(w.dir + "/mouse", Sys->ORDWR); + if(w.ptrfd == nil){ + sync <-= -1; + return; + } + sync <-= sys->pctl(0, nil); + + b:= array[Ptrsize] of byte; + while((n := sys->read(w.ptrfd, b, len b)) > 0){ + if(n > 0 && int b[0] == 'r'){ +#sys->print("ptrproc got resize: %s\n", string b[0:n]); + resize <-= w; + }else{ + p := bytes2ptr(b); + if(p != nil) + ptr <-= p; + } + } +#sys->print("eof on ptrproc\n"); +} + +bytes2ptr(b: array of byte): ref Draw->Pointer +{ + if(len b < Ptrsize || int b[0] != 'm') + return nil; + x := int string b[1:13]; + y := int string b[13:25]; + but := int string b[25:37]; + msec := int string b[37:49]; + return ref Draw->Pointer (but, (x, y), msec); +} + +readfile(f: string): string +{ + fd := sys->open(f, sys->OREAD); + if(fd == nil) + return nil; + + buf := array[8192] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return nil; + + return string buf[0:n]; +} + +readscreenr() +{ + fd := sys->open(P9PATH + "/dev/screen", Sys->OREAD); + if(fd == nil) + return ; + buf := array[5*12] of byte; + n := sys->read(fd, buf, len buf); + if(n <= len buf) + return; + screenr.min.x = int string buf[12:23]; + screenr.min.y = int string buf[24:35]; + screenr.max.x = int string buf[36:47]; + screenr.max.y = int string buf[48:]; +} + +r2s(r: Rect): string +{ + return string r.min.x + " " + string r.min.y + " " + + string r.max.x + " " + string r.max.y; +} + +kill(pid: int, note: string): int +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "%s", note) < 0) + return -1; + return 0; +} diff --git a/appl/cmd/rm.b b/appl/cmd/rm.b new file mode 100644 index 00000000..af8236be --- /dev/null +++ b/appl/cmd/rm.b @@ -0,0 +1,99 @@ +implement Rm; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +include "readdir.m"; + readdir: Readdir; + +include "arg.m"; + +Rm: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; +quiet := 0; +force := 0; +errcount := 0; + +usage() +{ + sys->fprint(stderr, "Usage: rm [-fr] file ...\n"); + raise "fail: usage"; +} +allwrite := Sys->nulldir; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + allwrite.mode = 8r777 | Sys->DMDIR; + + arg := load Arg Arg->PATH; + if(arg == nil){ + sys->fprint(stderr, "rm: can't load %s: %r\n", Arg->PATH); + raise "fail:load"; + } + arg->init(args); + while((o := arg->opt()) != 0) + case o { + 'r' => + readdir = load Readdir Readdir->PATH; + if(readdir == nil) + sys->fprint(stderr, "rm: can't load Readdir: %r\n"); # -r is regarded as optional + 'f' => + quiet = 1; + 'F' => + force = 1; + * => + usage(); + } + args = arg->argv(); + arg = nil; + sys->pctl(Sys->FORKNS, nil); + for(; args != nil; args = tl args) { + name := hd args; + if(sys->remove(name) < 0) { + e := sys->sprint("%r"); + (ok, d) := sys->stat(name); + if(readdir != nil && ok >= 0 && (d.mode & Sys->DMDIR) != 0) + rmdir(name); + else + err(name, e); + } + } + if(errcount > 0) + raise "fail:errors"; +} + +rmdir(name: string) +{ + if(force) + sys->wstat(name, allwrite); + (d, n) := readdir->init(name, Readdir->NONE|Readdir->COMPACT); + for(i := 0; i < n; i++){ + path := name+"/"+d[i].name; + if(d[i].mode & Sys->DMDIR) + rmdir(path); + else + remove(path); + } + remove(name); +} + +remove(name: string) +{ + if(sys->remove(name) < 0) + err(name, sys->sprint("%r")); +} + +err(name, e: string) +{ + if(!quiet) { + sys->fprint(stderr, "rm: %s: %s\n", name, e); + errcount++; + } +} diff --git a/appl/cmd/runas.b b/appl/cmd/runas.b new file mode 100644 index 00000000..d0112396 --- /dev/null +++ b/appl/cmd/runas.b @@ -0,0 +1,60 @@ +implement Runas; + +include "sys.m"; +include "draw.m"; +include "sh.m"; + +sys: Sys; +sh: Sh; + +Context: import sh; + +Runas: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +init(drawctxt: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + sh = load Sh Sh->PATH; + if (sh == nil) + badmodule(Sh->PATH); + + if (len argv < 3) + usage(); + + argv = tl argv; + user := hd argv; + argv = tl argv; + + fd := sys->open("/dev/user", Sys->OWRITE); + if (fd == nil) + error(sys->sprint("cannot open /dev/user: %r")); + u := array of byte user; + if (sys->write(fd, u, len u) != len u) + error(sys->sprint("cannot set user: %r")); + sh->run(drawctxt, argv); +} + +badmodule(p: string) +{ + sys->fprint(stderr(), "runas: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + +usage() +{ + sys->fprint(stderr(), "usage: runas user cmd [args...]\n"); + raise "fail:usage"; +} + +error(e: string) +{ + sys->fprint(stderr(), "runas: %s\n", e); + raise "fail:error"; +}
\ No newline at end of file diff --git a/appl/cmd/sed.b b/appl/cmd/sed.b new file mode 100644 index 00000000..30bfbd22 --- /dev/null +++ b/appl/cmd/sed.b @@ -0,0 +1,908 @@ +implement Sed; + +# +# partial sed implementation borrowed from plan9 sed. +# + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "arg.m"; + arg: Arg; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "string.m"; + str: String; +include "regex.m"; + regex: Regex; + Re: import regex; + +Sed : module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + + +false, true: con iota; +bool: type int; + +Addr: adt { + pick { + None => + Dollar => + Line => + line: int; + Regex => + re: Re; + } +}; + +Sedcom: adt { + command: fn(c: self ref Sedcom); + executable: fn(c: self ref Sedcom) : int; + + ad1, ad2: ref Addr; + negfl: bool; + active: int; + + pick { + S => + gfl, pfl: int; + re: Re; + b: ref Iobuf; + rhs: string; + D or CD or P or Q or EQ or G or CG or H or CH or N or CN or X or CP or L=> + A or C or I => + text: string; + R => + filename: string; + W => + b: ref Iobuf; + Y => + map: list of (int, int); + B or T or Lab => + lab: string; + } +}; + +dflag := false; +nflag := false; +gflag := false; +sflag := 0; + +delflag := 0; +dolflag := 0; +fhead := 0; +files: list of string; +fout: ref Iobuf; +infile: ref Iobuf; +jflag := 0; +lastregex: Re; +linebuf: string; +filename := ""; +lnum := 0; +peekc := 0; + +holdsp := ""; +patsp := ""; + +cmds: list of ref Sedcom; +appendlist: list of ref Sedcom; +bufioflush: list of ref Iobuf; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + if ((arg = load Arg Arg->PATH) == nil) + fatal(sys->sprint("could not load %s: %r", Arg->PATH)); + + if ((bufio = load Bufio Bufio->PATH) == nil) + fatal(sys->sprint("could not load %s: %r", Bufio->PATH)); + + if ((str = load String String->PATH) == nil) + fatal(sys->sprint("could not load %s: %r", String->PATH)); + + if ((regex = load Regex Regex->PATH) == nil) + fatal(sys->sprint("could not load %s: %r", Regex->PATH)); + + arg->init(args); + + compfl := 0; + while ((c := arg->opt()) != 0) + case c { + 'n' => + nflag = true; + 'g' => + gflag = true; + 'e' => + if ((s := arg->arg()) == nil) + usage(); + filename = ""; + cmds = compile(bufio->sopen(s + "\n"), cmds); + compfl = 1; + 'f' => if ((filename = arg->arg()) == nil) + usage(); + b := bufio->open(filename, bufio->OREAD); + if (b == nil) + fatal(sys->sprint("couldn't open '%s': %r", filename)); + cmds = compile(b, cmds); + compfl = 1; + 'd' => + dflag = true; + * => + usage(); + } + args = arg->argv(); + if (compfl == 0) { + if (len args == 0) + fatal("missing pattern"); + filename = ""; + cmds = compile(bufio->sopen(hd args + "\n"), cmds); + args = tl args; + } + + # reverse command list, we could compile addresses here if required + l: list of ref Sedcom; + for (p := cmds; p != nil; p = tl p) { + l = hd p :: l; + } + cmds = l; + + # add files to file list (and reverse to get in right order) + f: list of string; + if (len args == 0) + f = "" :: f; + else for (; len args != 0; args = tl args) + f = hd args :: f; + for (;f != nil; f = tl f) + files = hd f :: files; + + if ((fout = bufio->fopen(sys->fildes(1), bufio->OWRITE)) == nil) + fatal(sys->sprint("couldn't buffer stdout: %r")); + bufioflush = fout :: bufioflush; + lnum = 0; + execute(cmds); + exits(nil); +} + +depth := 0; +maxdepth: con 20; +cmdend := array [maxdepth] of string; +cmdcnt := array [maxdepth] of int; + +compile(b: ref Iobuf, l: list of ref Sedcom) : list of ref Sedcom +{ + lnum = 1; + +nextline: + for (;;) { + err: int; + (err, linebuf) = getline(b); + if (err < 0) + break; + + s := linebuf; + + do { + rep: ref Sedcom; + ad1, ad2: ref Addr; + negfl := 0; + + if (s != "") + s = str->drop(s, " \t;"); + + if (s == "" || s[0] == '#') + continue nextline; + + # read addresses + (s, ad1) = address(s); + pick a := ad1 { + None => + ad2 = ref Addr.None(); + * => + if (s != "" && (s[0] == ',' || s[0] == ';')) { + (s, ad2) = address(s[1:]); + } + else { + ad2 = ref Addr.None(); + } + } + + s = str->drop(s, " \t"); + + if (s != "" && str->in(s[0], "!")) { + negfl = true; + s = str->drop(s, "!"); + } + s = str->drop(s, " \t"); + if (s == "") + break; + c := s[0]; s = s[1:]; + + # mop up commands that got two addresses but only want one. + case c { + 'a' or 'c' or 'q' or '=' or 'i' => + if (tagof ad2 != tagof Addr.None) + fatal(sys->sprint("only one address allowed: '%s'", + linebuf)); + } + + case c { + * => + fatal(sys->sprint("unrecognised command: '%s' (%c)", + linebuf, c)); + 'a' => + if (s != "" && s[0] == '\\') + s = s[1:]; + if (s == "" || s[0] != '\n') + fatal("unexpected characters in a command: " + s); + rep = ref Sedcom.A (ad1, ad2, negfl, 0, s[1:]); + s = ""; + 'c' => + if (s != "" && s[0] == '\\') + s = s[1:]; + if (s == "" || s[0] != '\n') + fatal("unexpected characters in c command: " + s); + rep = ref Sedcom.C (ad1, ad2, negfl, 0, s[1:]); + s = ""; + 'i' => + if (s != "" && s[0] == '\\') + s = s[1:]; + if (s == "" || s[0] != '\n') + fatal("unexpected characters in i command: " + s); + rep = ref Sedcom.I (ad1, ad2, negfl, 0, s[1:]); + s = ""; + 'r' => + s = str->drop(s, " \t"); + rep = ref Sedcom.R (ad1, ad2, negfl, 0, s); + s = ""; + 'w' => + if (s != "") + s = str->drop(s, " \t"); + if (s == "") + fatal("no filename in w command: " + linebuf); + bo := bufio->open(s, bufio->OWRITE); + if (bo == nil) + bo = bufio->create(s, bufio->OWRITE, 8r666); + if (bo == nil) + fatal(sys->sprint("can't create output file: '%s'", s)); + bufioflush = bo :: bufioflush; + rep = ref Sedcom.W (ad1, ad2, negfl, 0, bo); + s = ""; + + 'd' => + rep = ref Sedcom.D (ad1, ad2, negfl, 0); + 'D' => + rep = ref Sedcom.CD (ad1, ad2, negfl, 0); + 'p' => + rep = ref Sedcom.P (ad1, ad2, negfl, 0); + 'P' => + rep = ref Sedcom.CP (ad1, ad2, negfl, 0); + 'q' => + rep = ref Sedcom.Q (ad1, ad2, negfl, 0); + '=' => + rep = ref Sedcom.EQ (ad1, ad2, negfl, 0); + 'g' => + rep = ref Sedcom.G (ad1, ad2, negfl, 0); + 'G' => + rep = ref Sedcom.CG (ad1, ad2, negfl, 0); + 'h' => + rep = ref Sedcom.H (ad1, ad2, negfl, 0); + 'H' => + rep = ref Sedcom.CH (ad1, ad2, negfl, 0); + 'n' => + rep = ref Sedcom.N (ad1, ad2, negfl, 0); + 'N' => + rep = ref Sedcom.CN (ad1, ad2, negfl, 0); + 'x' => + rep = ref Sedcom.X (ad1, ad2, negfl, 0); + 'l' => + rep = ref Sedcom.L (ad1, ad2, negfl, 0); + 'y' => + if (s == "") + fatal("expected args: " + linebuf); + seof := s[0:1]; + s = s[1:]; + if (s == "") + fatal("no lhs: " + linebuf); + (lhs, s2) := str->splitl(s, seof); + if (s2 == "") + fatal("no lhs terminator: " + linebuf); + s2 = s2[1:]; + (rhs, s4) := str->splitl(s2, seof); + if (s4 == "") + fatal("no rhs: " + linebuf); + s = s4[1:]; + if (len lhs != len rhs) + fatal("y command needs same length sets: " + linebuf); + map: list of (int, int); + for (i := 0; i < len lhs; i++) + map = (lhs[i], rhs[i]) :: map; + rep = ref Sedcom.Y (ad1, ad2, negfl, 0, map); + 's' => + seof := s[0:1]; + re: Re; + (re, s) = recomp(s); + rhs: string; + (s, rhs) = compsub(seof + s); + + gfl := gflag; + pfl := 0; + + if (s != "" && s[0] == 'g') { + gfl = 1; + s = s[1:]; + } + if (s != "" && s[0] == 'p') { + pfl = 1; + s = s[1:]; + } + if (s != "" && s[0] == 'P') { + pfl = 2; + s = s[1:]; + } + + b: ref Iobuf = nil; + if (s != "" && s[0] == 'w') { + s = s[1:]; + if (s != "") + s = str->drop(s, " \t"); + if (s == "") + fatal("no filename in s with w: " + linebuf); + b = bufio->open(s, bufio->OWRITE); + if (b == nil) + b = bufio->create(s, bufio->OWRITE, 8r666); + if (b == nil) + fatal(sys->sprint("can't create output file: '%s'", s)); + bufioflush = b :: bufioflush; + s = ""; + } + rep = ref Sedcom.S (ad1, ad2, negfl, 0, gfl, pfl, re, b, rhs); + ':' => + if (s != "") + s = str->drop(s, " \t"); + (lab, s1) := str->splitl(s, " \t;#"); + s = s1; + if (lab == "") + fatal(sys->sprint("null label: '%s'", linebuf)); + if (findlabel(lab)) + fatal(sys->sprint("duplicate label: '%s'", lab)); + rep = ref Sedcom.Lab (ad1, ad2, negfl, 0, lab); + 'b' or 't' => + if (s != "") + s = str->drop(s, " \t"); + (lab, s1) := str->splitl(s, " \t;#"); + s = s1; + if (c == 'b') + rep = ref Sedcom.B (ad1, ad2, negfl, 0, lab); + else + rep = ref Sedcom.T (ad1, ad2, negfl, 0, lab); + '{' => + # replace { with branch to }. + lab := mklab(depth); + depth++; + rep = ref Sedcom.B (ad1, ad2, !negfl, 0, lab); + s = ";" + s; + '}' => + if (tagof ad1 != tagof Addr.None) + fatal("did not expect address:" + linebuf); + if (--depth < 0) + fatal("too many }'s: " + linebuf); + lab := mklab(depth); + cmdcnt[depth]++; + rep = ref Sedcom.Lab ( ad1, ad2, negfl, 0, lab); + s = ";" + s; + } + + l = rep :: l; + } while (s != nil && str->in(s[0], ";{}")); + + if (s != nil) + fatal("leftover junk: " + s); + } + return l; +} + +findlabel(lab: string) : bool +{ + for (l := cmds; l != nil; l = tl l) + pick x := hd l { + Lab => + if (x.lab == lab) + return true; + } + return false; +} + +mklab(depth: int): string +{ + return "_" + string cmdcnt[depth] + "_" + string depth; +} + +Sedcom.command(c: self ref Sedcom) +{ + pick x := c { + S => + m: bool; + (m, patsp) = substitute(x, patsp); + if (m) { + case x.pfl { + 0 => + ; + 1 => + fout.puts(patsp + "\n"); + * => + l: string; + (l, patsp) = str->splitl(patsp, "\n"); + fout.puts(l + "\n"); + break; + } + if (x.b != nil) + x.b.puts(patsp + "\n"); + } + P => + fout.puts(patsp + "\n"); + CP => + (s, nil) := str->splitl(patsp, "\n"); + fout.puts(s + "\n"); + A => + appendlist = c :: appendlist; + R => + appendlist = c :: appendlist; + C => + delflag++; + if (c.active == 1) + fout.puts(x.text + "\n"); + I => + fout.puts(x.text + "\n"); + W => + x.b.puts(patsp + "\n"); + G => + patsp = holdsp; + CG => + patsp += holdsp; + H => + holdsp = patsp; + CH => + holdsp += patsp; + X => + (holdsp, patsp) = (patsp, holdsp); + Y => + # yes this is O(N²). + for (i := 0; i < len patsp; i++) + for (h := x.map; h != nil; h = tl h) { + (s, d) := hd h; + if (patsp[i] == s) + patsp[i] = d; + } + D => + delflag++; + CD => + # loose upto \n. + (s1, s2) := str->splitl(patsp, "\n"); + if (s2 == nil) + patsp = s1; + else if (len s2 > 1) + patsp = s2[1:]; + else + patsp = ""; + jflag++; + Q => + if (!nflag) + fout.puts(patsp + "\n"); + arout(); + exits(nil); + N => + if (!nflag) + fout.puts(patsp + "\n"); + arout(); + n: int; + (patsp, n) = gline(); + if (n < 0) + delflag++; + CN => + arout(); + (ns, n) := gline(); + if (n < 0) + delflag++; + patsp += "\n" + ns; + EQ => + fout.puts(sys->sprint("%d\n", lnum)); + Lab => + # labels don't do anything. + B => + jflag = true; + T => + if (sflag) { + sflag = false; + jflag = true; + } + L => + col := 0; + cc := 0; + for (i := 0; i < len patsp; i++) { + s := ""; + cc = patsp[i]; + if (cc >= 16r20 && cc < 16r7F && cc != '\n') + s[len s] = cc; + else + s = trans(cc); + for (j := 0; j < len s; j++) { + fout.putc(s[j]); + if (col++ > 71) { + fout.puts("\\\n"); + col = 0; + } + } + } + if (cc == ' ') + fout.puts("\\n"); + fout.putc('\n'); + * => + fatal("unhandled command"); + } +} + +trans(ch: int) : string +{ + case ch { + '\b' => + return "\\b"; + '\n' => + return "\\n"; + '\r' => + return "\\r"; + '\t' => + return "\\t"; + '\\' => + return "\\\\"; + * => + return sys->sprint("\\u%4x", ch); + } +} + +getline(b: ref Iobuf) : (int, string) +{ + w : string; + + lnum++; + + while ((c := b.getc()) != bufio->EOF) { + r := c; + if (r == '\\') { + w[len w] = r; + if ((c = b.getc()) == bufio->EOF) + break; + r = c; + } + else if (r == '\n') + return (1, w); + w[len w] = r; + } + return (-1, w); +} + +address(s: string) : (string, ref Addr) +{ + case s[0] { + '$' => + return (s[1:], ref Addr.Dollar()); + '/' => + (r, s1) := recomp(s); + if (r == nil) + r = lastregex; + if (r == nil) + fatal("First RE in address may not be null"); + return (s1, ref Addr.Regex(r)); + '0' to '9' => + (lno, ls) := str->toint(s, 10); + if (lno == 0) + fatal("line no 0 is illegal address"); + return (ls, ref Addr.Line(lno)); + * => + return (s, ref Addr.None()); + } +} + +recomp(s :string) : (Re, string) +{ + expbuf := ""; + + seof := s[0]; s = s[1:]; + if (s[0] == seof) + return (nil, s[1:]); # // + + c := s[0]; s = s[1:]; + do { + if (c == '\0' || c == '\n') + fatal("too much text: " + linebuf); + if (c == '\\') { + expbuf[len expbuf] = c; + c = s[0]; s = s[1:]; + if (c == 'n') + c = '\n'; + } + expbuf[len expbuf] = c; + c = s[0]; s = s[1:]; + } while (c != seof); + + (r, err) := regex->compile(expbuf, 1); + if (r == nil) + fatal(sys->sprint("%s '%s'", err, expbuf)); + + lastregex = r; + + return (r, s); +} + +compsub(s: string): (string, string) +{ + seof := s[0]; + rhs := ""; + for (i := 1; i < len s; i++) { + r := s[i]; + if (r == seof) + break; + if (r == '\\') { + rhs[len rhs] = r; + if(++i >= len s) + break; + r = s[i]; + } + rhs[len rhs] = r; + } + if (i >= len s) + fatal(sys->sprint("no closing %c in replacement text: %s", seof, linebuf)); + return (s[i+1:], rhs); +} + +execute(l: list of ref Sedcom) +{ + for (;;) { + n: int; + + (patsp, n) = gline(); + if (n < 0) + break; + +cmdloop: + for (p := l; p != nil;) { + c := hd p; + if (!c.executable()) { + p = tl p; + continue; + } + + c.command(); + + if (delflag) + break; + if (jflag) { + jflag = 0; + pick x := c { + B or T => + if (p == nil) + break cmdloop; + for (p = l; p != nil; p = tl p) { + pick cc := hd p { + Lab => + if (cc.lab == x.lab) + continue cmdloop; + } + } + break cmdloop; # unmatched branch => end of script + * => + # don't branch. + } + } + else + p = tl p; + } + if (!nflag && !delflag) + fout.puts(patsp + "\n"); + arout(); + delflag = 0; + } +} + +Sedcom.executable(c: self ref Sedcom) : int +{ + if (c.active) { + if (c.active == 1) + c.active = 2; + pick x := c.ad2 { + None => + c.active = 0; + Dollar => + return !c.negfl; + Line => + if (lnum <= x.line) { + if (x.line == lnum) + c.active = 0; + return !c.negfl; + } + c.active = 0; + return c.negfl; + Regex => + if (match(x.re, patsp)) + c.active = false; + return !c.negfl; + } + } + pick x := c.ad1 { + None => + return !c.negfl; + Dollar => + if (dolflag) + return !c.negfl; + Line => + if (x.line == lnum) { + c.active = 1; + return !c.negfl; + } + Regex => + if (match(x.re, patsp)) { + c.active = 1; + return !c.negfl; + } + } + return c.negfl; +} + +arout() +{ + a: list of ref Sedcom; + + while (appendlist != nil) { + a = hd appendlist :: a; + appendlist = tl appendlist; + } + + for (; a != nil; a = tl a) + pick x := hd a { + A => + fout.puts(x.text + "\n"); + R => + if ((b := bufio->open(x.filename, bufio->OREAD)) == nil) + fatal(sys->sprint("couldn't open '%s'", x.filename)); + while ((c := b.getc()) != bufio->EOF) + fout.putc(c); + b.close(); + * => + fatal("unexpected command on appendlist"); + } +} + +match(re: Re, s: string) : bool +{ + if (re != nil && regex->execute(re, s) != nil) + return true; + else + return false; +} + +substitute(c: ref Sedcom.S, s: string) : (bool, string) +{ + if (!match(c.re, s)) + return (false, s); + sflag = true; + start := 0; + + do { + se := (start, len s); + if ((m := regex->executese(c.re, s, se, true, true)) == nil) + break; + (l, r) := m[0]; + rep := ""; + for (i := 0; i < len c.rhs; i++){ + if (c.rhs[i] != '\\' ) + rep[len rep] = c.rhs[i]; + else { + i++; + case c.rhs[i] { + '0' to '9' => + n := c.rhs[i] - '0'; + # elide if too big + if (n < len m) { + (beg, end) := m[n]; + rep += s[beg:end]; + } + 'n' => + rep[len rep] = '\n'; + * => + rep[len rep] = c.rhs[i]; + } + } + } + s = s[0:l] + rep + s[r:]; + start++; + } while (c.gfl); + return (true, s); +} + +gline() : (string, int) +{ + if (infile == nil && opendatafile() < 0) + return (nil, -1); + + sflag = false; + lnum++; + + s := ""; + do { + c := peekc; + if (c == 0) + c = infile.getc(); + for (; c != bufio->EOF; c = infile.getc()) { + if (c == '\n') { + if ((peekc = infile.getc()) == bufio->EOF) + if (fhead == 0) + dolflag = 1; + return (s, 1); + } + s[len s] = c; + } + if (len s != 0) { + peekc = bufio->EOF; + if (fhead == 0) + dolflag = 1; + return (s, 1); + } + peekc = 0; + infile = nil; + } while (opendatafile() > 0); + infile = nil; + return (nil, -1); +} + +opendatafile() : int +{ + if (files == nil) + return -1; + if (hd files != nil) { + if ((infile = bufio->open(hd files, bufio->OREAD)) == nil) + fatal(sys->sprint("can't open '%s'", hd files)); + } + else if ((infile = bufio->fopen(sys->fildes(0), bufio->OREAD)) == nil) + fatal("can't buffer stdin"); + + files = tl files; + return 1; +} + +dbg(s: string) +{ + if (dflag) + sys->print("dbg: %s\n", s); +} + +usage() +{ + sys->fprint(stderr(), "usage: %s [-ngd] [-e expr] [-f file] [expr] [file...]\n", + arg->progname()); + exits("usage"); +} + +fatal(s: string) +{ + f := filename; + if (f == nil) + f = "<stdin>"; + sys->fprint(stderr(), "%s:%d %s\n", f, lnum, s); + exits("error"); +} + +exits(e: string) +{ + for(; bufioflush != nil; bufioflush = tl bufioflush) + (hd bufioflush).flush(); + if (e != nil) + raise "fail:" + e; + exit; +} + +stderr() : ref Sys->FD +{ + return sys->fildes(2); +} diff --git a/appl/cmd/sendmail.b b/appl/cmd/sendmail.b new file mode 100644 index 00000000..9b6d0c17 --- /dev/null +++ b/appl/cmd/sendmail.b @@ -0,0 +1,252 @@ +implement Sendmail; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "bufio.m"; +include "daytime.m"; +include "smtp.m"; +include "env.m"; + +sprint, fprint : import sys; + +DEBUG : con 0; +STRMAX : con 512; + +Sendmail : module +{ + PATH : con "/dis/sendmail.dis"; + + # argv is list of persons to send mail to (or nil if To: lines present in message) + # mail is read from standard input + # scans mail for headers (From: , To: , Cc: , Subject: , Re: ) where case is not sensitive + init: fn(ctxt : ref Draw->Context, argv : list of string); +}; + +init(nil : ref Draw->Context, args : list of string) { + from : string; + tos, cc : list of string = nil; + + sys = load Sys Sys->PATH; + smtp := load Smtp Smtp->PATH; + if (smtp == nil) + error(sprint("cannot load %s", Smtp->PATH), 1); + daytime := load Daytime Daytime->PATH; + if (daytime == nil) + error(sprint("cannot load %s", Daytime->PATH), 1); + msgl := readin(); + for (ml := msgl; ml != nil; ml = tl ml) { + msg := hd ml; + lenm := len msg; + sol := 1; + for (i := 0; i < lenm; i++) { + if (sol) { + for (j := i; j < lenm; j++) + if (msg[j] == '\n') + break; + s := msg[i:j]; + if (from == nil) { + from = match(s, "from"); + if (from != nil) + from = extract(from); + } + if (tos == nil) + tos = lmatch(s, "to"); + if (cc == nil) + cc = lmatch(s, "cc"); + sol = 0; + } + if (msg[i] == '\n') + sol = 1; + } + } + if (tos != nil && tl args != nil) + error("recipients specified on To: line and as args - aborted", 1); + if (from == nil) + from = readfile("/dev/user"); + from = adddom(from); + if (tos == nil) + tos = tl args; + (ok, err) := smtp->open(nil); + if (ok < 0) { + smtp->close(); + error(sprint("smtp open failed: %s", err), 1); + } + dump(from, tos, cc, msgl); + msgl = "From " + from + "\t" + daytime->time() + "\n" :: msgl; + # msgl = "From: " + from + "\n" + "Date: " + daytime->time() + "\n" :: msgl; + (ok, err) = smtp->sendmail(from, tos, cc, msgl); + if (ok < 0) { + smtp->close(); + error(sprint("send failed : %s", err), 0); + } + smtp->close(); +} + +readin() : list of string +{ + m : string; + ls : list of string; + nc : int; + + bufio := load Bufio Bufio->PATH; + Iobuf : import bufio; + b := bufio->fopen(sys->fildes(0), Bufio->OREAD); + ls = nil; + m = nil; + nc = 0; + while ((s := b.gets('\n')) != nil) { + if (nc > STRMAX) { + ls = m :: ls; + m = nil; + nc = 0; + } + m += s; + nc += len s; + } + b.close(); + if (m != nil) + ls = m :: ls; + return rev(ls); +} + +match(s: string, pat : string) : string +{ + ls := len s; + lp := len pat; + if (ls < lp) + return nil; + for (i := 0; i < lp; i++) { + c := s[i]; + if (c >= 'A' && c <= 'Z') + c += 'a'-'A'; + if (c != pat[i]) + return nil; + } + if (i < len s && s[i] == ':') + i++; + else if (i < len s - 1 && s[i] == ' ' && s[i+1] == ':') + i += 2; + else + return nil; + while (i < len s && (s[i] == ' ' || s[i] == '\t')) + i++; + j := ls-1; + while (j >= 0 && (s[j] == ' ' || s[j] == '\t' || s[j] == '\n')) + j--; + return s[i:j+1]; +} + +lmatch(s : string, pat : string) : list of string +{ + r := match(s, pat); + if (r != nil) { + (ok, lr) := sys->tokenize(r, " ,\t"); + return lr; + } + return nil; +} + +extract(s : string) : string +{ + ls := len s; + for(i := 0; i < ls; i++) { + if(s[i] == '<') { + for(j := i+1; j < ls; j++) + if(s[j] == '>') + break; + return s[i+1:j]; + } + } + return s; +} + +adddom(s : string) : string +{ + if (s == nil) + return nil; + for (i := 0; i < len s; i++) + if (s[i] == '@') + return s; + # better to get it from environment if possible + env := load Env Env->PATH; + if (env != nil && (dom := env->getenv("DOMAIN")) != nil) { + ldom := len dom; + if (dom[ldom - 1] == '\n') + dom = dom[0:ldom - 1]; + return s + "@" + dom; + } + d := readfile("/usr/" + s + "/mail/domain"); + if (d != nil) { + ld := len d; + if (d[ld - 1] == '\n') + d = d[0:ld - 1]; + return s + "@" + d; + } + return s; +} + +readfile(f : string) : string +{ + fd := sys->open(f, sys->OREAD); + if(fd == nil) + return nil; + buf := array[128] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return nil; + return string buf[0:n]; +} + +rev(l1 : list of string) : list of string +{ + l2 : list of string = nil; + + for ( ; l1 != nil; l1 = tl l1) + l2 = hd l1 :: l2; + return l2; +} + +lprint(fd : ref Sys->FD, ls : list of string) +{ + for ( ; ls != nil; ls = tl ls) + fprint(fd, "%s ", hd ls); + fprint(fd, "\n"); +} + +cfd : ref Sys->FD; + +opencons() +{ + if (cfd == nil) + cfd = sys->open("/dev/cons", Sys->OWRITE); +} + +dump(from : string, tos : list of string, cc : list of string, msgl : list of string) +{ + if (DEBUG) { + opencons(); + fprint(cfd, "from\n"); + fprint(cfd, "%s\n", from); + fprint(cfd, "to\n"); + lprint(cfd, tos); + fprint(cfd, "cc\n"); + lprint(cfd, cc); + fprint(cfd, "message\n"); + for ( ; msgl != nil; msgl = tl msgl) { + fprint(cfd, "%s", hd msgl); + fprint(cfd, "xxxx\n"); + } + } +} + +error(s : string, ex : int) +{ + if (DEBUG) { + opencons(); + fprint(cfd, "sendmail: %s\n", s); + } + fprint(sys->fildes(2), "sendmail: %s\n", s); + if (ex) + exit; +} diff --git a/appl/cmd/sh/arg.b b/appl/cmd/sh/arg.b new file mode 100644 index 00000000..a0b57b84 --- /dev/null +++ b/appl/cmd/sh/arg.b @@ -0,0 +1,181 @@ +implement Shellbuiltin; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("arg: cannot load self: %r")); + ctxt.addbuiltin("arg", myself); + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +runbuiltin(ctxt: ref Context, nil: Sh, + argv: list of ref Listnode, last: int): string +{ + case (hd argv).word { + "arg" => + return builtin_arg(ctxt, argv, last); + } + return nil; +} + +runsbuiltin(nil: ref Sh->Context, nil: Sh, + nil: list of ref Listnode): list of ref Listnode +{ + return nil; +} + +argusage(ctxt: ref Context) +{ + ctxt.fail("usage", "usage: arg [opts {command}]... - args"); +} + +builtin_arg(ctxt: ref Context, argv: list of ref Listnode, nil: int): string +{ + for (args := tl argv; args != nil; args = tl tl args) { + if ((hd args).word == "-") + break; + if ((hd args).cmd != nil && (hd args).word == nil) + argusage(ctxt); + if (tl args == nil) + argusage(ctxt); + if ((hd tl args).cmd == nil) + argusage(ctxt); + } + if (args == nil) + args = ctxt.get("*"); + else + args = tl args; + laststatus := ""; + ctxt.push(); + { + arg := Arg.init(args); + while ((opt := arg.opt()) != 0) { + for (argt := tl argv; argt != nil && (hd argt).word != "-"; argt = tl tl argt) { + w := (hd argt).word; + argcount := 0; + for (e := len w - 1; e >= 0; e--) { + if (w[e] != '+') + break; + argcount++; + } + w = w[0:e+1]; + if (w == nil) + continue; + for (i := 0; i < len w; i++) + if (w[i] == opt || w[i] == '*') + break; + if (i < len w) { + optstr := ""; optstr[0] = opt; + ctxt.setlocal("opt", ref Listnode(nil, optstr) :: nil); + args = arg.arg(argcount); + if (argcount > 0 && args == nil) + ctxt.fail("usage", sys->sprint("option -%c requires %d arguments", opt, argcount)); + ctxt.setlocal("arg", args); + laststatus = ctxt.run(hd tl argt :: nil, 0); + break; + } + } + if (argt == nil || (hd argt).word == "-") + ctxt.fail("usage", sys->sprint("unknown option -%c", opt)); + } + ctxt.pop(); + ctxt.set("args", arg.args); # XXX backward compatibility - should go + ctxt.set("*", arg.args); + return laststatus; + } + exception e{ + "fail:*" => + ctxt.pop(); + if (e[5:] == "break") + return laststatus; + raise e; + } +} + +Arg: adt { + args: list of ref Listnode; + curropt: string; + init: fn(argv: list of ref Listnode): ref Arg; + arg: fn(ctxt: self ref Arg, n: int): list of ref Listnode; + opt: fn(ctxt: self ref Arg): int; +}; + + +Arg.init(argv: list of ref Listnode): ref Arg +{ + return ref Arg(argv, nil); +} + +# get next n option arguments (nil list if not enough arguments found) +Arg.arg(ctxt: self ref Arg, n: int): list of ref Listnode +{ + if (n == 0) + return nil; + + args: list of ref Listnode; + while (--n >= 0) { + if (ctxt.curropt != nil) { + args = ref Listnode(nil, ctxt.curropt) :: args; + ctxt.curropt = nil; + } else if (ctxt.args == nil) + return nil; + else { + args = hd ctxt.args :: args; + ctxt.args = tl ctxt.args; + } + } + r: list of ref Listnode; + for (; args != nil; args = tl args) + r = hd args :: r; + return r; +} + +# get next option letter +# return 0 at end of options +Arg.opt(ctxt: self ref Arg): int +{ + if (ctxt.curropt != "") { + opt := ctxt.curropt[0]; + ctxt.curropt = ctxt.curropt[1:]; + return opt; + } + + if (ctxt.args == nil) + return 0; + + nextarg := (hd ctxt.args).word; + if (len nextarg < 2 || nextarg[0] != '-') + return 0; + + if (nextarg == "--") { + ctxt.args = tl ctxt.args; + return 0; + } + + opt := nextarg[1]; + if (len nextarg > 2) + ctxt.curropt = nextarg[2:]; + ctxt.args = tl ctxt.args; + return opt; +} diff --git a/appl/cmd/sh/csv.b b/appl/cmd/sh/csv.b new file mode 100644 index 00000000..601032d6 --- /dev/null +++ b/appl/cmd/sh/csv.b @@ -0,0 +1,244 @@ +implement Shellbuiltin; + +# parse/generate comma-separated values. + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("csv: cannot load self: %r")); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + ctxt.fail("bad module", + sys->sprint("csv: cannot load: %s: %r", Bufio->PATH)); + ctxt.addbuiltin("getcsv", myself); + ctxt.addsbuiltin("csv", myself); + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +runbuiltin(c: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode, last: int): string +{ + return builtin_getcsv(c, cmd, last); +} + +runsbuiltin(c: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode): list of ref Listnode +{ + return sbuiltin_csv(c, cmd); +} + +builtin_getcsv(ctxt: ref Context, argv: list of ref Listnode, nil: int) : string +{ + n := len argv; + if (n != 2 || !iscmd(hd tl argv)) + builtinusage(ctxt, "getcsv {cmd}"); + cmd := hd tl argv :: ctxt.get("*"); + stdin := bufio->fopen(sys->fildes(0), Sys->OREAD); + if (stdin == nil) + ctxt.fail("bad input", sys->sprint("getcsv: cannot open stdin: %r")); + status := ""; + ctxt.push(); + for(;;){ + { + for (;;) { + line: list of ref Listnode = nil; + sl := readcsvline(stdin); + if (sl == nil) + break; + for (; sl != nil; sl = tl sl) + line = ref Listnode(nil, hd sl) :: line; + ctxt.setlocal("line", line); + status = setstatus(ctxt, ctxt.run(cmd, 0)); + } + ctxt.pop(); + return status; + } + exception e{ + "fail:*" => + ctxt.pop(); + if (loopexcept(e) == BREAK) + return status; + ctxt.push(); + } + } +} + +CONTINUE, BREAK: con iota; +loopexcept(ename: string): int +{ + case ename[5:] { + "break" => + return BREAK; + "continue" => + return CONTINUE; + * => + raise ename; + } + return 0; +} + +iscmd(n: ref Listnode): int +{ + return n.cmd != nil || (n.word != nil && n.word[0] == '{'); +} + +builtinusage(ctxt: ref Context, s: string) +{ + ctxt.fail("usage", "usage: " + s); +} + +setstatus(ctxt: ref Context, val: string): string +{ + ctxt.setlocal("status", ref Listnode(nil, val) :: nil); + return val; +} + +# in csv format, is it possible to distinguish between a line containing +# one empty field and a line containing no fields at all? +# what does each one look like? +readcsvline(iob: ref Iobuf): list of string +{ + sl: list of string; + + for(;;) { + (s, eof) := readcsvword(iob); + if (sl == nil && s == nil && eof) + return nil; + + c := Bufio->EOF; + if (!eof) + c = iob.getc(); + sl = s :: sl; + if (c == '\n' || c == Bufio->EOF) + return sl; + } +} + +sbuiltin_csv(nil: ref Context, val: list of ref Listnode): list of ref Listnode +{ + val = tl val; + if (val == nil) + return nil; + s := s2qv(word(hd val)); + for (val = tl val; val != nil; val = tl val) + s += "," + s2qv(word(hd val)); + return ref Listnode(nil, s) :: nil; +} + +s2qv(s: string): string +{ + needquote := 0; + needscan := 0; + for (i := 0; i < len s; i++) { + c := s[i]; + if (c == '\n' || c == ',') + needquote = 1; + else if (c == '"') { + needquote = 1; + needscan = 1; + } + } + if (!needquote) + return s; + if (!needscan) + return "\"" + s + "\""; + r := "\""; + for (i = 0; i < len s; i++) { + c := s[i]; + if (c == '"') + r[len r] = c; + r[len r] = c; + } + r[len r] = '"'; + return r; +} + +readcsvword(iob: ref Iobuf): (string, int) +{ + s := ""; + case c := iob.getc() { + '"' => + for (;;) { + case c = iob.getc() { + Bufio->EOF => + return (s, 1); + '"' => + case c = iob.getc() { + '"' => + s[len s] = '"'; + '\n' or + ',' => + iob.ungetc(); + return (s, 0); + Bufio->EOF => + return (s, 1); + * => + # illegal + iob.ungetc(); + (t, eof) := readcsvword(iob); + return (s + t, eof); + } + * => + s[len s] = c; + } + } + ',' or + '\n' => + iob.ungetc(); + return (s, 0); + Bufio->EOF => + return (nil, 1); + * => + s[len s] = c; + for (;;) { + case c = iob.getc() { + ',' or + '\n' => + iob.ungetc(); + return (s, 0); + '"' => + # illegal + iob.ungetc(); + (t, eof) := readcsvword(iob); + return (s + t, eof); + Bufio->EOF => + return (s, 1); + * => + s[len s] = c; + } + } + } +} + +word(n: ref Listnode): string +{ + if (n.word != nil) + return n.word; + if (n.cmd != nil) + n.word = sh->cmd2string(n.cmd); + return n.word; +} diff --git a/appl/cmd/sh/doc/History b/appl/cmd/sh/doc/History new file mode 100644 index 00000000..5a9b4dca --- /dev/null +++ b/appl/cmd/sh/doc/History @@ -0,0 +1,14 @@ +14/11/96 started +12/12/96 first mostly working version +13/12/96 fixed bug in builtin_if +14/12/96 prompt fixed, dup fixed. +17/1/97 fiddled with shell script perm checking +16/2/97 converted to yacc grammar +18/2/97 got pipes and backquotes working, with only minor hacks... +2/4/00 revamped: + single process, single main module; added load builtin; added ${} operator; + added eval and std modules +17/4/00 added '=' and ':=' operators; removed builtin 'set' and 'local'. +11/6/00 added tuple assignment +2/3/01 added n-char lookahead in lexer; ':' no longer so special +15/2/01 store environment variables in standard quoted format. diff --git a/appl/cmd/sh/echo.b b/appl/cmd/sh/echo.b new file mode 100644 index 00000000..2fa85def --- /dev/null +++ b/appl/cmd/sh/echo.b @@ -0,0 +1,96 @@ +implement Shellbuiltin; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("echo: cannot load self: %r")); + ctxt.addbuiltin("echo", myself); + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +runbuiltin(ctxt: ref Context, nil: Sh, + argv: list of ref Listnode, last: int): string +{ + case (hd argv).word { + "echo" => + return builtin_echo(ctxt, argv, last); + } + return nil; +} + +runsbuiltin(nil: ref Sh->Context, nil: Sh, + nil: list of ref Listnode): list of ref Listnode +{ + return nil; +} + +argusage(ctxt: ref Context) +{ + ctxt.fail("usage", "usage: arg [opts {command}]... - args"); +} + +# converted from /appl/cmd/echo.b. +# should have exactly the same semantics. +builtin_echo(nil: ref Context, argv: list of ref Listnode, nil: int): string +{ + argv = tl argv; + nonewline := 0; + if (len argv > 0) { + w := (hd argv).word; + if (w == "-n" || w == "--") { + nonewline = (w == "-n"); + argv = tl argv; + } + } + s := ""; + if (argv != nil) { + s = word(hd argv); + for (argv = tl argv; argv != nil; argv = tl argv) + s += " " + word(hd argv); + } + if (nonewline == 0) + s[len s] = '\n'; + { + a := array of byte s; + if (sys->write(sys->fildes(1), a, len a) != len a) { + sys->fprint(sys->fildes(2), "echo: write error: %r\n"); + return "write error"; + } + return nil; + } + exception{ + "write on closed pipe" => + sys->fprint(sys->fildes(2), "echo: write error: write on closed pipe\n"); + return "write error"; + } +} + +word(n: ref Listnode): string +{ + if (n.word != nil) + return n.word; + if (n.cmd != nil) + n.word = sh->cmd2string(n.cmd); + return n.word; +} diff --git a/appl/cmd/sh/expr.b b/appl/cmd/sh/expr.b new file mode 100644 index 00000000..d613dce2 --- /dev/null +++ b/appl/cmd/sh/expr.b @@ -0,0 +1,281 @@ +implement Shellbuiltin; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("expr: cannot load self: %r")); + + ctxt.addsbuiltin("expr", myself); + ctxt.addbuiltin("ntest", myself); + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +EQ, GT, LT, GE, LE, PLUS, MINUS, DIVIDE, AND, TIMES, MOD, +OR, XOR, UMINUS, SHL, SHR, NOT, BNOT, NEQ, REP, SEQ: con iota; + +runbuiltin(ctxt: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode, nil: int): string +{ + case (hd cmd).word { + "ntest" => + if (len cmd != 2) + ctxt.fail("usage", "usage: ntest n"); + if (big (hd tl cmd).word == big 0) + return "false"; + } + return nil; +} + +runsbuiltin(ctxt: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode): list of ref Listnode +{ + # only one sbuiltin: expr. + stk: list of big; + lastop := -1; + lastn := -1; + lastname := ""; + radix: int; + (cmd, radix) = opts(ctxt, tl cmd); + for (; cmd != nil; cmd = tl cmd) { + w := (hd cmd).word; + op := -1; + nops := 2; + case w { + "+" => + op = PLUS; + "-" => + op = MINUS; + "x" or "*" or "×" => + op = TIMES; + "/" => + op = DIVIDE; + "%" => + op = MOD; + "and" => + op = AND; + "or" => + op = OR; + "xor" => + op = XOR; + "_"=> + (op, nops) = (UMINUS, 1); + "<<" or "shl" => + op = SHL; + ">>" or "shr" => + op = SHR; + "=" or "==" or "eq" => + op = EQ; + "!=" or "neq" => + op = NEQ; + ">" or "gt" => + op = GT; + "<" or "lt" => + op = LT; + ">=" or "ge" => + op = GE; + "<=" or "le" => + op = LE; + "!" or "not" => + (op, nops) = (NOT, 1); + "~" => + (op, nops) = (BNOT, 1); + "rep" => + (op, nops) = (REP, 0); + "seq" => + (op, nops) = (SEQ, 2); + } + if (op == -1) + stk = makenum(ctxt, w) :: stk; + else + stk = operator(ctxt, stk, op, nops, lastop, lastn, w, lastname); + lastop = op; + lastn = nops; + lastname = w; + } + r: list of ref Listnode; + for (; stk != nil; stk = tl stk) + r = ref Listnode(nil, big2string(hd stk, radix)) :: r; + return r; +} + +opts(ctxt: ref Context, cmd: list of ref Listnode): (list of ref Listnode, int) +{ + radix := 10; + if (cmd == nil) + return (nil, 10); + w := (hd cmd).word; + if (len w < 2) + return (cmd, 10); + if (w[0] != '-' || (w[1] >= '0' && w[1] <= '9')) + return (cmd, 10); + if (w[1] != 'r') + ctxt.fail("usage", "usage: expr [-r radix] [arg...]"); + if (len w > 2) + w = w[2:]; + else { + if (tl cmd == nil) + ctxt.fail("usage", "usage: expr [-r radix] [arg...]"); + cmd = tl cmd; + w = (hd cmd).word; + } + r := int w; + if (r <= 0 || r > 36) + ctxt.fail("usage", "expr: invalid radix " + string r); + return (tl cmd, int w); +} + +operator(ctxt: ref Context, stk: list of big, op, nops, lastop, lastn: int, + opname, lastopname: string): list of big +{ + al: list of big; + for (i := 0; i < nops; i++) { + if (stk == nil) + ctxt.fail("empty stack", + sys->sprint("expr: empty stack on op '%s'", opname)); + al = hd stk :: al; + stk = tl stk; + } + return oper(ctxt, al, op, lastop, lastn, lastopname, stk); +} + +# args are in reverse order +oper(ctxt: ref Context, args: list of big, op, lastop, lastn: int, + lastopname: string, stk: list of big): list of big +{ + if (op == REP) { + if (lastop == -1 || lastop == SEQ || lastn != 2) + ctxt.fail("usage", "expr: bad operator for rep"); + if (stk == nil || tl stk == nil) + return stk; + while (tl stk != nil) + stk = operator(ctxt, stk, lastop, 2, -1, -1, lastopname, nil); + return stk; + } + n2 := big 0; + n1 := hd args; + if (tl args != nil) + n2 = hd tl args; + r := big 0; + case op { + EQ => r = big(n1 == n2); + NEQ => r = big(n1 != n2); + GT => r = big(n1 > n2); + LT => r = big(n1 < n2); + GE => r = big(n1 >= n2); + LE => r = big(n1 <= n2); + PLUS => r = big(n1 + n2); + MINUS => r = big(n1 - n2); + NOT => r = big(n1 != big 0); + DIVIDE => + if (n2 == big 0) + ctxt.fail("divide by zero", "expr: division by zero"); + r = n1 / n2; + MOD => + if (n2 == big 0) + ctxt.fail("divide by zero", "expr: division by zero"); + r = n1 % n2; + TIMES => r = n1 * n2; + AND => r = n1 & n2; + OR => r = n1 | n2; + XOR => r = n1 ^ n2; + UMINUS => r = -n1; + BNOT => r = ~n1; + SHL => r = n1 << int n2; + SHR => r = n1 >> int n2; + SEQ => return seq(n1, n2, stk); + } + return r :: stk; +} + +seq(n1, n2: big, stk: list of big): list of big +{ + incr := big 1; + if (n2 < n1) + incr = big -1; + for (; n1 != n2; n1 += incr) + stk = n1 :: stk; + return n1 :: stk; +} + +makenum(ctxt: ref Context, s: string): big +{ + if (s == nil || (s[0] != '-' && (s[0] < '0' || s[0] > '9'))) + ctxt.fail("usage", sys->sprint("expr: unknown operator '%s'", s)); + + t := s; + if (neg := s[0] == '-') + s = s[1:]; + radix := 10; + for (i := 0; i < len s && i < 3; i++) { + if (s[i] == 'r') { + radix = int s; + s = s[i+1:]; + break; + } + } + if (radix == 10) + return big t; + if (radix == 0 || radix > 36) + ctxt.fail("usage", "expr: bad number " + t); + n := big 0; + for (i = 0; i < len s; i++) { + if ('0' <= s[i] && s[i] <= '9') + n = (n * big radix) + big(s[i] - '0'); + else if ('a' <= s[i] && s[i] < 'a' + radix - 10) + n = (n * big radix) + big(s[i] - 'a' + 10); + else if ('A' <= s[i] && s[i] < 'A' + radix - 10) + n = (n * big radix) + big(s[i] - 'A' + 10); + else + break; + } + if (neg) + return -n; + return n; +} + +big2string(n: big, radix: int): string +{ + if (neg := n < big 0) { + n = -n; + } + s := ""; + do { + c: int; + d := int (n % big radix); + if (d < 10) + c = '0' + d; + else + c = 'a' + d - 10; + s[len s] = c; + n /= big radix; + } while (n > big 0); + t := s; + for (i := len s - 1; i >= 0; i--) + t[len s - 1 - i] = s[i]; + if (radix != 10) + t = string radix + "r" + t; + if (neg) + return "-" + t; + return t; +} diff --git a/appl/cmd/sh/file2chan.b b/appl/cmd/sh/file2chan.b new file mode 100644 index 00000000..a54c9965 --- /dev/null +++ b/appl/cmd/sh/file2chan.b @@ -0,0 +1,459 @@ +implement Shellbuiltin; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "lock.m"; + lock: Lock; + Semaphore: import lock; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; + +Tag: adt { + tagid, blocked: int; + offset, fid: int; + pick { + Read => + count: int; + rc: chan of (array of byte, string); + Write => + data: array of byte; + wc: chan of (int, string); + } +}; + +taglock: ref Lock->Semaphore; +maxtagid := 1; +tags := array[16] of list of ref Tag; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("file2chan: cannot load self: %r")); + + lock = load Lock Lock->PATH; + if (lock == nil) ctxt.fail("bad module", sys->sprint("file2chan: cannot load %s: %r", Lock->PATH)); + lock->init(); + + taglock = Semaphore.new(); + if (taglock == nil) + ctxt.fail("no lock", "file2chan: cannot make lock"); + + + ctxt.addbuiltin("file2chan", myself); + ctxt.addbuiltin("rblock", myself); + ctxt.addbuiltin("rread", myself); + ctxt.addbuiltin("rreadone", myself); + ctxt.addbuiltin("rwrite", myself); + ctxt.addbuiltin("rerror", myself); + ctxt.addbuiltin("fetchwdata", myself); + ctxt.addbuiltin("putrdata", myself); + ctxt.addsbuiltin("rget", myself); + + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +runbuiltin(ctxt: ref Context, nil: Sh, + cmd: list of ref Listnode, nil: int): string +{ + case (hd cmd).word { + "file2chan" => return builtin_file2chan(ctxt, cmd); + "rblock" => return builtin_rblock(ctxt, cmd); + "rread" => return builtin_rread(ctxt, cmd, 0); + "rreadone" => return builtin_rread(ctxt, cmd, 1); + "rwrite" => return builtin_rwrite(ctxt, cmd); + "rerror" => return builtin_rerror(ctxt, cmd); + "fetchwdata" => return builtin_fetchwdata(ctxt, cmd); + "putrdata" => return builtin_putrdata(ctxt, cmd); + } + return nil; +} + +runsbuiltin(ctxt: ref Context, nil: Sh, + argv: list of ref Listnode): list of ref Listnode +{ + # could add ${rtags} to retrieve list of currently outstanding tags + case (hd argv).word { + "rget" => return sbuiltin_rget(ctxt, argv); + } + return nil; +} + +builtin_file2chan(ctxt: ref Context, argv: list of ref Listnode): string +{ + rcmd, wcmd, ccmd: ref Listnode; + path: string; + + n := len argv; + if (n < 4 || n > 5) + ctxt.fail("usage", "usage: file2chan file {readcmd} {writecmd} [ {closecmd} ]"); + + (path, argv) = ((hd tl argv).word, tl tl argv); + (rcmd, argv) = (hd argv, tl argv); + (wcmd, argv) = (hd argv, tl argv); + if (argv != nil) + ccmd = hd argv; + if (path == nil || !iscmd(rcmd) || !iscmd(wcmd) || (ccmd != nil && !iscmd(ccmd))) + ctxt.fail("usage", "usage: file2chan file {readcmd} {writecmd} [ {closecmd} ]"); + + (dir, f) := pathsplit(path); + if (sys->bind("#s", dir, Sys->MBEFORE|Sys->MCREATE) == -1) { + reporterror(ctxt, sys->sprint("file2chan: cannot bind #s: %r")); + return "no #s"; + } + fio := sys->file2chan(dir, f); + if (fio == nil) { + reporterror(ctxt, sys->sprint("file2chan: cannot make %s: %r", path)); + return "cannot make chan"; + } + sync := chan of int; + spawn srv(sync, ctxt, fio, rcmd, wcmd, ccmd); + apid := <-sync; + ctxt.set("apid", ref Listnode(nil, string apid) :: nil); + if (ctxt.options() & ctxt.INTERACTIVE) + sys->fprint(sys->fildes(2), "%d\n", apid); + return nil; +} + +srv(sync: chan of int, ctxt: ref Context, + fio: ref Sys->FileIO, rcmd, wcmd, ccmd: ref Listnode) +{ + ctxt = ctxt.copy(1); + sync <-= sys->pctl(0, nil); + for (;;) { + fid, offset, count: int; + rc: Sys->Rread; + wc: Sys->Rwrite; + d: array of byte; + t: ref Tag = nil; + cmd: ref Listnode = nil; + alt { + (offset, count, fid, rc) = <-fio.read => + if (rc != nil) { + t = ref Tag.Read(0, 0, offset, fid, count, rc); + cmd = rcmd; + } else + continue; # we get a close on both read and write... + (offset, d, fid, wc) = <-fio.write => + if (wc != nil) { + t = ref Tag.Write(0, 0, offset, fid, d, wc); + cmd = wcmd; + } + } + if (t != nil) { + addtag(t); + ctxt.setlocal("tag", ref Listnode(nil, string t.tagid) :: nil); + ctxt.run(cmd :: nil, 0); + taglock.obtain(); + # make a default reply if it hasn't been deliberately blocked. + del := 0; + if (t.tagid >= 0 && !t.blocked) { + pick mt := t { + Read => + rreply(mt.rc, nil, "invalid read"); + Write => + wreply(mt.wc, len mt.data, nil); + } + del = 1; + } + taglock.release(); + if (del) + deltag(t.tagid); + ctxt.setlocal("tag", nil); + } else if (ccmd != nil) { + t = ref Tag.Read(0, 0, -1, fid, -1, nil); + addtag(t); + ctxt.setlocal("tag", ref Listnode(nil, string t.tagid) :: nil); + ctxt.run(ccmd :: nil, 0); + deltag(t.tagid); + ctxt.setlocal("tag", nil); + } + } +} + +builtin_rread(ctxt: ref Context, argv: list of ref Listnode, one: int): string +{ + n := len argv; + if (n < 2 || n > 3) + ctxt.fail("usage", "usage: "+(hd argv).word+" [tag] data"); + argv = tl argv; + + t := envgettag(ctxt, argv, n == 3); + if (t == nil) + ctxt.fail("bad tag", "rread: cannot find tag"); + if (n == 3) + argv = tl argv; + mt := etr(ctxt, "rread", t); + arg := word(hd argv); + d := array of byte arg; + if (one) { + if (mt.offset >= len d) + d = nil; + else + d = d[mt.offset:]; + } + if (len d > mt.count) + d = d[0:mt.count]; + rreply(mt.rc, d, nil); + deltag(t.tagid); + return nil; +} + +builtin_rwrite(ctxt: ref Context, argv: list of ref Listnode): string +{ + n := len argv; + if (n > 3) + ctxt.fail("usage", "usage: rwrite [tag [count]]"); + t := envgettag(ctxt, tl argv, n > 1); + if (t == nil) + ctxt.fail("bad tag", "rwrite: cannot find tag"); + + mt := etw(ctxt, "rwrite", t); + count := len mt.data; + if (n == 3) { + arg := word(hd tl argv); + if (!isnum(arg)) + ctxt.fail("usage", "usage: freply [tag [count]]"); + count = int arg; + } + wreply(mt.wc, count, nil); + deltag(t.tagid); + return nil; +} + +builtin_rblock(ctxt: ref Context, argv: list of ref Listnode): string +{ + argv = tl argv; + if (len argv > 1) + ctxt.fail("usage", "usage: rblock [tag]"); + t := envgettag(ctxt, argv, argv != nil); + if (t == nil) + ctxt.fail("bad tag", "rblock: cannot find tag"); + t.blocked = 1; + return nil; +} + +sbuiltin_rget(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + n := len argv; + if (n < 2 || n > 3) + ctxt.fail("usage", "usage: rget (data|count|offset|fid) [tag]"); + argv = tl argv; + t := envgettag(ctxt, tl argv, tl argv != nil); + if (t == nil) + ctxt.fail("bad tag", "rget: cannot find tag"); + s := ""; + case (hd argv).word { + "data" => + s = string etw(ctxt, "rget", t).data; + "count" => + s = string etr(ctxt, "rget", t).count; + "offset" => + s = string t.offset; + "fid" => + s = string t.fid; + * => + ctxt.fail("usage", "usage: rget (data|count|offset|fid) [tag]"); + } + + return ref Listnode(nil, s) :: nil; +} + +builtin_fetchwdata(ctxt: ref Context, argv: list of ref Listnode): string +{ + argv = tl argv; + if (len argv > 1) + ctxt.fail("usage", "usage: fetchwdata [tag]"); + t := envgettag(ctxt, argv, argv != nil); + if (t == nil) + ctxt.fail("bad tag", "fetchwdata: cannot find tag"); + d := etw(ctxt, "fetchwdata", t).data; + sys->write(sys->fildes(1), d, len d); + return nil; +} + +builtin_putrdata(ctxt: ref Context, argv: list of ref Listnode): string +{ + argv = tl argv; + if (len argv > 1) + ctxt.fail("usage", "usage: putrdata [tag]"); + t := envgettag(ctxt, argv, argv != nil); + if (t == nil) + ctxt.fail("bad tag", "putrdata: cannot find tag"); + mt := etr(ctxt, "putrdata", t); + buf := array[mt.count] of byte; + n := 0; + fd := sys->fildes(0); + while (n < mt.count) { + nr := sys->read(fd, buf[n:mt.count], mt.count - n); + if (nr <= 0) + break; + n += nr; + } + + rreply(mt.rc, buf[0:n], nil); + deltag(t.tagid); + return nil; +} + +builtin_rerror(ctxt: ref Context, argv: list of ref Listnode): string +{ + # usage: ferror [tag] error + n := len argv; + if (n < 2 || n > 3) + ctxt.fail("usage", "usage: ferror [tag] error"); + t := envgettag(ctxt, tl argv, n == 3); + if (t == nil) + ctxt.fail("bad tag", "rerror: cannot find tag"); + if (n == 3) + argv = tl argv; + err := word(hd tl argv); + pick mt := t { + Read => + rreply(mt.rc, nil, err); + Write => + wreply(mt.wc, 0, err); + } + deltag(t.tagid); + return nil; +} + +envgettag(ctxt: ref Context, args: list of ref Listnode, useargs: int): ref Tag +{ + tagid: int; + if (useargs) + tagid = int (hd args).word; + else { + args = ctxt.get("tag"); + if (args == nil || tl args != nil) + return nil; + tagid = int (hd args).word; + } + return gettag(tagid); +} + +etw(ctxt: ref Context, cmd: string, t: ref Tag): ref Tag.Write +{ + pick mt := t { + Write => return mt; + } + ctxt.fail("bad tag", cmd + ": inappropriate tag id"); + return nil; +} + +etr(ctxt: ref Context, cmd: string, t: ref Tag): ref Tag.Read +{ + pick mt := t { + Read => return mt; + } + ctxt.fail("bad tag", cmd + ": inappropriate tag id"); + return nil; +} + +wreply(wc: chan of (int, string), count: int, err: string) +{ + alt { + wc <-= (count, err) => ; + * => ; + } +} + +rreply(rc: chan of (array of byte, string), d: array of byte, err: string) +{ + alt { + rc <-= (d, err) => ; + * => ; + } +} + +word(n: ref Listnode): string +{ + if (n.word != nil) + return n.word; + if (n.cmd != nil) + n.word = sh->cmd2string(n.cmd); + return n.word; +} + +isnum(s: string): int +{ + for (i := 0; i < len s; i++) + if (s[i] > '9' || s[i] < '0') + return 0; + return 1; +} + +iscmd(n: ref Listnode): int +{ + return n.cmd != nil || (n.word != nil && n.word[0] == '}'); +} + +addtag(t: ref Tag) +{ + taglock.obtain(); + t.tagid = maxtagid++; + slot := t.tagid % len tags; + tags[slot] = t :: tags[slot]; + taglock.release(); +} + +deltag(tagid: int) +{ + taglock.obtain(); + slot := tagid % len tags; + nwl: list of ref Tag; + for (wl := tags[slot]; wl != nil; wl = tl wl) + if ((hd wl).tagid != tagid) + nwl = hd wl :: nwl; + else + (hd wl).tagid = -1; + tags[slot] = nwl; + taglock.release(); +} + +gettag(tagid: int): ref Tag +{ + slot := tagid % len tags; + for (wl := tags[slot]; wl != nil; wl = tl wl) + if ((hd wl).tagid == tagid) + return hd wl; + return nil; +} + +pathsplit(p: string): (string, string) +{ + for (i := len p - 1; i >= 0; i--) + if (p[i] != '/') + break; + if (i < 0) + return (p, nil); + p = p[0:i+1]; + for (i = len p - 1; i >=0; i--) + if (p[i] == '/') + break; + if (i < 0) + return (".", p); + return (p[0:i+1], p[i+1:]); +} + +reporterror(ctxt: ref Context, err: string) +{ + if (ctxt.options() & ctxt.VERBOSE) + sys->fprint(sys->fildes(2), "%s\n", err); +} diff --git a/appl/cmd/sh/mkfile b/appl/cmd/sh/mkfile new file mode 100644 index 00000000..383c5ed9 --- /dev/null +++ b/appl/cmd/sh/mkfile @@ -0,0 +1,60 @@ +<../../../mkconfig + +TARG=sh.dis\ + arg.dis\ + expr.dis\ + file2chan.dis\ + regex.dis\ + sexprs.dis\ + std.dis\ + string.dis\ + tk.dis\ + echo.dis\ + csv.dis\ + test.dis\ + +INS= $ROOT/dis/sh.dis\ + $ROOT/dis/sh/arg.dis\ + $ROOT/dis/sh/expr.dis\ + $ROOT/dis/sh/regex.dis\ + $ROOT/dis/sh/std.dis\ + $ROOT/dis/sh/string.dis\ +# $ROOT/dis/sh/tk.dis\ + $ROOT/dis/sh/echo.dis\ + $ROOT/dis/sh/csv.dis\ + $ROOT/dis/sh/test.dis\ + +SYSMODULES=\ + bufio.m\ + draw.m\ + env.m\ + filepat.m\ + lock.m\ + sexprs.m\ + sh.m\ + string.m\ + sys.m\ + tk.m\ + tkclient.m\ + +DISBIN=$ROOT/dis/sh + +<$ROOT/mkfiles/mkdis + +all:V: $TARG + +install:V: $INS + +nuke:V: clean + rm -f $INS + +clean:V: + rm -f *.dis *.sbl + +uninstall:V: + rm -f $INS + +$ROOT/dis/sh.dis: sh.dis + rm -f $ROOT/dis/sh.dis && cp sh.dis $ROOT/dis/sh.dis + +%.dis: ${SYSMODULES:%=$MODDIR/%} diff --git a/appl/cmd/sh/regex.b b/appl/cmd/sh/regex.b new file mode 100644 index 00000000..e761a8ba --- /dev/null +++ b/appl/cmd/sh/regex.b @@ -0,0 +1,220 @@ +implement Shellbuiltin; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; +include "regex.m"; + regex: Regex; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("regex: cannot load self: %r")); + regex = load Regex Regex->PATH; + if (regex == nil) + ctxt.fail("bad module", + sys->sprint("regex: cannot load %s: %r", Regex->PATH)); + ctxt.addbuiltin("match", myself); + ctxt.addsbuiltin("re", myself); + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +runbuiltin(ctxt: ref Context, nil: Sh, + argv: list of ref Listnode, nil: int): string +{ + case (hd argv).word { + "match" => + return builtin_match(ctxt, argv); + } + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +runsbuiltin(ctxt: ref Context, nil: Sh, + argv: list of ref Listnode): list of ref Listnode +{ + name := (hd argv).word; + case name { + "re" => + return sbuiltin_re(ctxt, argv); + } + return nil; +} + +sbuiltin_re(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + if (tl argv == nil) + ctxt.fail("usage", "usage: re (g|v|s|sg|m|mg|M) arg..."); + argv = tl argv; + w := (hd argv).word; + case w { + "g" or + "v" => + return sbuiltin_sel(ctxt, argv, w == "v"); + "s" or + "sg" => + return sbuiltin_sub(ctxt, argv, w == "sg"); + "m" => + return sbuiltin_match(ctxt, argv, 0); + "mg" => + return sbuiltin_gmatch(ctxt, argv); + "M" => + return sbuiltin_match(ctxt, argv, 1); + * => + ctxt.fail("usage", "usage: re (g|v|s|sg|m|mg|M) arg..."); + return nil; + } +} + +sbuiltin_match(ctxt: ref Context, argv: list of ref Listnode, aflag: int): list of ref Listnode +{ + if (len argv != 3) + ctxt.fail("usage", "usage: re " + (hd argv).word + " arg"); + argv = tl argv; + re := getregex(ctxt, word(hd argv), aflag); + w := word(hd tl argv); + a := regex->execute(re, w); + if (a == nil) + return nil; + ret: list of ref Listnode; + for (i := len a - 1; i >= 0; i--) + ret = ref Listnode(nil, elem(a, i, w)) :: ret; + return ret; +} + +sbuiltin_gmatch(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + if (len argv != 3) + ctxt.fail("usage", "usage: re mg arg"); + argv = tl argv; + re := getregex(ctxt, word(hd argv), 0); + w := word(hd tl argv); + ret, nret: list of ref Listnode; + beg := 0; + while ((a := regex->executese(re, w, (beg, len w), beg == 0, 1)) != nil) { + (s, e) := a[0]; + ret = ref Listnode(nil, w[s:e]) :: ret; + if (s == e) + break; + beg = e; + } + for (; ret != nil; ret = tl ret) + nret = hd ret :: nret; + return nret; +} + +sbuiltin_sel(ctxt: ref Context, argv: list of ref Listnode, vflag: int): list of ref Listnode +{ + cmd := (hd argv).word; + argv = tl argv; + if (argv == nil) + ctxt.fail("usage", "usage: " + cmd + " regex [arg...]"); + re := getregex(ctxt, word(hd argv), 0); + ret, nret: list of ref Listnode; + for (argv = tl argv; argv != nil; argv = tl argv) + if (vflag ^ (regex->execute(re, word(hd argv)) != nil)) + ret = hd argv :: ret; + for (; ret != nil; ret = tl ret) + nret = hd ret :: nret; + return nret; +} + +sbuiltin_sub(ctxt: ref Context, argv: list of ref Listnode, gflag: int): list of ref Listnode +{ + cmd := (hd argv).word; + argv = tl argv; + if (argv == nil || tl argv == nil) + ctxt.fail("usage", "usage: " + cmd + " regex subs [arg...]"); + re := getregex(ctxt, word(hd argv), 1); + subs := word(hd tl argv); + ret, nret: list of ref Listnode; + for (argv = tl tl argv; argv != nil; argv = tl argv) + ret = ref Listnode(nil, substitute(word(hd argv), re, subs, gflag).t1) :: ret; + for (; ret != nil; ret = tl ret) + nret = hd ret :: nret; + return nret; +} + +builtin_match(ctxt: ref Context, argv: list of ref Listnode): string +{ + if (tl argv == nil) + ctxt.fail("usage", "usage: match regexp [arg...]"); + re := getregex(ctxt, word(hd tl argv), 0); + for (argv = tl tl argv; argv != nil; argv = tl argv) + if (regex->execute(re, word(hd argv)) == nil) + return "no match"; + return nil; +} + +substitute(w: string, re: Regex->Re, subs: string, gflag: int): (int, string) +{ + matched := 0; + s := ""; + beg := 0; + do { + a := regex->executese(re, w, (beg, len w), beg == 0, 1); + if (a == nil) + break; + matched = 1; + s += w[beg:a[0].t0]; + for (i := 0; i < len subs; i++) { + if (subs[i] != '\\' || i == len subs - 1) + s[len s] = subs[i]; + else { + c := subs[++i]; + if (c < '0' || c > '9') + s[len s] = c; + else + s += elem(a, c - '0', w); + } + } + beg = a[0].t1; + if (a[0].t0 == a[0].t1) + break; + } while (gflag && beg < len w); + return (matched, s + w[beg:]); +} + +elem(a: array of (int, int), i: int, w: string): string +{ + if (i < 0 || i >= len a) + return nil; # XXX could raise failure here. (invalid backslash escape) + (s, e) := a[i]; + if (s == -1) + return nil; + return w[s:e]; +} + +# XXX could do regex caching here if it was worth it. +getregex(ctxt: ref Context, res: string, flag: int): Regex->Re +{ + (re, err) := regex->compile(res, flag); + if (re == nil) + ctxt.fail("bad regex", "regex: bad regex \"" + res + "\": " + err); + return re; +} + +word(n: ref Listnode): string +{ + if (n.word != nil) + return n.word; + if (n.cmd != nil) + n.word = sh->cmd2string(n.cmd); + return n.word; +} diff --git a/appl/cmd/sh/sexprs.b b/appl/cmd/sh/sexprs.b new file mode 100644 index 00000000..1908078a --- /dev/null +++ b/appl/cmd/sh/sexprs.b @@ -0,0 +1,271 @@ +implement Shellbuiltin; + +# parse/generate sexprs. + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; +include "sexprs.m"; + sexprs: Sexprs; + Sexp: import sexprs; + +# getsexprs cmd +# islist val +# ${els se} +# ${text se} +# ${textels se} + +# ${mktext val} +# ${mklist [val...]} +# ${mktextlist [val...]} + +Maxerrs: con 10; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("sexpr: cannot load self: %r")); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + ctxt.fail("bad module", sys->sprint("sexpr: cannot load: %s: %r", Bufio->PATH)); + sexprs = load Sexprs Sexprs->PATH; + if(sexprs == nil) + ctxt.fail("bad module", sys->sprint("sexpr: cannot load: %s: %r", Sexprs->PATH)); + sexprs->init(); + ctxt.addbuiltin("getsexprs", myself); + ctxt.addbuiltin("islist", myself); + ctxt.addsbuiltin("els", myself); + ctxt.addsbuiltin("text", myself); + ctxt.addsbuiltin("b64", myself); + ctxt.addsbuiltin("textels", myself); + ctxt.addsbuiltin("mktext", myself); + ctxt.addsbuiltin("mklist", myself); + ctxt.addsbuiltin("mktextlist", myself); + + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +runbuiltin(c: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode, nil: int): string +{ + case (hd cmd).word { + "getsexprs" => + return builtin_getsexprs(c, tl cmd); + "islist" => + return builtin_islist(c, tl cmd); + } + return nil; +} + +runsbuiltin(c: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode): list of ref Listnode +{ + case (hd cmd).word { + "els" => + return sbuiltin_els(c, tl cmd); + "text" => + return sbuiltin_text(c, tl cmd); + "b64" => + return sbuiltin_b64(c, tl cmd); + "textels" => + return sbuiltin_textels(c, tl cmd); + "mktext" => + return sbuiltin_mktext(c, tl cmd); + "mklist" => + return sbuiltin_mklist(c, tl cmd); + "mktextlist" => + return sbuiltin_mktextlist(c, tl cmd); + } + return nil; +} + +builtin_getsexprs(ctxt: ref Context, argv: list of ref Listnode): string +{ + n := len argv; + if (n != 1 || !iscmd(hd argv)) + builtinusage(ctxt, "getsexprs {cmd}"); + cmd := hd argv :: ctxt.get("*"); + stdin := bufio->fopen(sys->fildes(0), Sys->OREAD); + if (stdin == nil) + ctxt.fail("bad input", sys->sprint("getsexprs: cannot open stdin: %r")); + status := ""; + nerrs := 0; + ctxt.push(); + for(;;){ + { + for (;;) { + (se, err) := Sexp.read(stdin); + if(err != nil){ + sys->fprint(sys->fildes(2), "getsexprs: error on read: %s\n", err); + if(++nerrs > Maxerrs) + raise "fail:too many errors"; + continue; + } + if(se == nil) + break; + nerrs = 0; + ctxt.setlocal("sexp", ref Listnode(nil, se.text()) :: nil); + status = setstatus(ctxt, ctxt.run(cmd, 0)); + } + ctxt.pop(); + return status; + }exception e{ + "fail:*" => + ctxt.pop(); + if (loopexcept(e) == BREAK) + return status; + ctxt.push(); + } + } +} + +builtin_islist(ctxt: ref Context, argv: list of ref Listnode): string +{ + if(argv == nil || tl argv != nil) + builtinusage(ctxt, "islist sexp"); + w := word(hd argv); + if(w != nil && w[0] =='(') + return nil; + if(parse(ctxt, hd argv).islist()) + return nil; + return "not a list"; +} + +CONTINUE, BREAK: con iota; +loopexcept(ename: string): int +{ + case ename[5:] { + "break" => + return BREAK; + "continue" => + return CONTINUE; + * => + raise ename; + } + return 0; +} + +iscmd(n: ref Listnode): int +{ + return n.cmd != nil || (n.word != nil && n.word[0] == '{'); +} + +builtinusage(ctxt: ref Context, s: string) +{ + ctxt.fail("usage", "usage: " + s); +} + +setstatus(ctxt: ref Context, val: string): string +{ + ctxt.setlocal("status", ref Listnode(nil, val) :: nil); + return val; +} + +sbuiltin_els(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode +{ + if (val == nil || tl val != nil) + builtinusage(ctxt, "els sexp"); + r, rr: list of ref Listnode; + for(els := parse(ctxt, hd val).els(); els != nil; els = tl els) + r = ref Listnode(nil, (hd els).text()) :: r; + for(; r != nil; r = tl r) + rr = hd r :: rr; + return rr; +} + +sbuiltin_text(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode +{ + if(val == nil || tl val != nil) + builtinusage(ctxt, "text sexp"); + return ref Listnode(nil, parse(ctxt, hd val).astext()) :: nil; +} + +sbuiltin_b64(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode +{ + if(val == nil || tl val != nil) + builtinusage(ctxt, "b64 sexp"); + return ref Listnode(nil, parse(ctxt, hd val).b64text()) :: nil; +} + +sbuiltin_textels(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode +{ + if (val == nil || tl val != nil) + builtinusage(ctxt, "textels sexp"); + r, rr: list of ref Listnode; + for(els := parse(ctxt, hd val).els(); els != nil; els = tl els) + r = ref Listnode(nil, (hd els).astext()) :: r; + for(; r != nil; r = tl r) + rr = hd r :: rr; + return rr; +} + +sbuiltin_mktext(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode +{ + if (val == nil || tl val != nil) + builtinusage(ctxt, "mktext sexp"); + return ref Listnode(nil, (ref Sexp.String(word(hd val), nil)).text()) :: nil; +} + +sbuiltin_mklist(nil: ref Context, val: list of ref Listnode): list of ref Listnode +{ + if(val == nil) + return ref Listnode(nil, "()") :: nil; + s := "(" + word(hd val); + for(val = tl val; val != nil; val = tl val) + s += " " + word(hd val); + s[len s] = ')'; + return ref Listnode(nil, s) :: nil; +} + +sbuiltin_mktextlist(nil: ref Context, val: list of ref Listnode): list of ref Listnode +{ + if(val == nil) + return ref Listnode(nil, "()") :: nil; + s := "(" + (ref Sexp.String(word(hd val), nil)).text(); + for(val = tl val; val != nil; val = tl val) + s += " " + (ref Sexp.String(word(hd val), nil)).text(); + s[len s] = ')'; + return ref Listnode(nil, s) :: nil; +} + +parse(ctxt: ref Context, val: ref Listnode): ref Sexp +{ + (se, rest, err) := Sexp.parse(word(val)); + if(rest != nil){ + for(i := 0; i < len rest; i++) + if(rest[i] != ' ' && rest[i] != '\t' && rest[i] != '\n') + ctxt.fail("bad sexp", sys->sprint("extra text found at end of s-expression %#q", word(val))); + } + if(err != nil) + ctxt.fail("bad sexp", err); + return se; +} + +word(n: ref Listnode): string +{ + if (n.word != nil) + return n.word; + if (n.cmd != nil) + n.word = sh->cmd2string(n.cmd); + return n.word; +} diff --git a/appl/cmd/sh/sh.b b/appl/cmd/sh/sh.b new file mode 100644 index 00000000..6040457f --- /dev/null +++ b/appl/cmd/sh/sh.b @@ -0,0 +1,2843 @@ +implement Sh; + +include "sys.m"; + sys: Sys; + sprint: import sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; +include "string.m"; + str: String; +include "filepat.m"; + filepat: Filepat; +include "env.m"; + env: Env; +include "sh.m"; + myself: Sh; + myselfbuiltin: Shellbuiltin; + +YYSTYPE: adt { + node: ref Node; + word: string; + + redir: ref Redir; + optype: int; +}; + +YYLEX: adt { + lval: YYSTYPE; + err: string; # if error has occurred + errline: int; # line it occurred on. + path: string; # name of file that's being read. + + # free caret state + wasdollar: int; + atendword: int; + eof: int; + cbuf: array of int; # last chars read + ncbuf: int; # number of chars in cbuf + + f: ref Bufio->Iobuf; + s: string; + strpos: int; # string pos/cbuf index + + linenum: int; + prompt: string; + lastnl: int; + + initstring: fn(s: string): ref YYLEX; + initfile: fn(fd: ref Sys->FD, path: string): ref YYLEX; + lex: fn(l: self ref YYLEX): int; + error: fn(l: self ref YYLEX, err: string); + getc: fn(l: self ref YYLEX): int; + ungetc: fn(l: self ref YYLEX); + + EOF: con -1; +}; + +Options: adt { + lflag, + nflag: int; + ctxtflags: int; + carg: string; +}; + + + # module definition is in shell.m +DUP: con 57346; +REDIR: con 57347; +WORD: con 57348; +OP: con 57349; +END: con 57350; +ERROR: con 57351; +ANDAND: con 57352; +OROR: con 57353; +YYEOFCODE: con 1; +YYERRCODE: con 2; +YYMAXDEPTH: con 200; + + + +EPERM: con "permission denied"; +EPIPE: con "write on closed pipe"; + +SHELLRC: con "lib/profile"; +LIBSHELLRC: con "/lib/sh/profile"; +BUILTINPATH: con "/dis/sh"; + +DEBUG: con 0; + +ENVSEP: con 0; # word seperator in external environment +ENVHASHSIZE: con 7; # XXX profile usage of this... +OAPPEND: con 16r80000; # make sure this doesn't clash with O* constants in sys.m +OMASK: con 7; + +usage() +{ + sys->fprint(stderr(), "usage: sh [-ilexn] [-c command] [file [arg...]]\n"); + raise "fail:usage"; +} + +badmodule(path: string) +{ + sys->fprint(sys->fildes(2), "sh: cannot load %s: %r\n", path); + raise "fail:bad module" ; +} + +initialise() +{ + if (sys == nil) { + sys = load Sys Sys->PATH; + + filepat = load Filepat Filepat->PATH; + if (filepat == nil) badmodule(Filepat->PATH); + + str = load String String->PATH; + if (str == nil) badmodule(String->PATH); + + bufio = load Bufio Bufio->PATH; + if (bufio == nil) badmodule(Bufio->PATH); + + myself = load Sh "$self"; + if (myself == nil) badmodule("$self(Sh)"); + + myselfbuiltin = load Shellbuiltin "$self"; + if (myselfbuiltin == nil) badmodule("$self(Shellbuiltin)"); + + env = load Env Env->PATH; + } +} +blankopts: Options; +init(drawcontext: ref Draw->Context, argv: list of string) +{ + initialise(); + opts := blankopts; + if (argv != nil) { + if ((hd argv)[0] == '-') + opts.lflag++; + argv = tl argv; + } + + interactive := 0; +loop: while (argv != nil && hd argv != nil && (hd argv)[0] == '-') { + for (i := 1; i < len hd argv; i++) { + c := (hd argv)[i]; + case c { + 'i' => + interactive = Context.INTERACTIVE; + 'l' => + opts.lflag++; # login (read $home/lib/profile) + 'n' => + opts.nflag++; # don't fork namespace + 'e' => + opts.ctxtflags |= Context.ERROREXIT; + 'x' => + opts.ctxtflags |= Context.EXECPRINT; + 'c' => + arg: string; + if (i < len hd argv - 1) { + arg = (hd argv)[i + 1:]; + } else if (tl argv == nil || hd tl argv == "") { + usage(); + } else { + arg = hd tl argv; + argv = tl argv; + } + argv = tl argv; + opts.carg = arg; + continue loop; + } + } + argv = tl argv; + } + + sys->pctl(Sys->FORKFD, nil); + if (!opts.nflag) + sys->pctl(Sys->FORKNS, nil); + ctxt := Context.new(drawcontext); + ctxt.setoptions(opts.ctxtflags, 1); + if (opts.carg != nil) { + status := ctxt.run(stringlist2list("{" + opts.carg + "}" :: argv), !interactive); + if (!interactive) { + if (status != nil) + raise "fail:" + status; + exit; + } + setstatus(ctxt, status); + } + + # if login shell, run standard init script + if (opts.lflag) + runscript(ctxt, LIBSHELLRC, nil, 0); + + if (argv == nil) { + if (opts.lflag) + runscript(ctxt, SHELLRC, nil, 0); + if (isconsole(sys->fildes(0))) + interactive |= ctxt.INTERACTIVE; + ctxt.setoptions(interactive, 1); + runfile(ctxt, sys->fildes(0), "stdin", nil); + } else { + ctxt.setoptions(interactive, 1); + runscript(ctxt, hd argv, stringlist2list(tl argv), 1); + } +} + +parse(s: string): (ref Node, string) +{ + initialise(); + + lex := YYLEX.initstring(s); + + return doparse(lex, "", 0); +} + +system(drawctxt: ref Draw->Context, cmd: string): string +{ + initialise(); + { + (n, err) := parse(cmd); + if (err != nil) + return err; + if (n == nil) + return nil; + return Context.new(drawctxt).run(ref Listnode(n, nil) :: nil, 0); + } exception e { + "fail:*" => + return e[5:]; + } +} + +run(drawctxt: ref Draw->Context, argv: list of string): string +{ + initialise(); + { + return Context.new(drawctxt).run(stringlist2list(argv), 0); + } exception e { + "fail:*" => + return e[5:]; + } +} + +isconsole(fd: ref Sys->FD): int +{ + (ok1, d1) := sys->fstat(fd); + (ok2, d2) := sys->stat("/dev/cons"); + if (ok1 < 0 || ok2 < 0) + return 0; + return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path; +} + +runscript(ctxt: ref Context, path: string, args: list of ref Listnode, reporterr: int) +{ + { + fd := sys->open(path, Sys->OREAD); + if (fd != nil) + runfile(ctxt, fd, path, args); + else if (reporterr) + ctxt.fail("bad script path", sys->sprint("sh: cannot open %s: %r", path)); + } exception e { + "fail:*" => + if(!reporterr) + return; + raise; + } +} + +runfile(ctxt: ref Context, fd: ref Sys->FD, path: string, args: list of ref Listnode) +{ + ctxt.push(); + { + ctxt.setlocal("0", stringlist2list(path :: nil)); + ctxt.setlocal("*", args); + lex := YYLEX.initfile(fd, path); + if (DEBUG) debug(sprint("parse(interactive == %d)", (ctxt.options() & ctxt.INTERACTIVE) != 0)); + prompt := "" :: "" :: nil; + laststatus: string; + while (!lex.eof) { + interactive := ctxt.options() & ctxt.INTERACTIVE; + if (interactive) { + prompt = list2stringlist(ctxt.get("prompt")); + if (prompt == nil) + prompt = "; " :: "" :: nil; + + sys->fprint(stderr(), "%s", hd prompt); + if (tl prompt == nil) { + prompt = hd prompt :: "" :: nil; + } + } + (n, err) := doparse(lex, hd tl prompt, !interactive); + if (err != nil) { + sys->fprint(stderr(), "sh: %s\n", err); + if (!interactive) + raise "fail:parse error"; + } else if (n != nil) { + if (interactive) { + { + laststatus = walk(ctxt, n, 0); + } exception e2 { + "fail:*" => + laststatus = e2[5:]; + } + } else + laststatus = walk(ctxt, n, 0); + setstatus(ctxt, laststatus); + if ((ctxt.options() & ctxt.ERROREXIT) && laststatus != nil) + break; + } + } + if (laststatus != nil) + raise "fail:" + laststatus; + ctxt.pop(); + } + exception e { + "fail:*" => + ctxt.pop(); + raise; + } +} + +nonexistent(e: string): int +{ + errs := array[] of {"does not exist", "directory entry not found"}; + for (i := 0; i < len errs; i++){ + j := len errs[i]; + if (j <= len e && e[len e-j:] == errs[i]) + return 1; + } + return 0; +} + +Redirword: adt { + fd: ref Sys->FD; + w: string; + r: Redir; +}; + +Redirlist: adt { + r: list of Redirword; +}; + +pipe2cmd(n: ref Node): ref Node +{ + if (n == nil || n.ntype != n_PIPE) + return n; + return mk(n_ADJ, mk(n_BLOCK,n,nil), mk(n_VAR,ref Node(n_WORD,nil,nil,"*",nil),nil)); +} + +walk(ctxt: ref Context, n: ref Node, last: int): string +{ + if (DEBUG) debug(sprint("walking: %s", cmd2string(n))); + # avoid tail recursion stack explosion + while (n != nil && n.ntype == n_SEQ) { + status := walk(ctxt, n.left, 0); + if (ctxt.options() & ctxt.ERROREXIT && status != nil) + raise "fail:" + status; + setstatus(ctxt, status); + n = n.right; + } + if (n == nil) + return nil; + case (n.ntype) { + n_PIPE => + return waitfor(ctxt, walkpipeline(ctxt, n, nil, -1)); + n_ASSIGN or n_LOCAL => + assign(ctxt, n); + return nil; + * => + bg := 0; + if (n.ntype == n_NOWAIT) { + bg = 1; + n = pipe2cmd(n.left); + } + + redirs := ref Redirlist(nil); + line := glob(glom(ctxt, n, redirs, nil)); + + if (bg) { + startchan := chan of (int, ref Expropagate); + spawn runasync(ctxt, 1, line, redirs, startchan); + (pid, nil) := <-startchan; + redirs = nil; + if (DEBUG) debug("started background process "+ string pid); + ctxt.set("apid", ref Listnode(nil, string pid) :: nil); + return nil; + } else { + return runsync(ctxt, line, redirs, last); + } + } +} + +assign(ctxt: ref Context, n: ref Node): list of ref Listnode +{ + redirs := ref Redirlist; + val: list of ref Listnode; + if (n.right != nil && (n.right.ntype == n_ASSIGN || n.right.ntype == n_LOCAL)) + val = assign(ctxt, n.right); + else + val = glob(glom(ctxt, n.right, redirs, nil)); + vars := glom(ctxt, n.left, redirs, nil); + if (vars == nil) + ctxt.fail("bad assign", "sh: nil variable name"); + if (redirs.r != nil) + ctxt.fail("bad assign", "sh: redirections not allowed in assignment"); + tval := val; + for (; vars != nil; vars = tl vars) { + vname := deglob((hd vars).word); + if (vname == nil) + ctxt.fail("bad assign", "sh: bad variable name"); + v: list of ref Listnode = nil; + if (tl vars == nil) + v = tval; + else if (tval != nil) + v = hd tval :: nil; + if (n.ntype == n_ASSIGN) + ctxt.set(vname, v); + else + ctxt.setlocal(vname, v); + if (tval != nil) + tval = tl tval; + } + return val; +} + +walkpipeline(ctxt: ref Context, n: ref Node, wrpipe: ref Sys->FD, wfdno: int): list of int +{ + if (n == nil) + return nil; + + fds := array[2] of ref Sys->FD; + pids: list of int; + rfdno := -1; + if (n.ntype == n_PIPE) { + if (sys->pipe(fds) == -1) + ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r")); + nwfdno := -1; + if (n.redir != nil) { + (fd1, fd2) := (n.redir.fd2, n.redir.fd1); + if (fd2 == -1) + (fd1, fd2) = (fd2, fd1); + (nwfdno, rfdno) = (fd2, fd1); + } + pids = walkpipeline(ctxt, n.left, fds[1], nwfdno); + fds[1] = nil; + n = n.right; + } + r := ref Redirlist(nil); + rlist := glob(glom(ctxt, n, r, nil)); + if (fds[0] != nil) { + if (rfdno == -1) + rfdno = 0; + r.r = Redirword(fds[0], nil, Redir(Sys->OREAD, rfdno, -1)) :: r.r; + } + if (wrpipe != nil) { + if (wfdno == -1) + wfdno = 1; + r.r = Redirword(wrpipe, nil, Redir(Sys->OWRITE, wfdno, -1)) :: r.r; + } + startchan := chan of (int, ref Expropagate); + spawn runasync(ctxt, 1, rlist, r, startchan); + (pid, nil) := <-startchan; + if (DEBUG) debug("started pipe process "+string pid); + return pid :: pids; +} + +makeredir(f: string, mode: int, fd: int): Redirword +{ + return Redirword(nil, f, Redir(mode, fd, -1)); +} + +glom(ctxt: ref Context, n: ref Node, redirs: ref Redirlist, onto: list of ref Listnode) + : list of ref Listnode +{ + if (n == nil) return nil; + + if (n.ntype != n_ADJ) + return listjoin(glomoperation(ctxt, n, redirs), onto); + + nlist := glom(ctxt, n.right, redirs, onto); + + if (n.left.ntype != n_ADJ) { + # if it's a terminal node + nlist = listjoin(glomoperation(ctxt, n.left, redirs), nlist); + } else + nlist = glom(ctxt, n.left, redirs, nlist); + return nlist; +} + +listjoin(left, right: list of ref Listnode): list of ref Listnode +{ + l: list of ref Listnode; + for (; left != nil; left = tl left) + l = hd left :: l; + for (; l != nil; l = tl l) + right = hd l :: right; + return right; +} + +glomoperation(ctxt: ref Context, n: ref Node, redirs: ref Redirlist): list of ref Listnode +{ + if (n == nil) + return nil; + + nlist: list of ref Listnode; + case n.ntype { + n_WORD => + nlist = ref Listnode(nil, n.word) :: nil; + n_REDIR => + wlist := glob(glom(ctxt, n.left, ref Redirlist(nil), nil)); + if (len wlist != 1 || (hd wlist).word == nil) + ctxt.fail("bad redir", "sh: single redirection operand required"); + + # add to redir list + redirs.r = Redirword(nil, (hd wlist).word, *n.redir) :: redirs.r; + n_DUP => + redirs.r = Redirword(nil, "", *n.redir) :: redirs.r; + n_LIST => + nlist = glom(ctxt, n.left, redirs, nil); + n_CONCAT => + nlist = concat(ctxt, glom(ctxt, n.left, redirs, nil), glom(ctxt, n.right, redirs, nil)); + n_VAR or n_SQUASH or n_COUNT => + arg := glom(ctxt, n.left, ref Redirlist(nil), nil); + if (len arg == 1 && (hd arg).cmd != nil) + nlist = subsbuiltin(ctxt, (hd arg).cmd.left); + else if (len arg != 1 || (hd arg).word == nil) + ctxt.fail("bad $ arg", "sh: bad variable name"); + else + nlist = ctxt.get(deglob((hd arg).word)); + case n.ntype { + n_VAR =>; + n_COUNT => + nlist = ref Listnode(nil, string len nlist) :: nil; + n_SQUASH => + # XXX could squash with first char of $ifs, perhaps + nlist = ref Listnode(nil, squash(list2stringlist(nlist), " ")) :: nil; + } + n_BQ or n_BQ2 => + arg := glom(ctxt, n.left, ref Redirlist(nil), nil); + seps := ""; + if (n.ntype == n_BQ) { + seps = squash(list2stringlist(ctxt.get("ifs")), ""); + if (seps == nil) + seps = " \t\n\r"; + } + (nlist, nil) = bq(ctxt, glob(arg), seps); + n_BLOCK => + nlist = ref Listnode(n, "") :: nil; + n_ASSIGN or n_LOCAL => + ctxt.fail("bad assign", "sh: assignment in invalid context"); + * => + panic("bad node type "+string n.ntype+" in glomop"); + } + return nlist; +} + +subsbuiltin(ctxt: ref Context, n: ref Node): list of ref Listnode +{ + if (n == nil || n.ntype == n_SEQ || + n.ntype == n_PIPE || n.ntype == n_NOWAIT) + ctxt.fail("bad $ arg", "sh: invalid argument to ${} operator"); + r := ref Redirlist; + cmd := glob(glom(ctxt, n, r, nil)); + if (r.r != nil) + ctxt.fail("bad $ arg", "sh: redirection not allowed in substitution"); + r = nil; + if (cmd == nil || (hd cmd).word == nil || (hd cmd).cmd != nil) + ctxt.fail("bad $ arg", "sh: bad builtin name"); + + (nil, bmods) := findbuiltin(ctxt.env.sbuiltins, (hd cmd).word); + if (bmods == nil) + ctxt.fail("builtin not found", + sys->sprint("sh: builtin %s not found", (hd cmd).word)); + return (hd bmods)->runsbuiltin(ctxt, myself, cmd); +} + + +getbq(nil: ref Context, fd: ref Sys->FD, seps: string): list of ref Listnode +{ + buf := array[Sys->ATOMICIO] of byte; + buflen := 0; + while ((n := sys->read(fd, buf[buflen:], len buf - buflen)) > 0) { + buflen += n; + if (buflen == len buf) { + nbuf := array[buflen * 2] of byte; + nbuf[0:] = buf[0:]; + buf = nbuf; + } + } + l: list of string; + if (seps != nil) + (nil, l) = sys->tokenize(string buf[0:buflen], seps); + else + l = string buf[0:buflen] :: nil; + buf = nil; + return stringlist2list(l); +} + +bq(ctxt: ref Context, cmd: list of ref Listnode, seps: string): (list of ref Listnode, string) +{ + fds := array[2] of ref Sys->FD; + if (sys->pipe(fds) == -1) + ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r")); + + r := rdir(fds[1]); + fds[1] = nil; + startchan := chan of (int, ref Expropagate); + spawn runasync(ctxt, 0, cmd, r, startchan); + (exepid, exprop) := <-startchan; + r = nil; + bqlist := getbq(ctxt, fds[0], seps); + waitfor(ctxt, exepid :: nil); + if (exprop.name != nil) + raise exprop.name; + return (bqlist, nil); +} + +rdir(fd: ref Sys->FD): ref Redirlist +{ + return ref Redirlist(Redirword(fd, nil, Redir(Sys->OWRITE, 1, -1)) :: nil); +} + + +concatwords(p1, p2: ref Listnode): ref Listnode +{ + if (p1.word == nil && p1.cmd != nil) + p1.word = cmd2string(p1.cmd); + if (p2.word == nil && p2.cmd != nil) + p2.word = cmd2string(p2.cmd); + return ref Listnode(nil, p1.word + p2.word); +} + +concat(ctxt: ref Context, nl1, nl2: list of ref Listnode): list of ref Listnode +{ + if (nl1 == nil || nl2 == nil) { + if (nl1 == nil && nl2 == nil) + return nil; + ctxt.fail("bad concatenation", "sh: null list in concatenation"); + } + + ret: list of ref Listnode; + if (tl nl1 == nil || tl nl2 == nil) { + for (p1 := nl1; p1 != nil; p1 = tl p1) + for (p2 := nl2; p2 != nil; p2 = tl p2) + ret = concatwords(hd p1, hd p2) :: ret; + } else { + if (len nl1 != len nl2) + ctxt.fail("bad concatenation", "sh: lists of differing sizes can't be concatenated"); + while (nl1 != nil) { + ret = concatwords(hd nl1, hd nl2) :: ret; + (nl1, nl2) = (tl nl1, tl nl2); + } + } + return revlist(ret); +} + +Expropagate: adt { + name: string; +}; + +runasync(ctxt: ref Context, copyenv: int, argv: list of ref Listnode, redirs: ref Redirlist, + startchan: chan of (int, ref Expropagate)) +{ + status: string; + + pid := sys->pctl(sys->FORKFD, nil); + if (DEBUG) debug(sprint("in async (len redirs: %d)", len redirs.r)); + ctxt = ctxt.copy(copyenv); + exprop := ref Expropagate; + { + newfdl := doredirs(ctxt, redirs); + redirs = nil; + if (newfdl != nil) + sys->pctl(Sys->NEWFD, newfdl); + # stop the old waitfd from holding the intermediate + # file descriptor group open. + ctxt.waitfd = waitfd(); + # N.B. it's important that the sync is done here, not + # before doredirs, as otherwise there's some sort of + # race condition that leads to pipe non-completion. + startchan <-= (pid, exprop); + startchan = nil; + status = ctxt.run(argv, copyenv); + } exception e { + "fail:*" => + exprop.name = e; + if (startchan != nil) + startchan <-= (pid, exprop); + raise e; + } + if (status != nil) { + # don't propagate bad status as an exception. + raise "fail:" + status; + } +} + +runsync(ctxt: ref Context, argv: list of ref Listnode, + redirs: ref Redirlist, last: int): string +{ + if (DEBUG) debug(sys->sprint("in sync (len redirs: %d; last: %d)", len redirs.r, last)); + if (redirs.r != nil && !last) { + # a new process is required to shield redirection side effects + startchan := chan of (int, ref Expropagate); + spawn runasync(ctxt, 0, argv, redirs, startchan); + (pid, exprop) := <-startchan; + redirs = nil; + r := waitfor(ctxt, pid :: nil); + if (exprop.name != nil) + raise exprop.name; + return r; + } else { + newfdl := doredirs(ctxt, redirs); + redirs = nil; + if (newfdl != nil) + sys->pctl(Sys->NEWFD, newfdl); + return ctxt.run(argv, last); + } +} + +absolute(p: string): int +{ + if (len p < 2) + return 0; + if (p[0] == '/' || p[0] == '#') + return 1; + if (len p < 3 || p[0] != '.') + return 0; + if (p[1] == '/') + return 1; + if (p[1] == '.' && p[2] == '/') + return 1; + return 0; +} + +runexternal(ctxt: ref Context, args: list of ref Listnode, last: int): string +{ + progname := (hd args).word; + disfile := 0; + if (len progname >= 4 && progname[len progname-4:] == ".dis") + disfile = 1; + pathlist: list of string; + if (absolute(progname)) + pathlist = list of {""}; + else if ((pl := ctxt.get("path")) != nil) + pathlist = list2stringlist(pl); + else + pathlist = list of {"/dis", "."}; + + err := ""; + do { + path: string; + if (hd pathlist != "") + path = hd pathlist + "/" + progname; + else + path = progname; + + npath := path; + if (!disfile) + npath += ".dis"; + mod := load Command npath; + if (mod != nil) { + argv := list2stringlist(args); + export(ctxt.env.localenv); + + if (last) { + { + sys->pctl(Sys->NEWFD, ctxt.keepfds); + mod->init(ctxt.drawcontext, argv); + exit; + } exception e { + EPIPE => + return EPIPE; + "fail:*" => + return e[5:]; + } + } + extstart := chan of int; + spawn externalexec(mod, ctxt.drawcontext, argv, extstart, ctxt.keepfds); + pid := <-extstart; + if (DEBUG) debug("started external externalexec; pid is "+string pid); + return waitfor(ctxt, pid :: nil); + } + err = sys->sprint("%r"); + if (nonexistent(err)) { + # try and run it as a shell script + if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) { + (ok, info) := sys->fstat(fd); + # make permission checking more accurate later + if (ok == 0 && (info.mode & Sys->DMDIR) == 0 + && (info.mode & 8r111) != 0) + return runhashpling(ctxt, fd, path, tl args, last); + }; + err = sys->sprint("%r"); + } + pathlist = tl pathlist; + } while (pathlist != nil && nonexistent(err)); + diagnostic(ctxt, sys->sprint("%s: %s", progname, err)); + return err; +} + +runhashpling(ctxt: ref Context, fd: ref Sys->FD, + path: string, argv: list of ref Listnode, last: int): string +{ + header := array[1024] of byte; + n := sys->read(fd, header, len header); + for (i := 0; i < n; i++) + if (header[i] == byte '\n') + break; + if (i == n || i < 3 || header[0] != byte('#') || header[1] != byte('!')) { + diagnostic(ctxt, "bad script header on " + path); + return "bad header"; + } + (nil, args) := sys->tokenize(string header[2:i], " \t"); + if (args == nil) { + diagnostic(ctxt, "empty header on " + path); + return "bad header"; + } + header = nil; + fd = nil; + nargs: list of ref Listnode; + for (; args != nil; args = tl args) + nargs = ref Listnode(nil, hd args) :: nargs; + nargs = ref Listnode(nil, path) :: nargs; + for (; argv != nil; argv = tl argv) + nargs = hd argv :: nargs; + return runexternal(ctxt, revlist(nargs), last); +} + +runblock(ctxt: ref Context, args: list of ref Listnode, last: int): string +{ + # block execute (we know that hd args represents a block) + cmd := (hd args).cmd; + if (cmd == nil) { + # parse block from first argument + lex := YYLEX.initstring((hd args).word); + + err: string; + (cmd, err) = doparse(lex, "", 0); + if (cmd == nil) + ctxt.fail("parse error", "sh: "+err); + + (hd args).cmd = cmd; + } + # now we've got a parsed block + ctxt.push(); + { + ctxt.setlocal("0", hd args :: nil); + ctxt.setlocal("*", tl args); + if (cmd != nil && cmd.ntype == n_BLOCK) + cmd = cmd.left; + status := walk(ctxt, cmd, last); + ctxt.pop(); + return status; + } exception e{ + "fail:*" => + ctxt.pop(); + raise; + } +} + +trybuiltin(ctxt: ref Context, args: list of ref Listnode, lseq: int) + : (int, string) +{ + (n, bmods) := findbuiltin(ctxt.env.builtins, (hd args).word); + if (bmods == nil) + return (0, nil); + return (1, (hd bmods)->runbuiltin(ctxt, myself, args, lseq)); +} + +keepfdstr(ctxt: ref Context): string +{ + s := ""; + for (f := ctxt.keepfds; f != nil; f = tl f) { + s += string hd f; + if (tl f != nil) + s += ","; + } + return s; +} + +externalexec(mod: Command, + drawcontext: ref Draw->Context, argv: list of string, startchan: chan of int, keepfds: list of int) +{ + if (DEBUG) debug(sprint("externalexec(%s,... [%d args])", hd argv, len argv)); + sys->pctl(Sys->NEWFD, keepfds); + startchan <-= sys->pctl(0, nil); + { + mod->init(drawcontext, argv); + } + exception e{ + EPIPE => + raise "fail:" + EPIPE; + } +} + +dup(ctxt: ref Context, fd1, fd2: int): int +{ + # shuffle waitfd out of the way if it's being attacked + if (ctxt.waitfd.fd == fd2) { + ctxt.waitfd = waitfd(); + if (ctxt.waitfd.fd == fd2) + panic(sys->sprint("reopen of waitfd gave same fd (%d)", ctxt.waitfd.fd)); + } + return sys->dup(fd1, fd2); +} + +doredirs(ctxt: ref Context, redirs: ref Redirlist): list of int +{ + if (redirs.r == nil) + return nil; + keepfds := ctxt.keepfds; + rl := redirs.r; + redirs = nil; + for (; rl != nil; rl = tl rl) { + (rfd, path, (mode, fd1, fd2)) := hd rl; + if (path == nil && rfd == nil) { + # dup + if (fd1 == -1 || fd2 == -1) + ctxt.fail("bad redir", "sh: invalid dup"); + + if (dup(ctxt, fd2, fd1) == -1) + ctxt.fail("bad redir", sys->sprint("sh: cannot dup: %r")); + keepfds = fd1 :: keepfds; + continue; + } + # redir + if (fd1 == -1) { + if ((mode & OMASK) == Sys->OWRITE) + fd1 = 1; + else + fd1 = 0; + } + if (rfd == nil) { + (append, omode) := (mode & OAPPEND, mode & ~OAPPEND); + err := ""; + case mode { + Sys->OREAD => + rfd = sys->open(path, omode); + Sys->OWRITE | OAPPEND or + Sys->ORDWR => + rfd = sys->open(path, omode); + err = sprint("%r"); + if (rfd == nil && nonexistent(err)) { + rfd = sys->create(path, omode, 8r666); + err = nil; + } + Sys->OWRITE => + rfd = sys->create(path, omode, 8r666); + err = sprint("%r"); + if (rfd == nil && err == EPERM) { + # try open; can't create on a file2chan (pipe) + rfd = sys->open(path, omode); + nerr := sprint("%r"); + if(!nonexistent(nerr)) + err = nerr; + } + } + if (rfd == nil) { + if (err == nil) + err = sprint("%r"); + ctxt.fail("bad redir", sys->sprint("sh: cannot open %s: %s", path, err)); + } + if (append) + sys->seek(rfd, big 0, Sys->SEEKEND); # not good enough, but alright for some purposes. + } + # XXX what happens if rfd.fd == fd1? + # it probably gets closed automatically... which is not what we want! + dup(ctxt, rfd.fd, fd1); + keepfds = fd1 :: keepfds; + } + ctxt.keepfds = keepfds; + return ctxt.waitfd.fd :: keepfds; +} + + +waitfd(): ref Sys->FD +{ + wf := string sys->pctl(0, nil) + "/wait"; + waitfd := sys->open("#p/"+wf, Sys->OREAD); + if (waitfd == nil) + waitfd = sys->open("/prog/"+wf, Sys->OREAD); + if (waitfd == nil) + panic(sys->sprint("cannot open wait file: %r")); + return waitfd; +} + +waitfor(ctxt: ref Context, pids: list of int): string +{ + if (pids == nil) + return nil; + status := array[len pids] of string; + wcount := len status; + buf := array[Sys->WAITLEN] of byte; + onebad := 0; + for(;;){ + n := sys->read(ctxt.waitfd, buf, len buf); + if(n < 0) + panic(sys->sprint("error on wait read: %r")); + (who, line, s) := parsewaitstatus(ctxt, string buf[0:n]); + if (s != nil) { + if (len s >= 5 && s[0:5] == "fail:") + s = s[5:]; + else + diagnostic(ctxt, line); + } + for ((i, pl) := (0, pids); pl != nil; (i, pl) = (i+1, tl pl)) + if (who == hd pl) + break; + if (i < len status) { + # wait returns two records for a killed process... + if (status[i] == nil || s != "killed") { + onebad += s != nil; + status[i] = s; + if (wcount-- <= 1) + break; + } + } + } + if (!onebad) + return nil; + r := status[len status - 1]; + for (i := len status - 2; i >= 0; i--) + r += "|" + status[i]; + return r; +} + +parsewaitstatus(ctxt: ref Context, status: string): (int, string, string) +{ + for (i := 0; i < len status; i++) + if (status[i] == ' ') + break; + if (i == len status - 1 || status[i+1] != '"') + ctxt.fail("bad wait read", + sys->sprint("sh: bad exit status '%s'", status)); + + for (i+=2; i < len status; i++) + if (status[i] == '"') + break; + if (i > len status - 2 || status[i+1] != ':') + ctxt.fail("bad wait read", + sys->sprint("sh: bad exit status '%s'", status)); + + return (int status, status, status[i+2:]); +} + +panic(s: string) +{ + sys->fprint(stderr(), "sh panic: %s\n", s); + raise "panic"; +} + +diagnostic(ctxt: ref Context, s: string) +{ + if (ctxt.options() & Context.VERBOSE) + sys->fprint(stderr(), "sh: %s\n", s); +} + + +Context.new(drawcontext: ref Draw->Context): ref Context +{ + initialise(); + if (env != nil) + env->clone(); + ctxt := ref Context( + ref Environment( + ref Builtins(nil, 0), + ref Builtins(nil, 0), + nil, + newlocalenv(nil) + ), + waitfd(), + drawcontext, + 0 :: 1 :: 2 :: nil + ); + myselfbuiltin->initbuiltin(ctxt, myself); + ctxt.env.localenv.flags = ctxt.VERBOSE; + for (vl := ctxt.get("autoload"); vl != nil; vl = tl vl) + if ((hd vl).cmd == nil && (hd vl).word != nil) + loadmodule(ctxt, (hd vl).word); + return ctxt; +} + +Context.copy(ctxt: self ref Context, copyenv: int): ref Context +{ + # XXX could check to see that we are definitely in a + # new process, because there'll be problems if not (two processes + # simultaneously reading the same wait file) + nctxt := ref Context(ctxt.env, waitfd(), ctxt.drawcontext, ctxt.keepfds); + + if (copyenv) { + if (env != nil) + env->clone(); + nctxt.env = ref Environment( + copybuiltins(ctxt.env.sbuiltins), + copybuiltins(ctxt.env.builtins), + ctxt.env.bmods, + copylocalenv(ctxt.env.localenv) + ); + } + return nctxt; +} + +Context.set(ctxt: self ref Context, name: string, val: list of ref Listnode) +{ + e := ctxt.env.localenv; + idx := hashfn(name, len e.vars); + for (;;) { + v := hashfind(e.vars, idx, name); + if (v == nil) { + if (e.pushed == nil) { + flags := Var.CHANGED; + if (noexport(name)) + flags |= Var.NOEXPORT; + hashadd(e.vars, idx, ref Var(name, val, flags)); + return; + } + } else { + v.val = val; + v.flags |= Var.CHANGED; + return; + } + e = e.pushed; + } +} + +Context.get(ctxt: self ref Context, name: string): list of ref Listnode +{ + if (name == nil) + return nil; + + idx := -1; + # cope with $1, $2, etc + if (name[0] > '0' && name[0] <= '9') { + i: int; + for (i = 0; i < len name; i++) + if (name[i] < '0' || name[i] > '9') + break; + if (i >= len name) { + idx = int name - 1; + name = "*"; + } + } + + v := varfind(ctxt.env.localenv, name); + if (v != nil) { + if (idx != -1) + return index(v.val, idx); + return v.val; + } + return nil; +} + +Context.envlist(ctxt: self ref Context): list of (string, list of ref Listnode) +{ + t := array[ENVHASHSIZE] of list of ref Var; + for (e := ctxt.env.localenv; e != nil; e = e.pushed) { + for (i := 0; i < len e.vars; i++) { + for (vl := e.vars[i]; vl != nil; vl = tl vl) { + v := hd vl; + idx := hashfn(v.name, len e.vars); + if (hashfind(t, idx, v.name) == nil) + hashadd(t, idx, v); + } + } + } + + l: list of (string, list of ref Listnode); + for (i := 0; i < ENVHASHSIZE; i++) { + for (vl := t[i]; vl != nil; vl = tl vl) { + v := hd vl; + l = (v.name, v.val) :: l; + } + } + return l; +} + +Context.setlocal(ctxt: self ref Context, name: string, val: list of ref Listnode) +{ + e := ctxt.env.localenv; + idx := hashfn(name, len e.vars); + v := hashfind(e.vars, idx, name); + if (v == nil) { + flags := Var.CHANGED; + if (noexport(name)) + flags |= Var.NOEXPORT; + hashadd(e.vars, idx, ref Var(name, val, flags)); + } else { + v.val = val; + v.flags |= Var.CHANGED; + } +} + + +Context.push(ctxt: self ref Context) +{ + ctxt.env.localenv = newlocalenv(ctxt.env.localenv); +} + +Context.pop(ctxt: self ref Context) +{ + if (ctxt.env.localenv.pushed == nil) + panic("unbalanced contexts in shell environment"); + else { + oldv := ctxt.env.localenv.vars; + ctxt.env.localenv = ctxt.env.localenv.pushed; + for (i := 0; i < len oldv; i++) { + for (vl := oldv[i]; vl != nil; vl = tl vl) { + if ((v := varfind(ctxt.env.localenv, (hd vl).name)) != nil) + v.flags |= Var.CHANGED; + else + ctxt.set((hd vl).name, nil); + } + } + } +} + +Context.run(ctxt: self ref Context, args: list of ref Listnode, last: int): string +{ + if (args == nil || ((hd args).cmd == nil && (hd args).word == nil)) + return nil; + cmd := hd args; + if (cmd.cmd != nil || cmd.word[0] == '{') # } + return runblock(ctxt, args, last); + + if (ctxt.options() & ctxt.EXECPRINT) + sys->fprint(stderr(), "%s\n", quoted(args, 0)); + (doneit, status) := trybuiltin(ctxt, args, last); + if (!doneit) + status = runexternal(ctxt, args, last); + + return status; +} + +Context.addmodule(ctxt: self ref Context, name: string, mod: Shellbuiltin) +{ + mod->initbuiltin(ctxt, myself); + ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods; +} + +Context.addbuiltin(c: self ref Context, name: string, mod: Shellbuiltin) +{ + addbuiltin(c.env.builtins, name, mod); +} + +Context.removebuiltin(c: self ref Context, name: string, mod: Shellbuiltin) +{ + removebuiltin(c.env.builtins, name, mod); +} + +Context.addsbuiltin(c: self ref Context, name: string, mod: Shellbuiltin) +{ + addbuiltin(c.env.sbuiltins, name, mod); +} + +Context.removesbuiltin(c: self ref Context, name: string, mod: Shellbuiltin) +{ + removebuiltin(c.env.sbuiltins, name, mod); +} + +varfind(e: ref Localenv, name: string): ref Var +{ + idx := hashfn(name, len e.vars); + for (; e != nil; e = e.pushed) + for (vl := e.vars[idx]; vl != nil; vl = tl vl) + if ((hd vl).name == name) + return hd vl; + return nil; +} + +Context.fail(ctxt: self ref Context, ename: string, err: string) +{ + if (ctxt.options() & Context.VERBOSE) + sys->fprint(stderr(), "%s\n", err); + raise "fail:" + ename; +} + +Context.setoptions(ctxt: self ref Context, flags, on: int): int +{ + old := ctxt.env.localenv.flags; + if (on) + ctxt.env.localenv.flags |= flags; + else + ctxt.env.localenv.flags &= ~flags; + return old; +} + +Context.options(ctxt: self ref Context): int +{ + return ctxt.env.localenv.flags; +} + +hashfn(s: string, n: int): int +{ + h := 0; + m := len s; + for(i:=0; i<m; i++){ + h = 65599*h+s[i]; + } + return (h & 16r7fffffff) % n; +} + +hashfind(ht: array of list of ref Var, idx: int, n: string): ref Var +{ + for (ent := ht[idx]; ent != nil; ent = tl ent) + if ((hd ent).name == n) + return hd ent; + return nil; +} + +hashadd(ht: array of list of ref Var, idx: int, v: ref Var) +{ + ht[idx] = v :: ht[idx]; +} + +copylocalenv(e: ref Localenv): ref Localenv +{ + nvars := array[len e.vars] of list of ref Var; + flags := e.flags; + for (; e != nil; e = e.pushed) + for (i := 0; i < len nvars; i++) + for (vl := e.vars[i]; vl != nil; vl = tl vl) { + idx := hashfn((hd vl).name, len nvars); + if (hashfind(nvars, idx, (hd vl).name) == nil) + hashadd(nvars, idx, ref *(hd vl)); + } + return ref Localenv(nvars, nil, flags); +} + +newlocalenv(pushed: ref Localenv): ref Localenv +{ + e := ref Localenv(array[ENVHASHSIZE] of list of ref Var, pushed, 0); + if (pushed == nil && env != nil) { + for (vl := env->getall(); vl != nil; vl = tl vl) { + (name, val) := hd vl; + hashadd(e.vars, hashfn(name, len e.vars), ref Var(name, envstringtoval(val), 0)); + } + } + if (pushed != nil) + e.flags = pushed.flags; + return e; +} + +copybuiltins(b: ref Builtins): ref Builtins +{ + nb := ref Builtins(array[b.n] of (string, list of Shellbuiltin), b.n); + nb.ba[0:] = b.ba[0:b.n]; + return nb; +} + +findbuiltin(b: ref Builtins, name: string): (int, list of Shellbuiltin) +{ + lo := 0; + hi := b.n - 1; + while (lo <= hi) { + mid := (lo + hi) / 2; + (bname, bmod) := b.ba[mid]; + if (name < bname) + hi = mid - 1; + else if (name > bname) + lo = mid + 1; + else + return (mid, bmod); + } + return (lo, nil); +} + +removebuiltin(b: ref Builtins, name: string, mod: Shellbuiltin) +{ + (n, bmods) := findbuiltin(b, name); + if (bmods == nil) + return; + if (hd bmods == mod) { + if (tl bmods != nil) + b.ba[n] = (name, tl bmods); + else { + b.ba[n:] = b.ba[n+1:b.n]; + b.ba[--b.n] = (nil, nil); + } + } +} + +addbuiltin(b: ref Builtins, name: string, mod: Shellbuiltin) +{ + if (mod == nil || (name == "builtin" && mod != myselfbuiltin)) + return; + (n, bmods) := findbuiltin(b, name); + if (bmods != nil) { + if (hd bmods == myselfbuiltin) + b.ba[n] = (name, mod :: bmods); + else + b.ba[n] = (name, mod :: nil); + } else { + if (b.n == len b.ba) { + nb := array[b.n + 10] of (string, list of Shellbuiltin); + nb[0:] = b.ba[0:b.n]; + b.ba = nb; + } + b.ba[n+1:] = b.ba[n:b.n]; + b.ba[n] = (name, mod :: nil); + b.n++; + } +} + +removebuiltinmod(b: ref Builtins, mod: Shellbuiltin) +{ + j := 0; + for (i := 0; i < b.n; i++) { + (name, bmods) := b.ba[i]; + if (hd bmods == mod) + bmods = tl bmods; + if (bmods != nil) + b.ba[j++] = (name, bmods); + } + b.n = j; + for (; j < i; j++) + b.ba[j] = (nil, nil); +} + +export(e: ref Localenv) +{ + if (env == nil) + return; + if (e.pushed != nil) + export(e.pushed); + + for (i := 0; i < len e.vars; i++) { + for (vl := e.vars[i]; vl != nil; vl = tl vl) { + v := hd vl; + # a bit inefficient: a local variable will get several putenvs. + if ((v.flags & Var.CHANGED) && !(v.flags & Var.NOEXPORT)) { + setenv(v.name, v.val); + v.flags &= ~Var.CHANGED; + } + } + } +} + +noexport(name: string): int +{ + case name { + "0" or "*" or "status" => return 1; + } + return 0; +} + +index(val: list of ref Listnode, k: int): list of ref Listnode +{ + for (; k > 0 && val != nil; k--) + val = tl val; + if (val != nil) + val = hd val :: nil; + return val; +} + +getenv(name: string): list of ref Listnode +{ + if (env == nil) + return nil; + return envstringtoval(env->getenv(name)); +} + +envstringtoval(v: string): list of ref Listnode +{ + return stringlist2list(str->unquoted(v)); +} + +XXXenvstringtoval(v: string): list of ref Listnode +{ + if (len v == 0) + return nil; + start := len v; + val: list of ref Listnode; + for (i := start - 1; i >= 0; i--) { + if (v[i] == ENVSEP) { + val = ref Listnode(nil, v[i+1:start]) :: val; + start = i; + } + } + return ref Listnode(nil, v[0:start]) :: val; +} + +setenv(name: string, val: list of ref Listnode) +{ + if (env == nil) + return; + env->setenv(name, quoted(val, 1)); +} + + +containswildchar(s: string): int +{ + # try and avoid being fooled by GLOB characters in quoted + # text. we'll only be fooled if the GLOB char is followed + # by a wildcard char, or another GLOB. + for (i := 0; i < len s; i++) { + if (s[i] == GLOB && i < len s - 1) { + case s[i+1] { + '*' or '[' or '?' or GLOB => + return 1; + } + } + } + return 0; +} + +patquote(word: string): string +{ + outword := ""; + for (i := 0; i < len word; i++) { + case word[i] { + '[' or '*' or '?' or '\\' => + outword[len outword] = '\\'; + GLOB => + i++; + if (i >= len word) + return outword; + } + outword[len outword] = word[i]; + } + return outword; +} + +deglob(s: string): string +{ + j := 0; + for (i := 0; i < len s; i++) { + if (s[i] != GLOB) { + if (i != j) # a worthy optimisation??? + s[j] = s[i]; + j++; + } + } + if (i == j) + return s; + return s[0:j]; +} + +glob(nl: list of ref Listnode): list of ref Listnode +{ + new: list of ref Listnode; + while (nl != nil) { + n := hd nl; + if (containswildchar(n.word)) { + qword := patquote(n.word); + files := filepat->expand(qword); + if (files == nil) + files = deglob(n.word) :: nil; + while (files != nil) { + new = ref Listnode(nil, hd files) :: new; + files = tl files; + } + } else + new = n :: new; + nl = tl nl; + } + ret := revlist(new); + return ret; +} + + +list2stringlist(nl: list of ref Listnode): list of string +{ + ret: list of string = nil; + + while (nl != nil) { + newel: string; + el := hd nl; + if (el.word != nil || el.cmd == nil) + newel = el.word; + else + el.word = newel = cmd2string(el.cmd); + ret = newel::ret; + nl = tl nl; + } + + sl := revstringlist(ret); + return sl; +} + +stringlist2list(sl: list of string): list of ref Listnode +{ + ret: list of ref Listnode; + + while (sl != nil) { + ret = ref Listnode(nil, hd sl) :: ret; + sl = tl sl; + } + return revlist(ret); +} + +revstringlist(l: list of string): list of string +{ + t: list of string; + + while(l != nil) { + t = hd l :: t; + l = tl l; + } + return t; +} + +revlist(l: list of ref Listnode): list of ref Listnode +{ + t: list of ref Listnode; + + while(l != nil) { + t = hd l :: t; + l = tl l; + } + return t; +} + + +fdassignstr(isassign: int, redir: ref Redir): string +{ + l: string = nil; + if (redir.fd1 >= 0) + l = string redir.fd1; + + if (isassign) { + r: string = nil; + if (redir.fd2 >= 0) + r = string redir.fd2; + return "[" + l + "=" + r + "]"; + } + return "[" + l + "]"; +} + +redirstr(rtype: int): string +{ + case rtype { + * or + Sys->OREAD => return "<"; + Sys->OWRITE => return ">"; + Sys->OWRITE|OAPPEND => return ">>"; + Sys->ORDWR => return "<>"; + } +} + +cmd2string(n: ref Node): string +{ + if (n == nil) + return ""; + + s: string; + case n.ntype { + n_BLOCK => s = "{" + cmd2string(n.left) + "}"; + n_VAR => s = "$" + cmd2string(n.left); + # XXX can this ever occur? + if (n.right != nil) + s += "(" + cmd2string(n.right) + ")"; + n_SQUASH => s = "$\"" + cmd2string(n.left); + n_COUNT => s = "$#" + cmd2string(n.left); + n_BQ => s = "`" + cmd2string(n.left); + n_BQ2 => s = "\"" + cmd2string(n.left); + n_REDIR => s = redirstr(n.redir.rtype); + if (n.redir.fd1 != -1) + s += fdassignstr(0, n.redir); + s += cmd2string(n.left); + n_DUP => s = redirstr(n.redir.rtype) + fdassignstr(1, n.redir); + n_LIST => s = "(" + cmd2string(n.left) + ")"; + n_SEQ => s = cmd2string(n.left) + ";" + cmd2string(n.right); + n_NOWAIT => s = cmd2string(n.left) + "&"; + n_CONCAT => s = cmd2string(n.left) + "^" + cmd2string(n.right); + n_PIPE => s = cmd2string(n.left) + "|"; + if (n.redir != nil && (n.redir.fd1 != -1 || n.redir.fd2 != -1)) + s += fdassignstr(n.redir.fd2 != -1, n.redir); + s += cmd2string(n.right); + n_ASSIGN => s = cmd2string(n.left) + "=" + cmd2string(n.right); + n_LOCAL => s = cmd2string(n.left) + ":=" + cmd2string(n.right); + n_ADJ => s = cmd2string(n.left) + " " + cmd2string(n.right); + n_WORD => s = quote(n.word, 1); + * => s = sys->sprint("unknown%d", n.ntype); + } + return s; +} + +quote(s: string, glob: int): string +{ + needquote := 0; + t := ""; + for (i := 0; i < len s; i++) { + case s[i] { + '{' or '}' or '(' or ')' or '`' or '&' or ';' or '=' or '>' or '<' or '#' or + '|' or '*' or '[' or '?' or '$' or '^' or ' ' or '\t' or '\n' or '\r' => + needquote = 1; + '\'' => + t[len t] = '\''; + needquote = 1; + GLOB => + if (glob) { + if (i < len s - 1) + i++; + } + } + t[len t] = s[i]; + } + if (needquote || t == nil) + t = "'" + t + "'"; + return t; +} + +squash(l: list of string, sep: string): string +{ + if (l == nil) + return nil; + s := hd l; + for (l = tl l; l != nil; l = tl l) + s += sep + hd l; + return s; +} + +debug(s: string) +{ + if (DEBUG) sys->fprint(stderr(), "%s\n", string sys->pctl(0, nil) + ": " + s); +} + + +initbuiltin(c: ref Context, nil: Sh): string +{ + names := array[] of {"load", "unload", "loaded", "builtin", "syncenv", "whatis", "run", "exit", "@"}; + for (i := 0; i < len names; i++) + c.addbuiltin(names[i], myselfbuiltin); + c.addsbuiltin("loaded", myselfbuiltin); + c.addsbuiltin("quote", myselfbuiltin); + c.addsbuiltin("bquote", myselfbuiltin); + c.addsbuiltin("unquote", myselfbuiltin); + c.addsbuiltin("builtin", myselfbuiltin); + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +runsbuiltin(ctxt: ref Context, nil: Sh, argv: list of ref Listnode): list of ref Listnode +{ + case (hd argv).word { + "loaded" => return sbuiltin_loaded(ctxt, argv); + "bquote" => return sbuiltin_quote(ctxt, argv, 0); + "quote" => return sbuiltin_quote(ctxt, argv, 1); + "unquote" => return sbuiltin_unquote(ctxt, argv); + "builtin" => return sbuiltin_builtin(ctxt, argv); + } + return nil; +} + +runbuiltin(ctxt: ref Context, nil: Sh, args: list of ref Listnode, lseq: int): string +{ + status := ""; + name := (hd args).word; + case name { + "load" => status = builtin_load(ctxt, args, lseq); + "loaded" => status = builtin_loaded(ctxt, args, lseq); + "unload" => status = builtin_unload(ctxt, args, lseq); + "builtin" => status = builtin_builtin(ctxt, args, lseq); + "whatis" => status = builtin_whatis(ctxt, args, lseq); + "run" => status = builtin_run(ctxt, args, lseq); + "exit" => status = builtin_exit(ctxt, args, lseq); + "syncenv" => export(ctxt.env.localenv); + "@" => status = builtin_subsh(ctxt, args, lseq); + } + return status; +} + +sbuiltin_loaded(ctxt: ref Context, nil: list of ref Listnode): list of ref Listnode +{ + v: list of ref Listnode; + for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) { + (name, nil) := hd bl; + v = ref Listnode(nil, name) :: v; + } + return v; +} + +sbuiltin_quote(nil: ref Context, argv: list of ref Listnode, quoteblocks: int): list of ref Listnode +{ + return ref Listnode(nil, quoted(tl argv, quoteblocks)) :: nil; +} + +sbuiltin_builtin(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode +{ + if (args == nil || tl args == nil) + builtinusage(ctxt, "builtin command [args ...]"); + name := (hd tl args).word; + (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name); + for (; mods != nil; mods = tl mods) + if (hd mods == myselfbuiltin) + return (hd mods)->runsbuiltin(ctxt, myself, tl args); + ctxt.fail("builtin not found", sys->sprint("sh: builtin %s not found", name)); + return nil; +} + +sbuiltin_unquote(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + argv = tl argv; + if (argv == nil || tl argv != nil) + builtinusage(ctxt, "unquote arg"); + + arg := (hd argv).word; + if (arg == nil && (hd argv).cmd != nil) + arg = cmd2string((hd argv).cmd); + return stringlist2list(str->unquoted(arg)); +} + +getself(): Shellbuiltin +{ + return myselfbuiltin; +} + +builtinusage(ctxt: ref Context, s: string) +{ + ctxt.fail("usage", "sh: usage: " + s); +} + +builtin_exit(nil: ref Context, nil: list of ref Listnode, nil: int): string +{ + # XXX using this primitive can cause + # environment stack not to be popped properly. + exit; +} + +builtin_subsh(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + if (tl args == nil) + return nil; + startchan := chan of (int, ref Expropagate); + spawn runasync(ctxt, 0, tl args, ref Redirlist, startchan); + (exepid, exprop) := <-startchan; + status := waitfor(ctxt, exepid :: nil); + if (exprop.name != nil) + raise exprop.name; + return status; +} + +builtin_loaded(ctxt: ref Context, nil: list of ref Listnode, nil: int): string +{ + b := ctxt.env.builtins; + for (i := 0; i < b.n; i++) { + (name, bmods) := b.ba[i]; + sys->print("%s\t%s\n", name, modname(ctxt, hd bmods)); + } + b = ctxt.env.sbuiltins; + for (i = 0; i < b.n; i++) { + (name, bmods) := b.ba[i]; + sys->print("${%s}\t%s\n", name, modname(ctxt, hd bmods)); + } + return nil; +} + +builtin_load(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + if (tl args == nil || (hd tl args).word == nil) + builtinusage(ctxt, "load path..."); + args = tl args; + path := (hd args).word; + if (args == nil) + builtinusage(ctxt, "load path..."); + status := ""; + for (; args != nil; args = tl args) { + s := loadmodule(ctxt, (hd args).word); + if (s != nil) + raise "fail:" + s; + } + return nil; +} + +builtin_unload(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + if (tl args == nil) + builtinusage(ctxt, "unload path..."); + status := ""; + for (args = tl args; args != nil; args = tl args) + if ((s := unloadmodule(ctxt, (hd args).word)) != nil) + status = s; + return status; +} + +builtin_run(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + if (tl args == nil || (hd tl args).word == nil) + builtinusage(ctxt, "run path"); + ctxt.push(); + { + ctxt.setoptions(ctxt.INTERACTIVE, 0); + runscript(ctxt, (hd tl args).word, tl tl args, 1); + ctxt.pop(); + return nil; + } exception e { + "fail:*" => + ctxt.pop(); + return e[5:]; + } +} + +builtin_whatis(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + if (len args < 2) + builtinusage(ctxt, "whatis name ..."); + err := ""; + for (args = tl args; args != nil; args = tl args) + if ((e := whatisit(ctxt, hd args)) != nil) + err = e; + return err; +} + +whatisit(ctxt: ref Context, el: ref Listnode): string +{ + if (el.cmd != nil) { + sys->print("%s\n", cmd2string(el.cmd)); + return nil; + } + found := 0; + name := el.word; + if (name != nil && name[0] == '{') { #} + sys->print("%s\n", name); + return nil;; + } + if (name == nil) + return nil; # XXX questionable + w: string; + val := ctxt.get(name); + if (val != nil) { + found++; + w += sys->sprint("%s=%s\n", quote(name, 0), quoted(val, 0)); + } + (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name); + if (mods != nil) { + mod := hd mods; + if (mod == myselfbuiltin) + w += "${builtin " + name + "}\n"; + else { + mw := mod->whatis(ctxt, myself, name, Shellbuiltin->SBUILTIN); + if (mw == nil) + mw = "${" + name + "}"; + w += "load " + modname(ctxt, mod) + "; " + mw + "\n"; + } + found++; + } + (nil, mods) = findbuiltin(ctxt.env.builtins, name); + if (mods != nil) { + mod := hd mods; + if (mod == myselfbuiltin) + sys->print("builtin %s\n", name); + else { + mw := mod->whatis(ctxt, myself, name, Shellbuiltin->BUILTIN); + if (mw == nil) + mw = name; + w += "load " + modname(ctxt, mod) + "; " + mw + "\n"; + } + found++; + } else { + disfile := 0; + if (len name >= 4 && name[len name-4:] == ".dis") + disfile = 1; + pathlist: list of string; + if (len name >= 2 && (name[0] == '/' || name[0:2] == "./")) + pathlist = list of {""}; + else if ((pl := ctxt.get("path")) != nil) + pathlist = list2stringlist(pl); + else + pathlist = list of {"/dis", "."}; + + foundpath := ""; + while (pathlist != nil) { + path: string; + if (hd pathlist != "") + path = hd pathlist + "/" + name; + else + path = name; + if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) { + if (executable(sys->fstat(fd), 8r111)) { + foundpath = path; + break; + } + } + if (!disfile) + path += ".dis"; + if (executable(sys->stat(path), 8r444)) { + foundpath = path; + break; + } + pathlist = tl pathlist; + } + if (foundpath != nil) + w += foundpath + "\n"; + } + for (bmods := ctxt.env.bmods; bmods != nil; bmods = tl bmods) { + (modname, mod) := hd bmods; + if ((mw := mod->whatis(ctxt, myself, name, Shellbuiltin->OTHER)) != nil) + w += "load " + modname + "; " + mw + "\n"; + } + if (w == nil) { + sys->fprint(stderr(), "%s: not found\n", name); + return "not found"; + } + sys->print("%s", w); + return nil; +} + +builtin_builtin(ctxt: ref Context, args: list of ref Listnode, last: int): string +{ + if (len args < 2) + builtinusage(ctxt, "builtin command [args ...]"); + name := (hd tl args).word; + if (name == nil || name[0] == '{') { + diagnostic(ctxt, name + " not found"); + return "not found"; + } + (nil, mods) := findbuiltin(ctxt.env.builtins, name); + for (; mods != nil; mods = tl mods) + if (hd mods == myselfbuiltin) + return (hd mods)->runbuiltin(ctxt, myself, tl args, last); + if (ctxt.options() & ctxt.EXECPRINT) + sys->fprint(stderr(), "%s\n", quoted(tl args, 0)); + return runexternal(ctxt, tl args, last); +} + +modname(ctxt: ref Context, mod: Shellbuiltin): string +{ + for (ml := ctxt.env.bmods; ml != nil; ml = tl ml) { + (bname, bmod) := hd ml; + if (bmod == mod) + return bname; + } + return "builtin"; +} + +loadmodule(ctxt: ref Context, name: string): string +{ + # avoid loading the same module twice (it's convenient + # to have load be a null-op if the module required is already loaded) + for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) { + (bname, nil) := hd bl; + if (bname == name) + return nil; + } + path := name; + if (len path < 4 || path[len path-4:] != ".dis") + path += ".dis"; + if (path[0] != '/' && path[0:2] != "./") + path = BUILTINPATH + "/" + path; + mod := load Shellbuiltin path; + if (mod == nil) { + diagnostic(ctxt, sys->sprint("load: cannot load %s: %r", path)); + return "bad module"; + } + s := mod->initbuiltin(ctxt, myself); + ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods; + if (s != nil) { + unloadmodule(ctxt, name); + diagnostic(ctxt, "load: module init failed: " + s); + } + return s; +} + +unloadmodule(ctxt: ref Context, name: string): string +{ + bl: list of (string, Shellbuiltin); + mod: Shellbuiltin; + for (cl := ctxt.env.bmods; cl != nil; cl = tl cl) { + (bname, bmod) := hd cl; + if (bname == name) + mod = bmod; + else + bl = hd cl :: bl; + } + if (mod == nil) { + diagnostic(ctxt, sys->sprint("module %s not found", name)); + return "not found"; + } + for (ctxt.env.bmods = nil; bl != nil; bl = tl bl) + ctxt.env.bmods = hd bl :: ctxt.env.bmods; + removebuiltinmod(ctxt.env.builtins, mod); + removebuiltinmod(ctxt.env.sbuiltins, mod); + return nil; +} + +executable(s: (int, Sys->Dir), mode: int): int +{ + (ok, info) := s; + return ok != -1 && (info.mode & Sys->DMDIR) == 0 + && (info.mode & mode) != 0; +} + +quoted(val: list of ref Listnode, quoteblocks: int): string +{ + s := ""; + for (; val != nil; val = tl val) { + el := hd val; + if (el.cmd == nil || (quoteblocks && el.word != nil)) + s += quote(el.word, 0); + else { + cmd := cmd2string(el.cmd); + if (quoteblocks) + cmd = quote(cmd, 0); + s += cmd; + } + if (tl val != nil) + s[len s] = ' '; + } + return s; +} + +setstatus(ctxt: ref Context, val: string): string +{ + ctxt.setlocal("status", ref Listnode(nil, val) :: nil); + return val; +} + + +doparse(l: ref YYLEX, prompt: string, showline: int): (ref Node, string) +{ + l.prompt = prompt; + l.err = nil; + l.lval.node = nil; + yyparse(l); + l.lastnl = 0; # don't print secondary prompt next time + if (l.err != nil) { + s: string; + if (l.err == nil) + l.err = "unknown error"; + if (l.errline > 0 && showline) + s = sys->sprint("%s:%d: %s", l.path, l.errline, l.err); + else + s = l.path + ": parse error: " + l.err; + return (nil, s); + } + return (l.lval.node, nil); +} + +blanklex: YYLEX; # for hassle free zero initialisation + +YYLEX.initstring(s: string): ref YYLEX +{ + ret := ref blanklex; + ret.s = s; + ret.path="internal"; + ret.strpos = 0; + return ret; +} + +YYLEX.initfile(fd: ref Sys->FD, path: string): ref YYLEX +{ + lex := ref blanklex; + lex.f = bufio->fopen(fd, bufio->OREAD); + lex.path = path; + lex.cbuf = array[2] of int; # number of characters of pushback + lex.linenum = 1; + lex.prompt = ""; + return lex; +} + +YYLEX.error(l: self ref YYLEX, s: string) +{ + if (l.err == nil) { + l.err = s; + l.errline = l.linenum; + } +} + +NOTOKEN: con -1; + +YYLEX.lex(l: self ref YYLEX): int +{ + # the following are allowed a free caret: + # $, word and quoted word; + # also, allowed chrs in unquoted word following dollar are [a-zA-Z0-9*_] + endword := 0; + wasdollar := 0; + tok := NOTOKEN; + while (tok == NOTOKEN) { + case c := l.getc() { + l.EOF => + tok = END; + '\n' => + tok = '\n'; + '\r' or '\t' or ' ' => + ; + '#' => + while ((c = l.getc()) != '\n' && c != l.EOF) + ; + l.ungetc(); + ';' => tok = ';'; + '&' => + c = l.getc(); + if(c == '&') + tok = ANDAND; + else{ + l.ungetc(); + tok = '&'; + } + '^' => tok = '^'; + '{' => tok = '{'; + '}' => tok = '}'; + ')' => tok = ')'; + '(' => tok = '('; + '=' => (tok, l.lval.optype) = ('=', n_ASSIGN); + '$' => + if (l.atendword) { + l.ungetc(); + tok = '^'; + break; + } + case (c = l.getc()) { + '#' => + l.lval.optype = n_COUNT; + '"' => + l.lval.optype = n_SQUASH; + * => + l.ungetc(); + l.lval.optype = n_VAR; + } + tok = OP; + wasdollar = 1; + '"' or '`'=> + if (l.atendword) { + tok = '^'; + l.ungetc(); + break; + } + tok = OP; + if (c == '"') + l.lval.optype = n_BQ2; + else + l.lval.optype = n_BQ; + '>' or '<' => + rtype: int; + nc := l.getc(); + if (nc == '>') { + if (c == '>') + rtype = Sys->OWRITE | OAPPEND; + else + rtype = Sys->ORDWR; + nc = l.getc(); + } else if (c == '>') + rtype = Sys->OWRITE; + else + rtype = Sys->OREAD; + tok = REDIR; + if (nc == '[') { + (tok, l.lval.redir) = readfdassign(l); + if (tok == ERROR) + (l.err, l.errline) = ("syntax error in redirection", l.linenum); + } else { + l.ungetc(); + l.lval.redir = ref Redir(-1, -1, -1); + } + if (l.lval.redir != nil) + l.lval.redir.rtype = rtype; + '|' => + tok = '|'; + l.lval.redir = nil; + if ((c = l.getc()) == '[') { + (tok, l.lval.redir) = readfdassign(l); + if (tok == ERROR) { + (l.err, l.errline) = ("syntax error in pipe redirection", l.linenum); + return tok; + } + tok = '|'; + } else if(c == '|') + tok = OROR; + else + l.ungetc(); + + '\'' => + if (l.atendword) { + l.ungetc(); + tok = '^'; + break; + } + startline := l.linenum; + s := ""; + for(;;) { + while ((nc := l.getc()) != '\'' && nc != l.EOF) + s[len s] = nc; + if (nc == l.EOF) { + (l.err, l.errline) = ("unterminated string literal", startline); + return ERROR; + } + if (l.getc() != '\'') { + l.ungetc(); + break; + } + s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy) + } + l.lval.word = s; + tok = WORD; + endword = 1; + + * => + if (c == ':') { + if (l.getc() == '=') { + tok = '='; + l.lval.optype = n_LOCAL; + break; + } + l.ungetc(); + } + if (l.atendword) { + l.ungetc(); + tok = '^'; + break; + } + allowed: string; + if (l.wasdollar) + allowed = "a-zA-Z0-9*_"; + else + allowed = "^\n \t\r|$'#<>;^(){}`&=\""; + word := ""; + loop: do { + case c { + '*' or '?' or '[' or GLOB => + word[len word] = GLOB; + ':' => + nc := l.getc(); + l.ungetc(); + if (nc == '=') + break loop; + } + word[len word] = c; + } while ((c = l.getc()) != l.EOF && str->in(c, allowed)); + l.ungetc(); + l.lval.word = word; + tok = WORD; + endword = 1; + } + l.atendword = endword; + l.wasdollar = wasdollar; + } + return tok; +} + +tokstr(t: int): string +{ + s: string; + case t { + '\n' => s = "'\\n'"; + 33 to 127 => s = sprint("'%c'", t); + DUP=> s = "DUP"; + REDIR =>s = "REDIR"; + WORD => s = "WORD"; + OP => s = "OP"; + END => s = "END"; + ERROR=> s = "ERROR"; + * => + s = "<unknowntok"+ string t + ">"; + } + return s; +} + +YYLEX.ungetc(lex: self ref YYLEX) +{ + lex.strpos--; + if (lex.f != nil) { + lex.ncbuf++; + if (lex.strpos < 0) + lex.strpos = len lex.cbuf - 1; + } +} + +YYLEX.getc(lex: self ref YYLEX): int +{ + if (lex.eof) # EOF sticks + return lex.EOF; + c: int; + if (lex.f != nil) { + if (lex.ncbuf > 0) { + c = lex.cbuf[lex.strpos++]; + if (lex.strpos >= len lex.cbuf) + lex.strpos = 0; + lex.ncbuf--; + } else { + if (lex.lastnl && lex.prompt != nil) + sys->fprint(stderr(), "%s", lex.prompt); + c = bufio->lex.f.getc(); + if (c == bufio->ERROR || c == bufio->EOF) { + lex.eof = 1; + c = lex.EOF; + } else if (c == '\n') + lex.linenum++; + lex.lastnl = (c == '\n'); + lex.cbuf[lex.strpos++] = c; + if (lex.strpos >= len lex.cbuf) + lex.strpos = 0; + } + } else { + if (lex.strpos >= len lex.s) { + lex.eof = 1; + c = lex.EOF; + } else + c = lex.s[lex.strpos++]; + } + return c; +} + +readnum(lex: ref YYLEX): int +{ + sum := nc := 0; + while ((c := lex.getc()) >= '0' && c <= '9') { + sum = (sum * 10) + (c - '0'); + nc++; + } + lex.ungetc(); + if (nc == 0) + return -1; + return sum; +} + +readfdassign(lex: ref YYLEX): (int, ref Redir) +{ + n1 := readnum(lex); + if ((c := lex.getc()) != '=') { + if (c == ']') + return (REDIR, ref Redir(-1, n1, -1)); + + return (ERROR, nil); + } + n2 := readnum(lex); + if (lex.getc() != ']') + return (ERROR, nil); + return (DUP, ref Redir(-1, n1, n2)); +} + +mkseq(left, right: ref Node): ref Node +{ + if (left != nil && right != nil) + return mk(n_SEQ, left, right); + else if (left == nil) + return right; + return left; +} + +mk(ntype: int, left, right: ref Node): ref Node +{ + return ref Node(ntype, left, right, nil, nil); +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} +yyexca := array[] of {-1, 0, + 8, 17, + 10, 17, + 11, 17, + 12, 17, + 14, 17, + 15, 17, + 16, 17, + -2, 0, +-1, 1, + 1, -1, + -2, 0, +}; +YYNPROD: con 45; +YYPRIVATE: con 57344; +yytoknames: array of string; +yystates: array of string; +yydebug: con 0; +YYLAST: con 93; +yyact := array[] of { + 12, 10, 15, 4, 5, 40, 8, 11, 9, 7, + 30, 31, 54, 6, 50, 35, 34, 32, 33, 21, + 36, 38, 34, 41, 43, 22, 29, 3, 28, 13, + 14, 16, 17, 20, 37, 42, 1, 23, 45, 51, + 44, 47, 48, 18, 39, 19, 41, 43, 56, 30, + 31, 46, 58, 57, 59, 60, 49, 13, 14, 16, + 17, 53, 13, 14, 16, 17, 2, 52, 0, 16, + 17, 18, 27, 19, 16, 17, 18, 52, 19, 0, + 26, 18, 0, 19, 24, 25, 18, 26, 19, 0, + 55, 24, 25, +}; +yypact := array[] of { + 25,-1000, 11, 11, 69, 58, 18, 14,-1000, 58, + 58,-1000, 5,-1000, 68,-1000,-1000, 68,-1000, 58, +-1000,-1000,-1000,-1000,-1000,-1000, 58,-1000, 58,-1000, + -1,-1000,-1000, 68,-1000, -1,-1000, -5, 63,-1000, + -9, 76, 58,-1000, 18, 14, 53,-1000, 58, 63, +-1000, -1,-1000, 53,-1000,-1000,-1000,-1000,-1000, -1, +-1000, +}; +yypgo := array[] of { + 0, 1, 0, 44, 8, 6, 36, 7, 35, 4, + 9, 2, 66, 5, 34, 13, 3, 33, 21, +}; +yyr1 := array[] of { + 0, 6, 6, 17, 17, 12, 12, 13, 13, 9, + 9, 8, 8, 16, 16, 15, 15, 10, 10, 10, + 5, 5, 5, 5, 7, 7, 7, 1, 1, 4, + 4, 4, 14, 14, 3, 3, 3, 2, 2, 11, + 11, 11, 11, 18, 18, +}; +yyr2 := array[] of { + 0, 2, 2, 1, 1, 1, 2, 1, 2, 2, + 2, 1, 2, 1, 3, 1, 3, 0, 1, 4, + 1, 2, 1, 1, 3, 3, 2, 1, 2, 1, + 2, 2, 1, 2, 2, 3, 3, 1, 4, 1, + 2, 3, 3, 0, 2, +}; +yychk := array[] of { +-1000, -6, -12, 2, -16, -9, -15, -10, -5, -4, + -1, -7, -2, 4, 5, -11, 6, 7, 18, 20, + -17, 8, 14, -17, 15, 16, 11, -12, 10, 12, + -2, -1, -5, 13, 17, -2, -11, -14, -18, -3, + -13, -16, -8, -9, -15, -10, -18, -7, -4, -18, + 19, -2, 14, -18, 21, 14, -13, -5, -11, -2, + -1, +}; +yydef := array[] of { + -2, -2, 0, 0, 5, 17, 13, 15, 18, 20, + 22, 23, 29, 27, 0, 37, 39, 0, 43, 17, + 1, 3, 4, 2, 9, 10, 17, 6, 17, 43, + 30, 31, 21, 26, 43, 28, 40, 0, 32, 43, + 0, 7, 17, 11, 14, 16, 0, 24, 25, 0, + 41, 34, 44, 33, 42, 12, 8, 19, 38, 35, + 36, +}; +yytok1 := array[] of { + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 14, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 16, 3, + 18, 19, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 15, + 3, 13, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 17, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 20, 12, 21, +}; +yytok2 := array[] of { + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, +}; +yytok3 := array[] of { + 0 +}; + +YYSys: module +{ + FD: adt + { + fd: int; + }; + fildes: fn(fd: int): ref FD; + fprint: fn(fd: ref FD, s: string, *): int; +}; + +yysys: YYSys; +yystderr: ref YYSys->FD; + +YYFLAG: con -1000; + + +yytokname(yyc: int): string +{ + if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil) + return yytoknames[yyc-1]; + return "<"+string yyc+">"; +} + +yystatname(yys: int): string +{ + if(yys >= 0 && yys < len yystates && yystates[yys] != nil) + return yystates[yys]; + return "<"+string yys+">\n"; +} + +yylex1(yylex: ref YYLEX): int +{ + c : int; + yychar := yylex.lex(); + if(yychar <= 0) + c = yytok1[0]; + else if(yychar < len yytok1) + c = yytok1[yychar]; + else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2) + c = yytok2[yychar-YYPRIVATE]; + else{ + n := len yytok3; + c = 0; + for(i := 0; i < n; i+=2) { + if(yytok3[i+0] == yychar) { + c = yytok3[i+1]; + break; + } + } + if(c == 0) + c = yytok2[1]; # unknown char + } + if(yydebug >= 3) + yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c)); + return c; +} + +YYS: adt +{ + yyv: YYSTYPE; + yys: int; +}; + +yyparse(yylex: ref YYLEX): int +{ + if(yydebug >= 1 && yysys == nil) { + yysys = load YYSys "$Sys"; + yystderr = yysys->fildes(2); + } + + yys := array[YYMAXDEPTH] of YYS; + + yyval: YYSTYPE; + yystate := 0; + yychar := -1; + yynerrs := 0; # number of errors + yyerrflag := 0; # error recovery flag + yyp := -1; + yyn := 0; + +yystack: + for(;;){ + # put a state and value onto the stack + if(yydebug >= 4) + yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + + yyp++; + if(yyp >= len yys) + yys = (array[len yys * 2] of YYS)[0:] = yys; + yys[yyp].yys = yystate; + yys[yyp].yyv = yyval; + + for(;;){ + yyn = yypact[yystate]; + if(yyn > YYFLAG) { # simple state + if(yychar < 0) + yychar = yylex1(yylex); + yyn += yychar; + if(yyn >= 0 && yyn < YYLAST) { + yyn = yyact[yyn]; + if(yychk[yyn] == yychar) { # valid shift + yychar = -1; + yyp++; + if(yyp >= len yys) + yys = (array[len yys * 2] of YYS)[0:] = yys; + yystate = yyn; + yys[yyp].yys = yystate; + yys[yyp].yyv = yylex.lval; + if(yyerrflag > 0) + yyerrflag--; + if(yydebug >= 4) + yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + continue; + } + } + } + + # default state action + yyn = yydef[yystate]; + if(yyn == -2) { + if(yychar < 0) + yychar = yylex1(yylex); + + # look through exception table + for(yyxi:=0;; yyxi+=2) + if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate) + break; + for(yyxi += 2;; yyxi += 2) { + yyn = yyexca[yyxi]; + if(yyn < 0 || yyn == yychar) + break; + } + yyn = yyexca[yyxi+1]; + if(yyn < 0){ + yyn = 0; + break yystack; + } + } + + if(yyn != 0) + break; + + # error ... attempt to resume parsing + if(yyerrflag == 0) { # brand new error + yylex.error("syntax error"); + yynerrs++; + if(yydebug >= 1) { + yysys->fprint(yystderr, "%s", yystatname(yystate)); + yysys->fprint(yystderr, "saw %s\n", yytokname(yychar)); + } + } + + if(yyerrflag != 3) { # incompletely recovered error ... try again + yyerrflag = 3; + + # find a state where "error" is a legal shift action + while(yyp >= 0) { + yyn = yypact[yys[yyp].yys] + YYERRCODE; + if(yyn >= 0 && yyn < YYLAST) { + yystate = yyact[yyn]; # simulate a shift of "error" + if(yychk[yystate] == YYERRCODE) + continue yystack; + } + + # the current yyp has no shift onn "error", pop stack + if(yydebug >= 2) + yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n", + yys[yyp].yys, yys[yyp-1].yys ); + yyp--; + } + # there is no state on the stack with an error shift ... abort + yyn = 1; + break yystack; + } + + # no shift yet; clobber input char + if(yydebug >= 2) + yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar)); + if(yychar == YYEOFCODE) { + yyn = 1; + break yystack; + } + yychar = -1; + # try again in the same state + } + + # reduction by production yyn + if(yydebug >= 2) + yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); + + yypt := yyp; + yyp -= yyr2[yyn]; + yym := yyn; + + # consult goto table to find next state + yyn = yyr1[yyn]; + yyg := yypgo[yyn]; + yyj := yyg + yys[yyp].yys + 1; + + if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) + yystate = yyact[yyg]; + case yym { + +1=> +{yylex.lval.node = yys[yypt-1].yyv.node; return 0;} +2=> +{yylex.lval.node = nil; return 0;} +5=> +yyval.node = yys[yyp+1].yyv.node; +6=> +{yyval.node = mkseq(yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); } +7=> +yyval.node = yys[yyp+1].yyv.node; +8=> +{yyval.node = mkseq(yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); } +9=> +{yyval.node = yys[yypt-1].yyv.node; } +10=> +{yyval.node = ref Node(n_NOWAIT, yys[yypt-1].yyv.node, nil, nil, nil); } +11=> +yyval.node = yys[yyp+1].yyv.node; +12=> +{yyval.node = yys[yypt-1].yyv.node; } +13=> +yyval.node = yys[yyp+1].yyv.node; +14=> +{ + yyval.node = mk(n_ADJ, + mk(n_ADJ, + ref Node(n_WORD,nil,nil,"or",nil), + mk(n_BLOCK, yys[yypt-2].yyv.node, nil) + ), + mk(n_BLOCK,yys[yypt-0].yyv.node,nil) + ); + } +15=> +yyval.node = yys[yyp+1].yyv.node; +16=> +{ + yyval.node = mk(n_ADJ, + mk(n_ADJ, + ref Node(n_WORD,nil,nil,"and",nil), + mk(n_BLOCK, yys[yypt-2].yyv.node, nil) + ), + mk(n_BLOCK,yys[yypt-0].yyv.node,nil) + ); + } +17=> +{yyval.node = nil;} +18=> +yyval.node = yys[yyp+1].yyv.node; +19=> +{yyval.node = ref Node(n_PIPE, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node, nil, yys[yypt-2].yyv.redir); } +20=> +yyval.node = yys[yyp+1].yyv.node; +21=> +{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); } +22=> +yyval.node = yys[yyp+1].yyv.node; +23=> +yyval.node = yys[yyp+1].yyv.node; +24=> +{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); } +25=> +{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); } +26=> +{yyval.node = mk(yys[yypt-0].yyv.optype, yys[yypt-1].yyv.node, nil); } +27=> +{yyval.node = ref Node(n_DUP, nil, nil, nil, yys[yypt-0].yyv.redir); } +28=> +{yyval.node = ref Node(n_REDIR, yys[yypt-0].yyv.node, nil, nil, yys[yypt-1].yyv.redir); } +29=> +yyval.node = yys[yyp+1].yyv.node; +30=> +{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); } +31=> +{yyval.node = mk(n_ADJ, yys[yypt-1].yyv.node, yys[yypt-0].yyv.node); } +32=> +{yyval.node = nil;} +33=> +yyval.node = yys[yyp+1].yyv.node; +34=> +{yyval.node = yys[yypt-0].yyv.node; } +35=> +{yyval.node = mk(n_ADJ, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); } +36=> +{yyval.node = mk(n_ADJ, yys[yypt-2].yyv.node, yys[yypt-0].yyv.node); } +37=> +yyval.node = yys[yyp+1].yyv.node; +38=> +{yyval.node = mk(n_CONCAT, yys[yypt-3].yyv.node, yys[yypt-0].yyv.node); } +39=> +{yyval.node = ref Node(n_WORD, nil, nil, yys[yypt-0].yyv.word, nil); } +40=> +{yyval.node = mk(yys[yypt-1].yyv.optype, yys[yypt-0].yyv.node, nil); } +41=> +{yyval.node = mk(n_LIST, yys[yypt-1].yyv.node, nil); } +42=> +{yyval.node = mk(n_BLOCK, yys[yypt-1].yyv.node, nil); } + } + } + + return yyn; +} diff --git a/appl/cmd/sh/sh.y b/appl/cmd/sh/sh.y new file mode 100644 index 00000000..083357c1 --- /dev/null +++ b/appl/cmd/sh/sh.y @@ -0,0 +1,2592 @@ +%{ +include "sys.m"; + sys: Sys; + sprint: import sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; +include "string.m"; + str: String; +include "filepat.m"; + filepat: Filepat; +include "env.m"; + env: Env; +include "sh.m"; + myself: Sh; + myselfbuiltin: Shellbuiltin; + +YYSTYPE: adt { + node: ref Node; + word: string; + + redir: ref Redir; + optype: int; +}; + +YYLEX: adt { + lval: YYSTYPE; + err: string; # if error has occurred + errline: int; # line it occurred on. + path: string; # name of file that's being read. + + # free caret state + wasdollar: int; + atendword: int; + eof: int; + cbuf: array of int; # last chars read + ncbuf: int; # number of chars in cbuf + + f: ref Bufio->Iobuf; + s: string; + strpos: int; # string pos/cbuf index + + linenum: int; + prompt: string; + lastnl: int; + + initstring: fn(s: string): ref YYLEX; + initfile: fn(fd: ref Sys->FD, path: string): ref YYLEX; + lex: fn(l: self ref YYLEX): int; + error: fn(l: self ref YYLEX, err: string); + getc: fn(l: self ref YYLEX): int; + ungetc: fn(l: self ref YYLEX); + + EOF: con -1; +}; + +Options: adt { + lflag, + nflag: int; + ctxtflags: int; + carg: string; +}; + +%} + +%module Sh { + # module definition is in shell.m +} + +%token DUP REDIR WORD OP END ERROR ANDAND OROR + +%type <node> redir word nlsimple simple cmd shell assign +%type <node> cmdsan cmdsa pipe comword line body list and2 or2 +%type <redir> DUP REDIR '|' +%type <optype> OP '=' +%type <word> WORD + +%start shell +%% +shell: line end {yylex.lval.node = $line; return 0;} + | error end {yylex.lval.node = nil; return 0;} +end: END + | '\n' +line: or2 + | cmdsa line {$$ = mkseq($cmdsa, $line); } +body: or2 + | cmdsan body {$$ = mkseq($cmdsan, $body); } +cmdsa: or2 ';' {$$ = $or2; } + | or2 '&' {$$ = ref Node(n_NOWAIT, $or2, nil, nil, nil); } +cmdsan: cmdsa + | or2 '\n' {$$ = $or2; } +or2: and2 + | or2 OROR and2 { + $$ = mk(n_ADJ, + mk(n_ADJ, + ref Node(n_WORD,nil,nil,"or",nil), + mk(n_BLOCK, $or2, nil) + ), + mk(n_BLOCK,$and2,nil) + ); + } +and2: pipe + | and2 ANDAND pipe { + $$ = mk(n_ADJ, + mk(n_ADJ, + ref Node(n_WORD,nil,nil,"and",nil), + mk(n_BLOCK, $and2, nil) + ), + mk(n_BLOCK,$pipe,nil) + ); + } +pipe: {$$ = nil;} + | cmd + | pipe '|' optnl cmd {$$ = ref Node(n_PIPE, $pipe, $cmd, nil, $2); } +cmd: simple + | redir cmd {$$ = mk(n_ADJ, $redir, $cmd); } + | redir + | assign +assign: word '=' assign {$$ = mk($2, $word, $assign); } + | word '=' simple {$$ = mk($2, $word, $simple); } + | word '=' {$$ = mk($2, $word, nil); } +redir: DUP {$$ = ref Node(n_DUP, nil, nil, nil, $DUP); } + | REDIR word {$$ = ref Node(n_REDIR, $word, nil, nil, $REDIR); } +simple: word + | simple word {$$ = mk(n_ADJ, $simple, $word); } + | simple redir {$$ = mk(n_ADJ, $simple, $redir); } +list: optnl {$$ = nil;} + | nlsimple optnl +nlsimple: optnl word {$$ = $word; } + | nlsimple optnl word {$$ = mk(n_ADJ, $nlsimple, $word); } + | nlsimple optnl redir {$$ = mk(n_ADJ, $nlsimple, $redir); } +word: comword + | word '^' optnl comword {$$ = mk(n_CONCAT, $word, $comword); } +comword: WORD {$$ = ref Node(n_WORD, nil, nil, $WORD, nil); } + | OP comword {$$ = mk($OP, $comword, nil); } + | '(' list ')' {$$ = mk(n_LIST, $list, nil); } + | '{' body '}' {$$ = mk(n_BLOCK, $body, nil); } +optnl: # null + | optnl '\n' +%% + +EPERM: con "permission denied"; +EPIPE: con "write on closed pipe"; + +#SHELLRC: con "lib/profile"; +LIBSHELLRC: con "/lib/sh/profile"; +BUILTINPATH: con "/dis/sh"; + +DEBUG: con 0; + +ENVSEP: con 0; # word seperator in external environment +ENVHASHSIZE: con 7; # XXX profile usage of this... +OAPPEND: con 16r80000; # make sure this doesn't clash with O* constants in sys.m +OMASK: con 7; + +usage() +{ + sys->fprint(stderr(), "usage: sh [-ilexn] [-c command] [file [arg...]]\n"); + raise "fail:usage"; +} + +badmodule(path: string) +{ + sys->fprint(sys->fildes(2), "sh: cannot load %s: %r\n", path); + raise "fail:bad module" ; +} + +initialise() +{ + if (sys == nil) { + sys = load Sys Sys->PATH; + + filepat = load Filepat Filepat->PATH; + if (filepat == nil) badmodule(Filepat->PATH); + + str = load String String->PATH; + if (str == nil) badmodule(String->PATH); + + bufio = load Bufio Bufio->PATH; + if (bufio == nil) badmodule(Bufio->PATH); + + myself = load Sh "$self"; + if (myself == nil) badmodule("$self(Sh)"); + + myselfbuiltin = load Shellbuiltin "$self"; + if (myselfbuiltin == nil) badmodule("$self(Shellbuiltin)"); + + env = load Env Env->PATH; + } +} +blankopts: Options; +init(drawcontext: ref Draw->Context, argv: list of string) +{ + initialise(); + opts := blankopts; + if (argv != nil) { + if ((hd argv)[0] == '-') + opts.lflag++; + argv = tl argv; + } + + interactive := 0; +loop: while (argv != nil && hd argv != nil && (hd argv)[0] == '-') { + for (i := 1; i < len hd argv; i++) { + c := (hd argv)[i]; + case c { + 'i' => + interactive = Context.INTERACTIVE; + 'l' => + opts.lflag++; # login (read $home/lib/profile) + 'n' => + opts.nflag++; # don't fork namespace + 'e' => + opts.ctxtflags |= Context.ERROREXIT; + 'x' => + opts.ctxtflags |= Context.EXECPRINT; + 'c' => + arg: string; + if (i < len hd argv - 1) { + arg = (hd argv)[i + 1:]; + } else if (tl argv == nil || hd tl argv == "") { + usage(); + } else { + arg = hd tl argv; + argv = tl argv; + } + argv = tl argv; + opts.carg = arg; + continue loop; + } + } + argv = tl argv; + } + + sys->pctl(Sys->FORKFD, nil); + if (!opts.nflag) + sys->pctl(Sys->FORKNS, nil); + ctxt := Context.new(drawcontext); + ctxt.setoptions(opts.ctxtflags, 1); + if (opts.carg != nil) { + status := ctxt.run(stringlist2list("{" + opts.carg + "}" :: argv), !interactive); + if (!interactive) { + if (status != nil) + raise "fail:" + status; + exit; + } + setstatus(ctxt, status); + } + + # if login shell, run standard init script + if (opts.lflag) + runscript(ctxt, LIBSHELLRC, nil, 0); + + if (argv == nil) { +# if (opts.lflag) +# runscript(ctxt, SHELLRC, nil, 0); + if (isconsole(sys->fildes(0))) + interactive |= ctxt.INTERACTIVE; + ctxt.setoptions(interactive, 1); + runfile(ctxt, sys->fildes(0), "stdin", nil); + } else { + ctxt.setoptions(interactive, 1); + runscript(ctxt, hd argv, stringlist2list(tl argv), 1); + } +} + +# XXX should this refuse to parse a non braced-block? +parse(s: string): (ref Node, string) +{ + initialise(); + + lex := YYLEX.initstring(s); + + return doparse(lex, "", 0); +} + +system(drawctxt: ref Draw->Context, cmd: string): string +{ + initialise(); + { + (n, err) := parse(cmd); + if (err != nil) + return err; + if (n == nil) + return nil; + return Context.new(drawctxt).run(ref Listnode(n, nil) :: nil, 0); + } exception e { + "fail:*" => + return e[5:]; + } +} + +run(drawctxt: ref Draw->Context, argv: list of string): string +{ + initialise(); + { + return Context.new(drawctxt).run(stringlist2list(argv), 0); + } exception e { + "fail:*" => + return e[5:]; + } +} + +isconsole(fd: ref Sys->FD): int +{ + (ok1, d1) := sys->fstat(fd); + (ok2, d2) := sys->stat("/dev/cons"); + if (ok1 < 0 || ok2 < 0) + return 0; + return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path; +} + +# run commands from file _path_ +runscript(ctxt: ref Context, path: string, args: list of ref Listnode, reporterr: int) +{ + { + fd := sys->open(path, Sys->OREAD); + if (fd != nil) + runfile(ctxt, fd, path, args); + else if (reporterr) + ctxt.fail("bad script path", sys->sprint("sh: cannot open %s: %r", path)); + } exception e { + "fail:*" => + if(!reporterr) + return; + raise; + } +} + +# run commands from the opened file fd. +# if interactive is non-zero, print a command prompt at appropriate times. +runfile(ctxt: ref Context, fd: ref Sys->FD, path: string, args: list of ref Listnode) +{ + ctxt.push(); + { + ctxt.setlocal("0", stringlist2list(path :: nil)); + ctxt.setlocal("*", args); + lex := YYLEX.initfile(fd, path); + if (DEBUG) debug(sprint("parse(interactive == %d)", (ctxt.options() & ctxt.INTERACTIVE) != 0)); + prompt := "" :: "" :: nil; + laststatus: string; + while (!lex.eof) { + interactive := ctxt.options() & ctxt.INTERACTIVE; + if (interactive) { + prompt = list2stringlist(ctxt.get("prompt")); + if (prompt == nil) + prompt = "; " :: "" :: nil; + + sys->fprint(stderr(), "%s", hd prompt); + if (tl prompt == nil) { + prompt = hd prompt :: "" :: nil; + } + } + (n, err) := doparse(lex, hd tl prompt, !interactive); + if (err != nil) { + sys->fprint(stderr(), "sh: %s\n", err); + if (!interactive) + raise "fail:parse error"; + } else if (n != nil) { + if (interactive) { + { + laststatus = walk(ctxt, n, 0); + } exception e2 { + "fail:*" => + laststatus = e2[5:]; + } + } else + laststatus = walk(ctxt, n, 0); + setstatus(ctxt, laststatus); + if ((ctxt.options() & ctxt.ERROREXIT) && laststatus != nil) + break; + } + } + if (laststatus != nil) + raise "fail:" + laststatus; + ctxt.pop(); + } + exception e { + "fail:*" => + ctxt.pop(); + raise; + } +} + +nonexistent(e: string): int +{ + errs := array[] of {"does not exist", "directory entry not found"}; + for (i := 0; i < len errs; i++){ + j := len errs[i]; + if (j <= len e && e[len e-j:] == errs[i]) + return 1; + } + return 0; +} + +Redirword: adt { + fd: ref Sys->FD; + w: string; + r: Redir; +}; + +Redirlist: adt { + r: list of Redirword; +}; + +# a hack so that the structure of walk() doesn't change much +# to accomodate echo|wc& +# transform the above into {echo|wc}$*& +# which should amount to exactly the same thing. +pipe2cmd(n: ref Node): ref Node +{ + if (n == nil || n.ntype != n_PIPE) + return n; + return mk(n_ADJ, mk(n_BLOCK,n,nil), mk(n_VAR,ref Node(n_WORD,nil,nil,"*",nil),nil)); +} + +# walk a node tree. +# last is non-zero if this walk is the last action +# this shell process will take before exiting (i.e. redirections +# don't require a new process to avoid side effects) +walk(ctxt: ref Context, n: ref Node, last: int): string +{ + if (DEBUG) debug(sprint("walking: %s", cmd2string(n))); + # avoid tail recursion stack explosion + while (n != nil && n.ntype == n_SEQ) { + status := walk(ctxt, n.left, 0); + if (ctxt.options() & ctxt.ERROREXIT && status != nil) + raise "fail:" + status; + setstatus(ctxt, status); + n = n.right; + } + if (n == nil) + return nil; + case (n.ntype) { + n_PIPE => + return waitfor(ctxt, walkpipeline(ctxt, n, nil, -1)); + n_ASSIGN or n_LOCAL => + assign(ctxt, n); + return nil; + * => + bg := 0; + if (n.ntype == n_NOWAIT) { + bg = 1; + n = pipe2cmd(n.left); + } + + redirs := ref Redirlist(nil); + line := glob(glom(ctxt, n, redirs, nil)); + + if (bg) { + startchan := chan of (int, ref Expropagate); + spawn runasync(ctxt, 1, line, redirs, startchan); + (pid, nil) := <-startchan; + redirs = nil; + if (DEBUG) debug("started background process "+ string pid); + ctxt.set("apid", ref Listnode(nil, string pid) :: nil); + return nil; + } else { + return runsync(ctxt, line, redirs, last); + } + } +} + +assign(ctxt: ref Context, n: ref Node): list of ref Listnode +{ + redirs := ref Redirlist; + val: list of ref Listnode; + if (n.right != nil && (n.right.ntype == n_ASSIGN || n.right.ntype == n_LOCAL)) + val = assign(ctxt, n.right); + else + val = glob(glom(ctxt, n.right, redirs, nil)); + vars := glom(ctxt, n.left, redirs, nil); + if (vars == nil) + ctxt.fail("bad assign", "sh: nil variable name"); + if (redirs.r != nil) + ctxt.fail("bad assign", "sh: redirections not allowed in assignment"); + tval := val; + for (; vars != nil; vars = tl vars) { + vname := deglob((hd vars).word); + if (vname == nil) + ctxt.fail("bad assign", "sh: bad variable name"); + v: list of ref Listnode = nil; + if (tl vars == nil) + v = tval; + else if (tval != nil) + v = hd tval :: nil; + if (n.ntype == n_ASSIGN) + ctxt.set(vname, v); + else + ctxt.setlocal(vname, v); + if (tval != nil) + tval = tl tval; + } + return val; +} + +walkpipeline(ctxt: ref Context, n: ref Node, wrpipe: ref Sys->FD, wfdno: int): list of int +{ + if (n == nil) + return nil; + + fds := array[2] of ref Sys->FD; + pids: list of int; + rfdno := -1; + if (n.ntype == n_PIPE) { + if (sys->pipe(fds) == -1) + ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r")); + nwfdno := -1; + if (n.redir != nil) { + (fd1, fd2) := (n.redir.fd2, n.redir.fd1); + if (fd2 == -1) + (fd1, fd2) = (fd2, fd1); + (nwfdno, rfdno) = (fd2, fd1); + } + pids = walkpipeline(ctxt, n.left, fds[1], nwfdno); + fds[1] = nil; + n = n.right; + } + r := ref Redirlist(nil); + rlist := glob(glom(ctxt, n, r, nil)); + if (fds[0] != nil) { + if (rfdno == -1) + rfdno = 0; + r.r = Redirword(fds[0], nil, Redir(Sys->OREAD, rfdno, -1)) :: r.r; + } + if (wrpipe != nil) { + if (wfdno == -1) + wfdno = 1; + r.r = Redirword(wrpipe, nil, Redir(Sys->OWRITE, wfdno, -1)) :: r.r; + } + startchan := chan of (int, ref Expropagate); + spawn runasync(ctxt, 1, rlist, r, startchan); + (pid, nil) := <-startchan; + if (DEBUG) debug("started pipe process "+string pid); + return pid :: pids; +} + +makeredir(f: string, mode: int, fd: int): Redirword +{ + return Redirword(nil, f, Redir(mode, fd, -1)); +} + +# expand substitution operators in a node list +glom(ctxt: ref Context, n: ref Node, redirs: ref Redirlist, onto: list of ref Listnode) + : list of ref Listnode +{ + if (n == nil) return nil; + + if (n.ntype != n_ADJ) + return listjoin(glomoperation(ctxt, n, redirs), onto); + + nlist := glom(ctxt, n.right, redirs, onto); + + if (n.left.ntype != n_ADJ) { + # if it's a terminal node + nlist = listjoin(glomoperation(ctxt, n.left, redirs), nlist); + } else + nlist = glom(ctxt, n.left, redirs, nlist); + return nlist; +} + +listjoin(left, right: list of ref Listnode): list of ref Listnode +{ + l: list of ref Listnode; + for (; left != nil; left = tl left) + l = hd left :: l; + for (; l != nil; l = tl l) + right = hd l :: right; + return right; +} + +glomoperation(ctxt: ref Context, n: ref Node, redirs: ref Redirlist): list of ref Listnode +{ + if (n == nil) + return nil; + + nlist: list of ref Listnode; + case n.ntype { + n_WORD => + nlist = ref Listnode(nil, n.word) :: nil; + n_REDIR => + wlist := glob(glom(ctxt, n.left, ref Redirlist(nil), nil)); + if (len wlist != 1 || (hd wlist).word == nil) + ctxt.fail("bad redir", "sh: single redirection operand required"); + + # add to redir list + redirs.r = Redirword(nil, (hd wlist).word, *n.redir) :: redirs.r; + n_DUP => + redirs.r = Redirword(nil, "", *n.redir) :: redirs.r; + n_LIST => + nlist = glom(ctxt, n.left, redirs, nil); + n_CONCAT => + nlist = concat(ctxt, glom(ctxt, n.left, redirs, nil), glom(ctxt, n.right, redirs, nil)); + n_VAR or n_SQUASH or n_COUNT => + arg := glom(ctxt, n.left, ref Redirlist(nil), nil); + if (len arg == 1 && (hd arg).cmd != nil) + nlist = subsbuiltin(ctxt, (hd arg).cmd.left); + else if (len arg != 1 || (hd arg).word == nil) + ctxt.fail("bad $ arg", "sh: bad variable name"); + else + nlist = ctxt.get(deglob((hd arg).word)); + case n.ntype { + n_VAR =>; + n_COUNT => + nlist = ref Listnode(nil, string len nlist) :: nil; + n_SQUASH => + # XXX could squash with first char of $ifs, perhaps + nlist = ref Listnode(nil, squash(list2stringlist(nlist), " ")) :: nil; + } + n_BQ or n_BQ2 => + arg := glom(ctxt, n.left, ref Redirlist(nil), nil); + seps := ""; + if (n.ntype == n_BQ) { + seps = squash(list2stringlist(ctxt.get("ifs")), ""); + if (seps == nil) + seps = " \t\n\r"; + } + (nlist, nil) = bq(ctxt, glob(arg), seps); + n_BLOCK => + nlist = ref Listnode(n, "") :: nil; + n_ASSIGN or n_LOCAL => + ctxt.fail("bad assign", "sh: assignment in invalid context"); + * => + panic("bad node type "+string n.ntype+" in glomop"); + } + return nlist; +} + +subsbuiltin(ctxt: ref Context, n: ref Node): list of ref Listnode +{ + if (n == nil || n.ntype == n_SEQ || + n.ntype == n_PIPE || n.ntype == n_NOWAIT) + ctxt.fail("bad $ arg", "sh: invalid argument to ${} operator"); + r := ref Redirlist; + cmd := glob(glom(ctxt, n, r, nil)); + if (r.r != nil) + ctxt.fail("bad $ arg", "sh: redirection not allowed in substitution"); + r = nil; + if (cmd == nil || (hd cmd).word == nil || (hd cmd).cmd != nil) + ctxt.fail("bad $ arg", "sh: bad builtin name"); + + (nil, bmods) := findbuiltin(ctxt.env.sbuiltins, (hd cmd).word); + if (bmods == nil) + ctxt.fail("builtin not found", + sys->sprint("sh: builtin %s not found", (hd cmd).word)); + return (hd bmods)->runsbuiltin(ctxt, myself, cmd); +} + +# +# backquote substitution (could be done in a builtin) +# + +getbq(nil: ref Context, fd: ref Sys->FD, seps: string): list of ref Listnode +{ + buf := array[Sys->ATOMICIO] of byte; + buflen := 0; + while ((n := sys->read(fd, buf[buflen:], len buf - buflen)) > 0) { + buflen += n; + if (buflen == len buf) { + nbuf := array[buflen * 2] of byte; + nbuf[0:] = buf[0:]; + buf = nbuf; + } + } + l: list of string; + if (seps != nil) + (nil, l) = sys->tokenize(string buf[0:buflen], seps); + else + l = string buf[0:buflen] :: nil; + buf = nil; + return stringlist2list(l); +} + +bq(ctxt: ref Context, cmd: list of ref Listnode, seps: string): (list of ref Listnode, string) +{ + fds := array[2] of ref Sys->FD; + if (sys->pipe(fds) == -1) + ctxt.fail("no pipe", sys->sprint("sh: cannot make pipe: %r")); + + r := rdir(fds[1]); + fds[1] = nil; + startchan := chan of (int, ref Expropagate); + spawn runasync(ctxt, 0, cmd, r, startchan); + (exepid, exprop) := <-startchan; + r = nil; + bqlist := getbq(ctxt, fds[0], seps); + waitfor(ctxt, exepid :: nil); + if (exprop.name != nil) + raise exprop.name; + return (bqlist, nil); +} + +# get around compiler temporaries bug +rdir(fd: ref Sys->FD): ref Redirlist +{ + return ref Redirlist(Redirword(fd, nil, Redir(Sys->OWRITE, 1, -1)) :: nil); +} + +# +# concatenation +# + +concatwords(p1, p2: ref Listnode): ref Listnode +{ + if (p1.word == nil && p1.cmd != nil) + p1.word = cmd2string(p1.cmd); + if (p2.word == nil && p2.cmd != nil) + p2.word = cmd2string(p2.cmd); + return ref Listnode(nil, p1.word + p2.word); +} + +concat(ctxt: ref Context, nl1, nl2: list of ref Listnode): list of ref Listnode +{ + if (nl1 == nil || nl2 == nil) { + if (nl1 == nil && nl2 == nil) + return nil; + ctxt.fail("bad concatenation", "sh: null list in concatenation"); + } + + ret: list of ref Listnode; + if (tl nl1 == nil || tl nl2 == nil) { + for (p1 := nl1; p1 != nil; p1 = tl p1) + for (p2 := nl2; p2 != nil; p2 = tl p2) + ret = concatwords(hd p1, hd p2) :: ret; + } else { + if (len nl1 != len nl2) + ctxt.fail("bad concatenation", "sh: lists of differing sizes can't be concatenated"); + while (nl1 != nil) { + ret = concatwords(hd nl1, hd nl2) :: ret; + (nl1, nl2) = (tl nl1, tl nl2); + } + } + return revlist(ret); +} + +Expropagate: adt { + name: string; +}; + +# run an asynchronous process, first redirecting its I/O +# as specified in _redirs_. +# it sends its process ID down _startchan_ before executing. +# it has to jump through one or two hoops to make sure +# Sys->FD ref counting is done correctly. this code +# is more sensitive than you might think. +runasync(ctxt: ref Context, copyenv: int, argv: list of ref Listnode, redirs: ref Redirlist, + startchan: chan of (int, ref Expropagate)) +{ + status: string; + + pid := sys->pctl(sys->FORKFD, nil); + if (DEBUG) debug(sprint("in async (len redirs: %d)", len redirs.r)); + ctxt = ctxt.copy(copyenv); + exprop := ref Expropagate; + { + newfdl := doredirs(ctxt, redirs); + redirs = nil; + if (newfdl != nil) + sys->pctl(Sys->NEWFD, newfdl); + # stop the old waitfd from holding the intermediate + # file descriptor group open. + ctxt.waitfd = waitfd(); + # N.B. it's important that the sync is done here, not + # before doredirs, as otherwise there's some sort of + # race condition that leads to pipe non-completion. + startchan <-= (pid, exprop); + startchan = nil; + status = ctxt.run(argv, copyenv); + } exception e { + "fail:*" => + exprop.name = e; + if (startchan != nil) + startchan <-= (pid, exprop); + raise e; + } + if (status != nil) { + # don't propagate bad status as an exception. + raise "fail:" + status; + } +} + +# run a synchronous process +runsync(ctxt: ref Context, argv: list of ref Listnode, + redirs: ref Redirlist, last: int): string +{ + if (DEBUG) debug(sys->sprint("in sync (len redirs: %d; last: %d)", len redirs.r, last)); + if (redirs.r != nil && !last) { + # a new process is required to shield redirection side effects + startchan := chan of (int, ref Expropagate); + spawn runasync(ctxt, 0, argv, redirs, startchan); + (pid, exprop) := <-startchan; + redirs = nil; + r := waitfor(ctxt, pid :: nil); + if (exprop.name != nil) + raise exprop.name; + return r; + } else { + newfdl := doredirs(ctxt, redirs); + redirs = nil; + if (newfdl != nil) + sys->pctl(Sys->NEWFD, newfdl); + return ctxt.run(argv, last); + } +} + +# path is prefixed with: "/", "#", "./" or "../" +absolute(p: string): int +{ + if (len p < 2) + return 0; + if (p[0] == '/' || p[0] == '#') + return 1; + if (len p < 3 || p[0] != '.') + return 0; + if (p[1] == '/') + return 1; + if (p[1] == '.' && p[2] == '/') + return 1; + return 0; +} + +runexternal(ctxt: ref Context, args: list of ref Listnode, last: int): string +{ + progname := (hd args).word; + disfile := 0; + if (len progname >= 4 && progname[len progname-4:] == ".dis") + disfile = 1; + pathlist: list of string; + if (absolute(progname)) + pathlist = list of {""}; + else if ((pl := ctxt.get("path")) != nil) + pathlist = list2stringlist(pl); + else + pathlist = list of {"/dis", "."}; + + err := ""; + do { + path: string; + if (hd pathlist != "") + path = hd pathlist + "/" + progname; + else + path = progname; + + npath := path; + if (!disfile) + npath += ".dis"; + mod := load Command npath; + if (mod != nil) { + argv := list2stringlist(args); + export(ctxt.env.localenv); + + if (last) { + { + sys->pctl(Sys->NEWFD, ctxt.keepfds); + mod->init(ctxt.drawcontext, argv); + exit; + } exception e { + EPIPE => + return EPIPE; + "fail:*" => + return e[5:]; + } + } + extstart := chan of int; + spawn externalexec(mod, ctxt.drawcontext, argv, extstart, ctxt.keepfds); + pid := <-extstart; + if (DEBUG) debug("started external externalexec; pid is "+string pid); + return waitfor(ctxt, pid :: nil); + } + err = sys->sprint("%r"); + if (nonexistent(err)) { + # try and run it as a shell script + if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) { + (ok, info) := sys->fstat(fd); + # make permission checking more accurate later + if (ok == 0 && (info.mode & Sys->DMDIR) == 0 + && (info.mode & 8r111) != 0) + return runhashpling(ctxt, fd, path, tl args, last); + }; + err = sys->sprint("%r"); + } + pathlist = tl pathlist; + } while (pathlist != nil && nonexistent(err)); + diagnostic(ctxt, sys->sprint("%s: %s", progname, err)); + return err; +} + +runhashpling(ctxt: ref Context, fd: ref Sys->FD, + path: string, argv: list of ref Listnode, last: int): string +{ + header := array[1024] of byte; + n := sys->read(fd, header, len header); + for (i := 0; i < n; i++) + if (header[i] == byte '\n') + break; + if (i == n || i < 3 || header[0] != byte('#') || header[1] != byte('!')) { + diagnostic(ctxt, "bad script header on " + path); + return "bad header"; + } + (nil, args) := sys->tokenize(string header[2:i], " \t"); + if (args == nil) { + diagnostic(ctxt, "empty header on " + path); + return "bad header"; + } + header = nil; + fd = nil; + nargs: list of ref Listnode; + for (; args != nil; args = tl args) + nargs = ref Listnode(nil, hd args) :: nargs; + nargs = ref Listnode(nil, path) :: nargs; + for (; argv != nil; argv = tl argv) + nargs = hd argv :: nargs; + return runexternal(ctxt, revlist(nargs), last); +} + +runblock(ctxt: ref Context, args: list of ref Listnode, last: int): string +{ + # block execute (we know that hd args represents a block) + cmd := (hd args).cmd; + if (cmd == nil) { + # parse block from first argument + lex := YYLEX.initstring((hd args).word); + + err: string; + (cmd, err) = doparse(lex, "", 0); + if (cmd == nil) + ctxt.fail("parse error", "sh: "+err); + + (hd args).cmd = cmd; + } + # now we've got a parsed block + ctxt.push(); + { + ctxt.setlocal("0", hd args :: nil); + ctxt.setlocal("*", tl args); + if (cmd != nil && cmd.ntype == n_BLOCK) + cmd = cmd.left; + status := walk(ctxt, cmd, last); + ctxt.pop(); + return status; + } exception e{ + "fail:*" => + ctxt.pop(); + raise; + } +} + +# return (ok, val) where ok is non-zero is builtin was found, +# val is return status of builtin +trybuiltin(ctxt: ref Context, args: list of ref Listnode, lseq: int) + : (int, string) +{ + (n, bmods) := findbuiltin(ctxt.env.builtins, (hd args).word); + if (bmods == nil) + return (0, nil); + return (1, (hd bmods)->runbuiltin(ctxt, myself, args, lseq)); +} + +keepfdstr(ctxt: ref Context): string +{ + s := ""; + for (f := ctxt.keepfds; f != nil; f = tl f) { + s += string hd f; + if (tl f != nil) + s += ","; + } + return s; +} + +externalexec(mod: Command, + drawcontext: ref Draw->Context, argv: list of string, startchan: chan of int, keepfds: list of int) +{ + if (DEBUG) debug(sprint("externalexec(%s,... [%d args])", hd argv, len argv)); + sys->pctl(Sys->NEWFD, keepfds); + startchan <-= sys->pctl(0, nil); + { + mod->init(drawcontext, argv); + } + exception e{ + EPIPE => + raise "fail:" + EPIPE; + } +} + +dup(ctxt: ref Context, fd1, fd2: int): int +{ + # shuffle waitfd out of the way if it's being attacked + if (ctxt.waitfd.fd == fd2) { + ctxt.waitfd = waitfd(); + if (ctxt.waitfd.fd == fd2) + panic(sys->sprint("reopen of waitfd gave same fd (%d)", ctxt.waitfd.fd)); + } + return sys->dup(fd1, fd2); +} + +# with thanks to tiny/sh.b +# return error status if redirs failed +doredirs(ctxt: ref Context, redirs: ref Redirlist): list of int +{ + if (redirs.r == nil) + return nil; + keepfds := ctxt.keepfds; + rl := redirs.r; + redirs = nil; + for (; rl != nil; rl = tl rl) { + (rfd, path, (mode, fd1, fd2)) := hd rl; + if (path == nil && rfd == nil) { + # dup + if (fd1 == -1 || fd2 == -1) + ctxt.fail("bad redir", "sh: invalid dup"); + + if (dup(ctxt, fd2, fd1) == -1) + ctxt.fail("bad redir", sys->sprint("sh: cannot dup: %r")); + keepfds = fd1 :: keepfds; + continue; + } + # redir + if (fd1 == -1) { + if ((mode & OMASK) == Sys->OWRITE) + fd1 = 1; + else + fd1 = 0; + } + if (rfd == nil) { + (append, omode) := (mode & OAPPEND, mode & ~OAPPEND); + err := ""; + case mode { + Sys->OREAD => + rfd = sys->open(path, omode); + Sys->OWRITE | OAPPEND or + Sys->ORDWR => + rfd = sys->open(path, omode); + err = sprint("%r"); + if (rfd == nil && nonexistent(err)) { + rfd = sys->create(path, omode, 8r666); + err = nil; + } + Sys->OWRITE => + rfd = sys->create(path, omode, 8r666); + err = sprint("%r"); + if (rfd == nil && err == EPERM) { + # try open; can't create on a file2chan (pipe) + rfd = sys->open(path, omode); + nerr := sprint("%r"); + if(!nonexistent(nerr)) + err = nerr; + } + } + if (rfd == nil) { + if (err == nil) + err = sprint("%r"); + ctxt.fail("bad redir", sys->sprint("sh: cannot open %s: %s", path, err)); + } + if (append) + sys->seek(rfd, big 0, Sys->SEEKEND); # not good enough, but alright for some purposes. + } + # XXX what happens if rfd.fd == fd1? + # it probably gets closed automatically... which is not what we want! + dup(ctxt, rfd.fd, fd1); + keepfds = fd1 :: keepfds; + } + ctxt.keepfds = keepfds; + return ctxt.waitfd.fd :: keepfds; +} + +# +# waiter utility routines +# + +waitfd(): ref Sys->FD +{ + wf := string sys->pctl(0, nil) + "/wait"; + waitfd := sys->open("#p/"+wf, Sys->OREAD); + if (waitfd == nil) + waitfd = sys->open("/prog/"+wf, Sys->OREAD); + if (waitfd == nil) + panic(sys->sprint("cannot open wait file: %r")); + return waitfd; +} + +waitfor(ctxt: ref Context, pids: list of int): string +{ + if (pids == nil) + return nil; + status := array[len pids] of string; + wcount := len status; + buf := array[Sys->WAITLEN] of byte; + onebad := 0; + for(;;){ + n := sys->read(ctxt.waitfd, buf, len buf); + if(n < 0) + panic(sys->sprint("error on wait read: %r")); + (who, line, s) := parsewaitstatus(ctxt, string buf[0:n]); + if (s != nil) { + if (len s >= 5 && s[0:5] == "fail:") + s = s[5:]; + else + diagnostic(ctxt, line); + } + for ((i, pl) := (0, pids); pl != nil; (i, pl) = (i+1, tl pl)) + if (who == hd pl) + break; + if (i < len status) { + # wait returns two records for a killed process... + if (status[i] == nil || s != "killed") { + onebad += s != nil; + status[i] = s; + if (wcount-- <= 1) + break; + } + } + } + if (!onebad) + return nil; + r := status[len status - 1]; + for (i := len status - 2; i >= 0; i--) + r += "|" + status[i]; + return r; +} + +parsewaitstatus(ctxt: ref Context, status: string): (int, string, string) +{ + for (i := 0; i < len status; i++) + if (status[i] == ' ') + break; + if (i == len status - 1 || status[i+1] != '"') + ctxt.fail("bad wait read", + sys->sprint("sh: bad exit status '%s'", status)); + + for (i+=2; i < len status; i++) + if (status[i] == '"') + break; + if (i > len status - 2 || status[i+1] != ':') + ctxt.fail("bad wait read", + sys->sprint("sh: bad exit status '%s'", status)); + + return (int status, status, status[i+2:]); +} + +panic(s: string) +{ + sys->fprint(stderr(), "sh panic: %s\n", s); + raise "panic"; +} + +diagnostic(ctxt: ref Context, s: string) +{ + if (ctxt.options() & Context.VERBOSE) + sys->fprint(stderr(), "sh: %s\n", s); +} + +# +# Sh environment stuff +# + +Context.new(drawcontext: ref Draw->Context): ref Context +{ + initialise(); + if (env != nil) + env->clone(); + ctxt := ref Context( + ref Environment( + ref Builtins(nil, 0), + ref Builtins(nil, 0), + nil, + newlocalenv(nil) + ), + waitfd(), + drawcontext, + 0 :: 1 :: 2 :: nil + ); + myselfbuiltin->initbuiltin(ctxt, myself); + ctxt.env.localenv.flags = ctxt.VERBOSE; + for (vl := ctxt.get("autoload"); vl != nil; vl = tl vl) + if ((hd vl).cmd == nil && (hd vl).word != nil) + loadmodule(ctxt, (hd vl).word); + return ctxt; +} + +Context.copy(ctxt: self ref Context, copyenv: int): ref Context +{ + # XXX could check to see that we are definitely in a + # new process, because there'll be problems if not (two processes + # simultaneously reading the same wait file) + nctxt := ref Context(ctxt.env, waitfd(), ctxt.drawcontext, ctxt.keepfds); + + if (copyenv) { + if (env != nil) + env->clone(); + nctxt.env = ref Environment( + copybuiltins(ctxt.env.sbuiltins), + copybuiltins(ctxt.env.builtins), + ctxt.env.bmods, + copylocalenv(ctxt.env.localenv) + ); + } + return nctxt; +} + +Context.set(ctxt: self ref Context, name: string, val: list of ref Listnode) +{ + e := ctxt.env.localenv; + idx := hashfn(name, len e.vars); + for (;;) { + v := hashfind(e.vars, idx, name); + if (v == nil) { + if (e.pushed == nil) { + flags := Var.CHANGED; + if (noexport(name)) + flags |= Var.NOEXPORT; + hashadd(e.vars, idx, ref Var(name, val, flags)); + return; + } + } else { + v.val = val; + v.flags |= Var.CHANGED; + return; + } + e = e.pushed; + } +} + +Context.get(ctxt: self ref Context, name: string): list of ref Listnode +{ + if (name == nil) + return nil; + + idx := -1; + # cope with $1, $2, etc + if (name[0] > '0' && name[0] <= '9') { + i: int; + for (i = 0; i < len name; i++) + if (name[i] < '0' || name[i] > '9') + break; + if (i >= len name) { + idx = int name - 1; + name = "*"; + } + } + + v := varfind(ctxt.env.localenv, name); + if (v != nil) { + if (idx != -1) + return index(v.val, idx); + return v.val; + } + return nil; +} + +# return the whole environment. +Context.envlist(ctxt: self ref Context): list of (string, list of ref Listnode) +{ + t := array[ENVHASHSIZE] of list of ref Var; + for (e := ctxt.env.localenv; e != nil; e = e.pushed) { + for (i := 0; i < len e.vars; i++) { + for (vl := e.vars[i]; vl != nil; vl = tl vl) { + v := hd vl; + idx := hashfn(v.name, len e.vars); + if (hashfind(t, idx, v.name) == nil) + hashadd(t, idx, v); + } + } + } + + l: list of (string, list of ref Listnode); + for (i := 0; i < ENVHASHSIZE; i++) { + for (vl := t[i]; vl != nil; vl = tl vl) { + v := hd vl; + l = (v.name, v.val) :: l; + } + } + return l; +} + +Context.setlocal(ctxt: self ref Context, name: string, val: list of ref Listnode) +{ + e := ctxt.env.localenv; + idx := hashfn(name, len e.vars); + v := hashfind(e.vars, idx, name); + if (v == nil) { + flags := Var.CHANGED; + if (noexport(name)) + flags |= Var.NOEXPORT; + hashadd(e.vars, idx, ref Var(name, val, flags)); + } else { + v.val = val; + v.flags |= Var.CHANGED; + } +} + + +Context.push(ctxt: self ref Context) +{ + ctxt.env.localenv = newlocalenv(ctxt.env.localenv); +} + +Context.pop(ctxt: self ref Context) +{ + if (ctxt.env.localenv.pushed == nil) + panic("unbalanced contexts in shell environment"); + else { + oldv := ctxt.env.localenv.vars; + ctxt.env.localenv = ctxt.env.localenv.pushed; + for (i := 0; i < len oldv; i++) { + for (vl := oldv[i]; vl != nil; vl = tl vl) { + if ((v := varfind(ctxt.env.localenv, (hd vl).name)) != nil) + v.flags |= Var.CHANGED; + else + ctxt.set((hd vl).name, nil); + } + } + } +} + +Context.run(ctxt: self ref Context, args: list of ref Listnode, last: int): string +{ + if (args == nil || ((hd args).cmd == nil && (hd args).word == nil)) + return nil; + cmd := hd args; + if (cmd.cmd != nil || cmd.word[0] == '{') # } + return runblock(ctxt, args, last); + + if (ctxt.options() & ctxt.EXECPRINT) + sys->fprint(stderr(), "%s\n", quoted(args, 0)); + (doneit, status) := trybuiltin(ctxt, args, last); + if (!doneit) + status = runexternal(ctxt, args, last); + + return status; +} + +Context.addmodule(ctxt: self ref Context, name: string, mod: Shellbuiltin) +{ + mod->initbuiltin(ctxt, myself); + ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods; +} + +Context.addbuiltin(c: self ref Context, name: string, mod: Shellbuiltin) +{ + addbuiltin(c.env.builtins, name, mod); +} + +Context.removebuiltin(c: self ref Context, name: string, mod: Shellbuiltin) +{ + removebuiltin(c.env.builtins, name, mod); +} + +Context.addsbuiltin(c: self ref Context, name: string, mod: Shellbuiltin) +{ + addbuiltin(c.env.sbuiltins, name, mod); +} + +Context.removesbuiltin(c: self ref Context, name: string, mod: Shellbuiltin) +{ + removebuiltin(c.env.sbuiltins, name, mod); +} + +varfind(e: ref Localenv, name: string): ref Var +{ + idx := hashfn(name, len e.vars); + for (; e != nil; e = e.pushed) + for (vl := e.vars[idx]; vl != nil; vl = tl vl) + if ((hd vl).name == name) + return hd vl; + return nil; +} + +Context.fail(ctxt: self ref Context, ename: string, err: string) +{ + if (ctxt.options() & Context.VERBOSE) + sys->fprint(stderr(), "%s\n", err); + raise "fail:" + ename; +} + +Context.setoptions(ctxt: self ref Context, flags, on: int): int +{ + old := ctxt.env.localenv.flags; + if (on) + ctxt.env.localenv.flags |= flags; + else + ctxt.env.localenv.flags &= ~flags; + return old; +} + +Context.options(ctxt: self ref Context): int +{ + return ctxt.env.localenv.flags; +} + +hashfn(s: string, n: int): int +{ + h := 0; + m := len s; + for(i:=0; i<m; i++){ + h = 65599*h+s[i]; + } + return (h & 16r7fffffff) % n; +} + +# the following two functions cheat by getting the caller +# to calculate the actual hash function. this is to avoid +# the hash function being calculated once in every scope +# of a context until the variable is found (or stored). +hashfind(ht: array of list of ref Var, idx: int, n: string): ref Var +{ + for (ent := ht[idx]; ent != nil; ent = tl ent) + if ((hd ent).name == n) + return hd ent; + return nil; +} + +hashadd(ht: array of list of ref Var, idx: int, v: ref Var) +{ + ht[idx] = v :: ht[idx]; +} + +copylocalenv(e: ref Localenv): ref Localenv +{ + nvars := array[len e.vars] of list of ref Var; + flags := e.flags; + for (; e != nil; e = e.pushed) + for (i := 0; i < len nvars; i++) + for (vl := e.vars[i]; vl != nil; vl = tl vl) { + idx := hashfn((hd vl).name, len nvars); + if (hashfind(nvars, idx, (hd vl).name) == nil) + hashadd(nvars, idx, ref *(hd vl)); + } + return ref Localenv(nvars, nil, flags); +} + +# make new local environment. if it's got no pushed levels, +# then get all variables from the global environment. +newlocalenv(pushed: ref Localenv): ref Localenv +{ + e := ref Localenv(array[ENVHASHSIZE] of list of ref Var, pushed, 0); + if (pushed == nil && env != nil) { + for (vl := env->getall(); vl != nil; vl = tl vl) { + (name, val) := hd vl; + hashadd(e.vars, hashfn(name, len e.vars), ref Var(name, envstringtoval(val), 0)); + } + } + if (pushed != nil) + e.flags = pushed.flags; + return e; +} + +copybuiltins(b: ref Builtins): ref Builtins +{ + nb := ref Builtins(array[b.n] of (string, list of Shellbuiltin), b.n); + nb.ba[0:] = b.ba[0:b.n]; + return nb; +} + +findbuiltin(b: ref Builtins, name: string): (int, list of Shellbuiltin) +{ + lo := 0; + hi := b.n - 1; + while (lo <= hi) { + mid := (lo + hi) / 2; + (bname, bmod) := b.ba[mid]; + if (name < bname) + hi = mid - 1; + else if (name > bname) + lo = mid + 1; + else + return (mid, bmod); + } + return (lo, nil); +} + +removebuiltin(b: ref Builtins, name: string, mod: Shellbuiltin) +{ + (n, bmods) := findbuiltin(b, name); + if (bmods == nil) + return; + if (hd bmods == mod) { + if (tl bmods != nil) + b.ba[n] = (name, tl bmods); + else { + b.ba[n:] = b.ba[n+1:b.n]; + b.ba[--b.n] = (nil, nil); + } + } +} + +# add builtin; if it already exists, then replace it. if mod is nil then remove it. +# builtins that refer to myselfbuiltin are special - they +# are never removed, neither are they entirely replaced, only covered. +# no external module can redefine the name "builtin" +addbuiltin(b: ref Builtins, name: string, mod: Shellbuiltin) +{ + if (mod == nil || (name == "builtin" && mod != myselfbuiltin)) + return; + (n, bmods) := findbuiltin(b, name); + if (bmods != nil) { + if (hd bmods == myselfbuiltin) + b.ba[n] = (name, mod :: bmods); + else + b.ba[n] = (name, mod :: nil); + } else { + if (b.n == len b.ba) { + nb := array[b.n + 10] of (string, list of Shellbuiltin); + nb[0:] = b.ba[0:b.n]; + b.ba = nb; + } + b.ba[n+1:] = b.ba[n:b.n]; + b.ba[n] = (name, mod :: nil); + b.n++; + } +} + +removebuiltinmod(b: ref Builtins, mod: Shellbuiltin) +{ + j := 0; + for (i := 0; i < b.n; i++) { + (name, bmods) := b.ba[i]; + if (hd bmods == mod) + bmods = tl bmods; + if (bmods != nil) + b.ba[j++] = (name, bmods); + } + b.n = j; + for (; j < i; j++) + b.ba[j] = (nil, nil); +} + +export(e: ref Localenv) +{ + if (env == nil) + return; + if (e.pushed != nil) + export(e.pushed); + + for (i := 0; i < len e.vars; i++) { + for (vl := e.vars[i]; vl != nil; vl = tl vl) { + v := hd vl; + # a bit inefficient: a local variable will get several putenvs. + if ((v.flags & Var.CHANGED) && !(v.flags & Var.NOEXPORT)) { + setenv(v.name, v.val); + v.flags &= ~Var.CHANGED; + } + } + } +} + +noexport(name: string): int +{ + case name { + "0" or "*" or "status" => return 1; + } + return 0; +} + +index(val: list of ref Listnode, k: int): list of ref Listnode +{ + for (; k > 0 && val != nil; k--) + val = tl val; + if (val != nil) + val = hd val :: nil; + return val; +} + +getenv(name: string): list of ref Listnode +{ + if (env == nil) + return nil; + return envstringtoval(env->getenv(name)); +} + +envstringtoval(v: string): list of ref Listnode +{ + return stringlist2list(str->unquoted(v)); +} + +XXXenvstringtoval(v: string): list of ref Listnode +{ + if (len v == 0) + return nil; + start := len v; + val: list of ref Listnode; + for (i := start - 1; i >= 0; i--) { + if (v[i] == ENVSEP) { + val = ref Listnode(nil, v[i+1:start]) :: val; + start = i; + } + } + return ref Listnode(nil, v[0:start]) :: val; +} + +setenv(name: string, val: list of ref Listnode) +{ + if (env == nil) + return; + env->setenv(name, quoted(val, 1)); +} + +# +# globbing and general wildcard handling +# + +containswildchar(s: string): int +{ + # try and avoid being fooled by GLOB characters in quoted + # text. we'll only be fooled if the GLOB char is followed + # by a wildcard char, or another GLOB. + for (i := 0; i < len s; i++) { + if (s[i] == GLOB && i < len s - 1) { + case s[i+1] { + '*' or '[' or '?' or GLOB => + return 1; + } + } + } + return 0; +} + +# remove GLOBs, and quote other wildcard characters +patquote(word: string): string +{ + outword := ""; + for (i := 0; i < len word; i++) { + case word[i] { + '[' or '*' or '?' or '\\' => + outword[len outword] = '\\'; + GLOB => + i++; + if (i >= len word) + return outword; + } + outword[len outword] = word[i]; + } + return outword; +} + +# get rid of GLOB characters +deglob(s: string): string +{ + j := 0; + for (i := 0; i < len s; i++) { + if (s[i] != GLOB) { + if (i != j) # a worthy optimisation??? + s[j] = s[i]; + j++; + } + } + if (i == j) + return s; + return s[0:j]; +} + +# expand wildcards in _nl_ +glob(nl: list of ref Listnode): list of ref Listnode +{ + new: list of ref Listnode; + while (nl != nil) { + n := hd nl; + if (containswildchar(n.word)) { + qword := patquote(n.word); + files := filepat->expand(qword); + if (files == nil) + files = deglob(n.word) :: nil; + while (files != nil) { + new = ref Listnode(nil, hd files) :: new; + files = tl files; + } + } else + new = n :: new; + nl = tl nl; + } + ret := revlist(new); + return ret; +} + +# +# general list manipulation utility routines +# + +# return string equivalent of nl +list2stringlist(nl: list of ref Listnode): list of string +{ + ret: list of string = nil; + + while (nl != nil) { + newel: string; + el := hd nl; + if (el.word != nil || el.cmd == nil) + newel = el.word; + else + el.word = newel = cmd2string(el.cmd); + ret = newel::ret; + nl = tl nl; + } + + sl := revstringlist(ret); + return sl; +} + +stringlist2list(sl: list of string): list of ref Listnode +{ + ret: list of ref Listnode; + + while (sl != nil) { + ret = ref Listnode(nil, hd sl) :: ret; + sl = tl sl; + } + return revlist(ret); +} + +revstringlist(l: list of string): list of string +{ + t: list of string; + + while(l != nil) { + t = hd l :: t; + l = tl l; + } + return t; +} + +revlist(l: list of ref Listnode): list of ref Listnode +{ + t: list of ref Listnode; + + while(l != nil) { + t = hd l :: t; + l = tl l; + } + return t; +} + +# +# node to string conversion functions +# + +fdassignstr(isassign: int, redir: ref Redir): string +{ + l: string = nil; + if (redir.fd1 >= 0) + l = string redir.fd1; + + if (isassign) { + r: string = nil; + if (redir.fd2 >= 0) + r = string redir.fd2; + return "[" + l + "=" + r + "]"; + } + return "[" + l + "]"; +} + +redirstr(rtype: int): string +{ + case rtype { + * or + Sys->OREAD => return "<"; + Sys->OWRITE => return ">"; + Sys->OWRITE|OAPPEND => return ">>"; + Sys->ORDWR => return "<>"; + } +} + +cmd2string(n: ref Node): string +{ + if (n == nil) + return ""; + + s: string; + case n.ntype { + n_BLOCK => s = "{" + cmd2string(n.left) + "}"; + n_VAR => s = "$" + cmd2string(n.left); + # XXX can this ever occur? + if (n.right != nil) + s += "(" + cmd2string(n.right) + ")"; + n_SQUASH => s = "$\"" + cmd2string(n.left); + n_COUNT => s = "$#" + cmd2string(n.left); + n_BQ => s = "`" + cmd2string(n.left); + n_BQ2 => s = "\"" + cmd2string(n.left); + n_REDIR => s = redirstr(n.redir.rtype); + if (n.redir.fd1 != -1) + s += fdassignstr(0, n.redir); + s += cmd2string(n.left); + n_DUP => s = redirstr(n.redir.rtype) + fdassignstr(1, n.redir); + n_LIST => s = "(" + cmd2string(n.left) + ")"; + n_SEQ => s = cmd2string(n.left) + ";" + cmd2string(n.right); + n_NOWAIT => s = cmd2string(n.left) + "&"; + n_CONCAT => s = cmd2string(n.left) + "^" + cmd2string(n.right); + n_PIPE => s = cmd2string(n.left) + "|"; + if (n.redir != nil && (n.redir.fd1 != -1 || n.redir.fd2 != -1)) + s += fdassignstr(n.redir.fd2 != -1, n.redir); + s += cmd2string(n.right); + n_ASSIGN => s = cmd2string(n.left) + "=" + cmd2string(n.right); + n_LOCAL => s = cmd2string(n.left) + ":=" + cmd2string(n.right); + n_ADJ => s = cmd2string(n.left) + " " + cmd2string(n.right); + n_WORD => s = quote(n.word, 1); + * => s = sys->sprint("unknown%d", n.ntype); + } + return s; +} + +# convert s into a suitable format for reparsing. +# if glob is true, then GLOB chars are significant. +# XXX it might be faster in the more usual cases +# to run through the string first and only build up +# a new string once we've discovered it's necessary. +quote(s: string, glob: int): string +{ + needquote := 0; + t := ""; + for (i := 0; i < len s; i++) { + case s[i] { + '{' or '}' or '(' or ')' or '`' or '&' or ';' or '=' or '>' or '<' or '#' or + '|' or '*' or '[' or '?' or '$' or '^' or ' ' or '\t' or '\n' or '\r' => + needquote = 1; + '\'' => + t[len t] = '\''; + needquote = 1; + GLOB => + if (glob) { + if (i < len s - 1) + i++; + } + } + t[len t] = s[i]; + } + if (needquote || t == nil) + t = "'" + t + "'"; + return t; +} + +squash(l: list of string, sep: string): string +{ + if (l == nil) + return nil; + s := hd l; + for (l = tl l; l != nil; l = tl l) + s += sep + hd l; + return s; +} + +debug(s: string) +{ + if (DEBUG) sys->fprint(stderr(), "%s\n", string sys->pctl(0, nil) + ": " + s); +} + +# +# built-in commands +# + +initbuiltin(c: ref Context, nil: Sh): string +{ + names := array[] of {"load", "unload", "loaded", "builtin", "syncenv", "whatis", "run", "exit", "@"}; + for (i := 0; i < len names; i++) + c.addbuiltin(names[i], myselfbuiltin); + c.addsbuiltin("loaded", myselfbuiltin); + c.addsbuiltin("quote", myselfbuiltin); + c.addsbuiltin("bquote", myselfbuiltin); + c.addsbuiltin("unquote", myselfbuiltin); + c.addsbuiltin("builtin", myselfbuiltin); + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +runsbuiltin(ctxt: ref Context, nil: Sh, argv: list of ref Listnode): list of ref Listnode +{ + case (hd argv).word { + "loaded" => return sbuiltin_loaded(ctxt, argv); + "bquote" => return sbuiltin_quote(ctxt, argv, 0); + "quote" => return sbuiltin_quote(ctxt, argv, 1); + "unquote" => return sbuiltin_unquote(ctxt, argv); + "builtin" => return sbuiltin_builtin(ctxt, argv); + } + return nil; +} + +runbuiltin(ctxt: ref Context, nil: Sh, args: list of ref Listnode, lseq: int): string +{ + status := ""; + name := (hd args).word; + case name { + "load" => status = builtin_load(ctxt, args, lseq); + "loaded" => status = builtin_loaded(ctxt, args, lseq); + "unload" => status = builtin_unload(ctxt, args, lseq); + "builtin" => status = builtin_builtin(ctxt, args, lseq); + "whatis" => status = builtin_whatis(ctxt, args, lseq); + "run" => status = builtin_run(ctxt, args, lseq); + "exit" => status = builtin_exit(ctxt, args, lseq); + "syncenv" => export(ctxt.env.localenv); + "@" => status = builtin_subsh(ctxt, args, lseq); + } + return status; +} + +sbuiltin_loaded(ctxt: ref Context, nil: list of ref Listnode): list of ref Listnode +{ + v: list of ref Listnode; + for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) { + (name, nil) := hd bl; + v = ref Listnode(nil, name) :: v; + } + return v; +} + +sbuiltin_quote(nil: ref Context, argv: list of ref Listnode, quoteblocks: int): list of ref Listnode +{ + return ref Listnode(nil, quoted(tl argv, quoteblocks)) :: nil; +} + +sbuiltin_builtin(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode +{ + if (args == nil || tl args == nil) + builtinusage(ctxt, "builtin command [args ...]"); + name := (hd tl args).word; + (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name); + for (; mods != nil; mods = tl mods) + if (hd mods == myselfbuiltin) + return (hd mods)->runsbuiltin(ctxt, myself, tl args); + ctxt.fail("builtin not found", sys->sprint("sh: builtin %s not found", name)); + return nil; +} + +sbuiltin_unquote(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + argv = tl argv; + if (argv == nil || tl argv != nil) + builtinusage(ctxt, "unquote arg"); + + arg := (hd argv).word; + if (arg == nil && (hd argv).cmd != nil) + arg = cmd2string((hd argv).cmd); + return stringlist2list(str->unquoted(arg)); +} + +getself(): Shellbuiltin +{ + return myselfbuiltin; +} + +builtinusage(ctxt: ref Context, s: string) +{ + ctxt.fail("usage", "sh: usage: " + s); +} + +builtin_exit(nil: ref Context, nil: list of ref Listnode, nil: int): string +{ + # XXX using this primitive can cause + # environment stack not to be popped properly. + exit; +} + +builtin_subsh(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + if (tl args == nil) + return nil; + startchan := chan of (int, ref Expropagate); + spawn runasync(ctxt, 0, tl args, ref Redirlist, startchan); + (exepid, exprop) := <-startchan; + status := waitfor(ctxt, exepid :: nil); + if (exprop.name != nil) + raise exprop.name; + return status; +} + +builtin_loaded(ctxt: ref Context, nil: list of ref Listnode, nil: int): string +{ + b := ctxt.env.builtins; + for (i := 0; i < b.n; i++) { + (name, bmods) := b.ba[i]; + sys->print("%s\t%s\n", name, modname(ctxt, hd bmods)); + } + b = ctxt.env.sbuiltins; + for (i = 0; i < b.n; i++) { + (name, bmods) := b.ba[i]; + sys->print("${%s}\t%s\n", name, modname(ctxt, hd bmods)); + } + return nil; +} + +# it's debateable whether this should throw an exception or +# return a failed exit status - however, most scripts don't +# check the status and do need the module they're loading, +# so i think the exception is probably more useful... +builtin_load(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + if (tl args == nil || (hd tl args).word == nil) + builtinusage(ctxt, "load path..."); + args = tl args; + path := (hd args).word; + if (args == nil) + builtinusage(ctxt, "load path..."); + status := ""; + for (; args != nil; args = tl args) { + s := loadmodule(ctxt, (hd args).word); + if (s != nil) + raise "fail:" + s; + } + return nil; +} + +builtin_unload(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + if (tl args == nil) + builtinusage(ctxt, "unload path..."); + status := ""; + for (args = tl args; args != nil; args = tl args) + if ((s := unloadmodule(ctxt, (hd args).word)) != nil) + status = s; + return status; +} + +builtin_run(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + if (tl args == nil || (hd tl args).word == nil) + builtinusage(ctxt, "run path"); + ctxt.push(); + { + ctxt.setoptions(ctxt.INTERACTIVE, 0); + runscript(ctxt, (hd tl args).word, tl tl args, 1); + ctxt.pop(); + return nil; + } exception e { + "fail:*" => + ctxt.pop(); + return e[5:]; + } +} + +# four categories: +# environment variables +# substitution builtins +# braced blocks +# builtins (including those defined by externally loaded modules) +# or external programs +# other +builtin_whatis(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + if (len args < 2) + builtinusage(ctxt, "whatis name ..."); + err := ""; + for (args = tl args; args != nil; args = tl args) + if ((e := whatisit(ctxt, hd args)) != nil) + err = e; + return err; +} + +whatisit(ctxt: ref Context, el: ref Listnode): string +{ + if (el.cmd != nil) { + sys->print("%s\n", cmd2string(el.cmd)); + return nil; + } + found := 0; + name := el.word; + if (name != nil && name[0] == '{') { #} + sys->print("%s\n", name); + return nil;; + } + if (name == nil) + return nil; # XXX questionable + w: string; + val := ctxt.get(name); + if (val != nil) { + found++; + w += sys->sprint("%s=%s\n", quote(name, 0), quoted(val, 0)); + } + (nil, mods) := findbuiltin(ctxt.env.sbuiltins, name); + if (mods != nil) { + mod := hd mods; + if (mod == myselfbuiltin) + w += "${builtin " + name + "}\n"; + else { + mw := mod->whatis(ctxt, myself, name, Shellbuiltin->SBUILTIN); + if (mw == nil) + mw = "${" + name + "}"; + w += "load " + modname(ctxt, mod) + "; " + mw + "\n"; + } + found++; + } + (nil, mods) = findbuiltin(ctxt.env.builtins, name); + if (mods != nil) { + mod := hd mods; + if (mod == myselfbuiltin) + sys->print("builtin %s\n", name); + else { + mw := mod->whatis(ctxt, myself, name, Shellbuiltin->BUILTIN); + if (mw == nil) + mw = name; + w += "load " + modname(ctxt, mod) + "; " + mw + "\n"; + } + found++; + } else { + disfile := 0; + if (len name >= 4 && name[len name-4:] == ".dis") + disfile = 1; + pathlist: list of string; + if (len name >= 2 && (name[0] == '/' || name[0:2] == "./")) + pathlist = list of {""}; + else if ((pl := ctxt.get("path")) != nil) + pathlist = list2stringlist(pl); + else + pathlist = list of {"/dis", "."}; + + foundpath := ""; + while (pathlist != nil) { + path: string; + if (hd pathlist != "") + path = hd pathlist + "/" + name; + else + path = name; + if (!disfile && (fd := sys->open(path, Sys->OREAD)) != nil) { + if (executable(sys->fstat(fd), 8r111)) { + foundpath = path; + break; + } + } + if (!disfile) + path += ".dis"; + if (executable(sys->stat(path), 8r444)) { + foundpath = path; + break; + } + pathlist = tl pathlist; + } + if (foundpath != nil) + w += foundpath + "\n"; + } + for (bmods := ctxt.env.bmods; bmods != nil; bmods = tl bmods) { + (modname, mod) := hd bmods; + if ((mw := mod->whatis(ctxt, myself, name, Shellbuiltin->OTHER)) != nil) + w += "load " + modname + "; " + mw + "\n"; + } + if (w == nil) { + sys->fprint(stderr(), "%s: not found\n", name); + return "not found"; + } + sys->print("%s", w); + return nil; +} + +# execute a command ignoring names defined by externally defined modules +builtin_builtin(ctxt: ref Context, args: list of ref Listnode, last: int): string +{ + if (len args < 2) + builtinusage(ctxt, "builtin command [args ...]"); + name := (hd tl args).word; + if (name == nil || name[0] == '{') { + diagnostic(ctxt, name + " not found"); + return "not found"; + } + (nil, mods) := findbuiltin(ctxt.env.builtins, name); + for (; mods != nil; mods = tl mods) + if (hd mods == myselfbuiltin) + return (hd mods)->runbuiltin(ctxt, myself, tl args, last); + if (ctxt.options() & ctxt.EXECPRINT) + sys->fprint(stderr(), "%s\n", quoted(tl args, 0)); + return runexternal(ctxt, tl args, last); +} + +modname(ctxt: ref Context, mod: Shellbuiltin): string +{ + for (ml := ctxt.env.bmods; ml != nil; ml = tl ml) { + (bname, bmod) := hd ml; + if (bmod == mod) + return bname; + } + return "builtin"; +} + +loadmodule(ctxt: ref Context, name: string): string +{ + # avoid loading the same module twice (it's convenient + # to have load be a null-op if the module required is already loaded) + for (bl := ctxt.env.bmods; bl != nil; bl = tl bl) { + (bname, nil) := hd bl; + if (bname == name) + return nil; + } + path := name; + if (len path < 4 || path[len path-4:] != ".dis") + path += ".dis"; + if (path[0] != '/' && path[0:2] != "./") + path = BUILTINPATH + "/" + path; + mod := load Shellbuiltin path; + if (mod == nil) { + diagnostic(ctxt, sys->sprint("load: cannot load %s: %r", path)); + return "bad module"; + } + s := mod->initbuiltin(ctxt, myself); + ctxt.env.bmods = (name, mod->getself()) :: ctxt.env.bmods; + if (s != nil) { + unloadmodule(ctxt, name); + diagnostic(ctxt, "load: module init failed: " + s); + } + return s; +} + +unloadmodule(ctxt: ref Context, name: string): string +{ + bl: list of (string, Shellbuiltin); + mod: Shellbuiltin; + for (cl := ctxt.env.bmods; cl != nil; cl = tl cl) { + (bname, bmod) := hd cl; + if (bname == name) + mod = bmod; + else + bl = hd cl :: bl; + } + if (mod == nil) { + diagnostic(ctxt, sys->sprint("module %s not found", name)); + return "not found"; + } + for (ctxt.env.bmods = nil; bl != nil; bl = tl bl) + ctxt.env.bmods = hd bl :: ctxt.env.bmods; + removebuiltinmod(ctxt.env.builtins, mod); + removebuiltinmod(ctxt.env.sbuiltins, mod); + return nil; +} + +executable(s: (int, Sys->Dir), mode: int): int +{ + (ok, info) := s; + return ok != -1 && (info.mode & Sys->DMDIR) == 0 + && (info.mode & mode) != 0; +} + +quoted(val: list of ref Listnode, quoteblocks: int): string +{ + s := ""; + for (; val != nil; val = tl val) { + el := hd val; + if (el.cmd == nil || (quoteblocks && el.word != nil)) + s += quote(el.word, 0); + else { + cmd := cmd2string(el.cmd); + if (quoteblocks) + cmd = quote(cmd, 0); + s += cmd; + } + if (tl val != nil) + s[len s] = ' '; + } + return s; +} + +setstatus(ctxt: ref Context, val: string): string +{ + ctxt.setlocal("status", ref Listnode(nil, val) :: nil); + return val; +} + +# +# beginning of parser routines +# + +doparse(l: ref YYLEX, prompt: string, showline: int): (ref Node, string) +{ + l.prompt = prompt; + l.err = nil; + l.lval.node = nil; + yyparse(l); + l.lastnl = 0; # don't print secondary prompt next time + if (l.err != nil) { + s: string; + if (l.err == nil) + l.err = "unknown error"; + if (l.errline > 0 && showline) + s = sys->sprint("%s:%d: %s", l.path, l.errline, l.err); + else + s = l.path + ": parse error: " + l.err; + return (nil, s); + } + return (l.lval.node, nil); +} + +blanklex: YYLEX; # for hassle free zero initialisation + +YYLEX.initstring(s: string): ref YYLEX +{ + ret := ref blanklex; + ret.s = s; + ret.path="internal"; + ret.strpos = 0; + return ret; +} + +YYLEX.initfile(fd: ref Sys->FD, path: string): ref YYLEX +{ + lex := ref blanklex; + lex.f = bufio->fopen(fd, bufio->OREAD); + lex.path = path; + lex.cbuf = array[2] of int; # number of characters of pushback + lex.linenum = 1; + lex.prompt = ""; + return lex; +} + +YYLEX.error(l: self ref YYLEX, s: string) +{ + if (l.err == nil) { + l.err = s; + l.errline = l.linenum; + } +} + +NOTOKEN: con -1; + +YYLEX.lex(l: self ref YYLEX): int +{ + # the following are allowed a free caret: + # $, word and quoted word; + # also, allowed chrs in unquoted word following dollar are [a-zA-Z0-9*_] + endword := 0; + wasdollar := 0; + tok := NOTOKEN; + while (tok == NOTOKEN) { + case c := l.getc() { + l.EOF => + tok = END; + '\n' => + tok = '\n'; + '\r' or '\t' or ' ' => + ; + '#' => + while ((c = l.getc()) != '\n' && c != l.EOF) + ; + l.ungetc(); + ';' => tok = ';'; + '&' => + c = l.getc(); + if(c == '&') + tok = ANDAND; + else{ + l.ungetc(); + tok = '&'; + } + '^' => tok = '^'; + '{' => tok = '{'; + '}' => tok = '}'; + ')' => tok = ')'; + '(' => tok = '('; + '=' => (tok, l.lval.optype) = ('=', n_ASSIGN); + '$' => + if (l.atendword) { + l.ungetc(); + tok = '^'; + break; + } + case (c = l.getc()) { + '#' => + l.lval.optype = n_COUNT; + '"' => + l.lval.optype = n_SQUASH; + * => + l.ungetc(); + l.lval.optype = n_VAR; + } + tok = OP; + wasdollar = 1; + '"' or '`'=> + if (l.atendword) { + tok = '^'; + l.ungetc(); + break; + } + tok = OP; + if (c == '"') + l.lval.optype = n_BQ2; + else + l.lval.optype = n_BQ; + '>' or '<' => + rtype: int; + nc := l.getc(); + if (nc == '>') { + if (c == '>') + rtype = Sys->OWRITE | OAPPEND; + else + rtype = Sys->ORDWR; + nc = l.getc(); + } else if (c == '>') + rtype = Sys->OWRITE; + else + rtype = Sys->OREAD; + tok = REDIR; + if (nc == '[') { + (tok, l.lval.redir) = readfdassign(l); + if (tok == ERROR) + (l.err, l.errline) = ("syntax error in redirection", l.linenum); + } else { + l.ungetc(); + l.lval.redir = ref Redir(-1, -1, -1); + } + if (l.lval.redir != nil) + l.lval.redir.rtype = rtype; + '|' => + tok = '|'; + l.lval.redir = nil; + if ((c = l.getc()) == '[') { + (tok, l.lval.redir) = readfdassign(l); + if (tok == ERROR) { + (l.err, l.errline) = ("syntax error in pipe redirection", l.linenum); + return tok; + } + tok = '|'; + } else if(c == '|') + tok = OROR; + else + l.ungetc(); + + '\'' => + if (l.atendword) { + l.ungetc(); + tok = '^'; + break; + } + startline := l.linenum; + s := ""; + for(;;) { + while ((nc := l.getc()) != '\'' && nc != l.EOF) + s[len s] = nc; + if (nc == l.EOF) { + (l.err, l.errline) = ("unterminated string literal", startline); + return ERROR; + } + if (l.getc() != '\'') { + l.ungetc(); + break; + } + s[len s] = '\''; # 'xxx''yyy' becomes WORD(xxx'yyy) + } + l.lval.word = s; + tok = WORD; + endword = 1; + + * => + if (c == ':') { + if (l.getc() == '=') { + tok = '='; + l.lval.optype = n_LOCAL; + break; + } + l.ungetc(); + } + if (l.atendword) { + l.ungetc(); + tok = '^'; + break; + } + allowed: string; + if (l.wasdollar) + allowed = "a-zA-Z0-9*_"; + else + allowed = "^\n \t\r|$'#<>;^(){}`&=\""; + word := ""; + loop: do { + case c { + '*' or '?' or '[' or GLOB => + word[len word] = GLOB; + ':' => + nc := l.getc(); + l.ungetc(); + if (nc == '=') + break loop; + } + word[len word] = c; + } while ((c = l.getc()) != l.EOF && str->in(c, allowed)); + l.ungetc(); + l.lval.word = word; + tok = WORD; + endword = 1; + } + l.atendword = endword; + l.wasdollar = wasdollar; + } +# sys->print("token %s\n", tokstr(tok)); + return tok; +} + +tokstr(t: int): string +{ + s: string; + case t { + '\n' => s = "'\\n'"; + 33 to 127 => s = sprint("'%c'", t); + DUP=> s = "DUP"; + REDIR =>s = "REDIR"; + WORD => s = "WORD"; + OP => s = "OP"; + END => s = "END"; + ERROR=> s = "ERROR"; + * => + s = "<unknowntok"+ string t + ">"; + } + return s; +} + +YYLEX.ungetc(lex: self ref YYLEX) +{ + lex.strpos--; + if (lex.f != nil) { + lex.ncbuf++; + if (lex.strpos < 0) + lex.strpos = len lex.cbuf - 1; + } +} + +YYLEX.getc(lex: self ref YYLEX): int +{ + if (lex.eof) # EOF sticks + return lex.EOF; + c: int; + if (lex.f != nil) { + if (lex.ncbuf > 0) { + c = lex.cbuf[lex.strpos++]; + if (lex.strpos >= len lex.cbuf) + lex.strpos = 0; + lex.ncbuf--; + } else { + if (lex.lastnl && lex.prompt != nil) + sys->fprint(stderr(), "%s", lex.prompt); + c = bufio->lex.f.getc(); + if (c == bufio->ERROR || c == bufio->EOF) { + lex.eof = 1; + c = lex.EOF; + } else if (c == '\n') + lex.linenum++; + lex.lastnl = (c == '\n'); + lex.cbuf[lex.strpos++] = c; + if (lex.strpos >= len lex.cbuf) + lex.strpos = 0; + } + } else { + if (lex.strpos >= len lex.s) { + lex.eof = 1; + c = lex.EOF; + } else + c = lex.s[lex.strpos++]; + } + return c; +} + +# read positive decimal number; return -1 if no number found. +readnum(lex: ref YYLEX): int +{ + sum := nc := 0; + while ((c := lex.getc()) >= '0' && c <= '9') { + sum = (sum * 10) + (c - '0'); + nc++; + } + lex.ungetc(); + if (nc == 0) + return -1; + return sum; +} + +# return tuple (toktype, lhs, rhs). +# -1 signifies no number present. +# '[' char has already been read. +readfdassign(lex: ref YYLEX): (int, ref Redir) +{ + n1 := readnum(lex); + if ((c := lex.getc()) != '=') { + if (c == ']') + return (REDIR, ref Redir(-1, n1, -1)); + + return (ERROR, nil); + } + n2 := readnum(lex); + if (lex.getc() != ']') + return (ERROR, nil); + return (DUP, ref Redir(-1, n1, n2)); +} + +mkseq(left, right: ref Node): ref Node +{ + if (left != nil && right != nil) + return mk(n_SEQ, left, right); + else if (left == nil) + return right; + return left; +} + +mk(ntype: int, left, right: ref Node): ref Node +{ + return ref Node(ntype, left, right, nil, nil); +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} diff --git a/appl/cmd/sh/std.b b/appl/cmd/sh/std.b new file mode 100644 index 00000000..6a944614 --- /dev/null +++ b/appl/cmd/sh/std.b @@ -0,0 +1,812 @@ +implement Shellbuiltin; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; +include "filepat.m"; + filepat: Filepat; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +builtinnames := array[] of { + "if", "while", "~", "!", "apply", "for", + "status", "pctl", "fn", "subfn", "and", "or", + "raise", "rescue", "flag", "getlines", "no", +}; + +sbuiltinnames := array[] of { + "hd", "tl", "index", "split", "join", "pid", "parse", "env", "pipe", +}; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("std: cannot load self: %r")); + filepat = load Filepat Filepat->PATH; + if (filepat == nil) + ctxt.fail("bad module", + sys->sprint("std: cannot load: %s: %r", Filepat->PATH)); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + ctxt.fail("bad module", + sys->sprint("std: cannot load: %s: %r", Bufio->PATH)); + names := builtinnames; + for (i := 0; i < len names; i++) + ctxt.addbuiltin(names[i], myself); + names = sbuiltinnames; + for (i = 0; i < len names; i++) + ctxt.addsbuiltin(names[i], myself); + env := ctxt.envlist(); + for (; env != nil; env = tl env) { + (name, val) := hd env; + if (len name > 3 && name[0:3] == "fn-") + fndef(ctxt, name[3:], val, 0); + if (len name > 4 && name[0:4] == "sfn-") + fndef(ctxt, name[4:], val, 1); + } + return nil; +} + +whatis(c: ref Sh->Context, sh: Sh, name: string, wtype: int): string +{ + ename, fname: string; + case wtype { + BUILTIN => + (ename, fname) = ("fn-", "fn "); + SBUILTIN => + (ename, fname) = ("sfn-", "subfn "); + OTHER => + return nil; + } + + val := c.get(ename + name); + if (val != nil) + return fname + name + " " + sh->quoted(hd val :: nil, 0); + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +runbuiltin(c: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode, last: int): string +{ + status: string; + name := (hd cmd).word; + val := c.get("fn-" + name); + if (val != nil) + return c.run(hd val :: tl cmd, last); + case name { + "if" => status = builtin_if(c, cmd, last); + "while" => status = builtin_while(c, cmd, last); + "and" => status = builtin_and(c, cmd, last); + "apply" => status = builtin_apply(c, cmd, last); + "for" => status = builtin_for(c, cmd, last); + "or" => status = builtin_or(c, cmd, last); + "!" => status = builtin_not(c, cmd, last); + "fn" => status = builtin_fn(c, cmd, last, 0); + "subfn" => status = builtin_fn(c, cmd, last, 1); + "~" => status = builtin_twiddle(c, cmd, last); + "status" => status = builtin_status(c, cmd, last); + "pctl" => status = builtin_pctl(c, cmd, last); + "raise" => status = builtin_raise(c, cmd, last); + "rescue" => status = builtin_rescue(c, cmd, last); + "flag" => status = builtin_flag(c, cmd, last); + "getlines" => status = builtin_getlines(c, cmd, last); + "no" => status = builtin_no(c, cmd, last); + } + return status; +} + +runsbuiltin(c: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode): list of ref Listnode +{ + name := (hd cmd).word; + val := c.get("sfn-" + name); + if (val != nil) + return runsubfn(c, val, tl cmd); + case name { + "pid" => + return ref Listnode(nil, string sys->pctl(0, nil)) :: nil; + "hd" => + if (tl cmd == nil) + return nil; + return hd tl cmd :: nil; + "tl" => + if (tl cmd == nil) + return nil; + return tl tl cmd; + "index" => + return sbuiltin_index(c, cmd); + "split" => + return sbuiltin_split(c, cmd); + "join" => + return sbuiltin_join(c, cmd); + "parse" => + return sbuiltin_parse(c, cmd); + "env" => + return sbuiltin_env(c, cmd); + "pipe" => + return sbuiltin_pipe(c, cmd); + } + return nil; +} + +runsubfn(ctxt: ref Context, body, args: list of ref Listnode): list of ref Listnode +{ + if (body == nil) + return nil; + ctxt.push(); + { + ctxt.setlocal("result", nil); + ctxt.run(hd body :: args, 0); + result := ctxt.get("result"); + ctxt.pop(); + return result; + } exception e { + "fail:*" => + ctxt.pop(); + raise e; + } +} + +sbuiltin_index(ctxt: ref Context, val: list of ref Listnode): list of ref Listnode +{ + if (len val < 2 || (hd tl val).word == nil) + builtinusage(ctxt, "index num list"); + k := int (hd tl val).word - 1; + val = tl tl val; + for (; k > 0 && val != nil; k--) + val = tl val; + if (val != nil) + val = hd val :: nil; + return val; +} + +# return a parsed version of a string, raising a "parse error" exception if +# it fails. the string must be a braced command block. +sbuiltin_parse(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode +{ + if (len args != 2) + builtinusage(ctxt, "parse arg"); + args = tl args; + if ((hd args).cmd != nil) + return ref Listnode((hd args).cmd, nil) :: nil; + w := (hd args).word; + if (w == nil || w[0] != '{') #} + ctxt.fail("parse error", "parse: argument must be a braced block"); + (n, err) := sh->parse(w); + if (err != nil) + ctxt.fail("parse error", "parse: " + err); + return ref Listnode(n, nil) :: nil; +} + +sbuiltin_env(ctxt: ref Context, nil: list of ref Listnode): list of ref Listnode +{ + vl: list of string; + for (e := ctxt.envlist(); e != nil; e = tl e) { + (n, v) := hd e; + if (v != nil) # XXX this is debatable... someone might want to see null local vars. + vl = n :: vl; + } + return sh->stringlist2list(vl); +} + +word(n: ref Listnode): string +{ + if (n.word != nil) + return n.word; + if (n.cmd != nil) + n.word = sh->cmd2string(n.cmd); + return n.word; +} + +# usage: split [separators] value +sbuiltin_split(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode +{ + n := len args; + if (n < 2 || n > 3) + builtinusage(ctxt, "split [separators] value"); + seps: string; + if (n == 2) { + ifs := ctxt.get("ifs"); + if (ifs == nil) + ctxt.fail("usage", "split: $ifs not set"); + seps = word(hd ifs); + } else { + args = tl args; + seps = word(hd args); + } + (nil, toks) := sys->tokenize(word(hd tl args), seps); + return sh->stringlist2list(toks); +} + +sbuiltin_join(ctxt: ref Context, args: list of ref Listnode): list of ref Listnode +{ + args = tl args; + if (args == nil) + builtinusage(ctxt, "join separator [arg...]"); + seps := word(hd args); + if (tl args == nil) + return ref Listnode(nil, nil) :: nil; + s := word(hd tl args); + for (args = tl tl args; args != nil; args = tl args) + s += seps + word(hd args); + return ref Listnode(nil, s) :: nil; +} + +builtin_fn(ctxt: ref Context, args: list of ref Listnode, nil: int, issub: int): string +{ + n := len args; + title := (hd args).word; + if (n < 2) + builtinusage(ctxt, title + " [name...] [{body}]"); + for (al := tl args; tl al != nil; al = tl al) + if ((hd al).cmd != nil) + builtinusage(ctxt, title + " [name...] [{body}]"); + if ((hd al).cmd != nil) { + cmd := hd al :: nil; + for (al = tl args; tl al != nil; al = tl al) + fndef(ctxt, (hd al).word, cmd, issub); + } else { + for (al = tl args; al != nil; al = tl al) + fnundef(ctxt, (hd al).word, issub); + } + return nil; +} + +fndef(ctxt: ref Context, name: string, cmd: list of ref Listnode, issub: int) +{ + if (cmd == nil) + return; + if (issub) { + ctxt.set("sfn-" + name, cmd); + ctxt.addsbuiltin(name, myself); + } else { + ctxt.set("fn-" + name, cmd); + ctxt.addbuiltin(name, myself); + } +} + +fnundef(ctxt: ref Context, name: string, issub: int) +{ + if (issub) { + ctxt.set("sfn-" + name, nil); + ctxt.removesbuiltin(name, myself); + } else { + ctxt.set("fn-" + name, nil); + ctxt.removebuiltin(name, myself); + } +} + +builtin_flag(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + n := len args; + if (n < 2 || n > 3 || len (hd tl args).word != 1) + builtinusage(ctxt, "flag [vxei] [+-]"); + flag := (hd tl args).word[0]; + p := ""; + if (n == 3) + p = (hd tl tl args).word; + mask := 0; + case flag { + 'v' => mask = Context.VERBOSE; + 'x' => mask = Context.EXECPRINT; + 'e' => mask = Context.ERROREXIT; + 'i' => mask = Context.INTERACTIVE; + * => builtinusage(ctxt, "flag [vxei] [+-]"); + } + case p { + "" => if (ctxt.options() & mask) + return nil; + return "not set"; + "-" => ctxt.setoptions(mask, 0); + "+" => ctxt.setoptions(mask, 1); + * => builtinusage(ctxt, "flag [vxei] [+-]"); + } + return nil; +} + +builtin_no(nil: ref Context, args: list of ref Listnode, nil: int): string +{ + if (tl args != nil) + return "yes"; + return nil; +} + +iscmd(n: ref Listnode): int +{ + return n.cmd != nil || (n.word != nil && n.word[0] == '{'); +} + +builtin_if(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + args = tl args; + nargs := len args; + if (nargs < 2) + builtinusage(ctxt, "if {cond} {action} [{cond} {action}]... [{elseaction}]"); + + status: string; + dolstar := ctxt.get("*"); + while (args != nil) { + cmd: ref Listnode = nil; + if (tl args == nil) { + cmd = hd args; + args = tl args; + } else { + if (!iscmd(hd args)) + builtinusage(ctxt, "if [{cond} {action}]... [{elseaction}]"); + + status = ctxt.run(hd args :: dolstar, 0); + if (status == nil) { + cmd = hd tl args; + args = nil; + } else + args = tl tl args; + setstatus(ctxt, status); + } + if (cmd != nil) { + if (!iscmd(cmd)) + builtinusage(ctxt, "if [{cond} {action}]... [{elseaction}]"); + + status = ctxt.run(cmd :: dolstar, 0); + } + } + return status; +} + +builtin_or(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + s: string; + dolstar := ctxt.get("*"); + for (args = tl args; args != nil; args = tl args) { + if (!iscmd(hd args)) + builtinusage(ctxt, "or [{cmd} ...]"); + if ((s = ctxt.run(hd args :: dolstar, 0)) == nil) + return nil; + else + setstatus(ctxt, s); + } + return s; +} + +builtin_and(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + dolstar := ctxt.get("*"); + for (args = tl args; args != nil; args = tl args) { + if (!iscmd(hd args)) + builtinusage(ctxt, "and [{cmd} ...]"); + if ((s := ctxt.run(hd args :: dolstar, 0)) != nil) + return s; + else + setstatus(ctxt, nil); + } + return nil; +} + +builtin_while(ctxt: ref Context, args: list of ref Listnode, nil: int) : string +{ + args = tl args; + if (len args != 2 || !iscmd(hd args) || !iscmd(hd tl args)) + builtinusage(ctxt, "while {condition} {cmd}"); + + dolstar := ctxt.get("*"); + cond := hd args :: dolstar; + action := hd tl args :: dolstar; + status := ""; + + for(;;){ + { + while (ctxt.run(cond, 0) == nil) + status = setstatus(ctxt, ctxt.run(action, 0)); + return status; + } exception e{ + "fail:*" => + if (loopexcept(e) == BREAK) + return status; + } + } +} + +builtin_getlines(ctxt: ref Context, argv: list of ref Listnode, nil: int) : string +{ + n := len argv; + if (n < 2 || n > 3) + builtinusage(ctxt, "getlines [separators] {cmd}"); + argv = tl argv; + seps := "\n"; + if (n == 3) { + seps = word(hd argv); + argv = tl argv; + } + if (len seps == 0) + builtinusage(ctxt, "getlines [separators] {cmd}"); + if (!iscmd(hd argv)) + builtinusage(ctxt, "getlines [separators] {cmd}"); + cmd := hd argv :: ctxt.get("*"); + stdin := bufio->fopen(sys->fildes(0), Sys->OREAD); + if (stdin == nil) + ctxt.fail("bad input", sys->sprint("getlines: cannot open stdin: %r")); + status := ""; + ctxt.push(); + for(;;){ + { + for (;;) { + s: string; + if (len seps == 1) + s = stdin.gets(seps[0]); + else + s = stdin.gett(seps); + if (s == nil) + break; + # make sure we don't lose the last unterminated line + lastc := s[len s - 1]; + if (lastc == seps[0]) + s = s[0:len s - 1]; + else for (i := 1; i < len seps; i++) { + if (lastc == seps[i]) { + s = s[0:len s - 1]; + break; + } + } + ctxt.setlocal("line", ref Listnode(nil, s) :: nil); + status = setstatus(ctxt, ctxt.run(cmd, 0)); + } + ctxt.pop(); + return status; + } exception e { + "fail:*" => + ctxt.pop(); + if (loopexcept(e) == BREAK) + return status; + ctxt.push(); + } + } +} + +# usage: raise [name] +builtin_raise(ctxt: ref Context, args: list of ref Listnode, nil: int) : string +{ + ename: ref Listnode; + if (tl args == nil) { + e := ctxt.get("exception"); + if (e == nil) + ctxt.fail("bad raise context", "raise: no exception found"); + ename = (hd e); + } else + ename = hd tl args; + if (ename.word == nil && ename.cmd != nil) + ctxt.fail("bad raise context", "raise: bad exception name"); + xraise("fail:" + ename.word); + return nil; +} + +# usage: rescue pattern rescuecmd cmd +builtin_rescue(ctxt: ref Context, args: list of ref Listnode, last: int) : string +{ + args = tl args; + if (len args != 3 || !iscmd(hd tl args) || !iscmd(hd tl tl args)) + builtinusage(ctxt, "rescue pattern {rescuecmd} {cmd}"); + if ((hd args).word == nil && (hd args).cmd != nil) + ctxt.fail("usage", "rescue: bad pattern"); + dolstar := ctxt.get("*"); + handler := hd tl args :: dolstar; + code := hd tl tl args :: dolstar; + { + return ctxt.run(code, 0); + } exception e { + "fail:*" => + ctxt.push(); + ctxt.set("exception", ref Listnode(nil, e[5:]) :: nil); + { + status := ctxt.run(handler, last); + ctxt.pop(); + return status; + } exception e2{ + "fail:*" => + ctxt.pop(); + raise e; + } + } +} + +builtin_not(ctxt: ref Context, args: list of ref Listnode, last: int): string +{ + # syntax: ! cmd [args...] + args = tl args; + if (args == nil || ctxt.run(args, last) == nil) + return "false"; + return ""; +} + +builtin_for(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + Usage: con "for var in [item...] {cmd}"; + args = tl args; + if (args == nil) + builtinusage(ctxt, Usage); + var := (hd args).word; + if (var == nil) + ctxt.fail("bad assign", "for: bad variable name"); + args = tl args; + if (args == nil || (hd args).word != "in") + builtinusage(ctxt, Usage); + args = tl args; + if (args == nil) + builtinusage(ctxt, Usage); + for (eargs := args; tl eargs != nil; eargs = tl eargs) + ; + cmd := hd eargs; + if (!iscmd(cmd)) + builtinusage(ctxt, Usage); + + status := ""; + dolstar := ctxt.get("*"); + for(;;){ + { + for (; tl args != nil; args = tl args) { + ctxt.setlocal(var, hd args :: nil); + status = setstatus(ctxt, ctxt.run(cmd :: dolstar, 0)); + } + return status; + } exception e { + "fail:*" => + if (loopexcept(e) == BREAK) + return status; + args = tl args; + } + } +} + +CONTINUE, BREAK: con iota; +loopexcept(ename: string): int +{ + case ename[5:] { + "break" => + return BREAK; + "continue" => + return CONTINUE; + * => + raise ename; + } + return 0; +} + +builtin_apply(ctxt: ref Context, args: list of ref Listnode, nil: int): string +{ + args = tl args; + if (args == nil || !iscmd(hd args)) + builtinusage(ctxt, "apply {cmd} [val...]"); + + status := ""; + cmd := hd args; + for(;;){ + { + for (args = tl args; args != nil; args = tl args) + status = setstatus(ctxt, ctxt.run(cmd :: hd args :: nil, 0)); + + return status; + } exception e{ + "fail:*" => + if (loopexcept(e) == BREAK) + return status; + } + } +} + +builtin_status(nil: ref Context, args: list of ref Listnode, nil: int): string +{ + if (tl args != nil) + return (hd tl args).word; + return ""; +} + +pctlnames := array[] of { + ("newfd", Sys->NEWFD), + ("forkfd", Sys->FORKFD), + ("newns", Sys->NEWNS), + ("forkns", Sys->FORKNS), + ("newpgrp", Sys->NEWPGRP), + ("nodevs", Sys->NODEVS) +}; + +builtin_pctl(ctxt: ref Context, argv: list of ref Listnode, nil: int): string +{ + if (len argv < 2) + builtinusage(ctxt, "pctl option... [fdnum...]"); + + finalmask := 0; + fdlist: list of int; + for (argv = tl argv; argv != nil; argv = tl argv) { + w := (hd argv).word; + if (isnum(w)) + fdlist = int w :: fdlist; + else { + for (i := 0; i < len pctlnames; i++) { + (name, mask) := pctlnames[i]; + if (name == w) { + finalmask |= mask; + break; + } + } + if (i == len pctlnames) + ctxt.fail("usage", "pctl: unknown flag " + w); + } + } + sys->pctl(finalmask, fdlist); + return nil; +} + +# usage: ~ value pattern... +builtin_twiddle(ctxt: ref Context, argv: list of ref Listnode, nil: int): string +{ + argv = tl argv; + if (argv == nil) + builtinusage(ctxt, "~ word [pattern...]"); + if (tl argv == nil) + return "no match"; + w := word(hd argv); + + for (argv = tl argv; argv != nil; argv = tl argv) + if (filepat->match(word(hd argv), w)) + return ""; + + return "no match"; +} + +#builtin_echo(ctxt: ref Context, argv: list of ref Listnode, nil: int): string +#{ +# argv = tl argv; +# nflag := 0; +# if (argv != nil && word(hd argv) == "-n") { +# nflag = 1; +# argv = tl argv; +# } +# s: string; +# if (argv != nil) { +# s = word(hd argv); +# for (argv = tl argv; argv != nil; argv = tl argv) +# s += " " + word(hd argv); +# } +# e: int; +# if (nflag) +# e = sys->print("%s", s); +# else +# e = sys->print("%s\n", s); +# if (e == -1) { +# err := sys->sprint("%r"); +# if (ctxt.options() & ctxt.VERBOSE) +# sys->fprint(sys->fildes(2), "echo: write error: %s\n", err); +# return err; +# } +# return nil; +#} + +ENOEXIST: con "file does not exist"; +TMPDIR: con "/tmp/pipes"; +sbuiltin_pipe(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + n: int; + if (len argv != 3 || !iscmd(hd tl tl argv)) + builtinusage(ctxt, "pipe (from|to|fdnum) {cmd}"); + s := (hd tl argv).word; + case s { + "from" => + n = 1; + "to" => + n = 0; + * => + if (!isnum(s)) + builtinusage(ctxt, "pipe (from|to|fdnum) {cmd}"); + n = int s; + } + pipeid := ctxt.get("pipeid"); + seq: int; + if (pipeid == nil) + seq = 0; + else + seq = int (hd pipeid).word; + id := "pipe." + string sys->pctl(0, nil) + "." + string seq; + ctxt.set("pipeid", ref Listnode(nil, string ++seq) :: nil); + mkdir(TMPDIR); + d := "/tmp/" + id + "d"; + if (mkdir(d) == -1) + ctxt.fail("bad pipe", sys->sprint("pipe: cannot make %s: %r", d)); + if (sys->bind("#|", d, Sys->MREPL) == -1) { + sys->remove(d); + ctxt.fail("bad pipe", sys->sprint("pipe: cannot bind pipe onto %s: %r", d)); + } + if (rename(d + "/data", id + "x") == -1 || rename(d + "/data1", id + "y")) { + sys->unmount(nil, d); + sys->remove(d); + ctxt.fail("bad pipe", sys->sprint("pipe: cannot rename pipe: %r")); + } + if (sys->bind(d, TMPDIR, Sys->MBEFORE) == -1) { + sys->unmount(nil, d); + sys->remove(d); + ctxt.fail("bad pipe", sys->sprint("pipe: cannot bind pipe dir: %r")); + } + sys->unmount(nil, d); + sys->remove(d); + sync := chan of string; + spawn runpipe(sync, ctxt, n, TMPDIR + "/" + id + "x", hd tl tl argv); + if ((e := <-sync) != nil) + ctxt.fail("bad pipe", e); + return ref Listnode(nil, TMPDIR + "/" + id + "y") :: nil; +} + +mkdir(f: string): int +{ + if (sys->create(f, Sys->OREAD, Sys->DMDIR | 8r777) == nil) + return -1; + return 0; +} + +runpipe(sync: chan of string, ctxt: ref Context, fdno: int, p: string, cmd: ref Listnode) +{ + sys->pctl(Sys->FORKFD, nil); + ctxt = ctxt.copy(1); + if ((fd := sys->open(p, Sys->ORDWR)) == nil) { + sync <-= sys->sprint("cannot open %s: %r", p); + exit; + } + sys->dup(fd.fd, fdno); + fd = nil; + sync <-= nil; + ctxt.run(cmd :: ctxt.get("*"), 1); +} + +rename(x, y: string): int +{ + (ok, nil) := sys->stat(x); + if (ok == -1) + return -1; + inf := sys->nulldir; + inf.name = y; + if (sys->wstat(x, inf) == -1) + return -1; + return 0; +} + +builtinusage(ctxt: ref Context, s: string) +{ + ctxt.fail("usage", "usage: " + s); +} + +setstatus(ctxt: ref Context, val: string): string +{ + ctxt.setlocal("status", ref Listnode(nil, val) :: nil); + return val; +} + +# same as sys->raise(), but check that length of error string is +# acceptable, and truncate as appropriate. +xraise(s: string) +{ + d := array of byte s; + if (len d > Sys->WAITLEN) + raise string d[0:Sys->WAITLEN]; + else { + d = nil; + raise s; + } +} + +isnum(s: string): int +{ + for (i := 0; i < len s; i++) + if (s[i] > '9' || s[i] < '0') + return 0; + return 1; +} + diff --git a/appl/cmd/sh/string.b b/appl/cmd/sh/string.b new file mode 100644 index 00000000..b6d079e4 --- /dev/null +++ b/appl/cmd/sh/string.b @@ -0,0 +1,212 @@ +implement Shellbuiltin; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; +include "string.m"; + str: String; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("string: cannot load self: %r")); + str = load String String->PATH; + if (str == nil) + ctxt.fail("bad module", + sys->sprint("string: cannot load %s: %r", String->PATH)); + ctxt.addbuiltin("prefix", myself); + ctxt.addbuiltin("in", myself); + names := array[] of { + "splitl", "splitr", "drop", "take", "splitstrl", "splitstrr", + "tolower", "toupper", "len", "alen", "slice", "fields", + "padl", "padr", + }; + for (i := 0; i < len names; i++) + ctxt.addsbuiltin(names[i], myself); + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +runbuiltin(ctxt: ref Context, nil: Sh, + argv: list of ref Listnode, nil: int): string +{ + case (hd argv).word { + "prefix" => + (a, b) := earg2("prefix", ctxt, argv); + if (!str->prefix(a, b)) + return "false"; + "in" => + (a, b) := earg2("in", ctxt, argv); + if (a == nil || !str->in(a[0], b)) + return "false"; + } + return nil; +} + +runsbuiltin(ctxt: ref Context, nil: Sh, + argv: list of ref Listnode): list of ref Listnode +{ + name := (hd argv).word; + case name { + "splitl" => + (a, b) := earg2("splitl", ctxt, argv); + return mk2(str->splitl(a, b)); + "splitr" => + (a, b) := earg2("splitr", ctxt, argv); + return mk2(str->splitr(a, b)); + "drop" => + (a, b) := earg2("drop", ctxt, argv); + return mk1(str->drop(a, b)); + "take" => + (a, b) := earg2("take", ctxt, argv); + return mk1(str->take(a, b)); + "splitstrl" => + (a, b) := earg2("splitstrl", ctxt, argv); + return mk2(str->splitstrl(a, b)); + "splitstrr" => + (a, b) := earg2("splitstrr", ctxt, argv); + return mk2(str->splitstrr(a, b)); + "tolower" => + return mk1(str->tolower(earg1("tolower", ctxt, argv))); + "toupper" => + return mk1(str->toupper(earg1("tolower", ctxt, argv))); + "len" => + return mk1(string len earg1("len", ctxt, argv)); + "alen" => + return mk1(string len array of byte earg1("alen", ctxt, argv)); + "slice" => + return sbuiltin_slice(ctxt, argv); + "fields" => + return sbuiltin_fields(ctxt, argv); + "padl" => + return sbuiltin_pad(ctxt, argv, -1); + "padr" => + return sbuiltin_pad(ctxt, argv, 1); + } + return nil; +} + +sbuiltin_pad(ctxt: ref Context, argv: list of ref Listnode, dir: int): list of ref Listnode +{ + if (tl argv == nil || !isnum((hd tl argv).word)) + ctxt.fail("usage", "usage: " + (hd argv).word + " n [arg...]"); + + argv = tl argv; + n := int (hd argv).word * dir; + s := ""; + for (argv = tl argv; argv != nil; argv = tl argv) { + s += word(hd argv); + if (tl argv != nil) + s[len s] = ' '; + } + if (n != 0) + s = sys->sprint("%*s", n, s); + return ref Listnode(nil, s) :: nil; +} + +sbuiltin_fields(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + argv = tl argv; + if (len argv != 2) + ctxt.fail("usage", "usage: fields cl s"); + cl := word(hd argv); + s := word(hd tl argv); + + r: list of string; + + n := 0; + for (i := 0; i < len s; i++) { + if (str->in(s[i], cl)) { + r = s[n:i] :: r; + n = i + 1; + } + } + r = s[n:i] :: r; + rl: list of ref Listnode; + for (; r != nil; r = tl r) + rl = ref Listnode(nil, hd r) :: rl; + return rl; +} + + +sbuiltin_slice(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + argv = tl argv; + if (len argv != 3 || !isnum((hd argv).word) || + (hd tl argv).word != "end" && !isnum((hd tl argv).word)) + ctxt.fail("usage", "usage: slice start end arg"); + n1 := int (hd argv).word; + n2: int; + s := word(hd tl tl argv); + r := ""; + if ((hd tl argv).word == "end") + n2 = len s; + else + n2 = int (hd tl argv).word; + if (n2 > len s) + n2 = len s; + if (n1 > len s) + n1 = len s; + if (n2 > n1) + r = s[n1:n2]; + return mk1(r); +} + +earg2(cmd: string, ctxt: ref Context, argv: list of ref Listnode): (string, string) +{ + argv = tl argv; + if (len argv != 2) + ctxt.fail("usage", "usage: " + cmd + " arg1 arg2"); + return (word(hd argv), word(hd tl argv)); +} + +earg1(cmd: string, ctxt: ref Context, argv: list of ref Listnode): string +{ + if (len argv != 2) + ctxt.fail("usage", "usage: " + cmd + " arg"); + return word(hd tl argv); +} + +mk2(x: (string, string)): list of ref Listnode +{ + (a, b) := x; + return ref Listnode(nil, a) :: ref Listnode(nil, b) :: nil; +} + +mk1(x: string): list of ref Listnode +{ + return ref Listnode(nil, x) :: nil; +} + +isnum(s: string): int +{ + for (i := 0; i < len s; i++) + if (s[i] > '9' || s[i] < '0') + return 0; + return 1; +} + +word(n: ref Listnode): string +{ + if (n.word != nil) + return n.word; + if (n.cmd != nil) + n.word = sh->cmd2string(n.cmd); + return n.word; +} diff --git a/appl/cmd/sh/test.b b/appl/cmd/sh/test.b new file mode 100644 index 00000000..d8a6b62a --- /dev/null +++ b/appl/cmd/sh/test.b @@ -0,0 +1,96 @@ +implement Shellbuiltin; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; +include "itslib.m"; + itslib: Itslib; + Tconfig, S_INFO, S_WARN, S_ERROR, S_FATAL: import itslib; + +tconf: ref Tconfig; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + itslib = load Itslib Itslib->PATH; + if (itslib != nil) + tconf = itslib->init(); + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("its: cannot load self: %r")); + ctxt.addbuiltin("report", myself); + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + + + +runbuiltin(ctxt: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode, nil: int): string +{ + case (hd cmd).word { + "report" => + if (len cmd < 4) + rusage(ctxt); + cmd = tl cmd; + sevstr := (hd cmd).word; + sev := sevtran(sevstr); + if (sev < 0) + rusage(ctxt); + cmd = tl cmd; + verb := (hd cmd).word; + cmd = tl cmd; + mtext := ""; + i := 0; + while (len cmd) { + msg := (hd cmd).word; + cmd = tl cmd; + if (i++ > 0) + mtext = mtext + " "; + mtext = mtext + msg; + } + if (tconf != nil) + tconf.report(int sev, int verb, mtext); + else + sys->fprint(sys->fildes(2), "[itslib missing] %s %s\n", sevstr, mtext); + } + return nil; +} + + +runsbuiltin(nil: ref Sh->Context, nil: Sh, + nil: list of ref Sh->Listnode): list of ref Listnode +{ + return nil; +} + + +sevtran(sname: string): int +{ + SEVMAP := array[] of {"INF", "WRN", "ERR", "FTL"}; + for (i:=0; i<len SEVMAP; i++) + if (sname == SEVMAP[i]) + return i; + return -1; +} + +rusage(ctxt: ref Context) +{ + ctxt.fail("usage", "usage: report INF|WRN|ERR|FTL verbosity message[...]"); +} + diff --git a/appl/cmd/sh/tk.b b/appl/cmd/sh/tk.b new file mode 100644 index 00000000..bc6fe753 --- /dev/null +++ b/appl/cmd/sh/tk.b @@ -0,0 +1,426 @@ +implement Shellbuiltin; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "tk.m"; + tk: Tk; +include "tkclient.m"; + tkclient: Tkclient; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; + +tklock: chan of int; + +chans := array[23] of list of (string, chan of string); +wins := array[16] of list of (int, ref Tk->Toplevel); +winid := 0; + +badmodule(ctxt: ref Context, p: string) +{ + ctxt.fail("bad module", sys->sprint("tk: cannot load %s: %r", p)); +} + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + + myself = load Shellbuiltin "$self"; + if (myself == nil) badmodule(ctxt, "self"); + + tk = load Tk Tk->PATH; + if (tk == nil) badmodule(ctxt, Tk->PATH); + + tkclient = load Tkclient Tkclient->PATH; + if (tkclient == nil) badmodule(ctxt, Tkclient->PATH); + tkclient->init(); + + tklock = chan[1] of int; + + ctxt.addbuiltin("tk", myself); + ctxt.addbuiltin("chan", myself); + ctxt.addbuiltin("send", myself); + + ctxt.addsbuiltin("tk", myself); + ctxt.addsbuiltin("recv", myself); + ctxt.addsbuiltin("alt", myself); + ctxt.addsbuiltin("tkquote", myself); + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +runbuiltin(ctxt: ref Context, nil: Sh, + cmd: list of ref Listnode, nil: int): string +{ + case (hd cmd).word { + "tk" => return builtin_tk(ctxt, cmd); + "chan" => return builtin_chan(ctxt, cmd); + "send" => return builtin_send(ctxt, cmd); + } + return nil; +} + +runsbuiltin(ctxt: ref Context, nil: Sh, + cmd: list of ref Listnode): list of ref Listnode +{ + case (hd cmd).word { + "tk" => return sbuiltin_tk(ctxt, cmd); + "recv" => return sbuiltin_recv(ctxt, cmd); + "alt" => return sbuiltin_alt(ctxt, cmd); + "tkquote" => return sbuiltin_tkquote(ctxt, cmd); + } + return nil; +} + +builtin_tk(ctxt: ref Context, argv: list of ref Listnode): string +{ + # usage: tk window _title_ _options_ + # tk wintitle _winid_ _title_ + # tk _winid_ _cmd_ + if (tl argv == nil) + ctxt.fail("usage", "usage: tk (<winid>|window|onscreen|winctlwintitle|del|namechan) args..."); + argv = tl argv; + w := (hd argv).word; + case w { + "window" => + remark(ctxt, string makewin(ctxt, tl argv)); + "wintitle" => + argv = tl argv; + # change the title of a window + if (len argv != 2 || !isnum((hd argv).word)) + ctxt.fail("usage", "usage: tk wintitle winid title"); + tkclient->settitle(egetwin(ctxt, hd argv), word(hd tl argv)); + "winctl" => + argv = tl argv; + if (len argv != 2 || !isnum((hd argv).word)) + ctxt.fail("usage", "usage: tk winctl winid cmd"); + wid := (hd argv).word; + win := egetwin(ctxt, hd argv); + rq := word(hd tl argv); + if (rq == "exit") { + delwin(int wid); + delchan(wid); + } + tkclient->wmctl(win, rq); + "onscreen" => + argv = tl argv; + if (len argv < 1 || !isnum((hd argv).word)) + ctxt.fail("usage", "usage: tk onscreen winid [how]"); + wid := (hd argv).word; + how := ""; + if(tl argv != nil) + how = word(hd tl argv); + win := egetwin(ctxt, hd argv); + tkclient->startinput(win, "ptr" :: "kbd" :: nil); + tkclient->onscreen(win, how); + "namechan" => + argv = tl argv; + n := len argv; + if (n < 2 || n > 3 || !isnum((hd argv).word)) + ctxt.fail("usage", "usage: tk namechan winid chan [name]"); + name: string; + if (n == 3) + name = word(hd tl tl argv); + else + name = word(hd tl argv); + tk->namechan(egetwin(ctxt, hd argv), egetchan(ctxt, hd tl argv), name); + + "del" => + if (len argv < 2) + ctxt.fail("usage", "usage: tk del id..."); + for (argv = tl argv; argv != nil; argv = tl argv) { + id := (hd argv).word; + if (isnum(id)) + delwin(int id); + delchan(id); + } + * => + e := tkcmd(ctxt, argv); + if (e != nil) + remark(ctxt, e); + if (e != nil && e[0] == '!') + return e; + } + return nil; +} + +remark(ctxt: ref Context, s: string) +{ + if (ctxt.options() & ctxt.INTERACTIVE) + sys->print("%s\n", s); +} + +# create a new window (and its associated channel) +makewin(ctxt: ref Context, argv: list of ref Listnode): int +{ + if (argv == nil) + ctxt.fail("usage", "usage: tk window title options"); + + if (ctxt.drawcontext == nil) + ctxt.fail("no draw context", sys->sprint("tk: no graphics context available")); + + (title, options) := (word(hd argv), concat(tl argv)); + (top, topchan) := tkclient->toplevel(ctxt.drawcontext, options, title, Tkclient->Appl); + newid := addwin(top); + addchan(string newid, topchan); + return newid; +} + +builtin_chan(ctxt: ref Context, argv: list of ref Listnode): string +{ + # create a new channel + argv = tl argv; + if (argv == nil) + ctxt.fail("usage", "usage: chan name...."); + for (; argv != nil; argv = tl argv) { + name := (hd argv).word; + if (name == nil || isnum(name)) + ctxt.fail("bad chan", "tk: bad channel name "+q(name)); + if (addchan(name, chan of string) == nil) + ctxt.fail("bad chan", "tk: channel "+q(name)+" already exists"); + } + return nil; +} + +builtin_send(ctxt: ref Context, argv: list of ref Listnode): string +{ + if (len argv != 3) + ctxt.fail("usage", "usage: send chan arg"); + argv = tl argv; + c := egetchan(ctxt, hd argv); + c <-= word(hd tl argv); + return nil; +} + + +sbuiltin_tk(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + # usage: tk _winid_ _command_ + # tk window _title_ _options_ + argv = tl argv; + if (argv == nil) + ctxt.fail("usage", "tk (window|wid) args"); + case (hd argv).word { + "window" => + return ref Listnode(nil, string makewin(ctxt, tl argv)) :: nil; + "winids" => + ret: list of ref Listnode; + for (i := 0; i < len wins; i++) + for (wl := wins[i]; wl != nil; wl = tl wl) + ret = ref Listnode(nil, string (hd wl).t0) :: ret; + return ret; + * => + return ref Listnode(nil, tkcmd(ctxt, argv)) :: nil; + } +} + +sbuiltin_alt(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + # usage: alt chan ... + argv = tl argv; + if (argv == nil) + ctxt.fail("usage", "usage: alt chan..."); + ca := array[len argv] of chan of string; + cname := array[len ca] of string; + i := 0; + for (; argv != nil; argv = tl argv) { + ca[i] = egetchan(ctxt, hd argv); + cname[i] = (hd argv).word; + i++; + } + n := 0; + v: string; + if (i == 1) + v = <-ca[0]; + else + (n, v) = <-ca; + + return ref Listnode(nil, cname[n]) :: ref Listnode(nil, v) :: nil; +} + +sbuiltin_recv(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + # usage: recv chan + if (len argv != 2) + ctxt.fail("usage", "usage: recv chan"); + ch := hd tl argv; + c := egetchan(ctxt, ch); + if(!isnum(ch.word)) + return ref Listnode(nil, <-c) :: nil; + + win := egetwin(ctxt, ch); + for(;;)alt{ + key := <-win.ctxt.kbd => + tk->keyboard(win, key); + p := <-win.ctxt.ptr => + tk->pointer(win, *p); + s := <-win.ctxt.ctl or + s = <-win.wreq or + s = <-c => + return ref Listnode(nil, s) :: nil; + } +} + +sbuiltin_tkquote(ctxt: ref Context, argv: list of ref Listnode): list of ref Listnode +{ + if (len argv != 2) + ctxt.fail("usage", "usage: tkquote arg"); + return ref Listnode(nil, tk->quote(word(hd tl argv))) :: nil; +} + +tkcmd(ctxt: ref Context, argv: list of ref Listnode): string +{ + if (argv == nil || !isnum((hd argv).word)) + ctxt.fail("usage", "usage: tk winid command"); + + return tk->cmd(egetwin(ctxt, hd argv), concat(tl argv)); +} + +hashfn(s: string, n: int): int +{ + h := 0; + m := len s; + for(i:=0; i<m; i++){ + h = 65599*h+s[i]; + } + return (h & 16r7fffffff) % n; +} + +q(s: string): string +{ + return "'" + s + "'"; +} + +egetchan(ctxt: ref Context, n: ref Listnode): chan of string +{ + if ((c := getchan(n.word)) == nil) + ctxt.fail("bad chan", "tk: bad channel name "+ q(n.word)); + return c; +} + +# assumes that n.word has been checked and found to be numeric. +egetwin(ctxt: ref Context, n: ref Listnode): ref Tk->Toplevel +{ + wid := int n.word; + if (wid < 0 || (top := getwin(wid)) == nil) + ctxt.fail("bad win", "tk: unknown window id " + q(n.word)); + return top; +} + +getchan(name: string): chan of string +{ + n := hashfn(name, len chans); + for (cl := chans[n]; cl != nil; cl = tl cl) { + (cname, c) := hd cl; + if (cname == name) + return c; + } + return nil; +} + +addchan(name: string, c: chan of string): chan of string +{ + n := hashfn(name, len chans); + tklock <-= 1; + if (getchan(name) == nil) + chans[n] = (name, c) :: chans[n]; + <-tklock; + return c; +} + +delchan(name: string) +{ + n := hashfn(name, len chans); + tklock <-= 1; + ncl: list of (string, chan of string); + for (cl := chans[n]; cl != nil; cl = tl cl) { + (cname, nil) := hd cl; + if (cname != name) + ncl = hd cl :: ncl; + } + chans[n] = ncl; + <-tklock; +} + +addwin(top: ref Tk->Toplevel): int +{ + tklock <-= 1; + id := winid++; + slot := id % len wins; + wins[slot] = (id, top) :: wins[slot]; + <-tklock; + return id; +} + +delwin(id: int) +{ + tklock <-= 1; + slot := id % len wins; + nwl: list of (int, ref Tk->Toplevel); + for (wl := wins[slot]; wl != nil; wl = tl wl) { + (wid, nil) := hd wl; + if (wid != id) + nwl = hd wl :: nwl; + } + wins[slot] = nwl; + <-tklock; +} + +getwin(id: int): ref Tk->Toplevel +{ + slot := id % len wins; + for (wl := wins[slot]; wl != nil; wl = tl wl) { + (wid, top) := hd wl; + if (wid == id) + return top; + } + return nil; +} + +word(n: ref Listnode): string +{ + if (n.word != nil) + return n.word; + if (n.cmd != nil) + n.word = sh->cmd2string(n.cmd); + return n.word; +} + +isnum(s: string): int +{ + for (i := 0; i < len s; i++) + if (s[i] > '9' || s[i] < '0') + return 0; + return 1; +} + +concat(argv: list of ref Listnode): string +{ + if (argv == nil) + return nil; + s := word(hd argv); + for (argv = tl argv; argv != nil; argv = tl argv) + s += " " + word(hd argv); + return s; +} + +lockproc(c: chan of int) +{ + sys->pctl(Sys->NEWFD|Sys->NEWNS, nil); + for(;;){ + c <-= 1; + <-c; + } +} diff --git a/appl/cmd/sha1sum.b b/appl/cmd/sha1sum.b new file mode 100644 index 00000000..0c39ac21 --- /dev/null +++ b/appl/cmd/sha1sum.b @@ -0,0 +1,65 @@ +implement SHA1sum; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + +SHA1sum: module +{ + init: fn(nil : ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + kr = load Keyring Keyring->PATH; + a := tl argv; + err := 0; + if(a != nil){ + for( ; a != nil; a = tl a) { + s := hd a; + fd := sys->open(s, Sys->OREAD); + if (fd == nil) { + sys->fprint(stderr, "sha1sum: cannot open %s: %r\n", s); + err = 1; + } else + err |= sha1sum(fd, s); + } + } else + err |= sha1sum(sys->fildes(0), ""); + if(err) + raise "fail:error"; +} + +sha1sum(fd: ref Sys->FD, file: string): int +{ + err := 0; + buf := array[Sys->ATOMICIO] of byte; + state: ref Keyring->DigestState = nil; + nbytes := big 0; + while((nr := sys->read(fd, buf, len buf)) > 0){ + state = kr->sha1(buf, nr, nil, state); + nbytes += big nr; + } + if(nr < 0) { + sys->fprint(stderr, "sha1sum: error reading %s: %r\n", file); + err = 1; + } + digest := array[Keyring->SHA1dlen] of byte; + kr->sha1(buf, 0, digest, state); + sum := ""; + for(i:=0; i<len digest; i++) + sum += sys->sprint("%2.2ux", int digest[i]); + if(file != nil) + sys->print("%s\t%s\n", sum, file); + else + sys->print("%s\n", sum); + return err; +} diff --git a/appl/cmd/shutdown.b b/appl/cmd/shutdown.b new file mode 100644 index 00000000..8eb7a86c --- /dev/null +++ b/appl/cmd/shutdown.b @@ -0,0 +1,72 @@ +implement Shutdown; + +include "sys.m"; +sys: Sys; +FD: import Sys; +stderr: ref FD; + +include "draw.m"; +Context: import Draw; + +sysctl: con "/dev/sysctl"; +reboot: con "reboot"; +halt: con "halt"; + +Shutdown: module +{ + init: fn(ctxt: ref Context, argv: list of string); +}; + +rflag: int; +hflag: int; + +init(nil: ref Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + + stderr = sys->fildes(2); + + argv = tl argv; + if(len argv < 1) + usage(); + + while(argv != nil && len hd argv && (arg := hd argv)[0] == '-' && len arg > 1){ + case arg[1] { + 'r' => + rflag = 1; + 'h' => + hflag = 1; + } + argv = tl argv; + } + + if(rflag == 0 && hflag == 0) + usage(); + + if(rflag == 1 && hflag == 1) + usage(); + + fd := sys->open(sysctl, sys->OWRITE); + if(fd == nil) { + sys->fprint(stderr, "shutdown: %r\n"); + exit; + } + + if(rflag == 1) + if (sys->write(fd, array of byte reboot, len reboot) < 0) { + sys->fprint(stderr, "shutdown: write failed: %r\n"); + exit; + } + + if(hflag == 1) + if (sys->write(fd, array of byte halt, len halt) < 0) { + sys->fprint(stderr, "shutdown: write failed: %r\n"); + exit; + } +} + +usage() +{ + sys->fprint(stderr, "usage: shutdown -r | -h\n"); + exit; +} diff --git a/appl/cmd/sleep.b b/appl/cmd/sleep.b new file mode 100644 index 00000000..4066f453 --- /dev/null +++ b/appl/cmd/sleep.b @@ -0,0 +1,46 @@ +implement Sleep; + +include "sys.m"; +sys: Sys; + +include "draw.m"; + +Sleep: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + if(sys == nil || argv == nil) + return; + argv = tl argv; + if(argv != nil && isvalid(hd argv)){ + t := int hd argv; + if(t > 16r7fffffff / 1000) + t = 16r7fffffff / 1000; + sys->sleep(t * 1000); + } else { + sys->fprint(sys->fildes(2), "usage: sleep time\n"); + raise "fail:usage"; + } +} + +isvalid(t: string): int +{ + l := len t; + if(l > 0 && (t[0] == '-' || t[0] == '+')) + x := 1; + else + x = 0; + ok := 0; + while(x < l) { + d := t[x]; + if(d < '0' || d > '9') + return 0; + ok = 1; + x++; + } + return ok; +} diff --git a/appl/cmd/sort.b b/appl/cmd/sort.b new file mode 100644 index 00000000..1accd583 --- /dev/null +++ b/appl/cmd/sort.b @@ -0,0 +1,129 @@ +implement Sort; + +include "sys.m"; + sys: Sys; +include "bufio.m"; +include "draw.m"; +include "arg.m"; + +Sort: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +usage() +{ + sys->fprint(sys->fildes(2), "usage: sort [-n] [file]\n"); + raise "fail:usage"; +} + +Incr: con 2000; # growth quantum for record array + +init(nil : ref Draw->Context, args : list of string) +{ + bio : ref Bufio->Iobuf; + + sys = load Sys Sys->PATH; + stderr := sys->fildes(2); + bufio := load Bufio Bufio->PATH; + if (bufio == nil) { + sys->fprint(stderr, "sort: cannot load %s: %r\n", Bufio->PATH); + raise "fail:bad module"; + } + Iobuf: import bufio; + arg := load Arg Arg->PATH; + if (arg == nil) { + sys->fprint(stderr, "sort: cannot load %s: %r\n", Arg->PATH); + raise "fail:bad module"; + } + + nflag := 0; + rflag := 0; + arg->init(args); + while ((opt := arg->opt()) != 0) { + case opt { + 'n' => + nflag = 1; + 'r' => + rflag = 1; + * => + usage(); + } + } + args = arg->argv(); + if (len args > 1) + usage(); + if (args != nil) { + bio = bufio->open(hd args, Bufio->OREAD); + if (bio == nil) { + sys->fprint(stderr, "sort: cannot open %s: %r\n", hd args); + raise "fail:open file"; + } + } + else + bio = bufio->fopen(sys->fildes(0), Bufio->OREAD); + a := array[Incr] of string; + n := 0; + while ((s := bio.gets('\n')) != nil) { + if (n >= len a) { + b := array[len a + Incr] of string; + b[0:] = a; + a = b; + } + a[n++] = s; + } + if (nflag) + mergesortnumeric(a, array[n] of string, n); + else + mergesort(a, array[n] of string, n); + + stdout := bufio->fopen(sys->fildes(1), Bufio->OWRITE); + if (rflag) { + for (i := n-1; i >= 0; i--) + stdout.puts(a[i]); + } else { + for (i := 0; i < n; i++) + stdout.puts(a[i]); + } + stdout.close(); +} + +mergesort(a, b: array of string, r: int) +{ + if (r > 1) { + m := (r-1)/2 + 1; + mergesort(a[0:m], b[0:m], m); + mergesort(a[m:r], b[m:r], r-m); + b[0:] = a[0:r]; + for ((i, j, k) := (0, m, 0); i < m && j < r; k++) { + if (b[i] > b[j]) + a[k] = b[j++]; + else + a[k] = b[i++]; + } + if (i < m) + a[k:] = b[i:m]; + else if (j < r) + a[k:] = b[j:r]; + } +} + +mergesortnumeric(a, b: array of string, r: int) +{ + if (r > 1) { + m := (r-1)/2 + 1; + mergesortnumeric(a[0:m], b[0:m], m); + mergesortnumeric(a[m:r], b[m:r], r-m); + b[0:] = a[0:r]; + for ((i, j, k) := (0, m, 0); i < m && j < r; k++) { + if (int b[i] > int b[j]) + a[k] = b[j++]; + else + a[k] = b[i++]; + } + if (i < m) + a[k:] = b[i:m]; + else if (j < r) + a[k:] = b[j:r]; + } +} diff --git a/appl/cmd/spki/mkfile b/appl/cmd/spki/mkfile new file mode 100644 index 00000000..b7edefd1 --- /dev/null +++ b/appl/cmd/spki/mkfile @@ -0,0 +1,22 @@ +<../../../mkconfig + +TARG=\ + verify.dis\ + +SYSMODULES=\ + arg.m\ + keyring.m\ + security.m\ + rand.m\ + sys.m\ + draw.m\ + bufio.m\ + string.m\ + styx.m\ + styxservers.m\ + sexprs.m\ + spki.m\ + +DISBIN=$ROOT/dis/spki + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/spki/verify.b b/appl/cmd/spki/verify.b new file mode 100644 index 00000000..9eab6b41 --- /dev/null +++ b/appl/cmd/spki/verify.b @@ -0,0 +1,107 @@ +implement Verify; + +# +# Copyright © 2004 Vita Nuova Holdings Limited +# + +# work in progress + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "keyring.m"; + kr: Keyring; + IPint: import kr; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "sexprs.m"; + sexprs: Sexprs; + Sexp: import sexprs; + +include "spki.m"; + spki: SPKI; + Hash, Key, Cert, Name, Subject, Signature, Seqel, Toplev, Valid: import spki; + dump: import spki; + + verifier: Verifier; + Speaksfor: import verifier; + +include "encoding.m"; + base64: Encoding; + +Verify: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +debug := 0; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + kr = load Keyring Keyring->PATH; + bufio = load Bufio Bufio->PATH; + sexprs = load Sexprs Sexprs->PATH; + spki = load SPKI SPKI->PATH; + verifier = load Verifier Verifier->PATH; + base64 = load Encoding Encoding->BASE64PATH; + + sexprs->init(); + spki->init(); + verifier->init(); + + f := bufio->fopen(sys->fildes(0), Sys->OREAD); + for(;;){ + (e, err) := Sexp.read(f); + if(e == nil && err == nil) + break; + if(err != nil) + error(sys->sprint("invalid s-expression: %s", err)); + (top, diag) := spki->parse(e); + if(diag != nil) + error(sys->sprint("invalid SPKI structure: %s", diag)); + pick t := top { + C => + if(debug) + sys->print("cert: %s\n", t.v.text()); + a := spki->hashexp(e, "md5"); + Sig => + sys->print("got signature %q\n", t.v.text()); + K => + sys->print("got key %q\n", t.v.text()); + Seq => + els := t.v; + if(debug){ + sys->print("(sequence"); + for(; els != nil; els = tl els) + sys->print(" %s", (hd els).text()); + sys->print(")"); + } + (claim, rem, whynot) := verifier->verify(t.v); + if(whynot != nil){ + if(rem == nil) + s := "end of sequence"; + else + s = (hd rem).text(); + sys->fprint(sys->fildes(2), "verify: failed to verify at %#q: %s\n", s, whynot); + }else{ + if(claim.regarding != nil) + scope := sys->sprint(" regarding %q", claim.regarding.text()); + sys->print("verified: %q speaks for %q%s\n", claim.subject.text(), claim.name.text(), scope); + } + * => + sys->print("unexpected SPKI type: %q\n", e.text()); + } + } +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "verify: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/src.b b/appl/cmd/src.b new file mode 100644 index 00000000..70c9da65 --- /dev/null +++ b/appl/cmd/src.b @@ -0,0 +1,28 @@ +implement Src; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "dis.m"; + dis: Dis; + +Src: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + dis = load Dis Dis->PATH; + + if(dis != nil){ + dis->init(); + for(argv = tl argv; argv != nil; argv = tl argv){ + src := dis->src(hd argv); + if(src == nil) + src = "?"; + sys->print("%s: %s\n", hd argv, src); + } + } +} diff --git a/appl/cmd/stack.b b/appl/cmd/stack.b new file mode 100644 index 00000000..7b90a0b5 --- /dev/null +++ b/appl/cmd/stack.b @@ -0,0 +1,184 @@ +implement Command; + +include "sys.m"; + sys: Sys; + print, fprint, FD: import sys; + stderr: ref FD; + +include "draw.m"; + +include "debug.m"; + debug: Debug; + Prog, Module, Exp: import debug; + +include "arg.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "env.m"; + env: Env; + +include "string.m"; + str: String; + +include "dis.m"; + dism: Dis; + +Command: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->fprint(stderr, "usage: stack [-v] pid\n"); + raise "fail:usage"; +} + +badmodule(p: string) +{ + sys->fprint(stderr, "stack: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +sbldirs: list of (string, string); + +init(nil: ref Draw->Context, argv: list of string) +{ + + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + arg := load Arg Arg->PATH; + if (arg == nil) + badmodule(Arg->PATH); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + badmodule(Bufio->PATH); + debug = load Debug Debug->PATH; + if(debug == nil) + badmodule(Debug->PATH); + env = load Env Env->PATH; + if (env != nil) { + str = load String String->PATH; + if (str == nil) + badmodule(String->PATH); + } + bout := bufio->fopen(sys->fildes(1), Sys->OWRITE); + + arg->init(argv); + verbose := 0; + while ((opt := arg->opt()) != 0) { + case opt { + 'v' => + verbose = 1; + 'p' => + dispath := arg->arg(); + sblpath := arg->arg(); + if (dispath == nil || sblpath == nil) + usage(); + sbldirs = (addslash(dispath), addslash(sblpath)) :: sbldirs; + * => + usage(); + } + } + if (env != nil && (pathl := env->getenv("sblpath")) != nil) { + toks := str->unquoted(pathl); + for (; toks != nil && tl toks != nil; toks = tl tl toks) + sbldirs = (addslash(hd toks), addslash(hd tl toks)) :: sbldirs; + } + t: list of (string, string); + for (; sbldirs != nil; sbldirs = tl sbldirs) + t = hd sbldirs :: t; + sbldirs = t; + + argv = arg->argv(); + if(argv == nil) + usage(); + + debug->init(); + + (p, err) := debug->prog(int hd argv); + if(err != nil){ + fprint(stderr, "stack: %s\n", err); + return; + } + stk: array of ref Exp; + (stk, err) = p.stack(); + + if(err != nil){ + fprint(stderr, "stack: %s\n", err); + return; + } + + for(i := 0; i < len stk; i++){ + stdsym(stk[i].m); + stk[i].m.stdsym(); + stk[i].findsym(); + bout.puts(stk[i].name + "("); + vs := stk[i].expand(); + if(verbose && vs != nil){ + for(j := 0; j < len vs; j++){ + if(vs[j].name == "args"){ + d := vs[j].expand(); + s := ""; + for(j = 0; j < len d; j++) { + bout.puts(sys->sprint("%s%s=%s", s, d[j].name, d[j].val().t0)); + s = ", "; + } + break; + } + } + } + bout.puts(sys->sprint(") %s\n", stk[i].srcstr())); + if(verbose && vs != nil){ + for(j := 0; j < len vs; j++){ + if(vs[j].name == "locals"){ + d := vs[j].expand(); + for(j = 0; j < len d; j++) + bout.puts("\t" + d[j].name + "=" + d[j].val().t0 + "\n"); + break; + } + } + } + } + bout.flush(); +} + +stdsym(m: ref Module) +{ + dis := m.dis(); + if(dism == nil){ + dism = load Dis Dis->PATH; + if(dism != nil) + dism->init(); + } + if(dism != nil && (sp := dism->src(dis)) != nil){ + sp = sp[0: len sp - 1] + "sbl"; + (sym, err) := debug->sym(sp); + if (sym != nil) { + m.addsym(sym); + return; + } + } + for (sbl := sbldirs; sbl != nil; sbl = tl sbl) { + (dispath, sblpath) := hd sbl; + if (len dis > len dispath && dis[0:len dispath] == dispath) { + sblpath = sblpath + dis[len dispath:]; + if (len sblpath > 4 && sblpath[len sblpath - 4:] == ".dis") + sblpath = sblpath[0:len sblpath - 4] + ".sbl"; + (sym, err) := debug->sym(sblpath); + if (sym != nil) { + m.addsym(sym); + return; + } + } + } +} + +addslash(p: string): string +{ + if (p != nil && p[len p - 1] != '/') + p[len p] = '/'; + return p; +} diff --git a/appl/cmd/stackv.b b/appl/cmd/stackv.b new file mode 100644 index 00000000..173f8b30 --- /dev/null +++ b/appl/cmd/stackv.b @@ -0,0 +1,445 @@ +implement Stackv; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "debug.m"; + debug: Debug; + Prog, Module, Exp: import debug; + Tadt, Tarray, Tbig, Tbyte, Treal, + Tfn, Tint, Tlist, + Tref, Tstring, Tslice: import Debug; +include "arg.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +stderr: ref Sys->FD; +stdout: ref Iobuf; +hasht := array[97] of list of string; + +Stackv: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +maxrecur := 16r7ffffffe; + +badmodule(p: string) +{ + sys->fprint(stderr, "stackv: cannot load %q: %r\n", p); + raise "fail:bad module"; +} + +currp: ref Prog; +showtypes := 1; +showsource := 0; +sep := "\t"; + +init(nil: ref Draw->Context, argv: list of string) +{ + + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + debug = load Debug Debug->PATH; + if(debug == nil) + badmodule(Debug->PATH); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + badmodule(Bufio->PATH); + arg := load Arg Arg->PATH; + if (arg == nil) + badmodule(Arg->PATH); + stdout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + + arg->init(argv); + arg->setusage("stackv [-Tl] [-i indent] [-r maxdepth] [-s dis sbl]... [pid[.sym...] ...]"); + sblfile := ""; + while((opt := arg->opt()) != 0){ + case opt { + 's' => + arg->earg(); # XXX make it a list of maps from dis to sbl later + sblfile = arg->earg(); + 'l' => + showsource = 1; + 'r' => + maxrecur = int arg->earg(); + 'T' => + showtypes = 0; + 'i' => + sep = arg->earg(); + * => + arg->usage(); + } + } + debug->init(); + argv = arg->argv(); + printpids := len argv > 1; + if(printpids) + maxrecur++; + for(; argv != nil; argv = tl argv) + db(sys->tokenize(hd argv, ".").t1, printpids); +} + +db(toks: list of string, printpid: int): int +{ + if(toks == nil){ + sys->fprint(stderr, "stackv: bad pid\n"); + return -1; + } + if((pid := int hd toks) <= 0){ + sys->fprint(stderr, "stackv: bad pid %q\n", hd toks); + return -1; + } + err: string; + p: ref Prog; + + # reuse process if possible + if(currp == nil || currp.id != pid){ + (currp, err) = debug->prog(pid); + if(err != nil){ + sys->fprint(stderr, "stackv: %s\n", err); + return -1; + } + if(currp == nil){ + sys->fprint(stderr, "stackv: nil prog from pid %d\n", pid); + return -1; + } + } + p = currp; + stk: array of ref Exp; + (stk, err) = p.stack(); + if(err != nil){ + sys->fprint(stderr, "stackv: %s\n", err); + return -1; + } + for (i := 0; i < len stk; i++) { + stk[i].m.stdsym(); + stk[i].findsym(); + } + depth := 0; + if(printpid){ + stdout.puts(sys->sprint("prog %d {\n", pid)); # } + depth++; + } + pexp(stk, tl toks, depth); + if(printpid) + stdout.puts("}\n"); + stdout.flush(); + return 0; +} + +pexp(stk: array of ref Exp, toks: list of string, depth: int) +{ + if(toks == nil){ + for (i := 0; i < len stk; i++) + pfn(stk[i], depth); + }else{ + exp := stackfindsym(stk, toks, depth); + if(exp == nil) + return; + pname(exp, depth); + stdout.putc('\n'); + } +} + +stackfindsym(stk: array of ref Exp, toks: list of string, depth: int): ref Exp +{ + fname := hd toks; + toks = tl toks; + for(i := 0; i < len stk; i++){ + s := stk[i].name; + if(s == fname) + break; + if(hasdot(s) && toks != nil && s == fname+"."+hd toks){ + fname += "."+hd toks; + toks = tl toks; + break; + } + } + if(i == len stk){ + indent(depth); + stdout.puts("function not found\n"); + return nil; + } + if(toks == nil) + return stk[i]; + stk = stk[i].expand(); + if(hd toks == "module"){ + if((e := getname(stk, "module")) == nil){ + indent(depth); + stdout.puts(sys->sprint("no module declarations in function %q\n", fname)); + }else if((e = symfindsym(e, tl toks, depth)) != nil) + return e; + return nil; + } + for(t := "locals" :: "args" :: "module" :: nil; t != nil; t = tl t){ + if((e := getname(stk, hd t)) == nil) + continue; + if((e = symfindsym(e, toks, depth)) != nil) + return e; + } + indent(depth); + stdout.puts(sys->sprint("symbol %q not found in function %q\n", hd toks, fname)); + return nil; +} + +hasdot(s: string): int +{ + for(i := 0; i < len s; i++) + if(s[i] == '.') + return 1; + return 0; +} + +symfindsym(e: ref Exp, toks: list of string, depth: int): ref Exp +{ + if(toks == nil) + return e; + exps := e.expand(); + for(i := 0; i < len exps; i++) + if(exps[i].name == hd toks) + return symfindsym(exps[i], tl toks, depth); + return nil; +} + +pfn(exp: ref Exp, depth: int) +{ + (v, w) := exp.val(); + if(!w || v == nil){ + indent(depth); + stdout.puts(sys->sprint("no value for fn %q\n", exp.name)); + return; + } + exps := exp.expand(); + indent(depth); + stdout.puts("["+exp.srcstr()+"]\n"); + indent(depth); + stdout.puts(symname(exp)+"("); + if((e := getname(exps, "args")) != nil){ + args := e.expand(); + for(i := 0; i < len args; i++){ + pname(args[i], depth+1); + if(i != len args - 1) + stdout.puts(", "); + } + } + stdout.puts(")\n"); + indent(depth); + stdout.puts("{\n"); # } + if((e = getname(exps, "locals")) != nil){ + locals := e.expand(); + for(i := 0; i < len locals; i++){ + indent(depth+1); + pname(locals[i], depth+1); + stdout.puts("\n"); + } + } + indent(depth); + stdout.puts("}\n"); +} + +getname(exps: array of ref Exp, name: string): ref Exp +{ + for(i := 0; i < len exps; i++) + if(exps[i].name == name) + return exps[i]; + return nil; +} + +strval(v: string): string +{ + for(i := 0; i < len v; i++) + if(v[i] == '"') + break; + if(i < len v) + v = v[i:]; + return v; +} + +pname(exp: ref Exp, depth: int) +{ + (v, w) := exp.val(); + if (!w && v == nil) { + stdout.puts(sys->sprint("%s: %s = novalue", symname(exp), exp.typename())); + return; + } + case exp.kind() { + Tfn => + pfn(exp, depth); + Tint => + stdout.puts(sys->sprint("%s := %s", symname(exp), v)); + Tstring => + stdout.puts(sys->sprint("%s := %s", symname(exp), strval(v))); + Tbyte or + Tbig or + Treal => + stdout.puts(sys->sprint("%s := %s %s", symname(exp), exp.typename(), v)); + * => + if(showtypes) + stdout.puts(sys->sprint("%s: %s = ", symname(exp), exp.typename())); + else + stdout.puts(sys->sprint("%s := ", symname(exp))); + pval(exp, v, w, depth); + } +} + +srcstr(src: ref Debug->Src): string +{ + if(src == nil) + return nil; + if(src.start.file != src.stop.file) + return sys->sprint("%q:%d.%d,%q:%d.%d", src.start.file, src.start.line, src.start.pos, src.stop.file, src.stop.line, src.stop.pos); + if(src.start.line != src.stop.line) + return sys->sprint("%q:%d.%d,%d.%d", src.start.file, src.start.line, src.start.pos, src.stop.line, src.stop.pos); + return sys->sprint("%q:%d.%d,%d", src.start.file, src.start.line, src.start.pos, src.stop.pos); +} + +pval(exp: ref Exp, v: string, w: int, depth: int) +{ + if(depth >= maxrecur){ + stdout.puts(v); + return; + } + case exp.kind() { + Tarray => + if(pref(v)){ + if(depth+1 >= maxrecur) + stdout.puts(v+"{...}"); + else{ + stdout.puts(v+"{\n"); + indent(depth+1); + parray(exp, depth+1); + stdout.puts("\n"); + indent(depth); + stdout.puts("}"); + } + } + Tlist => + if(v == "nil") + stdout.puts("nil"); + else + if(depth+1 >= maxrecur) + stdout.puts(v+"{...}"); + else{ + stdout.puts("{\n"); + indent(depth+1); + plist(exp, v, w, depth+1); + stdout.puts("\n"); + indent(depth); + stdout.puts("}"); + } + Tadt => + pgenval(exp, nil, w, depth); + Tref => + if(pref(v)) + pgenval(exp, v, w, depth); + Tstring => + stdout.puts(strval(v)); + * => + pgenval(exp, v, w, depth); + } +} + +parray(exp: ref Exp, depth: int) +{ + exps := exp.expand(); + for(i := 0; i < len exps; i++){ + e := exps[i]; + (v, w) := e.val(); + if(e.kind() == Tslice) + parray(e, depth); + else{ + pval(e, v, w, depth); + stdout.puts(", "); + } + } +} + +plist(exp: ref Exp, v: string, w: int, depth: int) +{ + while(w && v != "nil"){ + exps := exp.expand(); + h := getname(exps, "hd"); + (hv, vw) := h.val(); + if(pref(v) == 0) + return; + stdout.puts(v+"("); + pval(h, hv, vw, depth); + stdout.puts(") :: "); + h = nil; + exp = getname(exps, "tl"); + (v, w) = exp.val(); + } + stdout.puts("nil"); +} + +pgenval(exp: ref Exp, v: string, w: int, depth: int) +{ + if(w){ + exps := exp.expand(); + if(len exps == 0) + stdout.puts(v); + else{ + stdout.puts(v+"{\n"); # } + if (len exps > 0){ + if(depth >= maxrecur){ + indent(depth); + stdout.puts(sys->sprint("...[%d]\n", len exps)); + }else{ + for (i := 0; i < len exps; i++){ + indent(depth+1); + pname(exps[i], depth+1); + stdout.puts("\n"); + } + } + } + indent(depth); # { + stdout.puts("}"); + } + }else + stdout.puts(v); +} + +symname(exp: ref Exp): string +{ + if(showsource == 0) + return exp.name; + return exp.name+"["+srcstr(exp.src())+"]"; +} + +indent(n: int) +{ + while(n-- > 0) + stdout.puts(sep); +} + +pref(v: string): int +{ + if(addref(v) == 0){ + stdout.puts(v); + if(v != "nil") + stdout.puts("(qv)"); + return 0; + } + return 1; +} + +addref(v: string): int +{ + slot := hashfn(v, len hasht); + for(l := hasht[slot]; l != nil; l = tl l) + if((hd l) == v) + return 0; + hasht[slot] = v :: hasht[slot]; + return 1; +} + +hashfn(s: string, n: int): int +{ + h := 0; + m := len s; + for(i:=0; i<m; i++){ + h = 65599*h+s[i]; + } + return (h & 16r7fffffff) % n; +} diff --git a/appl/cmd/stream.b b/appl/cmd/stream.b new file mode 100644 index 00000000..4dd2cda3 --- /dev/null +++ b/appl/cmd/stream.b @@ -0,0 +1,98 @@ +# +# stream data from files +# +# Copyright © 2000 Vita Nuova Limited. All rights reserved. +# + +implement Stream; + +include "sys.m"; + sys: Sys; +include "draw.m"; + +Stream: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +usage() +{ + sys->fprint(stderr, "Usage: stream [-a] [-b bufsize] file1 [file2]\n"); + fail("usage"); +} + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + bsize := 0; + sync := chan of int; + if(argv != nil) + argv = tl argv; + for(; argv != nil && len hd argv && (s := hd argv)[0] == '-' && len s > 1; argv = tl argv) + case s[1] { + 'b' => + if(len s > 2) + bsize = int s[2:]; + else if((argv = tl argv) != nil) + bsize = int hd argv; + else + usage(); + 'a' => + sync = nil; + * => + usage(); + } + if(bsize <= 0 || bsize > 2*1024*1024) + bsize = Sys->ATOMICIO; + argc := len argv; + if(argc < 1) + usage(); + + if(argc > 1){ + f1 := eopen(hd argv, Sys->ORDWR); + f2 := eopen(hd tl argv, Sys->ORDWR); + spawn stream(f1, f2, bsize, sync); + spawn stream(f2, f1, bsize, sync); + }else{ + f2 := sys->fildes(1); + if(f2 == nil) { + sys->fprint(stderr, "stream: can't access standard output: %r\n"); + fail("stdout"); + } + f1 := eopen(hd argv, Sys->OREAD); + spawn stream(f1, f2, bsize, sync); + } + if(sync != nil){ # count them back in + <-sync; + if(argc > 1) + <-sync; + } +} + +stream(source: ref Sys->FD, sink: ref Sys->FD, bufsize: int, sync: chan of int) +{ + if(sys->stream(source, sink, bufsize) < 0) + sys->fprint(stderr, "stream: error streaming data: %r\n"); + if(sync != nil) + sync <-= 1; +} + +eopen(name: string, mode: int): ref Sys->FD +{ + fd := sys->open(name, mode); + if(fd == nil){ + sys->fprint(stderr, "stream: can't open %s: %r\n", name); + fail("open"); + } + return fd; +} + +fail(s: string) +{ + raise s; + exit; +} diff --git a/appl/cmd/strings.b b/appl/cmd/strings.b new file mode 100644 index 00000000..9f806fa5 --- /dev/null +++ b/appl/cmd/strings.b @@ -0,0 +1,87 @@ +# +# initially generated by c2l +# + +implement Strings; + +include "draw.m"; + +Strings: module +{ + init: fn(nil: ref Draw->Context, argl: list of string); +}; + +include "sys.m"; + sys: Sys; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +MINSPAN: con 6; +BUFSIZE: con 70; + +init(nil: ref Draw->Context, argl: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + argc := len argl; + if(argc < 2){ + stringit(""); + exit; + } + argl = tl argl; + for(i := 1; i < argc; i++){ + if(argc > 2) + sys->print("%s:\n", hd argl); + stringit(hd argl); + argl = tl argl; + } +} + +stringit(str: string) +{ + cnt := 0; + c: int; + buf := string array[BUFSIZE] of { * => byte 'z' }; + + if(str == nil) + fin := bufio->fopen(sys->fildes(0), Bufio->OREAD); + else + fin = bufio->open(str, Bufio->OREAD); + if(fin == nil){ + sys->fprint(sys->fildes(2), "cannot open %s\n", str); + return; + } + start := big -1; + posn := fin.offset(); + while((c = fin.getc()) >= 0){ + if(isprint(c)){ + if(start == big -1) + start = posn; + buf[cnt++] = c; + if(cnt == BUFSIZE){ + sys->print("%8bd: %s ...\n", start, buf[0: cnt]); + start = big -1; + cnt = 0; + } + } + else{ + if(cnt >= MINSPAN) + sys->print("%8bd: %s\n", start, buf[0: cnt]); + start = big -1; + cnt = 0; + } + posn = fin.offset(); + } + if(cnt >= MINSPAN) + sys->print("%8bd: %s\n", start, buf[0: cnt]); + fin = nil; +} + +isprint(r: int): int +{ + if(r >= ' ' && r < 16r7f || r > 16ra0) + return 1; + else + return 0; +} diff --git a/appl/cmd/styxchat.b b/appl/cmd/styxchat.b new file mode 100644 index 00000000..f0b1f2c5 --- /dev/null +++ b/appl/cmd/styxchat.b @@ -0,0 +1,557 @@ +implement Styxchat; + +# +# Copyright © 2002,2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + +include "string.m"; + str: String; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + +Styxchat: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +msgsize := 64*1024; +nexttag := 1; +verbose := 0; + +stdin: ref Sys->FD; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + styx = load Styx Styx->PATH; + str = load String String->PATH; + bufio = load Bufio Bufio->PATH; + styx->init(); + + client := 1; + addr := 0; + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("styxchat [-nsv] [-m messagesize] [dest]"); + while((o := arg->opt()) != 0) + case o { + 'm' => + msgsize = atoi(arg->earg()); + 's' => + client = 0; + 'n' => + addr = 1; + 'v' => + verbose++; + * => + arg->usage(); + } + args = arg->argv(); + arg = nil; + fd: ref Sys->FD; + if(args == nil){ + fd = sys->fildes(0); + stdin = sys->open("/dev/cons", Sys->ORDWR); + if (stdin == nil) + err(sys->sprint("can't open /dev/cons: %r")); + sys->dup(stdin.fd, 1); + }else{ + if(tl args != nil) + arg->usage(); + stdin = sys->fildes(0); + dest := hd args; + if(addr){ + dest = netmkaddr(dest, "net", "styx"); + if (client){ + (rc, c) := sys->dial(dest, nil); + if(rc < 0) + err(sys->sprint("can't dial %s: %r", dest)); + fd = c.dfd; + }else{ + (rlc, lc) := sys->announce(dest); + if (rlc < 0) + err(sys->sprint("can't announce %s: %r", dest)); + (rc, c) := sys->listen(lc); + if (rc < 0) + err(sys->sprint("can't listen on %s: %r", dest)); + fd = sys->open(c.dir + "/data", Sys->ORDWR); + if (fd == nil) + err(sys->sprint("can't open %s/data: %r", c.dir)); + } + }else{ + fd = sys->open(dest, Sys->ORDWR); + if(fd == nil) + err(sys->sprint("can't open %s: %r", dest)); + } + } + sys->pctl(Sys->NEWPGRP, nil); + if(client){ + spawn Rreader(fd); + Twriter(fd); + }else{ + spawn Treader(fd); + Rwriter(fd); + } +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} + +quit(e: int) +{ + fd := sys->open("/prog/"+string sys->pctl(0, nil)+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "killgrp"); + if(e) + raise "fail:error"; + exit; +} + +Rreader(fd: ref Sys->FD) +{ + while((m := Rmsg.read(fd, msgsize)) != nil){ + sys->print("<- %s\n%s", m.text(), Rdump(m)); + if(tagof m == tagof Rmsg.Readerror) + quit(1); + } + sys->print("styxchat: server hungup\n"); +} + +Twriter(fd: ref Sys->FD) +{ + in := bufio->fopen(stdin, Sys->OREAD); + while((l := in.gets('\n')) != nil){ + if(l != nil && l[0] == '#') + continue; + (t, err) := Tparse(l); + if(t == nil){ + if(err != nil) + sys->print("?%s\n", err); + }else{ + if(t.tag == 0) + t.tag = nexttag; + a := t.pack(); + if(a != nil){ + sys->print("-> %s\n%s", t.text(), Tdump(t)); + n := len a; + if(n <= msgsize){ + if(sys->write(fd, a, len a) != len a) + sys->print("?write error to server: %r\n"); + if(t.tag != Styx->NOTAG && t.tag != ~0) + nexttag++; + }else + sys->print("?message bigger than agreed: %d bytes\n", n); + }else + sys->fprint(sys->fildes(2), "styxchat: T-message conversion failed\n"); + } + } +} + +Rdump(m: ref Rmsg): string +{ + if(!verbose) + return ""; + pick r :=m { + Read => + return dump(r.data, len r.data, verbose>1); + * => + return ""; + } +} + +Tdump(m: ref Tmsg): string +{ + if(!verbose) + return ""; + pick t := m { + Write => + return dump(t.data, len t.data, verbose>1); + * => + return ""; + } +} + +isprint(c: int): int +{ + return c >= 16r20 && c < 16r7F || c == '\n' || c == '\t' || c == '\r'; +} + +textdump(a: array of byte, lim: int): string +{ + s := "\ttext(\""; + for(i := 0; i < lim; i++) + case c := int a[i] { + '\t' => + s += "\\t"; + '\n' => + s += "\\n"; + '\r' => + s += "\\r"; + '"' => + s += "\\\""; + * => + if(isprint(c)) + s[len s] = c; + else + s += sys->sprint("\\u%4.4ux", c); + } + s += "\")\n"; + return s; +} + +dump(a: array of byte, lim: int, text: int): string +{ + if(a == nil) + return ""; + if(len a < lim) + lim = len a; + printable := 1; + for(i := 0; i < lim; i++) + if(!isprint(int a[i])){ + printable = 0; + break; + } + if(printable) + return textdump(a, lim); + s := "\tdump("; + for(i = 0; i < lim; i++) + s += sys->sprint("%2.2ux", int a[i]); + s += ")\n"; + if(text) + s += textdump(a, lim); + return s; +} + +val(s: string): int +{ + if(s == "~0") + return ~0; + return atoi(s); +} + +bigval(s: string): big +{ + if(s == "~0") + return ~ big 0; + return atob(s); +} + +fid(s: string): int +{ + if(s == "nofid" || s == "NOFID") + return Styx->NOFID; + return val(s); +} + +tag(s: string): int +{ + if(s == "~0" || s == "notag" || s == "NOTAG") + return Styx->NOTAG; + return atoi(s); +} + +dir(name: string, uid: string, gid: string, mode: int, mtime: int, length: big): Sys->Dir +{ + d := sys->zerodir; + d.name = name; + d.uid = uid; + d.gid = gid; + d.mode = mode; + d.mtime = mtime; + d.length = length; + return d; +} + +Tparse(s: string): (ref Tmsg, string) +{ + args := str->unquoted(s); + if(args == nil) + return (nil, nil); + argc := len args; + av := array[argc] of string; + for(i:=0; args != nil; args = tl args) + av[i++] = hd args; + case av[0] { + "Tversion" => + if(argc != 3) + return (nil, "usage: Tversion messagesize version"); + return (ref Tmsg.Version(Styx->NOTAG, atoi(av[1]), av[2]), nil); + "Tauth" => + if(argc != 4) + return (nil, "usage: Tauth afid uname aname"); + return (ref Tmsg.Auth(0, fid(av[1]), av[2], av[3]), nil); + "Tflush" => + if(argc != 2) + return (nil, "usage: Tflush oldtag"); + return (ref Tmsg.Flush(0, tag(av[1])), nil); + "Tattach" => + if(argc != 5) + return (nil, "usage: Tattach fid afid uname aname"); + return (ref Tmsg.Attach(0, fid(av[1]), fid(av[2]), av[3], av[4]), nil); + "Twalk" => + if(argc < 3) + return (nil, "usage: Twalk fid newfid [name...]"); + names: array of string; + if(argc > 3) + names = av[3:]; + return (ref Tmsg.Walk(0, fid(av[1]), fid(av[2]), names), nil); + "Topen" => + if(argc != 3) + return (nil, "usage: Topen fid mode"); + return (ref Tmsg.Open(0, fid(av[1]), atoi(av[2])), nil); + "Tcreate" => + if(argc != 5) + return (nil, "usage: Tcreate fid name perm mode"); + return (ref Tmsg.Create(0, fid(av[1]), av[2], atoi(av[3]), atoi(av[4])), nil); + "Tread" => + if(argc != 4) + return (nil, "usage: Tread fid offset count"); + return (ref Tmsg.Read(0, fid(av[1]), atob(av[2]), atoi(av[3])), nil); + "Twrite" => + if(argc != 4) + return (nil, "usage: Twrite fid offset data"); + return (ref Tmsg.Write(0, fid(av[1]), atob(av[2]), array of byte av[3]), nil); + "Tclunk" => + if(argc != 2) + return (nil, "usage: Tclunk fid"); + return (ref Tmsg.Clunk(0, fid(av[1])), nil); + "Tremove" => + if(argc != 2) + return (nil, "usage: Tremove fid"); + return (ref Tmsg.Remove(0, fid(av[1])), nil); + "Tstat" => + if(argc != 2) + return (nil, "usage: Tstat fid"); + return (ref Tmsg.Stat(0, fid(av[1])), nil); + "Twstat" => + if(argc != 8) + return (nil, "usage: Twstat fid name uid gid mode mtime length"); + return (ref Tmsg.Wstat(0, fid(av[1]), dir(av[2], av[3], av[4], val(av[5]), val(av[6]), bigval(av[7]))), nil); + "nexttag" => + if(argc < 2) + return (nil, sys->sprint("next tag is %d", nexttag)); + nexttag = tag(av[1]); + return (nil, nil); + "dump" => + verbose++; + return (nil, nil); + * => + return (nil, "unknown message type"); + } +} + +# +# server side +# + +Treader(fd: ref Sys->FD) +{ + while((m := Tmsg.read(fd, msgsize)) != nil){ + sys->print("<- %s\n", m.text()); + if(tagof m == tagof Tmsg.Readerror) + quit(1); + } + sys->print("styxchat: clients hungup\n"); +} + +Rwriter(fd: ref Sys->FD) +{ + in := bufio->fopen(stdin, Sys->OREAD); + while((l := in.gets('\n')) != nil){ + if(l != nil && l[0] == '#') + continue; + (r, err) := Rparse(l); + if(r == nil){ + if(err != nil) + sys->print("?%s\n", err); + }else{ + a := r.pack(); + if(a != nil){ + sys->print("-> %s\n", r.text()); + n := len a; + if(n <= msgsize){ + if(sys->write(fd, a, len a) != len a) + sys->print("?write error to clients: %r\n"); + }else + sys->print("?message bigger than agreed: %d bytes\n", n); + }else + sys->fprint(sys->fildes(2), "styxchat: R-message conversion failed\n"); + } + } +} + +qid(s: string): Sys->Qid +{ + (nf, flds) := sys->tokenize(s, "."); + q := Sys->Qid(big 0, 0, 0); + if(nf < 1) + return q; + q.path = atob(hd flds); + if(nf < 2) + return q; + q.vers = atoi(hd tl flds); + if(nf < 3) + return q; + q.qtype = mode(hd tl tl flds); + return q; +} + +mode(s: string): int +{ + if(len s > 0 && s[0] >= '0' && s[0] <= '9') + return atoi(s); + mode := 0; + for(i := 0; i < len s; i++){ + case s[i] { + 'd' => + mode |= Sys->QTDIR; + 'a' => + mode |= Sys->QTAPPEND; + 'u' => + mode |= Sys->QTAUTH; + 'l' => + mode |= Sys->QTEXCL; + 'f' => + ; + * => + sys->fprint(sys->fildes(2), "styxchat: unknown mode character %c, ignoring\n", s[i]); + } + } + return mode; +} + +rdir(a: array of string): Sys->Dir +{ + d := sys->zerodir; + d.qid = qid(a[0]); + d.mode = atoi(a[1]) | (d.qid.qtype<<24); + d.atime = atoi(a[2]); + d.mtime = atoi(a[3]); + d.length = atob(a[4]); + d.name = a[5]; + d.uid = a[6]; + d.gid = a[7]; + d.muid = a[8]; + return d; +} + +Rparse(s: string): (ref Rmsg, string) +{ + args := str->unquoted(s); + if(args == nil) + return (nil, nil); + argc := len args; + av := array[argc] of string; + for(i:=0; args != nil; args = tl args) + av[i++] = hd args; + case av[0] { + "Rversion" => + if(argc != 4) + return (nil, "usage: Rversion tag messagesize version"); + return (ref Rmsg.Version(tag(av[1]), atoi(av[2]), av[3]), nil); + "Rauth" => + if(argc != 3) + return (nil, "usage: Rauth tag aqid"); + return (ref Rmsg.Auth(tag(av[1]), qid(av[2])), nil); + "Rflush" => + if(argc != 2) + return (nil, "usage: Rflush tag"); + return (ref Rmsg.Flush(tag(av[1])), nil); + "Rattach" => + if(argc != 3) + return (nil, "usage: Rattach tag qid"); + return (ref Rmsg.Attach(tag(av[1]), qid(av[2])), nil); + "Rwalk" => + if(argc < 2) + return (nil, "usage: Rwalk tag [qid ...]"); + qids := array[argc-2] of Sys->Qid; + for(i = 0; i < len qids; i++) + qids[i] = qid(av[i+2]); + return (ref Rmsg.Walk(tag(av[1]), qids), nil); + "Ropen" => + if(argc != 4) + return (nil, "usage: Ropen tag qid iounit"); + return (ref Rmsg.Open(tag(av[1]), qid(av[2]), atoi(av[3])), nil); + "Rcreate" => + if(argc != 4) + return (nil, "usage: Rcreate tag qid iounit"); + return (ref Rmsg.Create(tag(av[1]), qid(av[2]), atoi(av[3])), nil); + "Rread" => + if(argc != 3) + return (nil, "usage: Rread tag data"); + return (ref Rmsg.Read(tag(av[1]), array of byte av[2]), nil); + "Rwrite" => + if(argc != 3) + return (nil, "usage: Rwrite tag count"); + return (ref Rmsg.Write(tag(av[1]), atoi(av[2])), nil); + "Rclunk" => + if(argc != 2) + return (nil, "usage: Rclunk tag"); + return (ref Rmsg.Clunk(tag(av[1])), nil); + "Rremove" => + if(argc != 2) + return (nil, "usage: Rremove tag"); + return (ref Rmsg.Remove(tag(av[1])), nil); + "Rstat" => + if(argc != 11) + return (nil, "usage: Rstat tag qid mode atime mtime length name uid gid muid"); + return (ref Rmsg.Stat(tag(av[1]), rdir(av[2:])), nil); + "Rwstat" => + if(argc != 8) + return (nil, "usage: Rwstat tag"); + return (ref Rmsg.Wstat(tag(av[1])), nil); + "Rerror" => + if(argc != 3) + return (nil, "usage: Rerror tag ename"); + return (ref Rmsg.Error(tag(av[1]), av[2]), nil); + "dump" => + verbose++; + return (nil, nil); + * => + return (nil, "unknown message type"); + } +} + +atoi(s: string): int +{ + (i, nil) := str->toint(s, 0); + return i; +} + +# atoi with traditional unix semantics for octal and hex. +atob(s: string): big +{ + (b, nil) := str->tobig(s, 0); + return b; +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "styxchat: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/styxlisten.b b/appl/cmd/styxlisten.b new file mode 100644 index 00000000..2147e619 --- /dev/null +++ b/appl/cmd/styxlisten.b @@ -0,0 +1,262 @@ +implement Styxlisten; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "keyring.m"; + keyring: Keyring; +include "security.m"; + auth: Auth; +include "arg.m"; +include "sh.m"; + +Styxlisten: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +badmodule(p: string) +{ + sys->fprint(stderr(), "styxlisten: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +verbose := 0; +passhostnames := 0; + +init(ctxt: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + auth = load Auth Auth->PATH; + if (auth == nil) + badmodule(Auth->PATH); + if ((e := auth->init()) != nil) + error("auth init failed: " + e); + keyring = load Keyring Keyring->PATH; + if (keyring == nil) + badmodule(Keyring->PATH); + + arg := load Arg Arg->PATH; + if (arg == nil) + badmodule(Arg->PATH); + + arg->init(argv); + arg->setusage("styxlisten [-a alg]... [-Atsv] [-k keyfile] address cmd [arg...]"); + + algs: list of string; + doauth := 1; + synchronous := 0; + trusted := 0; + keyfile := ""; + + while ((opt := arg->opt()) != 0) { + case opt { + 'v' => + verbose = 1; + 'a' => + algs = arg->earg() :: algs; + 'f' or + 'k' => + keyfile = arg->earg(); + if (! (keyfile[0] == '/' || (len keyfile > 2 && keyfile[0:2] == "./"))) + keyfile = "/usr/" + user() + "/keyring/" + keyfile; + 'h' => + passhostnames = 1; + 't' => + trusted = 1; + 's' => + synchronous = 1; + 'A' => + doauth = 0; + * => + arg->usage(); + } + } + argv = arg->argv(); + if (len argv < 2) + arg->usage(); + arg = nil; + if (doauth && algs == nil) + algs = getalgs(); + addr := netmkaddr(hd argv, "tcp", "styx"); + cmd := tl argv; + + authinfo: ref Keyring->Authinfo; + if (doauth) { + if (keyfile == nil) + keyfile = "/usr/" + user() + "/keyring/default"; + authinfo = keyring->readauthinfo(keyfile); + if (authinfo == nil) + error(sys->sprint("cannot read %s: %r", keyfile)); + } + + (ok, c) := sys->announce(addr); + if (ok == -1) + error(sys->sprint("cannot announce on %s: %r", addr)); + if(!trusted){ + sys->unmount(nil, "/mnt/keys"); # should do for now + # become none? + } + + lsync := chan[1] of int; + if(synchronous) + listener(c, popen(ctxt, cmd, lsync), authinfo, algs, lsync); + else + spawn listener(c, popen(ctxt, cmd, lsync), authinfo, algs, lsync); +} + +listener(c: Sys->Connection, mfd: ref Sys->FD, authinfo: ref Keyring->Authinfo, algs: list of string, lsync: chan of int) +{ + lsync <-= sys->pctl(0, nil); + for (;;) { + (n, nc) := sys->listen(c); + if (n == -1) + error(sys->sprint("listen failed: %r")); + if (verbose) + sys->fprint(stderr(), "styxlisten: got connection from %s", + readfile(nc.dir + "/remote")); + dfd := sys->open(nc.dir + "/data", Sys->ORDWR); + if (dfd != nil) { + if(nc.cfd != nil) + sys->fprint(nc.cfd, "keepalive"); + hostname: string; + if(passhostnames){ + hostname = readfile(nc.dir + "/remote"); + if(hostname != nil) + hostname = hostname[0:len hostname - 1]; + } + if (algs == nil) { + sync := chan of int; + spawn exportproc(sync, mfd, nil, hostname, dfd); + <-sync; + } else + spawn authenticator(dfd, authinfo, mfd, algs, hostname); + } + } +} + +# authenticate a connection and set the user id. +authenticator(dfd: ref Sys->FD, authinfo: ref Keyring->Authinfo, mfd: ref Sys->FD, + algs: list of string, hostname: string) +{ + # authenticate and change user id appropriately + (fd, err) := auth->server(algs, authinfo, dfd, 1); + if (fd == nil) { + if (verbose) + sys->fprint(stderr(), "styxlisten: authentication failed: %s\n", err); + return; + } + if (verbose) + sys->fprint(stderr(), "styxlisten: client authenticated as %s\n", err); + sync := chan of int; + spawn exportproc(sync, mfd, err, hostname, fd); + <-sync; +} + +exportproc(sync: chan of int, fd: ref Sys->FD, uname, hostname: string, dfd: ref Sys->FD) +{ + sys->pctl(Sys->NEWFD | Sys->NEWNS, 2 :: fd.fd :: dfd.fd :: nil); + fd = sys->fildes(fd.fd); + dfd = sys->fildes(dfd.fd); + sync <-= 1; + + # XXX unfortunately we cannot pass through the aname from + # the original attach, an inherent shortcoming of this scheme. + if (sys->mount(fd, nil, "/", Sys->MREPL|Sys->MCREATE, hostname) == -1) + error(sys->sprint("cannot mount for user '%s': %r\n", uname)); + + sys->export(dfd, "/", Sys->EXPWAIT); +} + +error(e: string) +{ + sys->fprint(stderr(), "styxlisten: %s\n", e); + raise "fail:error"; +} + + +popen(ctxt: ref Draw->Context, argv: list of string, lsync: chan of int): ref Sys->FD +{ + sync := chan of int; + fds := array[2] of ref Sys->FD; + sys->pipe(fds); + spawn runcmd(ctxt, argv, fds[0], sync, lsync); + <-sync; + return fds[1]; +} + +runcmd(ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD, + sync: chan of int, lsync: chan of int) +{ + sys->pctl(Sys->FORKFD, nil); + sys->dup(stdin.fd, 0); + stdin = nil; + sync <-= 0; + sh := load Sh Sh->PATH; + e := sh->run(ctxt, argv); + kill(<-lsync, "kill"); # kill listener, as command has exited + if(verbose){ + if(e != nil) + sys->fprint(stderr(), "styxlisten: command exited with error: %s\n", e); + else + sys->fprint(stderr(), "styxlisten: command exited\n"); + } +} + +kill(pid: int, how: string) +{ + sys->fprint(sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE), "%s", how); +} + +user(): string +{ + if ((s := readfile("/dev/user")) == nil) + return "none"; + return s; +} + +readfile(f: string): string +{ + fd := sys->open(f, sys->OREAD); + if(fd == nil) + return nil; + + buf := array[1024] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return nil; + + return string buf[0:n]; +} + +getalgs(): list of string +{ + sslctl := readfile("#D/clone"); + if (sslctl == nil) { + sslctl = readfile("#D/ssl/clone"); + if (sslctl == nil) + return nil; + sslctl = "#D/ssl/" + sslctl; + } else + sslctl = "#D/" + sslctl; + (nil, algs) := sys->tokenize(readfile(sslctl + "/encalgs") + " " + readfile(sslctl + "/hashalgs"), " \t\n"); + return "none" :: algs; +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/styxmon.b b/appl/cmd/styxmon.b new file mode 100644 index 00000000..0e5cb412 --- /dev/null +++ b/appl/cmd/styxmon.b @@ -0,0 +1,110 @@ +implement Styxmon; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; +include "sh.m"; +include "arg.m"; + +Styxmon: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "styxmon: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +showdata := 0; +init(ctxt: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + styx = load Styx Styx->PATH; + if(styx == nil) + badmod(Styx->PATH); + styx->init(); + arg := load Arg Arg->PATH; + if(arg == nil) + badmod(Arg->PATH); + arg->init(argv); + arg->setusage("usage: styxmon [-d] cmd [arg...]"); + while((opt := arg->opt()) != 0){ + case opt{ + 'd' => + showdata = 1; + * => + arg->usage(); + } + } + argv = arg->argv(); + if(argv == nil) + arg->usage(); + fd0 := sys->fildes(0); + fd1 := popen(ctxt, argv); + sync := chan of int; + spawn msgtx(fd0, fd1, sync, "tmsg"); + <-sync; + spawn msgtx(fd1, fd0, sync, "rmsg"); + <-sync; +} + +msgtx(f0, f1: ref Sys->FD, sync: chan of int, what: string) +{ + sys->pctl(Sys->NEWFD|Sys->NEWNS, 2 :: f0.fd :: f1.fd :: nil); + sync <-= 1; + f0 = sys->fildes(f0.fd); + f1 = sys->fildes(f1.fd); + stderr := sys->fildes(2); + for (;;) { + (d, err) := styx->readmsg(f0, 0); + if(d == nil){ + if(err != nil) + sys->fprint(stderr, "styxmon: error from %s: %s\n", what, err); + else + sys->fprint(stderr, "styxmon: eof from %s\n", what); + exit; + } + if(styx->istmsg(d)){ + (n, m) := Tmsg.unpack(d); + if(n != len d){ + sys->fprint(stderr, "styxmon: %s message error (%d/%d)\n", what, n, len d); + }else{ + sys->fprint(stderr, "%s\n", m.text()); + } + }else{ + (n, m) := Rmsg.unpack(d); + if(n != len d){ + sys->fprint(stderr, "styxmon: %s message error (%d/%d)\n", what, n, len d); + if(m != nil) + sys->fprint(stderr, "err: %s\n", m.text()); + }else{ + sys->fprint(stderr, "%s\n", m.text()); + } + } + sys->write(f1, d, len d); + } +} + +popen(ctxt: ref Draw->Context, argv: list of string): ref Sys->FD +{ + sync := chan of int; + fds := array[2] of ref Sys->FD; + sys->pipe(fds); + spawn runcmd(ctxt, argv, fds[0], sync); + <-sync; + return fds[1]; +} + +runcmd(ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD, sync: chan of int) +{ + sys->pctl(Sys->FORKFD, nil); + sys->dup(stdin.fd, 0); + stdin = nil; + sync <-= 0; + sh := load Sh Sh->PATH; + sh->run(ctxt, argv); +} diff --git a/appl/cmd/sum.b b/appl/cmd/sum.b new file mode 100644 index 00000000..7e7a1335 --- /dev/null +++ b/appl/cmd/sum.b @@ -0,0 +1,59 @@ +implement Sum; + +include "sys.m"; +include "draw.m"; +include "crc.m"; + +Sum : module +{ + init : fn(nil : ref Draw->Context, argv : list of string); +}; + +init(nil : ref Draw->Context, argv : list of string) +{ + sys := load Sys Sys->PATH; + stderr := sys->fildes(2); + crcm := load Crc Crc->PATH; + crcs := crcm->init(0, 0); + a := tl argv; + buf := array[Sys->ATOMICIO] of byte; + err := 0; + for ( ; a != nil; a = tl a) { + s := hd a; + (ok, d) := sys->stat(s); + if (ok < 0) { + sys->fprint(stderr, "sum: cannot get status of %s: %r\n", s); + err = 1; + continue; + } + if (d.mode & Sys->DMDIR) + continue; + fd := sys->open(s, Sys->OREAD); + if (fd == nil) { + sys->fprint(stderr, "sum: cannot open %s: %r\n", s); + err = 1; + continue; + } + crc := 0; + nbytes := big 0; + while((nr := sys->read(fd, buf, len buf)) > 0){ + crc = crcm->crc(crcs, buf, nr); + nbytes += big nr; + } + if(nr < 0) { + sys->fprint(stderr, "sum: error reading %s: %r\n", s); + err = 1; + } + # encode the length but make n==0 not 0 + l := int (nbytes & big 16rFFFFFFFF); + buf[0] = byte((l>>24)^16rCC); + buf[1] = byte((l>>16)^16r55); + buf[2] = byte((l>>8)^16rCC); + buf[3] = byte(l^16r55); + crc = crcm->crc(crcs, buf, 4); + sys->print("%.8ux %6bd %s\n", crc, nbytes, s); + crcm->reset(crcs); + } + if(err) + raise "fail:error"; +} diff --git a/appl/cmd/tail.b b/appl/cmd/tail.b new file mode 100644 index 00000000..07d900d1 --- /dev/null +++ b/appl/cmd/tail.b @@ -0,0 +1,379 @@ +implement Tail; + +include "sys.m"; +sys: Sys; + +include "draw.m"; + +include "bufio.m"; +bufmod : Bufio; +Iobuf : import bufmod; + +include "string.m"; + str : String; + +count, anycount, follow : int; +file : ref sys->FD; +bout : ref Iobuf; +BSize : con 8*1024; + +BEG, END, CHARS, LINES , FWD, REV : con iota; + +origin := END; +units := LINES; +dir := FWD; + + +Tail: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + bufmod = load Bufio Bufio->PATH; + seekable : int; + bout = bufmod->fopen(sys->fildes(1),bufmod->OWRITE); + argv=parse(tl argv); + if(dir==REV && (units==CHARS || follow || origin==BEG)) + fail("incompatible options"); + if(!anycount){ + if (dir==REV) + count= 16r7fffffff; + else + count = 10; + } + if(origin==BEG && units==LINES && count>0) + count--; + if(len argv > 1) + usage(); + if(argv == nil || hd argv == "-") { + file = sys->fildes(0); + seekable = 0; + } + else { + if((file=sys->open(hd argv,sys->OREAD)) == nil ) + fatal(hd argv); + (ok, stat) := sys->fstat(file); + seekable = sys->seek(file,big 0,sys->SEEKSTART) == big 0 && stat.length > big 0; + } + + if(!seekable && origin==END) + keep(); + else if(!seekable && origin==BEG) + skip(); + else if(units==CHARS && origin==END){ + tseek(big -count, Sys->SEEKEND); + copy(); + } + else if(units==CHARS && origin==BEG){ + tseek(big count, Sys->SEEKSTART); + copy(); + } + else if(units==LINES && origin==END) + reverse(); + else if(units==LINES && origin==BEG) + skip(); + if(follow){ + if(seekable){ + d : sys->Dir; + d.length=big -1; + for(;;){ + d=trunc(d.length); + copy(); + sys->sleep(5000); + } + }else{ + for(;;){ + copy(); + sys->sleep(5000); + } + } + } + exit; +} + + +trunc(length : big) : sys->Dir +{ + (i,d):=sys->fstat(file); + if(d.length < length) + d.length = tseek(big 0, sys->SEEKSTART); + return d; +} + + +skip() # read past head of the file to find tail +{ + n : int; + buf := array[BSize] of byte; + if(units == CHARS) { + for( ; count>0; count -=n) { + if (count<BSize) + n=count; + else + n=BSize; + n = tread(buf, n); + if(n == 0) + return; + } + } else { # units == LINES + i:=0; + n=0; + while(count > 0) { + n = tread(buf, BSize); + if(n == 0) + return; + for(i=0; i<n && count>0; i++) + if(buf[i]==byte '\n') + count--; + } + twrite(buf[i:n]); + } + copy(); +} + + +copy() +{ + buf := array[BSize] of byte; + while((n := tread(buf, BSize)) > 0){ + twrite(buf[0:n]); + } + bout.flush(); +} + + +keep() # read whole file, keeping the tail +{ # complexity=length(file)*length(tail). could be linear + j, k : int; + length:=0; + buf : array of byte; + tbuf : array of byte; + bufsize := 0; + for(n:=1; n;) { + if(length+BSize > bufsize ) { + bufsize += 2*BSize; + tbuf = array[bufsize+1] of byte; + tbuf[0:]=buf[0:]; + buf = tbuf; + } + for( ; n && length<bufsize; length+=n) + n = tread(buf[length:], bufsize-length); + if(count >= length) + continue; + if(units == CHARS) + j = length - count; + else{ # units == LINES + if (int buf[length-1]=='\n') + j = length-1; + else + j=length; + for(k=0; j>0; j--) + if(int buf[j-1] == '\n') + if(++k >= count) + break; + } + length-=j; + buf[0:]=buf[j:j+length]; + } + if(dir == REV) { + if(length>0 && buf[length-1]!= byte '\n') + buf[length++] = byte '\n'; + for(j=length-1 ; j>0; j--) + if(buf[j-1] == byte '\n') { + twrite(buf[j:length]); + if(--count <= 0) + return; + length = j; + } + } + if(count > 0 && length > 0) + twrite(buf[0:length]); + bout.flush(); +} + +reverse() # count backward and print tail of file +{ + length := 0; + n := 0; + buf : array of byte; + pos := tseek(big 0, sys->SEEKEND); + bufsize := 0; + for(first:=1; pos>big 0 && count>0; first=0) { + if (pos>big BSize) + n = BSize; + else + n = int pos; + pos -= big n; + if(length+2*n > bufsize) { + bufsize += BSize*((length+2*n-bufsize+BSize-1)/BSize); + tbuf := array[bufsize+1] of byte; + tbuf[0:] = buf; + buf = tbuf; + } + length += n; + abuf := array[length] of byte; + abuf[0:] = buf[0:length]; + buf[n:] = abuf; + tseek(pos, sys->SEEKSTART); + if(tread(buf, n) != n) + fatal("length error"); + if(first && buf[length-1]!= byte '\n') + buf[length++] = byte '\n'; + for(n=length-1 ; n>0 && count>0; n--) + if(buf[n-1] == byte '\n') { + count--; + if(dir == REV){ + twrite(buf[n:length]); + bout.flush(); + } + length = n; + } + } + if(dir == FWD) { + if (n==0) + tseek(big 0 , sys->SEEKSTART); + else + tseek(pos+big n+big 1, sys->SEEKSTART); + + copy(); + } else if(count > 0) + twrite(buf[0:length]); + bout.flush(); +} + + +tseek(o : big, p: int) : big +{ + o = sys->seek(file, o, p); + if(o == big -1) + fatal(""); + return o; +} + + +tread(buf: array of byte, n: int): int +{ + r := sys->read(file, buf, n); + if(r == -1) + fatal(""); + return r; +} + + +twrite(buf:array of byte) +{ + str1:= string buf; + if(bout.puts(str1)!=len str1) + fatal(""); +} + + + +fatal(s : string) +{ + sys->fprint(sys->fildes(2), "tail: %s: %r\n", s); + exit; +} + +fail(s : string) +{ + sys->fprint(sys->fildes(2), "tail: %s\n", s); + exit; +} + + +usage() +{ + sys->fprint(sys->fildes(2), "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc][fr]] [file]\n"); + exit; +} + + +getnumber(s: string) : int +{ + i:=0; + if (len s == 0) return 0; + if(s[i]=='-' || s[i]=='+') { + if (len s == 1) + return 0; + i++; + } + if(!(s[i]>='0' && s[i]<='9')) + return 0; + if(s[0] == '+') + origin = BEG; + if(anycount++) + fail("excess option"); + if (s[0]=='-') + s=s[1:]; + (count,nil) = str->toint(s,10); + if(count < 0){ # protect int args (read, fwrite) + fail("too big"); + } + return 1; +} + +parse(args : list of string) : list of string +{ + for(; args!=nil ; args = tl args ) { + hdarg := hd args; + if(getnumber(hdarg)) + suffix(hdarg); + else if(len hdarg > 1 && hdarg[0] == '-') + case (hdarg[1]) { + 'c' or 'n'=> + if (hdarg[1]=='c') + units = CHARS; + if(len hdarg>2 && getnumber(hdarg[2:])) + ; + else if(tl args != nil && getnumber(hd tl args)) { + args = tl args; + } else + usage(); + 'r' => + dir = REV; + 'f' => + follow++; + '-' => + args = tl args; + } + else + break; + } + return args; +} + + +suffix(s : string) +{ + i:=0; + while(i < len s && str->in(s[i],"0123456789+-")) + i++; + if (i==len s) + return; + if (s[i]=='b') + if((count*=1024) < 0) + fail("too big"); + if (s[i]=='c' || s[i]=='b') + units = CHARS; + if (s[i]=='l' || s[i]=='c' || s[i]=='b') + i++; + if (i<len s){ + case s[i] { + 'r'=> + dir = REV; + return; + 'f'=> + follow++; + return; + } + } + i++; + if (i<len s) + usage(); +} diff --git a/appl/cmd/tarfs.b b/appl/cmd/tarfs.b new file mode 100644 index 00000000..2e0b6473 --- /dev/null +++ b/appl/cmd/tarfs.b @@ -0,0 +1,411 @@ +implement Tarfs; + +# +# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + Qid: import Sys; + +include "draw.m"; + +include "daytime.m"; + daytime: Daytime; + +include "arg.m"; + +include "styx.m"; + styx: Styx; + Tmsg, Rmsg: import styx; + +include "styxservers.m"; + styxservers: Styxservers; + Fid, Styxserver, Navigator, Navop: import styxservers; + Enotfound: import styxservers; + +Tarfs: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +File: adt { + x: int; + name: string; + mode: int; + uid: int; + gid: int; + mtime: int; + length: big; + offset: big; + parent: cyclic ref File; + children: cyclic list of ref File; + + find: fn(f: self ref File, name: string): ref File; + enter: fn(d: self ref File, f: ref File); + stat: fn(d: self ref File): ref Sys->Dir; +}; + +tarfd: ref Sys->FD; +root: ref File; +files: array of ref File; +pathgen: int; + +error(s: string) +{ + sys->fprint(sys->fildes(2), "tarfs: %s\n", s); + raise "fail:error"; +} + +checkload[T](m: T, path: string) +{ + if(m == nil) + error(sys->sprint("can't load %s: %r", path)); +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil); + styx = load Styx Styx->PATH; + checkload(styx, Styx->PATH); + styx->init(); + styxservers = load Styxservers Styxservers->PATH; + checkload(styxservers, Styxservers->PATH); + styxservers->init(styx); + daytime = load Daytime Daytime->PATH; + checkload(daytime, Daytime->PATH); + + arg := load Arg Arg->PATH; + checkload(arg, Arg->PATH); + arg->setusage("tarfs [-a|-b|-ac|-bc] [-D] file mountpoint"); + arg->init(args); + flags := Sys->MREPL; + while((o := arg->opt()) != 0) + case o { + 'a' => flags = Sys->MAFTER; + 'b' => flags = Sys->MBEFORE; + 'D' => styxservers->traceset(1); + * => arg->usage(); + } + args = arg->argv(); + if(len args != 2) + arg->usage(); + arg = nil; + + file := hd args; + args = tl args; + mountpt := hd args; + + sys->pctl(Sys->FORKFD, nil); + + files = array[100] of ref File; + root = files[0] = ref File; + root.x = 0; + root.name = "/"; + root.mode = Sys->DMDIR | 8r555; + root.uid = 0; + root.gid = 0; + root.length = big 0; + root.offset = big 0; + root.mtime = 0; + pathgen = 1; + + tarfd = sys->open(file, Sys->OREAD); + if(tarfd == nil) + error(sys->sprint("can't open %s: %r", file)); + if(readtar(tarfd) < 0) + error(sys->sprint("error reading %s: %r", file)); + + fds := array[2] of ref Sys->FD; + if(sys->pipe(fds) < 0) + error(sys->sprint("can't create pipe: %r")); + + navops := chan of ref Navop; + spawn navigator(navops); + + (tchan, srv) := Styxserver.new(fds[0], Navigator.new(navops), big 0); + fds[0] = nil; + + pidc := chan of int; + spawn server(tchan, srv, pidc, navops); + <-pidc; + + if(sys->mount(fds[1], nil, mountpt, flags, nil) < 0) + error(sys->sprint("can't mount tarfs: %r")); +} + +server(tchan: chan of ref Tmsg, srv: ref Styxserver, pidc: chan of int, navops: chan of ref Navop) +{ + pidc <-= sys->pctl(Sys->FORKNS|Sys->NEWFD, 1::2::srv.fd.fd::tarfd.fd::nil); +Serve: + while((gm := <-tchan) != nil){ + root.mtime = daytime->now(); + pick m := gm { + Readerror => + sys->fprint(sys->fildes(2), "tarfs: mount read error: %s\n", m.error); + break Serve; + Read => + (c, err) := srv.canread(m); + if(c == nil){ + srv.reply(ref Rmsg.Error(m.tag, err)); + break; + } + if(c.qtype & Sys->QTDIR){ + srv.default(m); # does readdir + break; + } + f := files[int c.path]; + n := m.count; + if(m.offset + big n > f.length) + n = int (f.length - m.offset); + if(n <= 0){ + srv.reply(ref Rmsg.Read(m.tag, nil)); + break; + } + a := array[n] of byte; + sys->seek(tarfd, f.offset+m.offset, 0); + n = sys->read(tarfd, a, len a); + if(n < 0) + srv.reply(ref Rmsg.Error(m.tag, sys->sprint("%r"))); + else + srv.reply(ref Rmsg.Read(m.tag, a[0:n])); + * => + srv.default(gm); + } + } + navops <-= nil; # shut down navigator +} + +File.enter(dir: self ref File, f: ref File) +{ + if(pathgen >= len files){ + t := array[pathgen+50] of ref File; + t[0:] = files; + files = t; + } + if(0) + sys->print("enter %s, %s [#%ux %bd]\n", dir.name, f.name, f.mode, f.length); + f.x = pathgen; + f.parent = dir; + dir.children = f :: dir.children; + files[pathgen++] = f; +} + +File.find(f: self ref File, name: string): ref File +{ + for(g := f.children; g != nil; g = tl g) + if((hd g).name == name) + return hd g; + return nil; +} + +File.stat(f: self ref File): ref Sys->Dir +{ + d := ref sys->zerodir; + d.mode = f.mode; + d.qid.path = big f.x; + d.qid.qtype = f.mode>>24; + d.name = f.name; + d.uid = string f.uid; + d.gid = string f.gid; + d.muid = d.uid; + d.length = f.length; + d.mtime = f.mtime; + d.atime = root.mtime; + return d; +} + +split(s: string): (string, string) +{ + for(i := 0; i < len s; i++) + if(s[i] == '/'){ + for(j := i+1; j < len s && s[j] == '/';) + j++; + return (s[0:i], s[j:]); + } + return (nil, s); +} + +putfile(f: ref File) +{ + n := f.name; + df := root; + for(;;){ + (d, rest) := split(n); + if(d == nil || rest == nil){ + f.name = n; + break; + } + g := df.find(d); + if(g == nil){ + g = ref *f; + g.name = d; + g.mode |= Sys->DMDIR; + df.enter(g); + } + n = rest; + df = g; + } + df.enter(f); +} + +navigator(navops: chan of ref Navop) +{ + while((m := <-navops) != nil){ + pick n := m { + Stat => + n.reply <-= (files[int n.path].stat(), nil); + Walk => + f := files[int n.path]; + if((f.mode & Sys->DMDIR) == 0){ + n.reply <-= (nil, "not a directory"); + break; + } + case n.name { + ".." => + if(f.parent != nil) + f = f.parent; + n.reply <-= (f.stat(), nil); + * => + f = f.find(n.name); + if(f != nil) + n.reply <-= (f.stat(), nil); + else + n.reply <-= (nil, Enotfound); + } + Readdir => + f := files[int n.path]; + if((f.mode & Sys->DMDIR) == 0){ + n.reply <-= (nil, "not a directory"); + break; + } + g := f.children; + for(i := n.offset; i > 0 && g != nil; i--) + g = tl g; + for(; --n.count >= 0 && g != nil; g = tl g) + n.reply <-= ((hd g).stat(), nil); + n.reply <-= (nil, nil); + } + } +} + +Blocksize: con 512; +Namelen: con 100; +Userlen: con 32; + +Oname: con 0; +Omode: con Namelen; +Ouid: con Omode+8; +Ogid: con Ouid+8; +Osize: con Ogid+8; +Omtime: con Osize+12; +Ochksum: con Omtime+12; +Olinkflag: con Ochksum+8; +Olinkname: con Olinkflag+1; +# POSIX extensions follow +Omagic: con Olinkname+Namelen; # ustar +Ouname: con Omagic+8; +Ogname: con Ouname+Userlen; +Omajor: con Ogname+Userlen; +Ominor: con Omajor+8; +Oend: con Ominor+8; + +readtar(fd: ref Sys->FD): int +{ + buf := array[Blocksize] of byte; + offset := big 0; + for(;;){ + sys->seek(fd, offset, 0); + n := sys->read(fd, buf, len buf); + if(n == 0) + break; + if(n < 0) + return -1; + if(n < len buf){ + sys->werrstr(sys->sprint("short read: expected %d, got %d", len buf, n)); + return -1; + } + if(buf[0] == byte 0) + break; + offset += big Blocksize; + mode := octal(buf[Omode:Ouid]); + linkflag := int buf[Olinkflag]; + # don't use linkname + if((mode & 8r170000) == 8r40000) + linkflag = '5'; + mode &= 8r777; + case linkflag { + '1' or '2' or 's' => # ignore links and symbolic links + continue; + '3' or '4' or '6' => # special file or fifo (leave them, but empty) + ; + '5' => + mode |= Sys->DMDIR; + } + f := ref File; + f.name = ascii(buf[Oname:Omode]); + while(len f.name > 0 && f.name[0] == '/') + f.name = f.name[1:]; + while(len f.name > 0 && f.name[len f.name-1] == '/'){ + mode |= Sys->DMDIR; + f.name = f.name[:len f.name-1]; + } + f.mode = mode; + f.uid = octal(buf[Ouid:Ogid]); + f.gid = octal(buf[Ogid:Osize]); + f.length = big octal(buf[Osize:Omtime]); + if(f.length < big 0) + error(sys->sprint("tar file size is negative: %s", f.name)); + if(mode & Sys->DMDIR) + f.length = big 0; + f.mtime = octal(buf[Omtime:Ochksum]); + sum := octal(buf[Ochksum:Olinkflag]); + if(sum != checksum(buf)) + error(sys->sprint("checksum error on %s", f.name)); + f.offset = offset; + offset += f.length; + v := int (f.length % big Blocksize); + if(v != 0) + offset += big (Blocksize-v); + putfile(f); + } + return 0; +} + +ascii(b: array of byte): string +{ + top := 0; + for(i := 0; i < len b && b[i] != byte 0; i++) + if(int b[i] >= 16r80) + top = 1; + if(top) + ; # TO DO: do it by hand if not utf-8 + return string b[0:i]; +} + +octal(b: array of byte): int +{ + v := 0; + for(i := 0; i < len b && b[i] == byte ' '; i++) + ; + for(; i < len b && b[i] != byte 0 && b[i] != byte ' '; i++){ + c := int b[i]; + if(!(c >= '0' && c <= '7')) + error(sys->sprint("bad octal value in tar header: %s (%c)", string b, c)); + v = (v<<3) | (c-'0'); + } + return v; +} + +checksum(b: array of byte): int +{ + c := 0; + for(i := 0; i < Ochksum; i++) + c += int b[i]; + for(; i < Olinkflag; i++) + c += ' '; + for(; i < len b; i++) + c += int b[i]; + return c; +} diff --git a/appl/cmd/tclsh.b b/appl/cmd/tclsh.b new file mode 100644 index 00000000..9a2664a9 --- /dev/null +++ b/appl/cmd/tclsh.b @@ -0,0 +1,48 @@ +implement Tclsh; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufmod : Bufio; +Iobuf : import bufmod; + +include "tk.m"; + +include "../lib/tcl.m"; + tcl : Tcl_Core; + +Tclsh: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +init(ctxt: ref Draw->Context, argv : list of string){ + sys=load Sys Sys->PATH; + tcl=load Tcl_Core Tcl_Core->PATH; + if (tcl==nil){ + sys->print("Cannot load Tcl (%r)\n"); + exit; + } + bufmod=load Bufio Bufio->PATH; + if (bufmod==nil){ + sys->print("Cannot load Bufio (%r)\n"); + exit; + } + lines:=chan of string; + tcl->init(ctxt,argv); + new_inp := "tcl%"; + spawn tcl->grab_lines(nil,nil,lines); + for(;;){ + alt{ + line := <-lines => + line = tcl->prepass(line); + msg:= tcl->evalcmd(line,0); + if (msg!=nil) + sys->print("%s\n",msg); + sys->print("%s ", new_inp); + tcl->clear_error(); + } + } +} diff --git a/appl/cmd/tcs.b b/appl/cmd/tcs.b new file mode 100644 index 00000000..4ad70167 --- /dev/null +++ b/appl/cmd/tcs.b @@ -0,0 +1,184 @@ +implement Tcs; + +include "sys.m"; +include "draw.m"; +include "arg.m"; +include "bufio.m"; +include "convcs.m"; + +Tcs : module { + init : fn (nil : ref Draw->Context, args : list of string); +}; + +sys : Sys; +convcs : Convcs; +bufio : Bufio; + +Iobuf : import bufio; + +stderr : ref Sys->FD; + +usage() +{ + sys->fprint(stderr, "tcs [-C configfile] [-l] [-f ics] [-t ocs] file ...\n"); + raise "fail:usage"; +} + +init(nil : ref Draw->Context, args : list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + if ((arg := load Arg Arg->PATH) == nil) + badmodule(Arg->PATH); + if ((bufio = load Bufio Bufio->PATH) == nil) + badmodule(Bufio->PATH); + if ((convcs = load Convcs Convcs->PATH) == nil) + badmodule(Convcs->PATH); + + arg->init(args); + lflag, vflag : int = 0; + ics, ocs : string = "utf8"; + csfile := ""; + while ((c := arg->opt()) != 0) { + case c { + 'C' => + csfile = arg->arg(); + 'f' => + ics = arg->arg(); + 'l' => + lflag = 1; + 't' => + ocs = arg->arg(); + 'v' => + vflag = 1; + * => + usage(); + } + } + file := arg->arg(); + + out := bufio->fopen(sys->fildes(1), Sys->OWRITE); + err := convcs->init(csfile); + if (err != nil) { + sys->fprint(stderr, "convcs: %s\n", err); + raise "fail:init"; + } + + if (lflag) { + if (file != nil) + dumpaliases(out, file, vflag); + else + dumpconvs(out, vflag); + return; + } + + stob : Stob; + btos : Btos; + (stob, err) = convcs->getstob(ocs); + if (err != nil) { + sys->fprint(stderr, "%s: %s\n", ocs, err); + raise "fail:badarg"; + } + (btos, err) = convcs->getbtos(ics); + if (err != nil) { + sys->fprint(stderr, "%s: %s\n", ics, err); + raise "fail:badarg"; + } + + fd := sys->fildes(0); + if (file != nil) + fd = open(file); + + inbuf := array [Sys->ATOMICIO] of byte; + start := 0; + while (fd != nil) { + btoss : Convcs->State = nil; + stobs : Convcs->State = nil; + + while ((n := sys->read(fd, inbuf[start:], len inbuf - start)) > 0) { + s := ""; + nc := 0; + outbuf : array of byte = nil; + (btoss, s, nc) = btos->btos(btoss, inbuf[0:n], -1); + if (s != nil) + (stobs, outbuf) = stob->stob(stobs, s); + if (outbuf != nil) { + out.write(outbuf, len outbuf); + } + # copy down unconverted part of buffer + start = n - nc; + if (start && nc) + inbuf[:] = inbuf[nc:n]; + } + + out.flush(); + file = arg->arg(); + if (file == nil) + break; + fd = open(file); + } +} + +badmodule(s : string) +{ + sys->fprint(stderr, "cannot load module %s: %r\n", s); + raise "fail:init"; +} + +dumpconvs(out : ref Iobuf, verbose : int) +{ + first := 1; + for (csl := convcs->enumcs(); csl != nil; csl = tl csl) { + (name, desc, mode) := hd csl; + if (!verbose) { + if (!first) + out.putc(' '); + out.puts(name); + } else { + ms := ""; + case mode { + Convcs->BTOS => + ms = "(from)"; + Convcs->STOB => + ms = "(to)"; + } + out.puts(sys->sprint("%s%s\t%s\n", name, ms, desc)); + } + first = 0; + } + if (!verbose) + out.putc('\n'); + out.flush(); +} + +dumpaliases(out : ref Iobuf, cs : string, verbose : int) +{ + (desc, asl) := convcs->aliases(cs); + if (asl == nil) { + sys->fprint(stderr, "%s\n", desc); + return; + } + + if (verbose) { + out.puts(desc); + out.putc('\n'); + } + first := 1; + for (; asl != nil; asl = tl asl) { + a := hd asl; + if (!first) + out.putc(' '); + out.puts(a); + first = 0; + } + out.putc('\n'); + out.flush(); +} + +open(path : string) : ref Sys->FD +{ + fd := sys->open(path, Bufio->OREAD); + if (fd == nil) + sys->fprint(stderr, "cannot open %s: %r\n", path); + return fd; +} diff --git a/appl/cmd/tee.b b/appl/cmd/tee.b new file mode 100644 index 00000000..a555487c --- /dev/null +++ b/appl/cmd/tee.b @@ -0,0 +1,79 @@ +implement Tee; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "arg.m"; + +Tee: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +File: adt +{ + fd: ref Sys->FD; + name: string; +}; + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: tee [-a] [file ...]\n"); + raise "fail:usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + arg := load Arg Arg->PATH; + if(arg == nil) + err(sys->sprint("can't load %s: %r", Arg->PATH)); + + append := 0; + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 'a' => append = 1; + * => usage(); + } + names := arg->argv(); + arg = nil; + + fd0 := sys->fildes(0); + if(fd0 == nil) + err("no standard input"); + nf := 0; + files := array[len names + 1] of ref File; + for(; names != nil; names = tl names){ + f := hd names; + fd: ref Sys->FD; + if(append){ + fd = sys->open(f, Sys->OWRITE); + if(fd != nil) + sys->seek(fd, big 0, 2); + else + fd = sys->create(f, Sys->OWRITE, 8r666); + }else + fd = sys->create(f, Sys->OWRITE, 8r666 ); + if(fd == nil) + err(sys->sprint("cannot open %s: %r", f)); + files[nf++] = ref File(fd, f); + } + files[nf++] = ref File(sys->fildes(1), "standard output"); + buf := array[Sys->ATOMICIO] of byte; + while((n := sys->read(fd0, buf, len buf)) > 0){ + for(i := 0; i < nf; i++) + if(sys->write(files[i].fd, buf, n) != n) + err(sys->sprint("error writing %s: %r", files[i].name)); + } + if(n < 0) + err(sys->sprint("read error: %r")); +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "tee: %s\n", s); + raise "fail:error"; +} diff --git a/appl/cmd/telnet.b b/appl/cmd/telnet.b new file mode 100644 index 00000000..0a30f3f9 --- /dev/null +++ b/appl/cmd/telnet.b @@ -0,0 +1,482 @@ +implement Telnet; + +include "sys.m"; + sys: Sys; + Connection: import sys; + +include "draw.m"; + draw: Draw; + Context: import draw; + +Telnet: module +{ + init: fn(ctxt: ref Draw->Context, args: list of string); +}; + +Debug: con 0; + +Inbuf: adt { + fd: ref Sys->FD; + out: ref Outbuf; + buf: array of byte; + ptr: int; + nbyte: int; +}; + +Outbuf: adt { + buf: array of byte; + ptr: int; +}; + +BS: con 8; # ^h backspace character +BSW: con 23; # ^w bacspace word +BSL: con 21; # ^u backspace line +EOT: con 4; # ^d end of file +ESC: con 27; # hold mode + +net: Connection; +stdin, stdout, stderr: ref Sys->FD; + +# control characters +Se: con 240; # end subnegotiation +NOP: con 241; +Mark: con 242; # data mark +Break: con 243; +Interrupt: con 244; +Abort: con 245; # TENEX ^O +AreYouThere: con 246; +Erasechar: con 247; # erase last character +Eraseline: con 248; # erase line +GoAhead: con 249; # half duplex clear to send +Sb: con 250; # start subnegotiation +Will: con 251; +Wont: con 252; +Do: con 253; +Dont: con 254; +Iac: con 255; + +# options +Binary, Echo, SGA, Stat, Timing, +Det, Term, EOR, Uid, Outmark, +Ttyloc, M3270, Padx3, Window, Speed, +Flow, Line, Xloc, Extend: con iota; + +Opt: adt +{ + name: string; + code: int; + noway: int; + remote: int; # remote value + local: int; # local value +}; + +opt := array[] of +{ + Binary => Opt("binary", 0, 0, 0, 0), + Echo => Opt("echo", 1, 0, 0, 0), + SGA => Opt("suppress go ahead", 3, 0, 0, 0), + Stat => Opt("status", 5, 1, 0, 0), + Timing => Opt("timing", 6, 1, 0, 0), + Det=> Opt("det", 20, 1, 0, 0), + Term => Opt("terminal", 24, 0, 0, 0), + EOR => Opt("end of record", 25, 1, 0, 0), + Uid => Opt("uid", 26, 1, 0, 0), + Outmark => Opt("outmark", 27, 1, 0, 0), + Ttyloc => Opt("ttyloc", 28, 1, 0, 0), + M3270 => Opt("3270 mode", 29, 1, 0, 0), + Padx3 => Opt("pad x.3", 30, 1, 0, 0), + Window => Opt("window size", 31, 1, 0, 0), + Speed => Opt("speed", 32, 1, 0, 0), + Flow => Opt("flow control", 33, 1, 0, 0), + Line => Opt("line mode", 34, 1, 0, 0), + Xloc => Opt("X display loc", 35, 1, 0, 0), + Extend => Opt("Extended", 255, 1, 0, 0), +}; + +usage() +{ + sys->fprint(stderr, "usage: telnet host [port]\n"); + raise "fail:usage"; +} + +init(nil: ref Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + stdout = sys->fildes(1); + stdin = sys->fildes(0); + + if (len argv < 2) + usage(); + argv = tl argv; + host := hd argv; + argv = tl argv; + port := "23"; + if(argv != nil) + port = hd argv; + connect(host, port); +} + +ccfd: ref Sys->FD; +connect(addr: string, port: string) +{ + ok: int; + (ok, net) = sys->dial(netmkaddr(addr, "tcp", port), nil); + if(ok < 0) { + sys->fprint(stderr, "telnet: %r\n"); + return; + } + sys->fprint(stderr, "telnet: connected to %s\n", addr); + + raw(1); + pidch := chan of int; + finished := chan of int; + spawn fromnet(pidch, finished); + spawn fromuser(pidch, finished); + pids := array[2] of {* => <-pidch}; + kill(pids[<-finished == pids[0]]); + raw(0); +} + + +fromuser(pidch, finished: chan of int) +{ + pidch <-= sys->pctl(0, nil); + b := array[1024] of byte; + while((n := sys->read(stdin, b, len b)) > 0) { + if (opt[Echo].remote == 0) + sys->write(stdout, b, n); + sys->write(net.dfd, b, n); + } + sys->fprint(stderr, "telnet: error reading stdin: %r\n"); + finished <-= sys->pctl(0, nil); +} + +getc(b: ref Inbuf): int +{ + if(b.nbyte == 0) { + if(b.out != nil) + flushout(b.out); + b.nbyte = sys->read(b.fd, b.buf, len b.buf); + if(b.nbyte <= 0) + return -1; + b.ptr = 0; + } + b.nbyte--; + return int b.buf[b.ptr++]; +} + +putc(b: ref Outbuf, c: int) +{ + b.buf[b.ptr++] = byte c; + if(b.ptr == len b.buf) + flushout(b); +} + +flushout(b: ref Outbuf) +{ + sys->write(stdout, b.buf, b.ptr); + b.ptr = 0; +} + +BUFSIZE: con 2048; +fromnet(pidch, finished: chan of int) +{ + pidch <-= sys->pctl(0, nil); + conout := ref Outbuf(array[BUFSIZE] of byte, 0); + netinp := ref Inbuf(net.dfd, conout, array[BUFSIZE] of byte, 0, 0); + +loop: for(;;) { + c := getc(netinp); + case c { + -1 => + break loop; + Iac => + c = getc(netinp); + if(c != Iac) { + flushout(conout); + if(control(netinp, c) < 0) + break loop; + } else + putc(conout, c); + * => + putc(conout, c); + } + } + sys->fprint(stderr, "telnet: remote host closed connection\n"); + finished <-= sys->pctl(0, nil); +} + +control(bp: ref Inbuf, c: int): int +{ + r := 0; + case c { + AreYouThere => + sys->fprint(net.dfd, "Inferno telnet\r\n"); + Sb => + r = sub(bp); + Will => + r = will(bp); + Wont => + r = wont(bp); + Do => + r = doit(bp); + Dont => + r = dont(bp); + Se => + sys->fprint(stderr, "telnet: SE without an SB\n"); + -1 => + r = -1; + } + + return r; +} + +sub(bp: ref Inbuf): int +{ + subneg: string; + i := 0; + for(;;){ + c := getc(bp); + if(c == Iac) { + c = getc(bp); + if(c == Se) + break; + subneg[i++] = Iac; + } + if(c < 0) + return -1; + subneg[i++] = c; + } + if(i == 0) + return 0; + + if (Debug) + sys->fprint(stderr, "telnet: sub(%s, %d, n = %d)\n", optname(subneg[0]), subneg[1], i); + + for(i = 0; i < len opt; i++) + if(opt[i].code == subneg[0]) + break; + + if(i >= len opt) + return 0; + + case i { + Term => + sbsend(opt[Term].code, array of byte "network"); + } + + return 0; +} + +sbsend(code: int, data: array of byte): int +{ + buf := array[4+len data+2] of byte; + o := 4+len data; + + buf[0] = byte Iac; + buf[1] = byte Sb; + buf[2] = byte code; + buf[3] = byte 0; + buf[4:] = data; + buf[o] = byte Iac; + o++; + buf[o] = byte Se; + + return sys->write(net.dfd, buf, len buf); +} + +will(bp: ref Inbuf): int +{ + c := getc(bp); + if(c < 0) + return -1; + + if (Debug) + sys->fprint(stderr, "telnet: will(%s)\n", optname(c)); + + for(i := 0; i < len opt; i++) + if(opt[i].code == c) + break; + + if(i >= len opt) { + send3(bp, Iac, Dont, c); + return 0; + } + + rv := 0; + if(opt[i].noway) + send3(bp, Iac, Dont, c); + else + if(opt[i].remote == 0) + rv |= send3(bp, Iac, Do, c); + + if(opt[i].remote == 0) + rv |= change(bp, i, Will); + opt[i].remote = 1; + return rv; +} + +wont(bp: ref Inbuf): int +{ + c := getc(bp); + if(c < 0) + return -1; + + if (Debug) + sys->fprint(stderr, "telnet: wont(%s)\n", optname(c)); + + for(i := 0; i < len opt; i++) + if(opt[i].code == c) + break; + + if(i >= len opt) + return 0; + + rv := 0; + if(opt[i].remote) { + rv |= change(bp, i, Wont); + rv |= send3(bp, Iac, Dont, c); + } + opt[i].remote = 0; + return rv; +} + +doit(bp: ref Inbuf): int +{ + c := getc(bp); + if(c < 0) + return -1; + + if (Debug) + sys->fprint(stderr, "telnet: do(%s)\n", optname(c)); + + for(i := 0; i < len opt; i++) + if(opt[i].code == c) + break; + + if(i >= len opt || opt[i].noway) { + send3(bp, Iac, Wont, c); + return 0; + } + rv := 0; + if(opt[i].local == 0) { + rv |= change(bp, i, Do); + rv |= send3(bp, Iac, Will, c); + } + opt[i].local = 1; + return rv; +} + +dont(bp: ref Inbuf): int +{ + c := getc(bp); + if(c < 0) + return -1; + + if (Debug) + sys->fprint(stderr, "telnet: dont(%s)\n", optname(c)); + + for(i := 0; i < len opt; i++) + if(opt[i].code == c) + break; + + if(i >= len opt || opt[i].noway) + return 0; + + rv := 0; + if(opt[i].local){ + opt[i].local = 0; + rv |= change(bp, i, Dont); + rv |= send3(bp, Iac, Wont, c); + } + opt[i].local = 0; + return rv; +} + +change(bp: ref Inbuf, o: int, what: int): int +{ + if(bp != nil) + {} + if(o != 0) + {} + if(what != 0) + {} + return 0; +} + +send3(bp: ref Inbuf, c0: int, c1: int, c2: int): int +{ + if (Debug) + sys->fprint(stderr, "telnet: reply(%s(%s))\n", negname(c1), optname(c2)); + + buf := array[3] of byte; + + buf[0] = byte c0; + buf[1] = byte c1; + buf[2] = byte c2; + + if (sys->write(bp.fd, buf, 3) != 3) + return -1; + return 0; +} + +kill(pid: int): int +{ + fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if (fd == nil) + return -1; + if (sys->write(fd, array of byte "kill", 4) != 4) + return -1; + return 0; +} + +negname(c: int): string +{ + t := "Unknown"; + case c { + Will => t = "will"; + Wont => t = "wont"; + Do => t = "do"; + Dont => t = "dont"; + } + return t; +} + +optname(c: int): string +{ + for (i := 0; i < len opt; i++) + if (opt[i].code == c) + return opt[i].name; + return "unknown"; +} + +raw(on: int) +{ + if(ccfd == nil) { + ccfd = sys->open("/dev/consctl", Sys->OWRITE); + if(ccfd == nil) { + sys->fprint(stderr, "telnet: cannot open /dev/consctl: %r\n"); + return; + } + } + if(on) + sys->fprint(ccfd, "rawon"); + else + sys->fprint(ccfd, "rawoff"); +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/test.b b/appl/cmd/test.b new file mode 100644 index 00000000..eb7bf46f --- /dev/null +++ b/appl/cmd/test.b @@ -0,0 +1,278 @@ +implement Test; +# +# POSIX standard +# test expression +# [ expression ] +# +# translated Brazil /sys/src/cmd/test.c + +# +# print "true" on stdout iff the expression evaluates to true +# + +include "sys.m"; +sys: Sys; +stderr: ref Sys->FD; + +include "draw.m"; + +Test: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +ap: int; +ac: int; +av: array of string; + +init(nil: ref Draw->Context, argl: list of string) +{ + if(argl == nil) + return; + + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + ac = len argl; + av = array [ac] of string; + for(i := 0; argl != nil; argl = tl argl) + av[i++] = hd argl; + + if(av[0] == "[") { + if(av[--ac] != "]") + synbad("] missing"); + } + + ap = 1; + if(ap<ac && e()) + sys->print("true"); +# exit; +# sys->raise "fail: false"; +} + +nxtarg(mt: int): string +{ + if(ap >= ac){ + if(mt){ + ap++; + return nil; + } + synbad("argument expected"); + } + return av[ap++]; +} + +nxtintarg(): (int, int) +{ + if(ap<ac && isint(av[ap])) + return (1, int av[ap++]); + return (0, 0); +} + +e(): int +{ + p1 := e1(); + if(nxtarg(1) == "-o") + return p1 || e(); + ap--; + return p1; +} + +e1(): int +{ + p1 := e2(); + if(nxtarg(1) == "-a") + return p1 && e1(); + ap--; + return p1; +} + +e2(): int +{ + if(nxtarg(0) == "!") + return !e2(); + ap--; + return e3(); +} + +e3(): int +{ + a := nxtarg(0); + if(a == "(") { + p1 := e(); + if(nxtarg(0) != ")") + synbad(") expected"); + return p1; + } + + if(a == "-f") + return filck(nxtarg(0), Topf); + + if(a == "-d") + return filck(nxtarg(0), Topd); + + if(a == "-r") + return filck(nxtarg(0), Topr); + + if(a == "-w") + return filck(nxtarg(0), Topw); + + if(a == "-x") + return filck(nxtarg(0), Topx); + + if(a == "-e") + return filck(nxtarg(0), Tope); + + if(a == "-c") + return 0; + + if(a == "-b") + return 0; + + if(a == "-u") + return 0; + + if(a == "-g") + return 0; + + if(a == "-s") + return filck(nxtarg(0), Tops); + + if(a == "-t") { + (ok, int1) := nxtintarg(); + if(!ok) + return isatty(1); + else + return isatty(int1); + } + + if(a == "-n") + return nxtarg(0) != ""; + if(a == "-z") + return nxtarg(0) == ""; + + p2 := nxtarg(1); + if (p2 == nil) + return a != nil; + if(p2 == "=") + return nxtarg(0) == a; + + if(p2 == "!=") + return nxtarg(0) != a; + + if(!isint(a)) + return a != nil; + int1 := int a; + + (ok, int2) := nxtintarg(); + if(ok){ + if(p2 == "-eq") + return int1 == int2; + if(p2 == "-ne") + return int1 != int2; + if(p2 == "-gt") + return int1 > int2; + if(p2 == "-lt") + return int1 < int2; + if(p2 == "-ge") + return int1 >= int2; + if(p2 == "-le") + return int1 <= int2; + } + + synbad("unknown operator " + p2); + return 0; # to shut ken up +} + +synbad(s: string) +{ + sys->fprint(stderr, "test: bad syntax: %s\n", s); + exit; +} + +isint(s: string): int +{ + if(s == nil) + return 0; + for(i := 0; i < len s; i++) + if(s[i] < '0' || s[i] > '9') + return 0; + return 1; +} + +Topr, +Topw, +Topx, +Tope, +Topf, +Topd, +Tops: con iota; + +filck(fname: string, Top: int): int +{ + (ok, dir) := sys->stat(fname); + + if(ok >= 0) { + ok = 0; + case Top { + Topr => # readable + ok = permck(dir, 8r004); + Topw => # writable + ok = permck(dir, 8r002); + Topx => # executable + ok = permck(dir, 8r001); + Tope => # exists + ok = 1; + Topf => # is a regular file + ok = (dir.mode & Sys->DMDIR) == 0; + Topd => # is a directory + ok = (dir.mode & Sys->DMDIR) != 0; + Tops => # has length > 0 + ok = dir.length > big 0; + } + } + + return ok > 0; +} + +uid, +gid: string; + +permck(dir: Sys->Dir, mask: int): int +{ + if(uid == nil) { + fd := sys->open("/dev/user", Sys->OREAD); + if(fd != nil) { + buf := array [28] of byte; + n := sys->read(fd, buf, len buf); + if(n > 0) + uid = string buf[:n]; + } + gid = nil; # how do I find out what my group is? + } + + ok: int = 0; + + ok = dir.mode & mask<<0; + if(!ok && dir.gid == gid) + ok = dir.mode & mask<<3; + if(!ok && dir.uid == uid) + ok = dir.mode & mask<<6; + + return ok > 0; +} + +isatty(fd: int): int +{ + d1, d2: Sys->Dir; + + ok: int; + (ok, d1) = sys->fstat(sys->fildes(fd)); + if(ok < 0) + return 0; + (ok, d2) = sys->stat("/dev/cons"); + if(ok < 0) + return 0; + + return d1.dtype==d2.dtype && d1.dev==d2.dev && d1.qid.path==d2.qid.path; +} diff --git a/appl/cmd/time.b b/appl/cmd/time.b new file mode 100644 index 00000000..b4fba159 --- /dev/null +++ b/appl/cmd/time.b @@ -0,0 +1,97 @@ +implement Time; + +include "sys.m"; +include "draw.m"; +include "sh.m"; + +FD: import Sys; +Context: import Draw; + +Time: module +{ + init: fn(ctxt: ref Context, argv: list of string); +}; + +sys: Sys; +stderr, waitfd: ref FD; + +init(ctxt: ref Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + + stderr = sys->fildes(2); + + waitfd = sys->open("#p/"+string sys->pctl(0, nil)+"/wait", sys->OREAD); + if(waitfd == nil){ + sys->fprint(stderr, "time: open wait: %r\n"); + return; + } + + argv = tl argv; + + if(argv == nil) { + sys->fprint(stderr, "usage: time cmd ...\n"); + return; + } + + file := hd argv; + + if(len file<4 || file[len file-4:]!=".dis") + file += ".dis"; + + t0 := sys->millisec(); + + c := load Command file; + if(c == nil) { + err := sys->sprint("%r"); + if(1){ + c = load Command "/dis/"+file; + if(c == nil) + err = sys->sprint("%r"); + } + if(c == nil) { + sys->fprint(stderr, "time: %s: %s\n", hd argv, err); + return; + } + } + + t1 := sys->millisec(); + + pidc := chan of int; + + spawn cmd(ctxt, c, pidc, argv); + waitfor(<-pidc); + + t2 := sys->millisec(); + + f1 := real (t1 - t0) /1000.; + f2 := real (t2 - t1) /1000.; + sys->fprint(stderr, "%.4gl %.4gr %.4gt\n", f1, f2, f1+f2); +} + +cmd(ctxt: ref Context, c: Command, pidc: chan of int, argv: list of string) +{ + pidc <-= sys->pctl(0, nil); + c->init(ctxt, argv); +} + +waitfor(pid: int) +{ + buf := array[sys->WAITLEN] of byte; + status := ""; + for(;;){ + n := sys->read(waitfd, buf, len buf); + if(n < 0) { + sys->fprint(stderr, "sh: read wait: %r\n"); + return; + } + status = string buf[0:n]; + if(status[len status-1] != ':') + sys->fprint(stderr, "%s\n", status); + who := int status; + if(who != 0) { + if(who == pid) + return; + } + } +} diff --git a/appl/cmd/timestamp.b b/appl/cmd/timestamp.b new file mode 100644 index 00000000..8f8554f8 --- /dev/null +++ b/appl/cmd/timestamp.b @@ -0,0 +1,42 @@ +implement Timestamp; +include "sys.m"; + sys: Sys; +include "draw.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +Timestamp: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +timefd: ref Sys->FD; +starttime: big; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + + note: string; + if(len argv > 1) + note = hd tl argv + " "; + + timefd = sys->open("/dev/time", Sys->OREAD); + starttime = now(); + + sys->print("%.10bd %sstart %bd\n", now(), note, starttime); + + iob := bufio->fopen(sys->fildes(0), Sys->OREAD); + while((s := iob.gets('\n')) != nil) + sys->print("%.10bd %s%s", now(), note, s); +} + +now(): big +{ + buf := array[24] of byte; + n := sys->pread(timefd, buf, len buf, big 0); + if(n <= 0) + return big 0; + return big string buf[0:n] / big 1000 - starttime; +} diff --git a/appl/cmd/tkcmd.b b/appl/cmd/tkcmd.b new file mode 100644 index 00000000..4dd607b1 --- /dev/null +++ b/appl/cmd/tkcmd.b @@ -0,0 +1,190 @@ +implement Tkcmd; + +include "sys.m"; + sys: Sys; + stderr: ref Sys->FD; +include "draw.m"; + draw: Draw; + Display, Image, Point: import draw; +include "tk.m"; + tk: Tk; +include "tkclient.m"; + tkclient: Tkclient; +include "bufio.m"; +include "arg.m"; + +Tkcmd : module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +usage() +{ + sys->print("usage: tkcmd [-iu] [toplevelarg]\n"); + raise "fail:usage"; +} + +badmodule(m: string) +{ + sys->fprint(stderr, "tkcmd: cannot load %s: %r\n", m); + raise "fail:bad module"; +} + +init(ctxt: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + draw = load Draw Draw->PATH; + tk = load Tk Tk->PATH; + if (tk == nil) + badmodule(Tk->PATH); + tkclient = load Tkclient Tkclient->PATH; + if (tkclient==nil) + badmodule(Tkclient->PATH); + arg := load Arg Arg->PATH; + if (arg == nil) + badmodule(Arg->PATH); + + arg->init(argv); + update := 1; + interactive := isconsole(sys->fildes(0)); + while ((opt := arg->opt()) != 0) { + case opt { + 'i' => + interactive = 1; + 'u' => + update = 0; + * => + usage(); + } + } + argv = arg->argv(); + arg = nil; + tkarg := ""; + if (argv != nil) { + if (tl argv != nil) + usage(); + tkarg = hd argv; + } + + sys->pctl(Sys->NEWPGRP, nil); + tkclient->init(); + shellit(ctxt, tkarg, interactive, update); +} + +isconsole(fd: ref Sys->FD): int +{ + (ok1, d1) := sys->fstat(fd); + (ok2, d2) := sys->stat("/dev/cons"); + if (ok1 < 0 || ok2 < 0) + return 0; + return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path; +} + +shellit(ctxt: ref Draw->Context, arg: string, interactive, update: int) +{ + (Wwsh, winctl) := tkclient->toplevel(ctxt, arg, "Tk", Tkclient->Appl); + tkclient->onscreen(Wwsh, nil); + tkclient->startinput(Wwsh, "ptr" :: "kbd" :: nil); + wm := Wwsh.ctxt; + if(update) + tk->cmd(Wwsh, "update"); + ps1 := ""; + ps2 := ""; + if (!interactive) + ps1 = ps2 = ""; + + lines := chan of string; + sync := chan of int; + spawn grab_lines(ps1, ps2, lines, sync); + output := chan of string; + tk->namechan(Wwsh, output, "stdout"); + pid := <-sync; +Loop: + for(;;) alt { + c := <-wm.kbd => + tk->keyboard(Wwsh, c); + m := <-wm.ptr => + tk->pointer(Wwsh, *m); + c := <-wm.ctl or + c = <-Wwsh.wreq => + tkclient->wmctl(Wwsh, c); + line := <-lines => + if (line == nil) + break Loop; + if (line[0] == '#') + break; + line = line[0:len line - 1]; + result := tk->cmd(Wwsh, line); + if (result != nil) + sys->print("#%s\n", result); + if (update) + tk->cmd(Wwsh, "update"); + sys->print("%s", ps1); + menu := <-winctl => + tkclient->wmctl(Wwsh, menu); + s := <-output => + sys->print("#<stdout>%s\n", s); + sys->print("%s", ps1); + } +} + +grab_lines(new_inp, unfin: string, lines: chan of string, sync: chan of int) +{ + sync <-= sys->pctl(0, nil); + { + bufmod := load Bufio Bufio->PATH; + Iobuf: import bufmod; + if (bufmod == nil) { + lines <-= nil; + return; + } + sys->print("%s", new_inp); + iob := bufmod->fopen(sys->fildes(0),bufmod->OREAD); + if (iob==nil){ + sys->fprint(stderr, "tkcmd: cannot open stdin for reading.\n"); + lines <-= nil; + return; + } + line := ""; + while((input := iob.gets('\n')) != nil) { + line+=input; + if (!finished(line,0)) + sys->print("%s", unfin); + else{ + lines <-= line; + line=nil; + } + } + lines <-= nil; + }exception e{ + "*" => + sys->fprint(stderr, "tkcmd: fail: %s\n", e); + lines <-= nil; + } +} + +# returns 1 if the line has matching braces, brackets and +# double-quotes and does not end in "\\\n" +finished(s : string, termchar : int) : int { + cb:=0; + dq:=0; + sb:=0; + if (s==nil) return 1; + if (termchar=='}') cb++; + if (termchar==']') sb++; + if (len s > 1 && s[len s -2]=='\\') + return 0; + if (s[0]=='{') cb++; + if (s[0]=='}' && cb>0) cb--; + if (s[0]=='[') sb++; + if (s[0]==']' && sb>0) sb--; + if (s[0]=='"') dq=1-dq; + for(i:=1;i<len s;i++){ + if (s[i]=='{' && s[i-1]!='\\') cb++; + if (s[i]=='}' && s[i-1]!='\\' && cb>0) cb--; + if (s[i]=='[' && s[i-1]!='\\') sb++; + if (s[i]==']' && s[i-1]!='\\' && sb>0) sb--; + if (s[i]=='"' && s[i-1]!='\\') dq=1-dq; + } + return (cb==0 && sb==0 && dq==0); +} diff --git a/appl/cmd/tokenize.b b/appl/cmd/tokenize.b new file mode 100644 index 00000000..e5bcf416 --- /dev/null +++ b/appl/cmd/tokenize.b @@ -0,0 +1,33 @@ +implement Tokenize; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +Tokenize: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +stderr: ref Sys->FD; + +usage() +{ + sys->fprint(stderr, "Usage: tokenize string delimiters\n"); + raise "fail: usage"; +} + +init(nil: ref Draw->Context, args : list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + if(args != nil) + args = tl args; + if(len args != 2) + usage(); + (nil, l) := sys->tokenize(hd args, hd tl args); + for(; l != nil; l = tl l) + sys->print("%s\n", hd l); +} diff --git a/appl/cmd/touch.b b/appl/cmd/touch.b new file mode 100644 index 00000000..9ff2dcc5 --- /dev/null +++ b/appl/cmd/touch.b @@ -0,0 +1,77 @@ +implement Touch; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "daytime.m"; + daytime: Daytime; + +include "arg.m"; + +stderr: ref Sys->FD; + +Touch: module +{ + init: fn(ctxt: ref Draw->Context, argl: list of string); +}; + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + force := 1; + status := 0; + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + cantload(Daytime->PATH); + arg := load Arg Arg->PATH; + if(arg == nil) + cantload(Arg->PATH); + arg->init(args); + arg->setusage("touch [-c] [-t time] file ..."); + now := daytime->now(); + while((c := arg->opt()) != 0) + case c { + 't' => now = int arg->earg(); + 'c' => force = 0; + * => arg->usage(); + } + args = arg->argv(); + arg = nil; + if(args == nil) + arg->usage(); + for(; args != nil; args = tl args) + status += touch(force, hd args, now); + if(status) + raise "fail:touch"; +} + +cantload(s: string) +{ + sys->fprint(stderr, "touch: can't load %s: %r\n", s); + raise "fail:load"; +} + +touch(force: int, name: string, now: int): int +{ + dir := sys->nulldir; + dir.mtime = now; + (rc, nil) := sys->stat(name); + if(rc >= 0){ + if(sys->wstat(name, dir) >= 0) + return 0; + force = 0; # we don't want to create it: it's there, we just can't wstat it + } + if(force == 0) { + sys->fprint(stderr, "touch: %s: cannot change time: %r\n", name); + return 1; + } + if((fd := sys->create(name, Sys->OREAD|Sys->OEXCL, 8r666)) == nil) { + sys->fprint(stderr, "touch: %s: cannot create: %r\n", name); + return 1; + } + sys->fwstat(fd, dir); + return 0; +} diff --git a/appl/cmd/touchcal.b b/appl/cmd/touchcal.b new file mode 100644 index 00000000..5557e324 --- /dev/null +++ b/appl/cmd/touchcal.b @@ -0,0 +1,278 @@ +implement Touchcal; + +# +# calibrate a touch screen +# +# Copyright © 2001 Vita Nuova Holdings Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + Display, Font, Image, Point, Pointer, Rect: import draw; + +include "tk.m"; + +include "wmclient.m"; + wmclient: Wmclient; + Window: import wmclient; + +include "translate.m"; + translate: Translate; + Dict: import translate; + +Touchcal: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + + +Margin: con 20; + +prompt:= "Please tap the centre\nof the cross\nwith the stylus"; + +mousepid := 0; + +init(ctxt: ref Draw->Context, args: list of string) +{ + r: Rect; + disp: ref Image; + + if(args != nil) + args = tl args; + debug := args != nil && hd args == "-d"; + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + if(draw == nil) + err(sys->sprint("no Draw module: %r")); + sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil); + translate = load Translate Translate->PATH; + if(translate != nil){ + translate->init(); + (dict, nil) := translate->opendict(translate->mkdictname("", "touchcal")); + if(dict != nil) + prompt = dict.xlate(prompt); + dict = nil; + translate = nil; + } + + display: ref Display; + win: ref Window; + ptr: chan of ref Pointer; + if(ctxt != nil){ + display = ctxt.display; + wmclient = load Wmclient Wmclient->PATH; + if(wmclient == nil) + err(sys->sprint("cannot load %s: %r", Wmclient->PATH)); + wmclient->init(); + win = wmclient->window(ctxt, "Touchcal", Wmclient->Plain); + win.reshape(ctxt.display.image.r); + ptr = chan of ref Pointer; + win.onscreen("exact"); + win.startinput("ptr"::nil); + pidc := chan of int; + ptr = win.ctxt.ptr; + display = ctxt.display; + disp = win.image; + r = disp.r; + }else{ + # standalone, catch them ourselves + display = draw->Display.allocate(nil); + disp = display.image; + r = disp.r; + mfd := sys->open("/dev/pointer", Sys->OREAD); + if(mfd == nil) + err(sys->sprint("can't open /dev/pointer: %r")); + pidc := chan of int; + ptr = chan of ref Pointer; + spawn rawmouse(mfd, ptr, pidc); + mousepid = <-pidc; + } + white := display.white; + black := display.black; + red := display.color(Draw->Red); + disp.draw(r, white, nil, r.min); + samples := array[4] of Point; + points := array[4] of Point; + points[0] = (r.min.x+Margin, r.min.y+Margin); + points[1] = (r.max.x-Margin, r.min.y+Margin); + points[2] = (r.max.x-Margin, r.max.y-Margin); + points[3] = (r.min.x+Margin, r.max.y-Margin); + midpoint := Point((r.min.x+r.max.x)/2, (r.min.y+r.max.y)/2); + refx := FX((points[1].x - points[0].x) + (points[2].x - points[3].x), 1); + refy := FX((points[3].y - points[0].y) + (points[2].y - points[1].y), 1); + ctl := sys->open("/dev/touchctl", Sys->ORDWR); + if(ctl == nil) + ctl = sys->open("/dev/null", Sys->ORDWR); + if(ctl == nil) + err(sys->sprint("can't open /dev/touchctl: %r")); + #oldvalues := array[128] of byte; + #nr := sys->read(ctl, oldvalues, len oldvalues); + #if(nr < 0) + # err(sys->sprint("can't read old values from /dev/touchctl: %r")); + #oldvalues = oldvalues[0:nr]; + sys->fprint(ctl, "X %d %d %d\nY %d %d %d\n", FX(1,1), 0, 0, 0, FX(1,1), 0); # identity + font := Font.open(display, sys->sprint("/fonts/lucida/unicode.%d.font", 6+(r.dx()/512))); + if(font == nil) + font = Font.open(display, "*default*"); + if(font != nil){ + drawtext(disp, midpoint, black, font, prompt); + font = nil; + } + for(;;) { + tm := array[] of {0 to 2 =>array[] of {0, 0, 0}}; + for(i := 0; i < 4; i++){ + cross(disp, points[i], red); + samples[i] = getpoint(ptr); + cross(disp, points[i], white); + } + # first, rotate if necessary + rotate := 0; + if(abs(samples[1].x-samples[2].x) > 80 && abs(samples[2].y-samples[3].y) > 80){ + rotate = 1; + for(i = 0; i < len samples; i++) + samples[i] = (samples[i].y, samples[i].x); + } + # calculate scaling and offset transformations + actx := (samples[1].x-samples[0].x)+(samples[2].x-samples[3].x); + acty := (samples[3].y-samples[0].y)+(samples[2].y-samples[1].y); + if(actx == 0 || acty == 0) + continue; # either the user or device is not trying + tm[0][rotate] = refx/actx; + tm[0][2] = FX(points[0].x - XF(tm[0][rotate]*samples[0].x), 1); + tm[1][1-rotate] = refy/acty; + tm[1][2] = FX(points[0].y - XF(tm[1][1-rotate]*samples[0].y), 1); + cross(disp, midpoint, red); + m := getpoint(ptr); + cross(disp, midpoint, white); + p := Point(ptmap(tm[0], m.x, m.y), ptmap(tm[1], m.x, m.y)); + if(debug){ + for(k:=0; k<4; k++) + sys->print("%d %d,%d %d,%d\n", k, points[k].x,points[k].y, samples[k].x, samples[k].y); + if(rotate) + sys->print("rotated\n"); + sys->print("rx=%d ax=%d ry=%d ay=%d tm[0][0]=%d\n", refx, actx, refy, acty, tm[0][0]); + sys->print("%g %g %g\n%g %g %g\n", + G(tm[0][0]), G(tm[0][1]), G(tm[0][2]), + G(tm[1][0]), G(tm[1][1]), G(tm[1][2])); + sys->print("%d %d -> %d %d (%d %d)\n", m.x, m.y, p.x, p.y, midpoint.x, midpoint.y); + } + if(abs(p.x-midpoint.x) > 5 || abs(p.y-midpoint.y) > 5) + continue; + printmat(sys->fildes(1), tm); + if(debug || printmat(ctl, tm) >= 0){ + disp.draw(r, white, nil, r.min); + break; + } + sys->fprint(sys->fildes(2), "touchcal: can't set calibration: %r\n"); + } + if(mousepid > 0) + kill(mousepid); +} + +printmat(fd: ref Sys->FD, tm: array of array of int): int +{ + return sys->fprint(fd, "X %d %d %d\nY %d %d %d\n", + tm[0][0], tm[0][1], tm[0][2], + tm[1][0], tm[1][1], tm[1][2]); +} + +FX(a, b: int): int +{ + return (a << 16)/b; +} + +XF(v: int): int +{ + return v>>16; +} + +G(v: int): real +{ + return real v / 65536.0; +} + +ptmap(m: array of int, x, y: int): int +{ + return XF(m[0]*x + m[1]*y + m[2]); +} + +rawmouse(fd: ref Sys->FD, mc: chan of ref Pointer, pidc: chan of int) +{ + pidc <-= sys->pctl(0, nil); + buf := array[64] of byte; + for(;;){ + n := sys->read(fd, buf, len buf); + if(n <= 0) + err(sys->sprint("can't read /dev/pointer: %r")); + + if(int buf[0] != 'm' || n < 1+3*12) + continue; + + x := int string buf[ 1:13]; + y := int string buf[12:25]; + b := int string buf[24:37]; + mc <-= ref Pointer(b, (x,y), 0); + } +} + +getpoint(mousec: chan of ref Pointer): Point +{ + p := Point(0,0); + while((m := <-mousec).buttons == 0) + p = m.xy; + n := 0; + do{ + if(abs(p.x-m.xy.x) > 10 || abs(p.y-m.xy.y) > 10){ + n = 0; + p = m.xy; + }else{ + p = p.mul(n).add(m.xy).div(n+1); + n++; + } + }while((m = <-mousec).buttons & 7); + return p; +} + +cross(im: ref Image, p: Point, col: ref Image) +{ + im.line(p.sub((0,10)), p.add((0,10)), Draw->Endsquare, Draw->Endsquare, 0, col, col.r.min); + im.line(p.sub((10,0)), p.add((10,0)), Draw->Endsquare, Draw->Endsquare, 0, col, col.r.min); + im.flush(Draw->Flushnow); +} + +drawtext(im: ref Image, p: Point, col: ref Image, font: ref Font, text: string) +{ + (n, lines) := sys->tokenize(text, "\n"); + p = p.sub((0, (n+1)*font.height)); + for(; lines != nil; lines = tl lines){ + s := hd lines; + w := font.width(s); + im.text(p.sub((w/2, 0)), col, col.r.min, font, s); + p = p.add((0, font.height)); + } +} + +abs(x: int): int +{ + if(x < 0) + return -x; + return x; +} + +kill(pid: int) +{ + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if(fd != nil) + sys->fprint(fd, "kill"); +} + +err(s: string) +{ + sys->fprint(sys->fildes(2), "touchcal: %s\n", s); + if(mousepid > 0) + kill(mousepid); + raise "fail:touch"; +} diff --git a/appl/cmd/tr.b b/appl/cmd/tr.b new file mode 100644 index 00000000..202ace53 --- /dev/null +++ b/appl/cmd/tr.b @@ -0,0 +1,319 @@ +implement Tr; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "arg.m"; + arg: Arg; + +Tr: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Pcb: adt { # Control block controlling specification parse + spec: string; # specification string + end: int; # its length + current: int; # current parse point + last: int; # last Rune returned + final: int; # final Rune in a span + + new: fn(nil: string): ref Pcb; + rewind: fn(nil: self ref Pcb); + getc: fn(nil: self ref Pcb): int; + canon: fn(nil: self ref Pcb): int; +}; + +bits := array [] of { byte 1, byte 2, byte 4, byte 8, byte 16, byte 32, byte 64, byte 128 }; + +SETBIT(a: array of byte, c: int) +{ + a[c>>3] |= bits[c & 7]; +} + +CLEARBIT(a: array of byte, c: int) +{ + a[c>>3] &= ~bits[c & 7]; +} + +BITSET(a: array of byte, c: int): int +{ + return int (a[c>>3] & bits[c&7]); +} + +MAXRUNE: con 16rFFFF; + +f := array[(MAXRUNE+1)/8] of byte; +t := array[(MAXRUNE+1)/8] of byte; + +pto, pfrom: ref Pcb; + +cflag := 0; +dflag := 0; +sflag := 0; +stderr: ref Sys->FD; + +ib: ref Iobuf; +ob: ref Iobuf; + +usage() +{ + sys->fprint(stderr, "Usage: tr [-sdc] [from-set [to-set]]\n"); + raise "fail: usage"; +} + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + arg = load Arg Arg->PATH; + arg->init(args); + while((c := arg->opt()) != 0) + case c { + 's' => sflag = 1; + 'd' => dflag = 1; + 'c' => cflag = 1; + * => usage(); + } + args = arg->argv(); + argc := len args; + if(args != nil){ + pfrom = Pcb.new(hd args); + args = tl args; + } + if(args != nil){ + pto = Pcb.new(hd args); + args = tl args; + } + if(args != nil) + usage(); + ib = bufio->fopen(sys->fildes(0), Bufio->OREAD); + ob = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + if(dflag) { + if(sflag && argc != 2 || !sflag && argc != 1) + usage(); + delete(); + } else { + if(argc != 2) + usage(); + if(cflag) + complement(); + else + translit(); + } + if(ob.flush() == Bufio->ERROR) + error(sys->sprint("write error: %r")); +} + +delete() +{ + if (cflag) { + for(i := 0; i < len f; i++) + f[i] = byte 16rFF; + while ((c := pfrom.canon()) >= 0) + CLEARBIT(f, c); + } else { + while ((c := pfrom.canon()) >= 0) + SETBIT(f, c); + } + if (sflag) { + while ((c := pto.canon()) >= 0) + SETBIT(t, c); + } + + last := MAXRUNE+1; + while ((c := ib.getc()) >= 0) { + if(!BITSET(f, c) && (c != last || !BITSET(t,c))) { + last = c; + ob.putc(c); + } + } +} + +complement() +{ + lastc := 0; + high := 0; + while ((from := pfrom.canon()) >= 0) { + if (from > high) + high = from; + SETBIT(f, from); + } + while ((cto := pto.canon()) >= 0) { + if (cto > high) + high = cto; + SETBIT(t,cto); + } + pto.rewind(); + p := array[high+1] of int; + for (i := 0; i <= high; i++){ + if (!BITSET(f,i)) { + if ((cto = pto.canon()) < 0) + cto = lastc; + else + lastc = cto; + p[i] = cto; + } else + p[i] = i; + } + if (sflag){ + lastc = MAXRUNE+1; + while ((from = ib.getc()) >= 0) { + if (from > high) + from = cto; + else + from = p[from]; + if (from != lastc || !BITSET(t,from)) { + lastc = from; + ob.putc(from); + } + } + } else { + while ((from = ib.getc()) >= 0){ + if (from > high) + from = cto; + else + from = p[from]; + ob.putc(from); + } + } +} + +translit() +{ + lastc := 0; + high := 0; + while ((from := pfrom.canon()) >= 0) + if (from > high) + high = from; + pfrom.rewind(); + p := array[high+1] of int; + for (i := 0; i <= high; i++) + p[i] = i; + while ((from = pfrom.canon()) >= 0) { + if ((cto := pto.canon()) < 0) + cto = lastc; + else + lastc = cto; + if (BITSET(f,from) && p[from] != cto) + error("ambiguous translation"); + SETBIT(f,from); + p[from] = cto; + SETBIT(t,cto); + } + while ((cto := pto.canon()) >= 0) + SETBIT(t,cto); + if (sflag){ + lastc = MAXRUNE+1; + while ((from = ib.getc()) >= 0) { + if (from <= high) + from = p[from]; + if (from != lastc || !BITSET(t,from)) { + lastc = from; + ob.putc(from); + } + } + + } else { + while ((from = ib.getc()) >= 0) { + if (from <= high) + from = p[from]; + ob.putc(from); + } + } +} + +Pcb.new(s: string): ref Pcb +{ + return ref Pcb(s, len s, 0, -1, -1); +} + +Pcb.rewind(p: self ref Pcb) +{ + p.current = 0; + p.last = p.final = -1; +} + +Pcb.getc(p: self ref Pcb): int +{ + if(p.current >= p.end) + return -1; + s := p.current; + r := p.spec[s++]; + if(r == '\\' && s < p.end){ + n := 0; + if ((r = p.spec[s]) == 'x') { + s++; + for (i := 0; i < 4 && s < p.end; i++) { + p.current = s; + r = p.spec[s++]; + if ('0' <= r && r <= '9') + n = 16*n + r - '0'; + else if ('a' <= r && r <= 'f') + n = 16*n + r - 'a' + 10; + else if ('A' <= r && r <= 'F') + n = 16*n + r - 'A' + 10; + else { + if (i == 0) + return 'x'; + return n; + } + } + r = n; + } else { + for(i := 0; i < 3 && s < p.end; i++) { + p.current = s; + r = p.spec[s++]; + if('0' <= r && r <= '7') + n = 8*n + r - '0'; + else { + if (i == 0) + return r; + return n; + } + } + if(n > 0377) + error("char>0377"); + r = n; + } + } + p.current = s; + return r; +} + +Pcb.canon(p: self ref Pcb): int +{ + if (p.final >= 0) { + if (p.last < p.final) + return ++p.last; + p.final = -1; + } + if (p.current >= p.end) + return -1; + if(p.spec[p.current] == '-' && p.last >= 0 && p.current+1 < p.end){ + p.current++; + r := p.getc(); + if (r < p.last) + error ("Invalid range specification"); + if (r > p.last) { + p.final = r; + return ++p.last; + } + } + r := p.getc(); + p.last = r; + return p.last; +} + +error(s: string) +{ + sys->fprint(stderr, "tr: %s\n", s); + raise "fail: error"; +} diff --git a/appl/cmd/tsort.b b/appl/cmd/tsort.b new file mode 100644 index 00000000..5993fa31 --- /dev/null +++ b/appl/cmd/tsort.b @@ -0,0 +1,133 @@ +implement Tsort; + +# +# tsort -- topological sort +# +# convert a partial ordering into a linear ordering +# +# Copyright © 2004 Vita Nuova Holdings Limited +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +Tsort: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +Item: adt { + name: string; + mark: int; + succ: cyclic list of ref Item; # node's successors + + precede: fn(a: self ref Item, b: ref Item); +}; + +Q: adt { + item: ref Item; + next: cyclic ref Q; +}; + +items, itemt: ref Q; # use a Q not a list only to keep input order +nitem := 0; +bout: ref Iobuf; + +init(nil: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + + bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + input(); + output(); + bout.flush(); +} + +error(s: string) +{ + sys->fprint(sys->fildes(2), "tsort: %s\n", s); + raise "fail:error"; +} + +input() +{ + b := bufio->fopen(sys->fildes(0), Sys->OREAD); + while((line := b.gets('\n')) != nil){ + (nf, fld) := sys->tokenize(line, " \t\n"); + if(fld != nil){ + a := finditem(hd fld); + while((fld = tl fld) != nil) + a.precede(finditem(hd fld)); + } + } +} + +Item.precede(a: self ref Item, b: ref Item) +{ + if(a != b){ + for(l := a.succ; l != nil; l = tl l) + if((hd l) == b) + return; + a.succ = b :: a.succ; + } +} + +finditem(s: string): ref Item +{ + # would use a hash table for large sets + for(il := items; il != nil; il = il.next) + if(il.item.name == s) + return il.item; + i := ref Item; + i.name = s; + i.mark = 0; + if(items != nil) + itemt = itemt.next = ref Q(i, nil); + else + itemt = items = ref Q(i, nil); + nitem++; + return i; +} + +dep: list of ref Item; + +output() +{ + for(k := items; k != nil; k = k.next) + if((q := k.item).mark == 0) + visit(q, nil); + for(; dep != nil; dep = tl dep) + bout.puts((hd dep).name+"\n"); +} + +# visit q's successors depth first +# parents is only used to print any cycles, and since it matches +# the stack, the recursion could be eliminated +visit(q: ref Item, parents: list of ref Item) +{ + q.mark = 2; + parents = q :: parents; + for(sl := q.succ; sl != nil; sl = tl sl) + if((s := hd sl).mark == 0) + visit(s, parents); + else if(s.mark == 2){ + sys->fprint(sys->fildes(2), "tsort: cycle in input\n"); + rl: list of ref Item; + for(l := parents;; l = tl l){ # reverse to be closer to input order + rl = hd l :: rl; + if(hd l == s) + break; + } + for(l = rl; l != nil; l = tl l) + sys->fprint(sys->fildes(2), "tsort: %s\n", (hd l).name); + } + q.mark = 1; + dep = q :: dep; +} diff --git a/appl/cmd/unicode.b b/appl/cmd/unicode.b new file mode 100644 index 00000000..9f1ae3d1 --- /dev/null +++ b/appl/cmd/unicode.b @@ -0,0 +1,162 @@ +implement Unicode; + +include "sys.m"; +sys: Sys; + +include "draw.m"; + +include "string.m"; + str: String; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + + +Unicode: module +{ + init: fn(c: ref Draw->Context, v: list of string); +}; + +usage: con "unicode { [-t] hex hex ... | hexmin-hexmax ... | [-n] char ... }"; +hex: con "0123456789abcdefABCDEF"; +numout:= 0; +text:= 0; +out: ref Bufio->Iobuf; +stderr: ref sys->FD; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + str = load String String->PATH; + bufio = load Bufio Bufio->PATH; + + stderr = sys->fildes(2); + + if(str==nil || bufio==nil){ + sys->fprint(stderr, "unicode: can't load String or Bufio module: %r\n"); + return; + } + + if(argv == nil){ + sys->fprint(stderr, "usage: %s\n", usage); + return; + } + argv = tl argv; + while(argv != nil) { + s := hd argv; + if(s != nil && s[0] != '-') + break; + case s{ + "-n" => + numout = 1; + "-t" => + text = 1; + } + argv = tl argv; + } + if(argv == nil){ + sys->fprint(stderr, "usage: %s\n", usage); + return; + } + + out = bufio->fopen(sys->fildes(1), Bufio->OWRITE); + + if(!numout && oneof(hd argv, '-')) + range(argv); + else if(numout || oneof(hex, (hd argv)[0]) == 0) + nums(argv); + else + chars(argv); + out.flush(); +} + +oneof(s: string, c: int): int +{ + for(i:=0; i<len s; i++) + if(s[i] == c) + return 1; + return 0; +} + +badrange(q: string) +{ + sys->fprint(stderr, "unicode: bad range %s\n", q); +} + +range(argv: list of string) +{ + min, max: int; + + while(argv != nil){ + q := hd argv; + if(oneof(hex, q[0]) == 0){ + badrange(q); + return; + } + (min, q) = str->toint(q,16); + if(min<0 || min>16rFFFF || len q==0 || q[0]!='-'){ + badrange(hd argv); + return; + } + q = q[1:]; + if(oneof(hex, q[0]) == 0){ + badrange(hd argv); + return; + } + (max, q) = str->toint(q,16); + if(max<0 || max>16rFFFF || max<min || len q>0){ + badrange(hd argv); + return; + } + i := 0; + do{ + out.puts(sys->sprint("%.4x %c", min, min)); + i++; + if(min==max || (i&7)==0) + out.puts("\n"); + else + out.puts("\t"); + min++; + }while(min<=max); + argv = tl argv; + } +} + + +nums(argv: list of string) +{ + while(argv != nil){ + q := hd argv; + for(i:=0; i<len q; i++) + out.puts(sys->sprint("%.4x\n", q[i])); + argv = tl argv; + } +} + +badvalue(s: string) +{ + sys->fprint(stderr, "unicode: bad unicode value %s\n", s); +} + +chars(argv: list of string) +{ + m: int; + + while(argv != nil){ + q := hd argv; + if(oneof(hex, q[0]) == 0){ + badvalue(hd argv); + return; + } + (m, q) = str->toint(q, 16); + if(m<0 || m>16rFFFF || len q>0){ + badvalue(hd argv); + return; + } + out.puts(sys->sprint("%c", m)); + if(!text) + out.puts("\n"); + argv = tl argv; + } +} diff --git a/appl/cmd/uniq.b b/appl/cmd/uniq.b new file mode 100644 index 00000000..4442c22f --- /dev/null +++ b/appl/cmd/uniq.b @@ -0,0 +1,79 @@ +implement Uniq; + +include "sys.m"; + sys: Sys; +include "bufio.m"; +include "draw.m"; +include "arg.m"; + +Uniq: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +usage() +{ + fail("usage", sys->sprint("usage: uniq [-ud] [file]")); +} + +init(nil : ref Draw->Context, args : list of string) +{ + bio : ref Bufio->Iobuf; + + sys = load Sys Sys->PATH; + bufio := load Bufio Bufio->PATH; + if (bufio == nil) + fail("bad module", sys->sprint("uniq: cannot load %s: %r", Bufio->PATH)); + Iobuf: import bufio; + arg := load Arg Arg->PATH; + if (arg == nil) + fail("bad module", sys->sprint("uniq: cannot load %s: %r", Arg->PATH)); + + uflag := 0; + dflag := 0; + arg->init(args); + while ((opt := arg->opt()) != 0) { + case opt { + 'u' => + uflag = 1; + 'd' => + dflag = 1; + * => + usage(); + } + } + args = arg->argv(); + if (len args > 1) + usage(); + if (args != nil) { + bio = bufio->open(hd args, Bufio->OREAD); + if (bio == nil) + fail("open file", sys->sprint("uniq: cannot open %s: %r\n", hd args)); + } else + bio = bufio->fopen(sys->fildes(0), Bufio->OREAD); + + stdout := bufio->fopen(sys->fildes(1), Bufio->OWRITE); + if (!(uflag || dflag)) + uflag = dflag = 1; + prev := ""; + n := 0; + while ((s := bio.gets('\n')) != nil) { + if (s == prev) + n++; + else { + if ((uflag && n == 1) || (dflag && n > 1)) + stdout.puts(prev); + n = 1; + prev = s; + } + } + if ((uflag && n == 1) || (dflag && n > 1)) + stdout.puts(prev); + stdout.close(); +} + +fail(ex, msg: string) +{ + sys->fprint(sys->fildes(2), "%s\n", msg); + raise "fail:"+ex; +} diff --git a/appl/cmd/units.b b/appl/cmd/units.b new file mode 100644 index 00000000..b1dffc60 --- /dev/null +++ b/appl/cmd/units.b @@ -0,0 +1,1061 @@ +implement Units; + +#line 2 "units.y" +# +# subject to the Lucent Public License 1.02 +# +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "math.m"; + math: Math; + +include "arg.m"; + +Ndim: con 15; # number of dimensions +Nvar: con 203; # hash table size +Maxe: con 695.0; # log of largest number + +Node: adt +{ + val: real; + dim: array of int; # [Ndim] schar + + mk: fn(v: real): Node; + text: fn(n: self Node): string; + add: fn(a: self Node, b: Node): Node; + sub: fn(a: self Node, b: Node): Node; + mul: fn(a: self Node, b: Node): Node; + div: fn(a: self Node, b: Node): Node; + xpn: fn(a: self Node, b: int): Node; + copy: fn(a: self Node): Node; +}; +Var: adt +{ + name: string; + node: Node; +}; +Prefix: adt +{ + val: real; + pname: string; +}; + +digval := 0; +fi: ref Iobuf; +fund := array[Ndim] of ref Var; +line: string; +lineno := 0; +linep := 0; +nerrors := 0; +peekrune := 0; +retnode1: Node; +retnode2: Node; +retnode: Node; +sym: string; +vars := array[Nvar] of list of ref Var; +vflag := 0; + +YYSTYPE: adt { + node: Node; + var: ref Var; + numb: int; + val: real; +}; + +YYLEX: adt { + lval: YYSTYPE; + lex: fn(l: self ref YYLEX): int; + error: fn(l: self ref YYLEX, msg: string); +}; + +Units: module { + + init: fn(nil: ref Draw->Context, args: list of string); +VAL: con 57346; +VAR: con 57347; +SUP: con 57348; + +}; +YYEOFCODE: con 1; +YYERRCODE: con 2; +YYMAXDEPTH: con 200; + +#line 203 "units.y" + + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + math = load Math Math->PATH; + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("units [-v] [file]"); + while((o := arg->opt()) != 0) + case o { + 'v' => vflag = 1; + * => arg->usage(); + } + args = arg->argv(); + arg = nil; + + file := "/lib/units"; + if(args != nil) + file = hd args; + fi = bufio->open(file, Sys->OREAD); + if(fi == nil) { + sys->fprint(sys->fildes(2), "units: cannot open %s: %r\n", file); + raise "fail:open"; + } + lex := ref YYLEX; + + # + # read the 'units' file to + # develop a database + # + lineno = 0; + for(;;) { + lineno++; + if(readline()) + break; + if(len line == 0 || line[0] == '/') + continue; + peekrune = ':'; + yyparse(lex); + } + + # + # read the console to + # print ratio of pairs + # + fi = bufio->fopen(sys->fildes(0), Sys->OREAD); + lineno = 0; + for(;;) { + if(lineno & 1) + sys->print("you want: "); + else + sys->print("you have: "); + if(readline()) + break; + peekrune = '?'; + nerrors = 0; + yyparse(lex); + if(nerrors) + continue; + if(lineno & 1) { + isspcl: int; + (isspcl, retnode) = specialcase(retnode2, retnode1); + if(isspcl) + sys->print("\tis %s\n", retnode.text()); + else { + retnode = retnode2.div(retnode1); + sys->print("\t* %s\n", retnode.text()); + retnode = retnode1.div(retnode2); + sys->print("\t/ %s\n", retnode.text()); + } + } else + retnode2 = retnode1.copy(); + lineno++; + } + sys->print("\n"); +} + +YYLEX.lex(lex: self ref YYLEX): int +{ + c := peekrune; + peekrune = ' '; + + while(c == ' ' || c == '\t'){ + if(linep >= len line) + return 0; # -1? + c = line[linep++]; + } + case c { + '0' to '9' or '.' => + digval = c; + (lex.lval.val, peekrune) = readreal(gdigit, lex); + return VAL; + '×' => + return '*'; + '÷' => + return '/'; + '¹' or + 'ⁱ' => + lex.lval.numb = 1; + return SUP; + '²' or + '' => + lex.lval.numb = 2; + return SUP; + '³' or + '' => + lex.lval.numb = 3; + return SUP; + * => + if(ralpha(c)){ + sym = ""; + for(i:=0;; i++) { + sym[i] = c; + if(linep >= len line){ + c = ' '; + break; + } + c = line[linep++]; + if(!ralpha(c)) + break; + } + peekrune = c; + lex.lval.var = lookup(0); + return VAR; + } + } + return c; +} + +# +# all characters that have some +# meaning. rest are usable as names +# +ralpha(c: int): int +{ + case c { + 0 or + '+' or + '-' or + '*' or + '/' or + '[' or + ']' or + '(' or + ')' or + '^' or + ':' or + '?' or + ' ' or + '\t' or + '.' or + '|' or + '#' or + '¹' or + 'ⁱ' or + '²' or + '' or + '³' or + '' or + '×' or + '÷' => + return 0; + } + return 1; +} + +gdigit(nil: ref YYLEX): int +{ + c := digval; + if(c) { + digval = 0; + return c; + } + if(linep >= len line) + return 0; + return line[linep++]; +} + +YYLEX.error(lex: self ref YYLEX, s: string) +{ + # + # hack to intercept message from yaccpar + # + if(s == "syntax error") { + lex.error(sys->sprint("syntax error, last name: %s", sym)); + return; + } + sys->print("%d: %s\n\t%s\n", lineno, line, s); + nerrors++; + if(nerrors > 5) { + sys->print("too many errors\n"); + raise "fail:errors"; + } +} + +yyerror(s: string) +{ + l := ref YYLEX; + l.error(s); +} + +Node.mk(v: real): Node +{ + return (v, array[Ndim] of {* => 0}); +} + +Node.add(a: self Node, b: Node): Node +{ + c := Node.mk(fadd(a.val, b.val)); + for(i:=0; i<Ndim; i++) { + d := a.dim[i]; + c.dim[i] = d; + if(d != b.dim[i]) + yyerror("add must be like units"); + } + return c; +} + +Node.sub(a: self Node, b: Node): Node +{ + c := Node.mk(fadd(a.val, -b.val)); + for(i:=0; i<Ndim; i++) { + d := a.dim[i]; + c.dim[i] = d; + if(d != b.dim[i]) + yyerror("sub must be like units"); + } + return c; +} + +Node.mul(a: self Node, b: Node): Node +{ + c := Node.mk(fmul(a.val, b.val)); + for(i:=0; i<Ndim; i++) + c.dim[i] = a.dim[i] + b.dim[i]; + return c; +} + +Node.div(a: self Node, b: Node): Node +{ + c := Node.mk(fdiv(a.val, b.val)); + for(i:=0; i<Ndim; i++) + c.dim[i] = a.dim[i] - b.dim[i]; + return c; +} + +Node.xpn(a: self Node, b: int): Node +{ + c := Node.mk(1.0); + if(b < 0) { + b = -b; + for(i:=0; i<b; i++) + c = c.div(a); + } else + for(i:=0; i<b; i++) + c = c.mul(a); + return c; +} + +Node.copy(a: self Node): Node +{ + c := Node.mk(a.val); + c.dim[0:] = a.dim; + return c; +} + +specialcase(a, b: Node): (int, Node) +{ + c := Node.mk(0.0); + d1 := 0; + d2 := 0; + for(i:=1; i<Ndim; i++) { + d := a.dim[i]; + if(d) { + if(d != 1 || d1) + return (0, c); + d1 = i; + } + d = b.dim[i]; + if(d) { + if(d != 1 || d2) + return (0, c); + d2 = i; + } + } + if(d1 == 0 || d2 == 0) + return (0, c); + + if(fund[d1].name == "°C" && + fund[d2].name == "°F" && + b.val == 1.0) { + c = b.copy(); + c.val = a.val * 9. / 5. + 32.; + return (1, c); + } + + if(fund[d1].name == "°F" && + fund[d2].name == "°C" && + b.val == 1.0) { + c = b.copy(); + c.val = (a.val - 32.) * 5. / 9.; + return (1, c); + } + return (0, c); +} + +printdim(d: int, n: int): string +{ + s := ""; + if(n) { + v := fund[d]; + if(v != nil) + s += " "+v.name; + else + s += sys->sprint(" [%d]", d); + case n { + 1 => + ; + 2 => + s += "²"; + 3 => + s += "³"; + 4 => + s += "⁴"; + * => + s += sys->sprint("^%d", n); + } + } + return s; +} + +Node.text(n: self Node): string +{ + str := sys->sprint("%.7g", n.val); + f := 0; + for(i:=1; i<len n.dim; i++) { + d := n.dim[i]; + if(d > 0) + str += printdim(i, d); + else if(d < 0) + f = 1; + } + + if(f) { + str += " /"; + for(i=1; i<len n.dim; i++) { + d := n.dim[i]; + if(d < 0) + str += printdim(i, -d); + } + } + + return str; +} + +readline(): int +{ + linep = 0; + line = ""; + for(i:=0;; i++) { + c := fi.getc(); + if(c < 0) + return 1; + if(c == '\n') + return 0; + line[i] = c; + } +} + +lookup(f: int): ref Var +{ + h := 0; + for(i:=0; i < len sym; i++) + h = h*13 + sym[i]; + if(h < 0) + h ^= int 16r80000000; + h %= len vars; + + for(vl:=vars[h]; vl != nil; vl = tl vl) + if((hd vl).name == sym) + return hd vl; + if(f) + return nil; + v := ref Var(sym, Node.mk(0.0)); + vars[h] = v :: vars[h]; + + p := 1.0; + for(;;) { + p = fmul(p, pname()); + if(p == 0.0) + break; + w := lookup(1); + if(w != nil) { + v.node = w.node.copy(); + v.node.val = fmul(v.node.val, p); + break; + } + } + return v; +} + +prefix: array of Prefix = array[] of { + (1e-24, "yocto"), + (1e-21, "zepto"), + (1e-18, "atto"), + (1e-15, "femto"), + (1e-12, "pico"), + (1e-9, "nano"), + (1e-6, "micro"), + (1e-6, "μ"), + (1e-3, "milli"), + (1e-2, "centi"), + (1e-1, "deci"), + (1e1, "deka"), + (1e2, "hecta"), + (1e2, "hecto"), + (1e3, "kilo"), + (1e6, "mega"), + (1e6, "meg"), + (1e9, "giga"), + (1e12, "tera"), + (1e15, "peta"), + (1e18, "exa"), + (1e21, "zetta"), + (1e24, "yotta") +}; + +pname(): real +{ + # + # rip off normal prefices + # +Pref: + for(i:=0; i < len prefix; i++) { + p := prefix[i].pname; + for(j:=0; j < len p; j++) + if(j >= len sym || p[j] != sym[j]) + continue Pref; + sym = sym[j:]; + return prefix[i].val; + } + + # + # rip off 's' suffixes + # + for(j:=0; j < len sym; j++) + ; + j--; + # j>1 is special hack to disallow ms finding m + if(j > 1 && sym[j] == 's') { + sym = sym[0:j]; + return 1.0; + } + return 0.0; +} + +# +# reads a floating-point number +# + +readreal[T](f: ref fn(t: T): int, vp: T): (real, int) +{ + s := ""; + c := f(vp); + while(c == ' ' || c == '\t') + c = f(vp); + if(c == '-' || c == '+'){ + s[len s] = c; + c = f(vp); + } + start := len s; + while(c >= '0' && c <= '9'){ + s[len s] = c; + c = f(vp); + } + if(c == '.'){ + s[len s] = c; + c = f(vp); + while(c >= '0' && c <= '9'){ + s[len s] = c; + c = f(vp); + } + } + if(len s > start && (c == 'e' || c == 'E')){ + s[len s] = c; + c = f(vp); + if(c == '-' || c == '+'){ + s[len s] = c; + c = f(vp); + } + while(c >= '0' && c <= '9'){ + s[len s] = c; + c = f(vp); + } + } + return (real s, c); +} + +# +# careful floating point +# + +fmul(a, b: real): real +{ + l: real; + + if(a <= 0.0) { + if(a == 0.0) + return 0.0; + l = math->log(-a); + } else + l = math->log(a); + + if(b <= 0.0) { + if(b == 0.0) + return 0.0; + l += math->log(-b); + } else + l += math->log(b); + + if(l > Maxe) { + yyerror("overflow in multiply"); + return 1.0; + } + if(l < -Maxe) { + yyerror("underflow in multiply"); + return 0.0; + } + return a*b; +} + +fdiv(a, b: real): real +{ + l: real; + + if(a <= 0.0) { + if(a == 0.0) + return 0.0; + l = math->log(-a); + } else + l = math->log(a); + + if(b <= 0.0) { + if(b == 0.0) { + yyerror("division by zero"); + return 1.0; + } + l -= math->log(-b); + } else + l -= math->log(b); + + if(l > Maxe) { + yyerror("overflow in divide"); + return 1.0; + } + if(l < -Maxe) { + yyerror("underflow in divide"); + return 0.0; + } + return a/b; +} + +fadd(a, b: real): real +{ + return a + b; +} +yyexca := array[] of {-1, 1, + 1, -1, + -2, 0, +}; +YYNPROD: con 21; +YYPRIVATE: con 57344; +yytoknames: array of string; +yystates: array of string; +yydebug: con 0; +YYLAST: con 41; +yyact := array[] of { + 8, 10, 7, 9, 16, 17, 12, 11, 20, 21, + 15, 31, 23, 6, 4, 12, 11, 22, 13, 5, + 1, 27, 28, 0, 14, 30, 29, 13, 20, 20, + 25, 26, 0, 24, 18, 19, 16, 17, 2, 0, + 3, +}; +yypact := array[] of { + 31,-1000, 9, 11, 2, 26, 22, 11, 3, -3, +-1000,-1000,-1000, 11, 26,-1000, 11, 11, 11, 11, + 3,-1000, 11, 11, -6, 22, 22, 11, 11, -3, +-1000,-1000, +}; +yypgo := array[] of { + 0, 20, 19, 1, 3, 0, 2, 13, +}; +yyr1 := array[] of { + 0, 1, 1, 1, 1, 2, 2, 2, 7, 7, + 7, 6, 6, 5, 5, 5, 4, 4, 3, 3, + 3, +}; +yyr2 := array[] of { + 0, 3, 3, 2, 1, 1, 3, 3, 1, 3, + 3, 1, 2, 1, 2, 3, 1, 3, 1, 1, + 3, +}; +yychk := array[] of { +-1000, -1, 7, 9, 5, -2, -7, -6, -5, -4, + -3, 5, 4, 16, -2, 8, 10, 11, 12, 13, + -5, 6, 14, 15, -2, -7, -7, -6, -6, -4, + -3, 17, +}; +yydef := array[] of { + 0, -2, 0, 4, 0, 3, 5, 8, 11, 13, + 16, 18, 19, 0, 1, 2, 0, 0, 0, 0, + 12, 14, 0, 0, 0, 6, 7, 9, 10, 15, + 17, 20, +}; +yytok1 := array[] of { + 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 8, 3, 3, 3, 3, + 16, 17, 12, 10, 3, 11, 3, 13, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, + 3, 3, 3, 9, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 14, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 15, +}; +yytok2 := array[] of { + 2, 3, 4, 5, 6, +}; +yytok3 := array[] of { + 0 +}; + +YYSys: module +{ + FD: adt + { + fd: int; + }; + fildes: fn(fd: int): ref FD; + fprint: fn(fd: ref FD, s: string, *): int; +}; + +yysys: YYSys; +yystderr: ref YYSys->FD; + +YYFLAG: con -1000; + +# parser for yacc output + +yytokname(yyc: int): string +{ + if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil) + return yytoknames[yyc-1]; + return "<"+string yyc+">"; +} + +yystatname(yys: int): string +{ + if(yys >= 0 && yys < len yystates && yystates[yys] != nil) + return yystates[yys]; + return "<"+string yys+">\n"; +} + +yylex1(yylex: ref YYLEX): int +{ + c : int; + yychar := yylex.lex(); + if(yychar <= 0) + c = yytok1[0]; + else if(yychar < len yytok1) + c = yytok1[yychar]; + else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2) + c = yytok2[yychar-YYPRIVATE]; + else{ + n := len yytok3; + c = 0; + for(i := 0; i < n; i+=2) { + if(yytok3[i+0] == yychar) { + c = yytok3[i+1]; + break; + } + } + if(c == 0) + c = yytok2[1]; # unknown char + } + if(yydebug >= 3) + yysys->fprint(yystderr, "lex %.4ux %s\n", yychar, yytokname(c)); + return c; +} + +YYS: adt +{ + yyv: YYSTYPE; + yys: int; +}; + +yyparse(yylex: ref YYLEX): int +{ + if(yydebug >= 1 && yysys == nil) { + yysys = load YYSys "$Sys"; + yystderr = yysys->fildes(2); + } + + yys := array[YYMAXDEPTH] of YYS; + + yyval: YYSTYPE; + yystate := 0; + yychar := -1; + yynerrs := 0; # number of errors + yyerrflag := 0; # error recovery flag + yyp := -1; + yyn := 0; + +yystack: + for(;;){ + # put a state and value onto the stack + if(yydebug >= 4) + yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + + yyp++; + if(yyp >= len yys) + yys = (array[len yys * 2] of YYS)[0:] = yys; + yys[yyp].yys = yystate; + yys[yyp].yyv = yyval; + + for(;;){ + yyn = yypact[yystate]; + if(yyn > YYFLAG) { # simple state + if(yychar < 0) + yychar = yylex1(yylex); + yyn += yychar; + if(yyn >= 0 && yyn < YYLAST) { + yyn = yyact[yyn]; + if(yychk[yyn] == yychar) { # valid shift + yychar = -1; + yyp++; + if(yyp >= len yys) + yys = (array[len yys * 2] of YYS)[0:] = yys; + yystate = yyn; + yys[yyp].yys = yystate; + yys[yyp].yyv = yylex.lval; + if(yyerrflag > 0) + yyerrflag--; + if(yydebug >= 4) + yysys->fprint(yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate)); + continue; + } + } + } + + # default state action + yyn = yydef[yystate]; + if(yyn == -2) { + if(yychar < 0) + yychar = yylex1(yylex); + + # look through exception table + for(yyxi:=0;; yyxi+=2) + if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate) + break; + for(yyxi += 2;; yyxi += 2) { + yyn = yyexca[yyxi]; + if(yyn < 0 || yyn == yychar) + break; + } + yyn = yyexca[yyxi+1]; + if(yyn < 0){ + yyn = 0; + break yystack; + } + } + + if(yyn != 0) + break; + + # error ... attempt to resume parsing + if(yyerrflag == 0) { # brand new error + yylex.error("syntax error"); + yynerrs++; + if(yydebug >= 1) { + yysys->fprint(yystderr, "%s", yystatname(yystate)); + yysys->fprint(yystderr, "saw %s\n", yytokname(yychar)); + } + } + + if(yyerrflag != 3) { # incompletely recovered error ... try again + yyerrflag = 3; + + # find a state where "error" is a legal shift action + while(yyp >= 0) { + yyn = yypact[yys[yyp].yys] + YYERRCODE; + if(yyn >= 0 && yyn < YYLAST) { + yystate = yyact[yyn]; # simulate a shift of "error" + if(yychk[yystate] == YYERRCODE) + continue yystack; + } + + # the current yyp has no shift onn "error", pop stack + if(yydebug >= 2) + yysys->fprint(yystderr, "error recovery pops state %d, uncovers %d\n", + yys[yyp].yys, yys[yyp-1].yys ); + yyp--; + } + # there is no state on the stack with an error shift ... abort + yyn = 1; + break yystack; + } + + # no shift yet; clobber input char + if(yydebug >= 2) + yysys->fprint(yystderr, "error recovery discards %s\n", yytokname(yychar)); + if(yychar == YYEOFCODE) { + yyn = 1; + break yystack; + } + yychar = -1; + # try again in the same state + } + + # reduction by production yyn + if(yydebug >= 2) + yysys->fprint(yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate)); + + yypt := yyp; + yyp -= yyr2[yyn]; +# yyval = yys[yyp+1].yyv; + yym := yyn; + + # consult goto table to find next state + yyn = yyr1[yyn]; + yyg := yypgo[yyn]; + yyj := yyg + yys[yyp].yys + 1; + + if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn) + yystate = yyact[yyg]; + case yym { + +1=> +#line 90 "units.y" +{ + f := yys[yypt-1].yyv.var.node.dim[0]; + yys[yypt-1].yyv.var.node = yys[yypt-0].yyv.node.copy(); + yys[yypt-1].yyv.var.node.dim[0] = 1; + if(f) + yyerror(sys->sprint("redefinition of %s", yys[yypt-1].yyv.var.name)); + else if(vflag) + sys->print("%s\t%s\n", yys[yypt-1].yyv.var.name, yys[yypt-1].yyv.var.node.text()); + } +2=> +#line 100 "units.y" +{ + for(i:=1; i<Ndim; i++) + if(fund[i] == nil) + break; + if(i >= Ndim) { + yyerror("too many dimensions"); + i = Ndim-1; + } + fund[i] = yys[yypt-1].yyv.var; + + f := yys[yypt-1].yyv.var.node.dim[0]; + yys[yypt-1].yyv.var.node = Node.mk(1.0); + yys[yypt-1].yyv.var.node.dim[0] = 1; + yys[yypt-1].yyv.var.node.dim[i] = 1; + if(f) + yyerror(sys->sprint("redefinition of %s", yys[yypt-1].yyv.var.name)); + else if(vflag) + sys->print("%s\t#\n", yys[yypt-1].yyv.var.name); + } +3=> +#line 120 "units.y" +{ + retnode1 = yys[yypt-0].yyv.node.copy(); + } +4=> +#line 124 "units.y" +{ + retnode1 = Node.mk(1.0); + } +5=> +yyval.node = yys[yyp+1].yyv.node; +6=> +#line 131 "units.y" +{ + yyval.node = yys[yypt-2].yyv.node.add(yys[yypt-0].yyv.node); + } +7=> +#line 135 "units.y" +{ + yyval.node = yys[yypt-2].yyv.node.sub(yys[yypt-0].yyv.node); + } +8=> +yyval.node = yys[yyp+1].yyv.node; +9=> +#line 142 "units.y" +{ + yyval.node = yys[yypt-2].yyv.node.mul(yys[yypt-0].yyv.node); + } +10=> +#line 146 "units.y" +{ + yyval.node = yys[yypt-2].yyv.node.div(yys[yypt-0].yyv.node); + } +11=> +yyval.node = yys[yyp+1].yyv.node; +12=> +#line 153 "units.y" +{ + yyval.node = yys[yypt-1].yyv.node.mul(yys[yypt-0].yyv.node); + } +13=> +yyval.node = yys[yyp+1].yyv.node; +14=> +#line 160 "units.y" +{ + yyval.node = yys[yypt-1].yyv.node.xpn(yys[yypt-0].yyv.numb); + } +15=> +#line 164 "units.y" +{ + for(i:=1; i<Ndim; i++) + if(yys[yypt-0].yyv.node.dim[i]) { + yyerror("exponent has units"); + yyval.node = yys[yypt-2].yyv.node; + break; + } + if(i >= Ndim) { + i = int yys[yypt-0].yyv.node.val; + if(real i != yys[yypt-0].yyv.node.val) + yyerror("exponent not integral"); + yyval.node = yys[yypt-2].yyv.node.xpn(i); + } + } +16=> +yyval.node = yys[yyp+1].yyv.node; +17=> +#line 182 "units.y" +{ + yyval.node = yys[yypt-2].yyv.node.div(yys[yypt-0].yyv.node); + } +18=> +#line 188 "units.y" +{ + if(yys[yypt-0].yyv.var.node.dim[0] == 0) { + yyerror(sys->sprint("undefined %s", yys[yypt-0].yyv.var.name)); + yyval.node = Node.mk(1.0); + } else + yyval.node = yys[yypt-0].yyv.var.node.copy(); + } +19=> +#line 196 "units.y" +{ + yyval.node = Node.mk(yys[yypt-0].yyv.val); + } +20=> +#line 200 "units.y" +{ + yyval.node = yys[yypt-1].yyv.node; + } + } + } + + return yyn; +} diff --git a/appl/cmd/units.y b/appl/cmd/units.y new file mode 100644 index 00000000..70284868 --- /dev/null +++ b/appl/cmd/units.y @@ -0,0 +1,771 @@ +%{ +# +# subject to the Lucent Public License 1.02 +# +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "math.m"; + math: Math; + +include "arg.m"; + +Ndim: con 15; # number of dimensions +Nvar: con 203; # hash table size +Maxe: con 695.0; # log of largest number + +Node: adt +{ + val: real; + dim: array of int; # [Ndim] schar + + mk: fn(v: real): Node; + text: fn(n: self Node): string; + add: fn(a: self Node, b: Node): Node; + sub: fn(a: self Node, b: Node): Node; + mul: fn(a: self Node, b: Node): Node; + div: fn(a: self Node, b: Node): Node; + xpn: fn(a: self Node, b: int): Node; + copy: fn(a: self Node): Node; +}; +Var: adt +{ + name: string; + node: Node; +}; +Prefix: adt +{ + val: real; + pname: string; +}; + +digval := 0; +fi: ref Iobuf; +fund := array[Ndim] of ref Var; +line: string; +lineno := 0; +linep := 0; +nerrors := 0; +peekrune := 0; +retnode1: Node; +retnode2: Node; +retnode: Node; +sym: string; +vars := array[Nvar] of list of ref Var; +vflag := 0; + +YYSTYPE: adt { + node: Node; + var: ref Var; + numb: int; + val: real; +}; + +YYLEX: adt { + lval: YYSTYPE; + lex: fn(l: self ref YYLEX): int; + error: fn(l: self ref YYLEX, msg: string); +}; + +%} +%module Units +{ + init: fn(nil: ref Draw->Context, args: list of string); +} + +%type <node> prog expr expr0 expr1 expr2 expr3 expr4 + +%token <val> VAL +%token <var> VAR +%token <numb> SUP +%% +prog: + ':' VAR expr + { + f := $2.node.dim[0]; + $2.node = $3.copy(); + $2.node.dim[0] = 1; + if(f) + yyerror(sys->sprint("redefinition of %s", $2.name)); + else if(vflag) + sys->print("%s\t%s\n", $2.name, $2.node.text()); + } +| ':' VAR '#' + { + for(i:=1; i<Ndim; i++) + if(fund[i] == nil) + break; + if(i >= Ndim) { + yyerror("too many dimensions"); + i = Ndim-1; + } + fund[i] = $2; + + f := $2.node.dim[0]; + $2.node = Node.mk(1.0); + $2.node.dim[0] = 1; + $2.node.dim[i] = 1; + if(f) + yyerror(sys->sprint("redefinition of %s", $2.name)); + else if(vflag) + sys->print("%s\t#\n", $2.name); + } +| '?' expr + { + retnode1 = $2.copy(); + } +| '?' + { + retnode1 = Node.mk(1.0); + } + +expr: + expr4 +| expr '+' expr4 + { + $$ = $1.add($3); + } +| expr '-' expr4 + { + $$ = $1.sub($3); + } + +expr4: + expr3 +| expr4 '*' expr3 + { + $$ = $1.mul($3); + } +| expr4 '/' expr3 + { + $$ = $1.div($3); + } + +expr3: + expr2 +| expr3 expr2 + { + $$ = $1.mul($2); + } + +expr2: + expr1 +| expr2 SUP + { + $$ = $1.xpn($2); + } +| expr2 '^' expr1 + { + for(i:=1; i<Ndim; i++) + if($3.dim[i]) { + yyerror("exponent has units"); + $$ = $1; + break; + } + if(i >= Ndim) { + i = int $3.val; + if(real i != $3.val) + yyerror("exponent not integral"); + $$ = $1.xpn(i); + } + } + +expr1: + expr0 +| expr1 '|' expr0 + { + $$ = $1.div($3); + } + +expr0: + VAR + { + if($1.node.dim[0] == 0) { + yyerror(sys->sprint("undefined %s", $1.name)); + $$ = Node.mk(1.0); + } else + $$ = $1.node.copy(); + } +| VAL + { + $$ = Node.mk($1); + } +| '(' expr ')' + { + $$ = $2; + } +%% + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + math = load Math Math->PATH; + + arg := load Arg Arg->PATH; + arg->init(args); + arg->setusage("units [-v] [file]"); + while((o := arg->opt()) != 0) + case o { + 'v' => vflag = 1; + * => arg->usage(); + } + args = arg->argv(); + arg = nil; + + file := "/lib/units"; + if(args != nil) + file = hd args; + fi = bufio->open(file, Sys->OREAD); + if(fi == nil) { + sys->fprint(sys->fildes(2), "units: cannot open %s: %r\n", file); + raise "fail:open"; + } + lex := ref YYLEX; + + # + # read the 'units' file to + # develop a database + # + lineno = 0; + for(;;) { + lineno++; + if(readline()) + break; + if(len line == 0 || line[0] == '/') + continue; + peekrune = ':'; + yyparse(lex); + } + + # + # read the console to + # print ratio of pairs + # + fi = bufio->fopen(sys->fildes(0), Sys->OREAD); + lineno = 0; + for(;;) { + if(lineno & 1) + sys->print("you want: "); + else + sys->print("you have: "); + if(readline()) + break; + peekrune = '?'; + nerrors = 0; + yyparse(lex); + if(nerrors) + continue; + if(lineno & 1) { + isspcl: int; + (isspcl, retnode) = specialcase(retnode2, retnode1); + if(isspcl) + sys->print("\tis %s\n", retnode.text()); + else { + retnode = retnode2.div(retnode1); + sys->print("\t* %s\n", retnode.text()); + retnode = retnode1.div(retnode2); + sys->print("\t/ %s\n", retnode.text()); + } + } else + retnode2 = retnode1.copy(); + lineno++; + } + sys->print("\n"); +} + +YYLEX.lex(lex: self ref YYLEX): int +{ + c := peekrune; + peekrune = ' '; + + while(c == ' ' || c == '\t'){ + if(linep >= len line) + return 0; # -1? + c = line[linep++]; + } + case c { + '0' to '9' or '.' => + digval = c; + (lex.lval.val, peekrune) = readreal(gdigit, lex); + return VAL; + '×' => + return '*'; + '÷' => + return '/'; + '¹' or + 'ⁱ' => + lex.lval.numb = 1; + return SUP; + '²' or + '' => + lex.lval.numb = 2; + return SUP; + '³' or + '' => + lex.lval.numb = 3; + return SUP; + * => + if(ralpha(c)){ + sym = ""; + for(i:=0;; i++) { + sym[i] = c; + if(linep >= len line){ + c = ' '; + break; + } + c = line[linep++]; + if(!ralpha(c)) + break; + } + peekrune = c; + lex.lval.var = lookup(0); + return VAR; + } + } + return c; +} + +# +# all characters that have some +# meaning. rest are usable as names +# +ralpha(c: int): int +{ + case c { + 0 or + '+' or + '-' or + '*' or + '/' or + '[' or + ']' or + '(' or + ')' or + '^' or + ':' or + '?' or + ' ' or + '\t' or + '.' or + '|' or + '#' or + '¹' or + 'ⁱ' or + '²' or + '' or + '³' or + '' or + '×' or + '÷' => + return 0; + } + return 1; +} + +gdigit(nil: ref YYLEX): int +{ + c := digval; + if(c) { + digval = 0; + return c; + } + if(linep >= len line) + return 0; + return line[linep++]; +} + +YYLEX.error(lex: self ref YYLEX, s: string) +{ + # + # hack to intercept message from yaccpar + # + if(s == "syntax error") { + lex.error(sys->sprint("syntax error, last name: %s", sym)); + return; + } + sys->print("%d: %s\n\t%s\n", lineno, line, s); + nerrors++; + if(nerrors > 5) { + sys->print("too many errors\n"); + raise "fail:errors"; + } +} + +yyerror(s: string) +{ + l := ref YYLEX; + l.error(s); +} + +Node.mk(v: real): Node +{ + return (v, array[Ndim] of {* => 0}); +} + +Node.add(a: self Node, b: Node): Node +{ + c := Node.mk(fadd(a.val, b.val)); + for(i:=0; i<Ndim; i++) { + d := a.dim[i]; + c.dim[i] = d; + if(d != b.dim[i]) + yyerror("add must be like units"); + } + return c; +} + +Node.sub(a: self Node, b: Node): Node +{ + c := Node.mk(fadd(a.val, -b.val)); + for(i:=0; i<Ndim; i++) { + d := a.dim[i]; + c.dim[i] = d; + if(d != b.dim[i]) + yyerror("sub must be like units"); + } + return c; +} + +Node.mul(a: self Node, b: Node): Node +{ + c := Node.mk(fmul(a.val, b.val)); + for(i:=0; i<Ndim; i++) + c.dim[i] = a.dim[i] + b.dim[i]; + return c; +} + +Node.div(a: self Node, b: Node): Node +{ + c := Node.mk(fdiv(a.val, b.val)); + for(i:=0; i<Ndim; i++) + c.dim[i] = a.dim[i] - b.dim[i]; + return c; +} + +Node.xpn(a: self Node, b: int): Node +{ + c := Node.mk(1.0); + if(b < 0) { + b = -b; + for(i:=0; i<b; i++) + c = c.div(a); + } else + for(i:=0; i<b; i++) + c = c.mul(a); + return c; +} + +Node.copy(a: self Node): Node +{ + c := Node.mk(a.val); + c.dim[0:] = a.dim; + return c; +} + +specialcase(a, b: Node): (int, Node) +{ + c := Node.mk(0.0); + d1 := 0; + d2 := 0; + for(i:=1; i<Ndim; i++) { + d := a.dim[i]; + if(d) { + if(d != 1 || d1) + return (0, c); + d1 = i; + } + d = b.dim[i]; + if(d) { + if(d != 1 || d2) + return (0, c); + d2 = i; + } + } + if(d1 == 0 || d2 == 0) + return (0, c); + + if(fund[d1].name == "°C" && + fund[d2].name == "°F" && + b.val == 1.0) { + c = b.copy(); + c.val = a.val * 9. / 5. + 32.; + return (1, c); + } + + if(fund[d1].name == "°F" && + fund[d2].name == "°C" && + b.val == 1.0) { + c = b.copy(); + c.val = (a.val - 32.) * 5. / 9.; + return (1, c); + } + return (0, c); +} + +printdim(d: int, n: int): string +{ + s := ""; + if(n) { + v := fund[d]; + if(v != nil) + s += " "+v.name; + else + s += sys->sprint(" [%d]", d); + case n { + 1 => + ; + 2 => + s += "²"; + 3 => + s += "³"; + 4 => + s += "⁴"; + * => + s += sys->sprint("^%d", n); + } + } + return s; +} + +Node.text(n: self Node): string +{ + str := sys->sprint("%.7g", n.val); + f := 0; + for(i:=1; i<len n.dim; i++) { + d := n.dim[i]; + if(d > 0) + str += printdim(i, d); + else if(d < 0) + f = 1; + } + + if(f) { + str += " /"; + for(i=1; i<len n.dim; i++) { + d := n.dim[i]; + if(d < 0) + str += printdim(i, -d); + } + } + + return str; +} + +readline(): int +{ + linep = 0; + line = ""; + for(i:=0;; i++) { + c := fi.getc(); + if(c < 0) + return 1; + if(c == '\n') + return 0; + line[i] = c; + } +} + +lookup(f: int): ref Var +{ + h := 0; + for(i:=0; i < len sym; i++) + h = h*13 + sym[i]; + if(h < 0) + h ^= int 16r80000000; + h %= len vars; + + for(vl:=vars[h]; vl != nil; vl = tl vl) + if((hd vl).name == sym) + return hd vl; + if(f) + return nil; + v := ref Var(sym, Node.mk(0.0)); + vars[h] = v :: vars[h]; + + p := 1.0; + for(;;) { + p = fmul(p, pname()); + if(p == 0.0) + break; + w := lookup(1); + if(w != nil) { + v.node = w.node.copy(); + v.node.val = fmul(v.node.val, p); + break; + } + } + return v; +} + +prefix: array of Prefix = array[] of { + (1e-24, "yocto"), + (1e-21, "zepto"), + (1e-18, "atto"), + (1e-15, "femto"), + (1e-12, "pico"), + (1e-9, "nano"), + (1e-6, "micro"), + (1e-6, "μ"), + (1e-3, "milli"), + (1e-2, "centi"), + (1e-1, "deci"), + (1e1, "deka"), + (1e2, "hecta"), + (1e2, "hecto"), + (1e3, "kilo"), + (1e6, "mega"), + (1e6, "meg"), + (1e9, "giga"), + (1e12, "tera"), + (1e15, "peta"), + (1e18, "exa"), + (1e21, "zetta"), + (1e24, "yotta") +}; + +pname(): real +{ + # + # rip off normal prefices + # +Pref: + for(i:=0; i < len prefix; i++) { + p := prefix[i].pname; + for(j:=0; j < len p; j++) + if(j >= len sym || p[j] != sym[j]) + continue Pref; + sym = sym[j:]; + return prefix[i].val; + } + + # + # rip off 's' suffixes + # + for(j:=0; j < len sym; j++) + ; + j--; + # j>1 is special hack to disallow ms finding m + if(j > 1 && sym[j] == 's') { + sym = sym[0:j]; + return 1.0; + } + return 0.0; +} + +# +# reads a floating-point number +# + +readreal[T](f: ref fn(t: T): int, vp: T): (real, int) +{ + s := ""; + c := f(vp); + while(c == ' ' || c == '\t') + c = f(vp); + if(c == '-' || c == '+'){ + s[len s] = c; + c = f(vp); + } + start := len s; + while(c >= '0' && c <= '9'){ + s[len s] = c; + c = f(vp); + } + if(c == '.'){ + s[len s] = c; + c = f(vp); + while(c >= '0' && c <= '9'){ + s[len s] = c; + c = f(vp); + } + } + if(len s > start && (c == 'e' || c == 'E')){ + s[len s] = c; + c = f(vp); + if(c == '-' || c == '+'){ + s[len s] = c; + c = f(vp); + } + while(c >= '0' && c <= '9'){ + s[len s] = c; + c = f(vp); + } + } + return (real s, c); +} + +# +# careful floating point +# + +fmul(a, b: real): real +{ + l: real; + + if(a <= 0.0) { + if(a == 0.0) + return 0.0; + l = math->log(-a); + } else + l = math->log(a); + + if(b <= 0.0) { + if(b == 0.0) + return 0.0; + l += math->log(-b); + } else + l += math->log(b); + + if(l > Maxe) { + yyerror("overflow in multiply"); + return 1.0; + } + if(l < -Maxe) { + yyerror("underflow in multiply"); + return 0.0; + } + return a*b; +} + +fdiv(a, b: real): real +{ + l: real; + + if(a <= 0.0) { + if(a == 0.0) + return 0.0; + l = math->log(-a); + } else + l = math->log(a); + + if(b <= 0.0) { + if(b == 0.0) { + yyerror("division by zero"); + return 1.0; + } + l -= math->log(-b); + } else + l -= math->log(b); + + if(l > Maxe) { + yyerror("overflow in divide"); + return 1.0; + } + if(l < -Maxe) { + yyerror("underflow in divide"); + return 0.0; + } + return a/b; +} + +fadd(a, b: real): real +{ + return a + b; +} diff --git a/appl/cmd/unmount.b b/appl/cmd/unmount.b new file mode 100644 index 00000000..7be037c4 --- /dev/null +++ b/appl/cmd/unmount.b @@ -0,0 +1,44 @@ +implement Unmount; + +include "sys.m"; +include "draw.m"; + +FD: import Sys; +Context: import Draw; + +Unmount: module +{ + init: fn(ctxt: ref Context, argv: list of string); +}; + +sys: Sys; +stderr: ref FD; + +usage() +{ + sys->fprint(stderr, "Usage: unmount [source] target\n"); +} + +init(nil: ref Context, argv: list of string) +{ + r: int; + + sys = load Sys Sys->PATH; + + stderr = sys->fildes(2); + + argv = tl argv; + + case len argv { + * => + usage(); + return; + 1 => + r = sys->unmount(nil, hd argv); + 2 => + r = sys->unmount(hd argv, hd tl argv); + }; + + if(r < 0) + sys->fprint(stderr, "unmount: %r\n"); +} diff --git a/appl/cmd/usb/mkfile b/appl/cmd/usb/mkfile new file mode 100644 index 00000000..9a6ea4fa --- /dev/null +++ b/appl/cmd/usb/mkfile @@ -0,0 +1,11 @@ +<../../../mkconfig + +TARG=\ + usbd.dis\ + +SYSMODULES=\ + usb.m\ + +DISBIN=$ROOT/dis/usb + +<$ROOT/mkfiles/mkdis diff --git a/appl/cmd/usb/usbd.b b/appl/cmd/usb/usbd.b new file mode 100644 index 00000000..1594da08 --- /dev/null +++ b/appl/cmd/usb/usbd.b @@ -0,0 +1,835 @@ +implement Usbd; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "string.m"; + str: String; +include "lock.m"; + lock: Lock; + Semaphore: import lock; +include "arg.m"; + arg: Arg; + +include "usb.m"; + usb: Usb; + Device, Configuration, Endpt: import Usb; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +Detached, Attached, Enabled, Assigned, Configured: con (iota); + +Usbd: module +{ + init: fn(nil: ref Draw->Context, args: list of string); +}; + +Hub: adt { + nport, pwrmode, compound, pwrms, maxcurrent, removable, pwrctl: int; + ports: cyclic ref DDevice; +}; + +DDevice: adt { + port: int; + pids: list of int; + parent: cyclic ref DDevice; + next: cyclic ref DDevice; + cfd, setupfd, rawfd: ref Sys->FD; + id: int; + ls: int; + state: int; + ep: array of ref Endpt; + config: array of ref Usb->Configuration; + hub: Hub; + mod: UsbDriver; + d: ref Device; +}; + +Line: adt { + level: int; + command: string; + value: int; + svalue: string; +}; + +ENUMERATE_POLL_INTERVAL: con 1000; +FAILED_ENUMERATE_RETRY_INTERVAL: con 10000; + +verbose: int; +debug: int; +stderr: ref Sys->FD; + +usbportfd: ref Sys->FD; +usbctlfd: ref Sys->FD; +usbctl0: ref Sys->FD; +usbsetup0: ref Sys->FD; + +usbbase: string; + +configsema, setupsema, treesema: ref Semaphore; + + +# UHCI style status which is returned by the driver. +UHCIstatus_Suspend: con 1 << 12; +UHCIstatus_PortReset: con 1 << 9; +UHCIstatus_SlowDevice: con 1 << 8; +UHCIstatus_ResumeDetect: con 1 << 6; +UHCIstatus_PortEnableChange: con 1 << 3; +UHCIstatus_PortEnable: con 1 << 2; +UHCIstatus_ConnectStatusChange: con 1 << 1; +UHCIstatus_DevicePresent: con 1 << 0; + +obt() +{ +# sys->fprint(stderr, "%d waiting\n", sys->pctl(0, nil)); + setupsema.obtain(); +# sys->fprint(stderr, "%d got\n", sys->pctl(0, nil)); +} + +rel() +{ +# sys->fprint(stderr, "%d releasing\n", sys->pctl(0, nil)); + setupsema.release(); +} + +hubid(hub: ref DDevice): int +{ + if (hub == nil) + return 0; + return hub.id; +} + +hubfeature(d: ref DDevice, p: int, feature: int, on: int): int +{ + rtyp: int; + if (p == 0) + rtyp = Usb->Rclass; + else + rtyp = Usb->Rclass | Usb->Rother; + obt(); + rv := usb->setclear_feature(d.setupfd, rtyp, feature, p, on); + rel(); + return rv; +} + +portpower(hub: ref DDevice, port: int, on: int) +{ + if (verbose) + sys->fprint(stderr, "portpower %d/%d %d\n", hubid(hub), port, on); + if (hub == nil) + return; + if (port) + hubfeature(hub, port, Usb->PORT_POWER, on); +} + +countrootports(): int +{ + sys->seek(usbportfd, big 0, Sys->SEEKSTART); + buf := array [256] of byte; + n := sys->read(usbportfd, buf, len buf); + if (n <= 0) { + sys->fprint(stderr, "usbd: countrootports: error reading root port status\n"); + exit; + } + (nv, nil) := sys->tokenize(string buf[0: n], "\n"); + if (nv < 1) { + sys->fprint(stderr, "usbd: countrootports: strange root port status\n"); + exit; + } + return nv; +} + +portstatus(hub: ref DDevice, port: int): int +{ + rv: int; +# setupsema.obtain(); + obt(); + if (hub == nil) { + sys->seek(usbportfd, big 0, Sys->SEEKSTART); + buf := array [256] of byte; + n := sys->read(usbportfd, buf, len buf); + if (n < 1) { + sys->fprint(stderr, "usbd: portstatus: read error\n"); + rel(); + return 0; + } + (nil, l) := sys->tokenize(string buf[0: n], "\n"); + for(; l != nil; l = tl l){ + (nv, f) := sys->tokenize(hd l, " "); + if(nv < 2){ + sys->fprint(stderr, "usbd: portstatus: odd status line\n"); + rel(); + return 0; + } + if(int hd f == port){ + (rv, nil) = usb->strtol(hd tl f, 16); + # the status change bits are not used so mask them off + rv &= 16rffff; + break; + } + } + if (l == nil) { + sys->fprint(stderr, "usbd: portstatus: no status for port %d\n", port); + rel(); + return 0; + } + } + else + rv = usb->get_status(hub.setupfd, port); +# setupsema.release(); + rel(); + if (rv < 0) + return 0; + return rv; +} + +portenable(hub: ref DDevice, port: int, enable: int) +{ + if (verbose) + sys->fprint(stderr, "portenable %d/%d %d\n", hubid(hub), port, enable); + if (hub == nil) { + if (enable) + sys->fprint(usbctlfd, "enable %d", port); + else + sys->fprint(usbctlfd, "disable %d", port); + return; + } + if (port) + hubfeature(hub, port, Usb->PORT_ENABLE, enable); +} + +portreset(hub: ref DDevice, port: int) +{ + if (verbose) + sys->fprint(stderr, "portreset %d/%d\n", hubid(hub), port); + if (hub == nil) { + if(0)sys->fprint(usbctlfd, "reset %d", port); + for (i := 0; i < 4; ++i) { + sys->sleep(20); # min 10 milli second reset recovery. + s := portstatus(hub, port); + if ((s & UHCIstatus_PortReset) == 0) # only leave when reset is finished. + break; + } + return; + } + if (port) + hubfeature(hub, port, Usb->PORT_RESET, 1); + return; +} + +devspeed(d: ref DDevice) +{ + sys->fprint(d.cfd, "speed %d", !d.ls); + if (debug) { + s: string; + if (d.ls) + s = "low"; + else + s = "high"; + sys->fprint(stderr, "%d: set speed %s\n", d.id, s); + } +} + +devmaxpkt0(d: ref DDevice, size: int) +{ + sys->fprint(d.cfd, "maxpkt 0 %d", size); + if (debug) + sys->fprint(stderr, "%d: set maxpkt0 %d\n", d.id, size); +} + +closedev(d: ref DDevice) +{ + d.cfd = usbctl0; + d.rawfd = nil; + d.setupfd = usbsetup0; +} + +openusb(f: string, mode: int): ref Sys->FD +{ + fd := sys->open(usbbase + f, mode); + if (fd == nil) { + sys->fprint(stderr, "usbd: can't open %s: %r\n", usbbase + f); + raise "fail:open"; + } + return fd; +} + +opendevf(id: int, f: string, mode: int): ref Sys->FD +{ + fd := sys->open(usbbase + string id + "/" + f, mode); + if (fd == nil) { + sys->fprint(stderr, "usbd: can't open %s: %r\n", usbbase + string id + "/" + f); + exit; + } + return fd; +} + +kill(pid: int): int +{ + if (debug) + sys->print("killing %d\n", pid); + fd := sys->open("#p/"+string pid+"/ctl", Sys->OWRITE); + if (fd == nil) { + sys->print("kill: open failed\n"); + return -1; + } + if (sys->write(fd, array of byte "kill", 4) != 4) { + sys->print("kill: write failed\n"); + return -1; + } + return 0; +} + +rdetach(d: ref DDevice) +{ + if (d.mod != nil) { + d.mod->shutdown(); + d.mod = nil; + } + while (d.pids != nil) { + if (verbose) + sys->fprint(stderr, "kill %d\n", hd d.pids); + kill(hd d.pids); + d.pids = tl d.pids; + } + if (d.parent != nil) { + last, hp: ref DDevice; + last = nil; + hp = d.parent.hub.ports; + while (hp != nil && hp != d) + hp = hp.next; + if (last != nil) + last.next = d.next; + else + d.parent.hub.ports = d.next; + } + if (d.hub.ports != nil) { + for (c := d.hub.ports; c != nil; c = c.next) { + c.parent = nil; + rdetach(c); + } + } + d.state = Detached; + if (sys->fprint(d.cfd, "detach") < 0) + sys->fprint(stderr, "detach failed\n"); + d.cfd = nil; + d.rawfd = nil; + d.setupfd = nil; +} + +detach(d: ref DDevice) +{ + configsema.obtain(); + treesema.obtain(); + obt(); +# setupsema.obtain(); + + if (verbose) + sys->fprint(stderr, "detach %d\n", d.id); + rdetach(d); + if (verbose) + sys->fprint(stderr, "detach %d done\n", d.id); +# setupsema.release(); + rel(); + treesema.release(); + configsema.release(); +} + +readnum(fd: ref Sys->FD): int +{ + buf := array [16] of byte; + n := sys->read(fd, buf, len buf); + if (n <= 0) + return -1; + (rv , nil) := usb->strtol(string buf[0: n], 0); + return rv; +} + +setaddress(d: ref DDevice): int +{ + if (d.state == Assigned) + return d.id; + closedev(d); + d.id = 0; + d.cfd = openusb("new", Sys->ORDWR); + id := readnum(d.cfd); + if (id <= 0) { + if (debug) + sys->fprint(stderr, "usbd: usb/new ID: %r\n"); + d.cfd = nil; + return -1; + } +# setupsema.obtain(); + obt(); + if (usb->set_address(d.setupfd, id) < 0) { +# setupsema.release(); + rel(); + return -1; + } +# setupsema.release(); + rel(); + d.id = id; + d.state = Assigned; + return id; +} + +#optstring(d: ref DDevice, langids: list of int, desc: string, index: int) +#{ +# if (index) { +# buf := array [256] of byte; +# while (langids != nil) { +# nr := usb->get_descriptor(d.setupfd, Usb->Rstandard, Usb->STRING, index, hd langids, buf); +# if (nr > 2) { +# sys->fprint(stderr, "%s: ", desc); +# usbdump->desc(d, -1, buf[0: nr]); +# } +# langids = tl langids; +# } +# } +#} + +langid(d: ref DDevice): (list of int) +{ + l: list of int; + buf := array [256] of byte; + nr := usb->get_standard_descriptor(d.setupfd, Usb->STRING, 0, buf); + if (nr < 4) + return nil; + if (nr & 1) + nr--; + l = nil; + for (i := nr - 2; i >= 2; i -= 2) + l = usb->get2(buf[i:]) :: l; + return l; +} + +describedevice(d: ref DDevice): int +{ + obt(); + devmaxpkt0(d, 64); # guess 64 byte max packet to avoid overrun on read + for (x := 0; x < 3; x++) { # retry 3 times + d.d = usb->get_parsed_device_descriptor(d.setupfd); + if (d.d != nil) + break; + sys->sleep(200); # tolerate out of spec. devices + } + + if (d.d == nil) { + rel(); + return -1; + } + + if (d.d.maxpkt0 != 64) { + devmaxpkt0(d, d.d.maxpkt0); + d.d = usb->get_parsed_device_descriptor(d.setupfd); + if (d.d == nil) { + rel(); + return -1; + } + } + + rel(); + + if (verbose) { + sys->fprint(stderr, "usb %x.%x", d.d.usbmajor, d.d.usbminor); + sys->fprint(stderr, " class %d subclass %d proto %d [%s] max0 %d", + d.d.class, d.d.subclass, d.d.proto, + usb->sclass(d.d.class, d.d.subclass, d.d.proto), d.d.maxpkt0); + sys->fprint(stderr, " vendor 0x%.4x product 0x%.4x rel %x.%x", + d.d.vid, d.d.did, d.d.relmajor, d.d.relminor); + sys->fprint(stderr, " nconf %d", d.d.nconf); + sys->fprint(stderr, "\n"); + obt(); + l := langid(d); + if (l != nil) { + l2 := l; + sys->fprint(stderr, "langids ["); + while (l2 != nil) { + sys->fprint(stderr, " %d", hd l2); + l2 = tl l2; + } + sys->fprint(stderr, "]\n"); + } +# optstring(d, l, "manufacturer", int buf[14]); +# optstring(d, l, "product", int buf[15]); +# optstring(d, l, "serial number", int buf[16]); + rel(); + } + return 0; +} + +describehub(d: ref DDevice): int +{ + b := array [256] of byte; +# setupsema.obtain(); + obt(); + nr := usb->get_class_descriptor(d.setupfd, 0, 0, b); + if (nr < Usb->DHUBLEN) { +# setupsema.release(); + rel(); + sys->fprint(stderr, "usbd: error reading hub descriptor: got %d of %d\n", nr, Usb->DHUBLEN); + return -1; + } +# setupsema.release(); + rel(); + if (verbose) + sys->fprint(stderr, "nport %d charac 0x%.4ux pwr %dms current %dmA remov 0x%.2ux pwrctl 0x%.2ux", + int b[2], usb->get2(b[3:]), int b[5] * 2, int b[6] * 2, int b[7], int b[8]); + d.hub.nport = int b[2]; + d.hub.pwrms = int b[5] * 2; + d.hub.maxcurrent = int b[6] * 2; + char := usb->get2(b[3:]); + d.hub.pwrmode = char & 3; + d.hub.compound = (char & 4) != 0; + d.hub.removable = int b[7]; + d.hub.pwrctl = int b[8]; + return 0; +} + +loadconfig(d: ref DDevice, n: int): int +{ + obt(); + d.config[n] = usb->get_parsed_configuration_descriptor(d.setupfd, n); + if (d.config[n] == nil) { + rel(); + sys->fprint(stderr, "usbd: error reading configuration descriptor\n"); + return -1; + } + rel(); + if (verbose) + usb->dump_configuration(stderr, d.config[n]); + return 0; +} + +#setdevclass(d: ref DDevice, n: int) +#{ +# dd := d.config[n]; +# if (dd != nil) +# sys->fprint(d.cfd, "class %d %d %d %d %d", d.d.nconf, n, dd.class, dd.subclass, dd.proto); +#} + +setconfig(d: ref DDevice, n: int): int +{ + obt(); + rv := usb->set_configuration(d.setupfd, n); + rel(); + if (rv < 0) + return -1; + d.state = Configured; + return 0; +} + +configure(hub: ref DDevice, port: int): ref DDevice +{ + configsema.obtain(); + portreset(hub, port); + sys->sleep(300); # long sleep necessary for strange hardware.... +# sys->sleep(20); + s := portstatus(hub, port); + s = portstatus(hub, port); + + if (debug) + sys->fprint(stderr, "port %d status 0x%ux\n", port, s); + + if ((s & UHCIstatus_DevicePresent) == 0) { + configsema.release(); + return nil; + } + + if ((s & UHCIstatus_PortEnable) == 0) { + if (debug) + sys->fprint(stderr, "hack: re-enabling port %d\n", port); + portenable(hub, port, 1); + s = portstatus(hub, port); + if (debug) + sys->fprint(stderr, "port %d status now 0x%.ux\n", port, s); + } + + d := ref DDevice; + d.port = port; + d.cfd = usbctl0; + d.setupfd = usbsetup0; + d.id = 0; + if (hub == nil) + d.ls = (s & UHCIstatus_SlowDevice) != 0; + else + d.ls = (s & (1 << 9)) != 0; + d.state = Enabled; + devspeed(d); + if (describedevice(d) < 0) { + portenable(hub, port, 0); + configsema.release(); + return nil; + } + if (setaddress(d) < 0) { + portenable(hub, port, 0); + configsema.release(); + return nil; + } + d.setupfd = opendevf(d.id, "setup", Sys->ORDWR); + d.cfd = opendevf(d.id, "ctl", Sys->ORDWR); + devspeed(d); + devmaxpkt0(d, d.d.maxpkt0); + d.config = array [d.d.nconf] of ref Configuration; + for (i := 0; i < d.d.nconf; i++) { + loadconfig(d, i); +# setdevclass(d, i); + } + if (hub != nil) { + treesema.obtain(); + d.parent = hub; + d.next = hub.hub.ports; + hub.hub.ports = d; + treesema.release(); + } + configsema.release(); + return d; +} + +enumerate(hub: ref DDevice, port: int) +{ + if (hub != nil) + hub.pids = sys->pctl(0, nil) :: hub.pids; + reenumerate := 0; + for (;;) { + if (verbose) + sys->fprint(stderr, "enumerate: starting\n"); + if ((portstatus(hub, port) & UHCIstatus_DevicePresent) == 0) { + if (verbose) + sys->fprint(stderr, "%d: port %d empty\n", hubid(hub), port); + do { + sys->sleep(ENUMERATE_POLL_INTERVAL); + } while ((portstatus(hub, port) & UHCIstatus_DevicePresent) == 0); + } + if (verbose) + sys->fprint(stderr, "%d: port %d attached\n", hubid(hub), port); + # Δt3 (TATTDB) guarantee 100ms after attach detected + sys->sleep(200); + d := configure(hub, port); + if (d == nil) { + if (verbose) + sys->fprint(stderr, "%d: can't configure port %d\n", hubid(hub), port); + } + else if (d.d.class == Usb->CL_HUB) { + i: int; + if (setconfig(d, 1) < 0) { + if (verbose) + sys->fprint(stderr, "%d: can't set configuration for hub on port %d\n", hubid(hub), port); + detach(d); + d = nil; + } + else if (describehub(d) < 0) { + if (verbose) + sys->fprint(stderr, "%d: failed to describe hub on port %d\n", hubid(hub), port); + detach(d); + d = nil; + } + else { + for (i = 1; i <= d.hub.nport; i++) + portpower(d, i, 1); + sys->sleep(d.hub.pwrms); + for (i = 1; i <= d.hub.nport; i++) + spawn enumerate(d, i); + } + } + else if (d.d.nconf >= 1 && (path := searchdriverdatabase(d.d, d.config[0])) != nil) { + d.mod = load UsbDriver path; + if (d.mod == nil) + sys->fprint(stderr, "usbd: failed to load %s\n", path); + else { + rv := d.mod->init(usb, d.setupfd, d.cfd, d.d, d.config, usbbase + string d.id + "/"); + if (rv == -11) { + sys->fprint(stderr, "usbd: %s: reenumerate\n", path); + d.mod = nil; + reenumerate = 1; + } + else if (rv < 0) { + sys->fprint(stderr, "usbd: %s:init failed\n", path); + d.mod = nil; + } + else if (verbose) + sys->fprint(stderr, "%s running\n", path); + } + } + else if (setconfig(d, 1) < 0) { + if (verbose) + sys->fprint(stderr, "%d: can't set configuration for port %d\n", hubid(hub), port); + detach(d); + d = nil; + } + if (!reenumerate) { + if (d != nil) { + # wait for it to be unplugged + while (portstatus(hub, port) & UHCIstatus_DevicePresent) + sys->sleep(ENUMERATE_POLL_INTERVAL); + } + else { + # wait a bit and prod it again + if (portstatus(hub, port) & UHCIstatus_DevicePresent) + sys->sleep(FAILED_ENUMERATE_RETRY_INTERVAL); + } + } + if (d != nil) { + detach(d); + d = nil; + } + reenumerate = 0; + } +} + +lines: array of Line; + +searchdriverdatabase(d: ref Device, conf: ref Configuration): string +{ + backtracking := 0; + level := 0; + for (i := 0; i < len lines; i++) { + if (verbose > 1) + sys->fprint(stderr, "search line %d: lvl %d cmd %s val %d (back %d lvl %d)\n", + i, lines[i].level, lines[i].command, lines[i].value, backtracking, level); + if (backtracking) { + if (lines[i].level > level) + continue; + backtracking = 0; + } + if (lines[i].level != level) { + level = 0; + backtracking = 1; + } + case lines[i].command { + "class" => + if (d.class != 0) { + if (lines[i].value != d.class) + backtracking = 1; + } + else if (lines[i].value != (hd conf.iface[0].altiface).class) + backtracking = 1; + "subclass" => + if (d.class != 0) { + if (lines[i].value != d.subclass) + backtracking = 1; + } + else if (lines[i].value != (hd conf.iface[0].altiface).subclass) + backtracking = 1; + "proto" => + if (d.class != 0) { + if (lines[i].value != d.proto) + backtracking = 1; + } + else if (lines[i].value != (hd conf.iface[0].altiface).proto) + backtracking = 1; + "vendor" => + if (lines[i].value != d.vid) + backtracking =1; + "product" => + if (lines[i].value != d.did) + backtracking =1; + "load" => + return lines[i].svalue; + * => + continue; + } + if (!backtracking) + level++; + } + return nil; +} + +loaddriverdatabase() +{ + newlines: array of Line; + + if (bufio == nil) + bufio = load Bufio Bufio->PATH; + + iob := bufio->open(Usb->DATABASEPATH, Sys->OREAD); + if (iob == nil) { + sys->fprint(stderr, "usbd: couldn't open %s: %r\n", Usb->DATABASEPATH); + return; + } + lines = array[100] of Line; + lc := 0; + while ((line := iob.gets('\n')) != nil) { + if (line[0] == '#') + continue; + level := 0; + while (line[0] == '\t') { + level++; + line = line[1:]; + } + (n, l) := sys->tokenize(line[0: len line - 1], "\t "); + if (n != 2) + continue; + if (lc >= len lines) { + newlines = array [len lines * 2] of Line; + newlines[0:] = lines[0: len lines]; + lines = newlines; + } + lines[lc].level = level; + lines[lc].command = hd l; + case hd l { + "class" or "subclass" or "proto" or "vendor" or "product" => + (lines[lc].value, nil) = usb->strtol(hd tl l, 0); + "load" => + lines[lc].svalue = hd tl l; + * => + continue; + } + lc++; + } + if (verbose) + sys->fprint(stderr, "usbd: loaded %d lines\n", lc); + newlines = array [lc] of Line; + newlines[0:] = lines[0 : lc]; + lines = newlines; +} + +init(nil: ref Draw->Context, args: list of string) +{ + usbbase = "/dev/usbh/"; + sys = load Sys Sys->PATH; + str = load String String->PATH; + + lock = load Lock Lock->PATH; + lock->init(); + + usb = load Usb Usb->PATH; + usb->init(); + + arg = load Arg Arg->PATH; + + stderr = sys->fildes(2); + + verbose = 0; + debug = 0; + + arg->init(args); + arg->setusage("usbd [-dv] [-i interface]"); + while ((c := arg->opt()) != 0) + case c { + 'v' => verbose = 1; + 'd' => debug = 1; + 'i' => usbbase = arg->earg() + "/"; + * => arg->usage(); + } + args = arg->argv(); + + usbportfd = openusb("port", Sys->OREAD); + usbctlfd = sys->open(usbbase + "ctl", Sys->OWRITE); + if(usbctlfd == nil) + usbctlfd = openusb("port", Sys->OWRITE); + usbctl0 = opendevf(0, "ctl", Sys->ORDWR); + usbsetup0 = opendevf(0, "setup", Sys->ORDWR); + setupsema = Semaphore.new(); + configsema = Semaphore.new(); + treesema = Semaphore.new(); + loaddriverdatabase(); + ports := countrootports(); + if (verbose) + sys->print("%d root ports found\n", ports); + for (p := 2; p <= ports; p++) + spawn enumerate(nil, p); + if (p >= 1) + enumerate(nil, 1); +} diff --git a/appl/cmd/uudecode.b b/appl/cmd/uudecode.b new file mode 100644 index 00000000..12894aa3 --- /dev/null +++ b/appl/cmd/uudecode.b @@ -0,0 +1,132 @@ +implement Uudecode; + +include "sys.m"; + sys : Sys; +include "draw.m"; +include "string.m"; + str : String; +include "bufio.m"; + bufio : Bufio; + Iobuf : import bufio; + +Uudecode : module +{ + init : fn(nil : ref Draw->Context, argv : list of string); +}; + +fatal(s : string) +{ + sys->fprint(sys->fildes(2), "%s\n", s); + exit; +} + +usage() +{ + fatal("usage: uudecode [ -p ] [ encodedfile... ]"); +} + +init(nil : ref Draw->Context, argv : list of string) +{ + fd : ref Sys->FD; + + tostdout := 0; + sys = load Sys Sys->PATH; + str = load String String->PATH; + bufio = load Bufio Bufio->PATH; + argv = tl argv; + if (argv != nil && hd argv == "-p") { + tostdout = 1; + argv = tl argv; + } + if (argv != nil) { + for (; argv != nil; argv = tl argv) { + fd = sys->open(hd argv, Sys->OREAD); + if (fd == nil) + fatal(sys->sprint("cannot open %s", hd argv)); + decode(fd, tostdout); + } + } + else + decode(sys->fildes(0), tostdout); +} + +code(c : byte) : int +{ + return (int c - ' ')&16r3f; +} + +LEN : con 45; + +decode(ifd : ref Sys->FD, tostdout : int) +{ + mode : int; + ofile : string; + + bio := bufio->fopen(ifd, Bufio->OREAD); + if (bio == nil) + fatal("cannot open input for buffered io: %r"); + while ((s := bio.gets('\n')) != nil) { + if (len s >= 6 && s[0:6] == "begin ") { + (n, l) := sys->tokenize(s, " \n"); + if (n < 3) + fatal("bad begin line"); + (mode, nil) = str->toint(hd tl l, 8); + ofile = hd tl tl l; + break; + } + } + if (ofile == nil) + fatal("no begin line"); + if (tostdout) + ofd := sys->fildes(1); + else { + if (ofile[0] == '~') # ~user/file + ofile = "/usr/" + ofile[1:]; + ofd = sys->create(ofile, Sys->OWRITE, 8r666); + if (ofd == nil) + fatal(sys->sprint("cannot create %s: %r", ofile)); + } + ob := array[LEN] of byte; + while ((s = bio.gets('\n')) != nil) { + b := array of byte s; + n := code(b[0]); + if (n == 0 && (len b != 2 || b[1] != byte '\n')) + fatal("bad 0 count line"); + if (n <= 0) + break; + if (n > LEN) + fatal("too many bytes on line"); + e := 0; f := 0; + if (n%3 == 1) { + e = 2; f = 4; + } + else if (n%3 == 2) { + e = 3; f = 4; + } + if (len b < 4*(n/3)+e+2 || len b > 4*(n/3)+f+2) + fatal("bad uuencode count"); + b = b[1:]; + i := 0; + nl := n; + for (j := 0; nl > 0; j += 4) { + if (nl >= 1) + ob[i++] = byte (code(b[j+0])<<2 | code(b[j+1])>>4); + if (nl >= 2) + ob[i++] = byte (code(b[j+1])<<4 | code(b[j+2])>>2); + if (nl >= 3) + ob[i++] = byte (code(b[j+2])<<6 | code(b[j+3])>>0); + nl -= 3; + } + if (sys->write(ofd, ob, i) != i) + fatal("bad write to output: %r"); + } + s = bio.gets('\n'); + if (s == nil || len s < 4 || s[0:4] != "end\n") + fatal("missing end line"); + if (!tostdout) { + d := sys->nulldir; + d.mode = mode; + if (sys->fwstat(ofd, d) < 0) + fatal(sys->sprint("cannot wstat %s: %r", ofile)); + } +} diff --git a/appl/cmd/uuencode.b b/appl/cmd/uuencode.b new file mode 100644 index 00000000..54dedfdf --- /dev/null +++ b/appl/cmd/uuencode.b @@ -0,0 +1,101 @@ +implement Uuencode; + +include "sys.m"; + sys : Sys; +include "draw.m"; + +Uuencode : module +{ + init : fn(nil : ref Draw->Context, argv : list of string); +}; + +fatal(s : string) +{ + sys->fprint(sys->fildes(2), "%s\n", s); + exit; +} + +usage() +{ + fatal("usage: uuencode [ sourcefile ] remotefile"); +} + +init(nil : ref Draw->Context, argv : list of string) +{ + fd : ref Sys->FD; + mode : int; + + sys = load Sys Sys->PATH; + argv = tl argv; + if (argv == nil) + usage(); + if (tl argv != nil) { + fd = sys->open(hd argv, Sys->OREAD); + if (fd == nil) + fatal(sys->sprint("cannot open %s", hd argv)); + (ok, d) := sys->fstat(fd); + if (ok < 0) + fatal(sys->sprint("cannot stat %s: %r", hd argv)); + if (d.mode & Sys->DMDIR) + fatal("cannot uuencode a directory"); + mode = d.mode; + argv = tl argv; + } + else { + fd = sys->fildes(0); + mode = 8r666; + } + if (tl argv != nil) + usage(); + sys->print("begin %o %s\n", mode, hd argv); + encode(fd); + sys->print("end\n"); +} + +LEN : con 45; + +code(c : int) : byte +{ + return byte ((c&16r3f) + ' '); +} + +encode(ifd : ref Sys->FD) +{ + c, d, e : int; + + ofd := sys->fildes(1); + ib := array[LEN] of byte; + ob := array[4*LEN/3 + 2] of byte; + for (;;) { + n := sys->read(ifd, ib, LEN); + if (n < 0) + fatal("cannot read input file: %r"); + if (n == 0) + break; + i := 0; + ob[i++] = code(n); + for (j := 0; j < n; j += 3) { + c = int ib[j]; + ob[i++] = code((0<<6)&16r00 | (c>>2)&16r3f); + if (j+1 < n) + d = int ib[j+1]; + else + d = 0; + ob[i++] = code((c<<4)&16r30 | (d>>4)&16r0f); + if (j+2 < n) + e = int ib[j+2]; + else + e = 0; + ob[i++] = code((d<<2)&16r3c | (e>>6)&16r03); + ob[i++] = code((e<<0)&16r3f | (0>>8)&16r00); + } + ob[i++] = byte '\n'; + if (sys->write(ofd, ob, i) != i) + fatal("bad write to output: %r"); + } + ob[0] = code(0); + ob[1] = byte '\n'; + if (sys->write(ofd, ob, 2) != 2) + fatal("bad write to output: %r"); +} + diff --git a/appl/cmd/wav2iaf.b b/appl/cmd/wav2iaf.b new file mode 100644 index 00000000..d00dc69e --- /dev/null +++ b/appl/cmd/wav2iaf.b @@ -0,0 +1,171 @@ +implement Wav2Iaf; + +include "sys.m"; +include "draw.m"; +include "bufio.m"; + +sys: Sys; +FD: import sys; +bufio: Bufio; +Iobuf: import bufio; + +stderr: ref FD; +inf: ref Iobuf; +prog: string; +buff4: array of byte; + +pad := array[] of { " ", " ", "", " " }; + +Wav2Iaf: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +ioerror() +{ + sys->fprint(stderr, "%s: read error: %r\n", prog); + exit; +} + +shortfile(diag: string) +{ + sys->fprint(stderr, "%s: short read: %s\n", prog, diag); + exit; +} + +error(s: string) +{ + sys->fprint(stderr, "%s: bad wave file: %s\n", prog, s); + exit; +} + +get(c: int, s: string) +{ + n := inf.read(buff4, c); + if (n < 0) + ioerror(); + if (n != c) + shortfile("expected " + s); +} + +gets(c: int, s: string) : string +{ + get(c, s); + return string buff4[0:c]; +} + +need(s: string) +{ + get(4, s); + if (string buff4 != s) { + sys->fprint(stderr, "%s: not a wave file\n", prog); + exit; + } +} + +getl(s: string) : int +{ + get(4, s); + return int buff4[0] + (int buff4[1] << 8) + (int buff4[2] << 16) + (int buff4[3] << 24); +} + +getw(s: string) : int +{ + get(2, s); + return int buff4[0] + (int buff4[1] << 8); +} + +skip(n: int) +{ + while (n > 0) { + inf.getc(); + n--; + } +} + +bufcp(s, d: ref Iobuf, n: int) +{ + while (n > 0) { + b := s.getb(); + if (b < 0) { + if (b == Bufio->EOF) + sys->fprint(stderr, "%s: short input file\n", prog); + else + sys->fprint(stderr, "%s: read error: %r\n", prog); + exit; + } + d.putb(byte b); + n--; + } +} + +init(nil: ref Draw->Context, argv: list of string) +{ + l: int; + a: string; + + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + prog = hd argv; + argv = tl argv; + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + sys->fprint(stderr, "%s: could not load %s: %r\n", prog, Bufio->PATH); + if (argv == nil) { + inf = bufio->fopen(sys->fildes(0), Bufio->OREAD); + if (inf == nil) { + sys->fprint(stderr, "%s: could not fopen stdin: %r\n", prog); + exit; + } + } + else if (tl argv != nil) { + sys->fprint(stderr, "usage: %s [infile]\n", prog); + exit; + } + else { + inf = bufio->open(hd argv, Sys->OREAD); + if (inf == nil) { + sys->fprint(stderr, "%s: could not open %s: %r\n", prog, hd argv); + exit; + } + } + buff4 = array[4] of byte; + need("RIFF"); + getl("length"); + need("WAVE"); + for (;;) { + a = gets(4, "tag"); + l = getl("length"); + if (a == "fmt ") + break; + skip(l); + } + if (getw("format") != 1) + error("not PCM"); + chans := getw("channels"); + rate := getl("rate"); + getl("AvgBytesPerSec"); + getw("BlockAlign"); + bits := getw("bits"); + l -= 16; + do { + skip(l); + a = gets(4, "tag"); + l = getl("length"); + } + while (a != "data"); + outf := bufio->fopen(sys->fildes(1), Sys->OWRITE); + if (outf == nil) { + sys->fprint(stderr, "%s: could not fopen stdout: %r\n", prog); + exit; + } + s := "rate\t" + string rate + "\n" + + "chans\t" + string chans + "\n" + + "bits\t" + string bits + "\n" + + "enc\tpcm"; + outf.puts(s); + outf.puts(pad[len s % 4]); + outf.puts("\n\n"); + bufcp(inf, outf, l); + outf.flush(); +} diff --git a/appl/cmd/wc.b b/appl/cmd/wc.b new file mode 100644 index 00000000..c0a57d35 --- /dev/null +++ b/appl/cmd/wc.b @@ -0,0 +1,303 @@ +implement Wc; + +# +# wc -- count things in utf-encoded text files +# Bugs: +# The only white space characters recognized are ' ', '\t' and '\n', even though +# ISO 10646 has many more blanks scattered through it. +# Should count characters that cannot occur in any rune (hex f0-ff) separately. +# Should count non-canonical runes (e.g. hex c1,80 instead of hex 40). +# + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +Wc: module +{ + init: fn(ctxt: ref Draw->Context, args: list of string); +}; + +NBUF: con 8*1024; + +stderr: ref Sys->FD; +nline, tnline, pline: int; +nword, tnword, pword: int; +nchar, tnchar, pchar: int; +nbadr, tnbadr, pbadr: int; +nbyte, tnbyte, pbyte: int; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + for(argv = tl argv; argv != nil; argv = tl argv){ + arg := hd argv; + if(len arg < 2 || arg[0] != '-' || arg[1] == '-') + break; + for(i := 1; i < len arg; i++){ + case arg[i]{ + 'l' => pline++; + 'w' => pword++; + 'c' => pchar++; + 'e' => pbadr++; + 'b' => pbyte++; + * => + sys->fprint(stderr, "usage: wc [-lwcbe] [file ...]\n"); + raise "fail:usage"; + } + } + } + if(pline+pword+pchar+pbadr+pbyte == 0) + pline = pword = pchar = 1; + argc := len argv; + if(argc == 0) + count(sys->fildes(0), ""); + else{ + for(; argv != nil; argv = tl argv){ + name := hd argv; + f := sys->open(name, sys->OREAD); + if(f == nil) + sys->fprint(stderr, "wc: can't open %s: %r\n", name); + else{ + count(f, name); + tnline += nline; + tnword += nword; + tnchar += nchar; + tnbadr += nbadr; + tnbyte += nbyte; + f = nil; + } + } + if(argc > 1) + report(tnline, tnword, tnchar, tnbadr, tnbyte, "total"); + } + exit; +} +report(nline, nword, nchar, nbadr, nbyte: int, fname: string) +{ + line := ""; + if(pline) + line += sys->sprint(" %7d", nline); + if(pword) + line += sys->sprint(" %7d", nword); + if(pchar) + line += sys->sprint(" %7d", nchar); + if(pbadr) + line += sys->sprint(" %7d", nbadr); + if(pbyte) + line += sys->sprint(" %7d", nbyte); + if(fname != nil) + line += sys->sprint(" %s", fname); + sys->print("%s\n", line[1:]); +} +# +# How it works. Start in statesp. Each time we read a character, +# increment various counts, and do state transitions according to the +# following table. If we're not in statesp or statewd when done, the +# file ends with a partial rune. +# | character +# state |09,20| 0a |00-7f|80-bf|c0-df|e0-ef|f0-ff +# -------+-----+-----+-----+-----+-----+-----+----- +# statesp|ASP |ASPN |AWDW |AWDWX|AC2W |AC3W |AWDWX +# statewd|ASP |ASPN |AWD |AWDX |AC2 |AC3 |AWDX +# statec2|ASPX |ASPNX|AWDX |AWDR |AC2X |AC3X |AWDX +# statec3|ASPX |ASPNX|AWDX |AC2R |AC2X |AC3X |AWDX +# + # actions + AC2, # enter statec2 + AC2R, # enter statec2, don't count a rune + AC2W, # enter statec2, count a word + AC2X, # enter statec2, count a bad rune + AC3, # enter statec3 + AC3W, # enter statec3, count a word + AC3X, # enter statec3, count a bad rune + ASP, # enter statesp + ASPN, # enter statesp, count a newline + ASPNX, # enter statesp, count a newline, count a bad rune + ASPX, # enter statesp, count a bad rune + AWD, # enter statewd + AWDR, # enter statewd, don't count a rune + AWDW, # enter statewd, count a word + AWDWX, # enter statewd, count a word, count a bad rune + AWDX: # enter statewd, count a bad rune + con byte iota; + +statesp := array[256] of{ # looking for the start of a word +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 00-07 +AWDW, ASP, ASPN, AWDW, AWDW, AWDW, AWDW, AWDW, # 08-0f +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 10-17 +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 18-1f +ASP, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 20-27 +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 28-2f +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 30-37 +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 38-3f +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 40-47 +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 48-4f +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 50-57 +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 58-5f +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 60-67 +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 68-6f +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 70-77 +AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 78-7f +AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 80-87 +AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 88-8f +AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 90-97 +AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 98-9f +AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# a0-a7 +AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# a8-af +AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# b0-b7 +AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# b8-bf +AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # c0-c7 +AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # c8-cf +AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # d0-d7 +AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # d8-df +AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, # e0-e7 +AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, # e8-ef +AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# f0-f7 +AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# f8-ff +}; +statewd := array[256] of { # looking for the next character in a word +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 00-07 +AWD, ASP, ASPN, AWD, AWD, AWD, AWD, AWD, # 08-0f +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 10-17 +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 18-1f +ASP, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 20-27 +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 28-2f +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 30-37 +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 38-3f +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 40-47 +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 48-4f +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 50-57 +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 58-5f +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 60-67 +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 68-6f +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 70-77 +AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 78-7f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 80-87 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 88-8f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 90-97 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 98-9f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # a0-a7 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # a8-af +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # b0-b7 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # b8-bf +AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # c0-c7 +AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # c8-cf +AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # d0-d7 +AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # d8-df +AC3, AC3, AC3, AC3, AC3, AC3, AC3, AC3, # e0-e7 +AC3, AC3, AC3, AC3, AC3, AC3, AC3, AC3, # e8-ef +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f0-f7 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f8-ff +}; +statec2 := array[256] of { # looking for 10xxxxxx to complete a rune +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 00-07 +AWDX, ASPX, ASPNX,AWDX, AWDX, AWDX, AWDX, AWDX, # 08-0f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 10-17 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 18-1f +ASPX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 20-27 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 28-2f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 30-37 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 38-3f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 40-47 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 48-4f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 50-57 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 58-5f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 60-67 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 68-6f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 70-77 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 78-7f +AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 80-87 +AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 88-8f +AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 90-97 +AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 98-9f +AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # a0-a7 +AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # a8-af +AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # b0-b7 +AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # b8-bf +AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c0-c7 +AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c8-cf +AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d0-d7 +AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d8-df +AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e0-e7 +AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e8-ef +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f0-f7 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f8-ff +}; +statec3 := array[256] of { # looking for 10xxxxxx,10xxxxxx to complete a rune +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 00-07 +AWDX, ASPX, ASPNX,AWDX, AWDX, AWDX, AWDX, AWDX, # 08-0f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 10-17 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 18-1f +ASPX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 20-27 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 28-2f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 30-37 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 38-3f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 40-47 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 48-4f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 50-57 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 58-5f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 60-67 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 68-6f +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 70-77 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 78-7f +AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 80-87 +AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 88-8f +AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 90-97 +AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 98-9f +AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # a0-a7 +AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # a8-af +AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # b0-b7 +AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # b8-bf +AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c0-c7 +AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c8-cf +AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d0-d7 +AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d8-df +AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e0-e7 +AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e8-ef +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f0-f7 +AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f8-ff +}; +buf := array[NBUF] of byte; +count(f: ref Sys->FD, name: string) +{ + state := statesp; + nline = nword = nchar = nbadr = nbyte = 0; + n := 0; + for(;;){ + n = sys->read(f, buf, NBUF); + if(n <= 0) + break; + nbyte += n; + nchar += n; # might be too large, gets decreased later + i := 0; + do{ + case int state[int buf[i++]]{ + int AC2 => state = statec2; + int AC2R => state = statec2; nchar--; + int AC2W => state = statec2; nword++; + int AC2X => state = statec2; nbadr++; + int AC3 => state = statec3; + int AC3W => state = statec3; nword++; + int AC3X => state = statec3; nbadr++; + int ASP => state = statesp; + int ASPN => state = statesp; nline++; + int ASPNX => state = statesp; nline++; nbadr++; + int ASPX => state = statesp; nbadr++; + int AWD => state = statewd; + int AWDR => state = statewd; nchar--; + int AWDW => state = statewd; nword++; + int AWDWX => state = statewd; nword++; nbadr++; + int AWDX => state = statewd; nbadr++; + } + }while(i < n); + } + if(state!=statesp && state!=statewd) + nbadr++; + if(n < 0) + sys->fprint(stderr, "wc: error reading %s: %r\n", name); + report(nline, nword, nchar, nbadr, nbyte, name); +} diff --git a/appl/cmd/webgrab.b b/appl/cmd/webgrab.b new file mode 100644 index 00000000..0659f398 --- /dev/null +++ b/appl/cmd/webgrab.b @@ -0,0 +1,532 @@ +# Webgrab -- for getting html pages and the subordinate files (images, frame children) +# they refer to (using "src=..." in a tag) into the local file space. +# Assume http: scheme if none specified. +# Usage: +# webgrab [-r] [-v] [-o stem] url +# If stem is specified, file will be saved in stem.html and images will +# go in stem_1.jpg (or .gif, ...), stem_2.jpg, etc. +# If stem is not specified, derive it from url (see getstem comment, below). +# If -r is specified, get "raw", i.e., no image fetching/html munging. +# If -v is specified (verbose), print some progress information, +# with more if -vv is given. + +implement Webgrab; + +include "sys.m"; + sys: Sys; + FD: import sys; + +include "draw.m"; + +include "string.m"; + S: String; + +include "url.m"; + U: Url; + ParsedUrl: import U; + +include "daytime.m"; + DT: Daytime; + +include "bufio.m"; + B: Bufio; + +include "arg.m"; + +Webgrab: module +{ + init: fn(ctxt: ref Draw->Context, args: list of string); +}; + +stderr: ref FD; +verbose := 0; + +httpproxy: ref Url->ParsedUrl; +noproxydoms: list of string; # domains that don't require proxy + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + S = load String String->PATH; + U = load Url Url->PATH; + DT = load Daytime Daytime->PATH; + B = load Bufio Bufio->PATH; + arg := load Arg Arg->PATH; + if(S == nil || U == nil || DT == nil || B == nil || arg == nil) + error_exit("can't load a module"); + U->init(); + stem := ""; + rawflag := 0; + arg->init(args); + arg->setusage("webgrab [-r] [-v[v]] [-o stem] url"); + url := ""; + while((o := arg->opt()) != 0) + case o { + 'r' => + rawflag = 1; + 'v' => + verbose++; + 'o' => + stem = arg->earg(); + * => + arg->usage(); + } + args = arg->argv(); + if(len args != 1) + arg->usage(); + url = hd args; + arg = nil; + (nil,xr) := S->splitstrl(url,"//"); + (nil,yr) := S->splitl(url,":"); + if(xr == "" && yr == "") + url = "http://" + url; + u := U->makeurl(url); + if(stem == "") + stem = getstem(u); + readconfig(); + grab(u, stem, rawflag); +} + +readconfig() +{ + cfgio := B->open("/services/webget/config", sys->OREAD); + if(cfgio != nil) { + for(;;) { + line := B->cfgio.gets('\n'); + if(line == "") { + B->cfgio.close(); + break; + } + if(line[0]=='#') + continue; + (key, val) := S->splitl(line, " \t="); + val = S->take(S->drop(val, " \t="), "^\r\n"); + if(val == "") + continue; + case key { + "httpproxy" => + if(val == "none") + continue; + # val should be host or host:port + httpproxy = U->makeurl("http://" + val); + if(verbose) + sys->fprint(stderr, "Using http proxy %s\n", httpproxy.tostring()); + "noproxy" or + "noproxydoms" => + (nil, noproxydoms) = sys->tokenize(val, ";, \t"); + } + } + } +} + +# Make up a stem for forming save-file-names, based on url u. +# Use the last non-nil component of u.path, without a final extension, +# else use the host. Then, if the stem still contains a '.' (e.g., www.lucent) +# use the part after the final '.'. +# Finally, if all else fails, use use "grabout". +getstem(u: ref ParsedUrl) : string +{ + stem := ""; + if(u.path != "") { + (l, r) := S->splitr(u.path, "/"); + if(r == "") { + # path ended with '/'; try next to last component + if(l != "") + (l, r) = S->splitr(l[0:len l - 1], "/"); + } + if(r != "") + stem = r; + } + if(stem == "") + stem = u.host; + if(stem != "") { + ext: string; + (stem, ext) = S->splitr(stem, "."); + if(stem == "") + stem = ext; + else + stem = stem[0:len stem - 1]; + (nil, stem) = S->splitr(stem, "."); + } + if(stem == "") + stem = "grabout"; + return stem; +} + +grab(u: ref ParsedUrl, stem: string, rawflag: int) +{ + (err, contents, fd, actual) := httpget(u); + if(err != "") + error_exit(err); + ish := is_html(contents); + if(ish) + contents = addfetchcomment(contents, u, actual); + if(rawflag || !ish) { + writebytes(stem, contents, fd); + return; + } + # get subordinates, modify contents + subs : list of (string, string); + (contents, subs) = subfix(contents, stem); + writebytes(stem + ".html", contents, fd); + for(l := subs; l != nil; l = tl l) { + (fname, suburl) := hd l; + subu := U->makeurl(suburl); + subu.makeabsolute(actual); + (suberr, subcontents, subfd, subactual) := httpget(subu); + if(suberr != "") { + sys->fprint(stderr, "webgrab: can't fetch subordinate %s from %s: %s\n", fname, subu.tostring(), suberr); + continue; + } + writebytes(fname, subcontents, subfd); + } +} + +# Fix the html in array a so that referenced subordinate files (SRC= or BACKGROUND= fields of tags) +# are replaced with local names (stem_1.xxx, stem_2.xxx, etc.), +# and return the fixed array along with a list of (local name, subordinate url) +# of images to be fetched. +subfix(a: array of byte, stem: string) : (array of byte, list of (string, string)) +{ + alen := len a; + if(alen == 0) + return (a, nil); + nsubs := 0; + newa := array[alen + 1000] of byte; + newai := 0; + j := 0; + intag := 0; + incom := 0; + quote := 0; + subs : list of (string, string) = nil; + for(i := 0; i < alen; i++) { + c := int a[i]; + if(incom) { + if(amatch(a, i, alen, "-->")) { + incom = 0; + i = i+2; + } + } + else if(intag) { + if(quote==0 && (amatch(a, i, alen, "src") || amatch(a, i, alen, "background"))) { + v := ""; + eqi := 0; + if(amatch(a, i, alen, "src")) + k := i+3; + else + k = i+10; + for(; k < alen; k++) + if(!iswhite(int a[k])) + break; + if(k < alen && int a[k] == '=') { + eqi = k; + k++; + while(k<alen && iswhite(int a[k])) + k++; + if(k<alen) { + kstart := k; + c = int a[k]; + if(c == '\'' || c== '"') { + quote = int a[k++]; + while(k<alen && (int a[k])!=quote) + k++; + v = string a[kstart+1:k]; + k++; + } + else { + while(k<alen && !iswhite(int a[k]) && int a[k] != '>') + k++; + v = string a[kstart:k]; + } + } + } + if(v != "") { + f := ""; + for(l := subs; l != nil; l = tl l) { + (ff,uu) := hd l; + if(v == uu) { + f = ff; + break; + } + } + if(f == "") { + nsubs++; + f = stem + "_" + string nsubs + getsuff(v); + subs = (f, v) :: subs; + } + # should check for newa too small + newa[newai:] = a[j:eqi+1]; + newai += eqi+1-j; + xa := array of byte f; + newa[newai:] = xa; + newai += len xa; + j = k; + } + i = k-1; + } + if(c == '>' && quote == 0) + intag = 0; + if(quote) { + if(quote == c) + quote = 0; + else if(c == '"' || c == '\'') + quote = c; + } + } + else if(c == '<') + intag = 1; + } + if(nsubs == 0) + return (a, nil); + if(i > j) { + newa[newai:] = a[j:i]; + newai += i-j; + } + ans := array[newai] of byte; + ans[0:] = newa[0:newai]; + anssubs : list of (string, string) = nil; + for(ll := subs; ll != nil; ll = tl ll) + anssubs = hd ll :: anssubs; + return (ans, anssubs); +} + +# add c after all f's in a +fixnames(a: array of byte, f: string, c: byte) +{ + alen := len a; + n := alen - len f; + for(i := 0; i < n; i++) { + if(amatch(a, i, alen, f)) { + a[i+len f] = c; + } + } +} + +amatch(a: array of byte, i, alen: int, s: string) : int +{ + slen := len s; + for(k := 0; i+k < alen && k < slen; k++) { + c := int a[i+k]; + if(c >= 'A' && c <= 'Z') + c = c + (int 'a' - int 'A'); + if(c != s[k]) + break; + } + if(k == slen) { + return 1; + } + return 0; +} + +getsuff(ustr: string) : string +{ + u := U->makeurl(ustr); + if(u.path != "") { + for(i := len u.path - 1; i >= 0; i--) { + c := u.path[i]; + if(c == '.') + return u.path[i:]; + if(c == '/') + break; + } + } + return ""; +} + +iswhite(c: int) : int +{ + return (c==' ' || c=='\t' || c=='\n' || c=='\r'); +} + +# Add a comment to end of a giving date and source of fetch +addfetchcomment(a: array of byte, u, actu: ref ParsedUrl) : array of byte +{ + now := DT->text(DT->local(DT->now())); + ustr := u.tostring(); + actustr := actu.tostring(); + comment := "\n<!-- Fetched " + now + " from " + ustr; + if(ustr != actustr) + comment += ", redirected to " + actustr; + comment += " -->\n"; + acom := array of byte comment; + newa := array[len a + len acom] of byte; + newa[0:] = a; + newa[len a:] = acom; + return newa; +} + +# Get u, return (error string, body, actual url of source, after redirection) +httpget(u: ref ParsedUrl) : (string, array of byte, ref Sys->FD, ref ParsedUrl) +{ + ans, body : array of byte; + restfd: ref Sys->FD; + for(redir := 0; redir < 10; redir++) { + if(u.port == "") + u.port = "80"; # default IP port for HTTP + if(verbose) + sys->fprint(stderr, "connecting to %s\n", u.host); + dialhost, port: string; + req := "GET "; + if(httpproxy != nil && need_proxy(u.host)) { + dialhost = httpproxy.host; + port = httpproxy.port; + req += "http://" + u.host; + } + else { + dialhost = u.host; + port = u.port; + } + (ok, net) := sys->dial("tcp!" + dialhost + "!" + port, nil); + if(ok < 0) + return (sys->sprint("can't dial %s: %r", dialhost), nil, nil, nil); + req += "/" + u.path; + if(u.query != "") + req += "?" + u.query; + req += " HTTP/1.0\r\nHost: "+u.host+"\r\nUser-agent: Inferno/webgrab\r\n\r\n"; + if(verbose) + sys->fprint(stderr, "writing request: %s\n", req); + areq := array of byte req; + n := sys->write(net.dfd, areq, len areq); + if(n != len areq) + return (sys->sprint("write problem: %r"), nil, nil, nil); + (ans, restfd) = readbytes(net.dfd); + (status, rest) := stripline(ans); + if(verbose) + sys->fprint(stderr, "response: %s\n", status); + (vers, statusrest) := S->splitl(status, " "); + if(!S->prefix("HTTP/", vers)) + return ("bad reply status: " + status, rest, restfd, nil); + code := int statusrest; + location := ""; + body = rest; + for(;;) { + hline: string; + (hline, body) = stripline(body); + if(hline == "") + break; + if(verbose > 1) + sys->fprint(stderr, "%s\n", hline); + if(!iswhite(hline[0])) { + (hname, hrest) := S->splitl(hline, ":"); + if(hrest != "") { + hname = S->tolower(hname); + hval := S->drop(hrest, ": \t"); + hval = S->take(hval, "^ \t"); + if(hname == "location") + location = hval; + } + } + } + if(code != 200) { + if((code == 300 || code == 301 || code == 302) && location != "") { + # MultipleChoices, MovedPerm, or MovedTemp + if(verbose) + sys->fprint(stderr, "redirect to %s\n", location); + u = U->makeurl(location); + continue; + } + return ("status not ok: " + status, rest, restfd, u); + } + break; + } + return ("", body, restfd, u); +} + +need_proxy(h: string) : int +{ + doml := noproxydoms; + if(doml == nil) + return 1; # all domains need proxy + + lh := len h; + for(dom := hd doml; doml != nil; doml = tl doml) { + ld := len dom; + if(lh >= ld && h[lh-ld:] == dom) + return 0; # domain is on the noproxy list + } + + return 1; +} + +# Simple guess test for HTML: first non-white byte is '<' +is_html(a: array of byte) : int +{ + for(i := 0; i < len a; i++) + if(!iswhite(int a[i])) + break; + if(i < len a && a[i] == byte '<') + return 1; + return 0; +} + +readbytes(fd: ref Sys->FD) : (array of byte, ref Sys->FD) +{ + buf := array[Sys->ATOMICIO] of byte; + i := 0; + avail := len buf; + while (avail > 0) { + n := sys->read(fd, buf[i:], avail); + if(n <= 0) { + fd = nil; + break; + } + i += n; + avail -= n; + } + return (buf[0:i], fd); +} + +writebytes(f: string, a: array of byte, fd: ref Sys->FD) +{ + ofd: ref Sys->FD; + if (f == "-") + ofd = sys->fildes(1); + else + ofd = sys->create(f, Sys->OWRITE, 8r666); + if(ofd == nil) { + sys->fprint(stderr, "webgrab: can't create %s: %r\n", f); + return; + } + i := 0; + clen := len a; + while(i < clen) { + n := sys->write(ofd, a[i:], clen-i); + if(n < 0) { + sys->fprint(stderr, "webgrab: write error: %r\n"); + return; + } + i += n; + } + if(fd != nil) { + buf := array[Sys->ATOMICIO] of byte; + while((n := sys->read(fd, buf, len buf)) > 0) { + if(sys->write(ofd, buf, n) != n) { + sys->fprint(stderr, "webgrab: write error: %r\n"); + return; + } + } + if(n < 0) { + sys->fprint(stderr, "webgrab: read error: %r\n"); + return; + } + clen += n; + } + if (f != "-") + sys->fprint(stderr, "created %s, %d bytes\n", f, clen); +} + +stripline(b: array of byte) : (string, array of byte) +{ + n := len b - 1; + for(i := 0; i < n; i++) + if(b[i] == byte '\r' && b[i+1] == byte '\n') + return (string b[0:i], b[i+2:]); + return ("", b); +} + +error_exit(msg: string) +{ + sys->fprint(sys->fildes(2), "%s\n", msg); + raise "fail:error"; +} diff --git a/appl/cmd/wish.b b/appl/cmd/wish.b new file mode 100644 index 00000000..39be49a0 --- /dev/null +++ b/appl/cmd/wish.b @@ -0,0 +1,191 @@ +implement Test; + +include "sys.m"; +include "draw.m"; +draw: Draw; +Screen, Display, Image: import draw; +include "tk.m"; + +Test: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +tk: Tk; +sys: Sys; + +init(nil: ref Draw->Context, argv: list of string) +{ + cmd: string; + + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + tk = load Tk Tk->PATH; + + display := Display.allocate(nil); + if(display == nil) { + sys->print("can't initialize display: %r\n"); + return; + } + + disp := display.image; + screen := Screen.allocate(disp, display.rgb(161, 195, 209), 1); + if(screen == nil) { + sys->print("can't allocate screen: %r\n"); + return; + } + fd := sys->open("/dev/pointer", sys->OREAD); + if(fd == nil) { + sys->print("open: %s: %r\n", "/dev/pointer"); + sys->print("run wm/wish instead\n"); + return; + } + + t := tk->toplevel(display, ""); + spawn mouse(t, fd); + spawn keyboard(t); + disp.draw(disp.r, screen.fill, nil, disp.r.min); + + input := array[8192] of byte; + stdin := sys->fildes(0); + + if(argv != nil) + argv = tl argv; + while(argv != nil) { + exec(t, hd argv); + argv = tl argv; + } + + for(;;) { + tk->cmd(t, "update"); + + prompt := '%'; + if(cmd != nil) + prompt = '>'; + sys->print("%c ", prompt); + + n := sys->read(stdin, input, len input); + if(n <= 0) + break; + if(n == 1) + continue; + cmd += string input[0:n-1]; + if(cmd[len cmd-1] != '\\') { + cmd = esc(cmd); + s := tk->cmd(t, cmd); + if(len s != 0) + sys->print("%s\n", s); + cmd = nil; + continue; + } + cmd = cmd[0:len cmd-1]; + } +} + +esc(s: string): string +{ + c: int; + + for(i := 0; i < len s; i++) { + if(s[i] != '\\') + continue; + case s[i+1] { + 'n'=> c = '\n'; + 't'=> c = '\t'; + 'b'=> c = '\b'; + '\\'=> c = '\\'; + * => c = 0; + } + if(c != 0) { + s[i] = c; + s = s[0:i+1]+s[i+2:len s]; + } + } + return s; +} + +exec(t: ref Tk->Toplevel, path: string) +{ + fd := sys->open(path, sys->OREAD); + if(fd == nil) { + sys->print("open: %s: %r\n", path); + return; + } + (ok, d) := sys->fstat(fd); + if(ok < 0) { + sys->print("fstat: %s: %r\n", path); + return; + } + buf := array[int d.length] of byte; + if(sys->read(fd, buf, len buf) < 0) { + sys->print("read: %s: %r\n", path); + return; + } + (n, l) := sys->tokenize(string buf, "\n"); + buf = nil; + n = -1; + for(; l != nil; l = tl l) { + n++; + s := hd l; + if(len s == 0 || s[0] == '#') + continue; + + while(s[len s-1] == '\\') { + s = s[0:len s-1]; + if(tl l != nil) { + l = tl l; + s = s + hd l; + } + else + break; + } + + s = tk->cmd(t, esc(s)); + + if(len s != 0 && s[0] == '!') { + sys->print("%s:%d %s\n", path, n, s); + sys->print("%s:%d %s\n", path, n, hd l); + } + } +} + +mouse(t: ref Tk->Toplevel, fd: ref Sys->FD) +{ + n := 0; + buf := array[100] of byte; + for(;;) { + n = sys->read(fd, buf, len buf); + if(n <= 0) + break; + + if(int buf[0] == 'm' && n >= 1+3*12) { + x := int(string buf[ 1:13]); + y := int(string buf[12:25]); + b := int(string buf[24:37]); + tk->pointer(t, Draw->Pointer(b, Draw->Point(x, y), sys->millisec())); + } + } +} + +keyboard(t: ref Tk->Toplevel) +{ + dfd := sys->open("/dev/keyboard", sys->OREAD); + if(dfd == nil) + return; + + b:= array[1] of byte; + buf := array[10] of byte; + i := 0; + for(;;) { + n := sys->read(dfd, buf[i:], len buf - i); + if(n < 1) + break; + i += n; + while(i >0 && (nutf := sys->utfbytes(buf, i)) > 0){ + s := string buf[0:nutf]; + tk->keyboard(t, int s[0]); + buf[0:] = buf[nutf:i]; + i -= nutf; + } + } +} diff --git a/appl/cmd/wmexport.b b/appl/cmd/wmexport.b new file mode 100644 index 00000000..204337cd --- /dev/null +++ b/appl/cmd/wmexport.b @@ -0,0 +1,557 @@ +implement Wmexport; + +# +# Copyright © 2003 Vita Nuova Holdings Limited. +# + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Wmcontext, Image: import draw; +include "wmlib.m"; + wmlib: Wmlib; +include "styx.m"; + styx: Styx; + Rmsg, Tmsg: import styx; +include "styxservers.m"; + styxservers: Styxservers; + Styxserver, Fid, Navigator, Navop: import styxservers; + Enotdir, Enotfound: import Styxservers; + nametree: Nametree; + +Wmexport: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +# filesystem looks like: +# clone +# 1 +# wmctl +# keyboard +# pointer +# winname + +badmodule(p: string) +{ + sys->fprint(sys->fildes(2), "wmexport: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +user := "me"; +qidseq := 1; +imgseq := 0; + +pidregister: chan of (int, int); +flush: chan of (int, int, chan of int); + +makeconn: chan of chan of (ref Conn, string); +delconn: chan of ref Conn; +reqpool: list of chan of (ref Tmsg, ref Conn, ref Fid); +reqidle: int; +reqdone: chan of chan of (ref Tmsg, ref Conn, ref Fid); + +srv: ref Styxserver; +ctxt: ref Draw->Context; + +conns: array of ref Conn; +nconns := 0; + +Qerror, Qroot, Qdir, Qclone, Qwmctl, Qptr, Qkbd, Qwinname: con iota; +Shift: con 4; +Mask: con 16rf; + +Maxreqidle: con 3; +Maxreplyidle: con 3; + +Conn: adt { + wm: ref Wmcontext; + iname: string; # name of image + n: int; + nreads: int; +}; + +# initial connection provides base-name (fid?) for images. +# full name could be: +# window.fid.tag + +init(drawctxt: ref Draw->Context, nil: list of string) +{ + sys = load Sys Sys->PATH; + ctxt = drawctxt; + if(ctxt == nil || ctxt.wm == nil){ + sys->fprint(sys->fildes(2), "wmexport: no window manager context\n"); + raise "fail:no wm"; + } + draw = load Draw Draw->PATH; + styx = load Styx Styx->PATH; + if (styx == nil) + badmodule(Styx->PATH); + styx->init(); + styxservers = load Styxservers Styxservers->PATH; + if (styxservers == nil) + badmodule(Styxservers->PATH); + styxservers->init(styx); + + wmlib = load Wmlib Wmlib->PATH; + if(wmlib == nil) + badmodule(Wmlib->PATH); + wmlib->init(); + + sys->pctl(Sys->FORKNS|Sys->NEWPGRP, nil); # fork pgrp? + + ctxt = drawctxt; + navops := chan of ref Navop; + spawn navigator(navops); + tchan: chan of ref Tmsg; + (tchan, srv) = Styxserver.new(sys->fildes(0), Navigator.new(navops), big Qroot); + srv.replychan = chan of ref Styx->Rmsg; + spawn replymarshal(srv.replychan); + spawn serve(tchan, navops); +} + +serve(tchan: chan of ref Tmsg, navops: chan of ref Navop) +{ + pidregister = chan of (int, int); + makeconn = chan of chan of (ref Conn, string); + delconn = chan of ref Conn; + flush = chan of (int, int, chan of int); + reqdone = chan of chan of (ref Tmsg, ref Conn, ref Fid); + spawn flushproc(flush); + +Serve: + for(;;)alt{ + gm := <-tchan => + if(gm == nil) + break Serve; + pick m := gm { + Readerror => + sys->fprint(sys->fildes(2), "wmexport: fatal read error: %s\n", m.error); + break Serve; + Open => + (fid, mode, d, err) := srv.canopen(m); + if(err != nil) + srv.reply(ref Rmsg.Error(m.tag, err)); + else if(fid.qtype & Sys->QTDIR) + srv.default(m); + else + request(ctxt, m, fid); + Read => + (fid, err) := srv.canread(m); + if(err != nil) + srv.reply(ref Rmsg.Error(m.tag, err)); + else if(fid.qtype & Sys->QTDIR) + srv.read(m); + else + request(ctxt, m, fid); + Write => + (fid, err) := srv.canwrite(m); + if(err != nil) + srv.reply(ref Rmsg.Error(m.tag, err)); + else + request(ctxt, m, fid); + Flush => + done := chan of int; + flush <-= (m.tag, m.oldtag, done); + <-done; + Clunk => + request(ctxt, m, srv.clunk(m)); + * => + srv.default(gm); + } + rc := <-makeconn => + if(nconns >= len conns) + conns = (array[len conns + 5] of ref Conn)[0:] = conns; + wm := wmlib->connect(ctxt); + if(wm == nil) # XXX this can't happen - give wmlib->connect an error return + rc <-= (nil, "cannot connect"); + else{ + c := ref Conn(wm, nil, qidseq++, 0); + conns[nconns++] = c; + rc <-= (c, nil); + } + c := <-delconn => + for(i := 0; i < nconns; i++) + if(conns[i] == c) + break; + nconns--; + if(i < nconns) + conns[i] = conns[nconns]; + conns[nconns] = nil; + reqpool = <-reqdone :: reqpool => + if(reqidle++ > Maxreqidle){ + hd reqpool <-= (nil, nil, nil); + reqpool = tl reqpool; + reqidle--; + } + } + navops <-= nil; + kill(sys->pctl(0, nil), "killgrp"); +} + +nameimage(nil: ref Conn, img: ref Draw->Image): string +{ + if(img.iname != nil) + return img.iname; + for(i := 0; i < 100; i++){ + s := "inferno." + string imgseq++; + if(img.name(s, 1) > 0) + return s; + if(img.iname != nil) + return img.iname; # a competing process has done it for us. + } +sys->print("wmexport: no image names: %r\n"); +raise "panic"; +} + +request(nil: ref Draw->Context, m: ref Styx->Tmsg, fid: ref Fid) +{ + n := int fid.path >> Shift; + conn: ref Conn; + for(i := 0; i < nconns; i++){ + if(conns[i].n == n){ + conn = conns[i]; + break; + } + } + c: chan of (ref Tmsg, ref Conn, ref Fid); + if(reqpool == nil){ + c = chan of (ref Tmsg, ref Conn, ref Fid); + spawn requestproc(c); + }else{ + (c, reqpool) = (hd reqpool, tl reqpool); + reqidle--; + } + c <-= (m, conn, fid); +} + +requestproc(req: chan of (ref Tmsg, ref Conn, ref Fid)) +{ + pid := sys->pctl(0, nil); + for(;;){ + (gm, c, fid) := <-req; + if(gm == nil) + break; + pidregister <-= (pid, gm.tag); + path := int fid.path; + pick m := gm { + Read => + if(c == nil) + srv.replydirect(ref Rmsg.Error(m.tag, "connection is dead")); + case path & Mask { + Qwmctl => + # first read gets number of connection. + m.offset = big 0; + if(c.nreads++ == 0) + srv.replydirect(styxservers->readstr(m, string c.n)); + else + srv.replydirect(styxservers->readstr(m, <-c.wm.ctl)); + Qptr => + m.offset = big 0; + p := <-c.wm.ptr; + srv.replydirect(styxservers->readbytes(m, + sys->aprint("m%11d %11d %11d %11ud ", p.xy.x, p.xy.y, p.buttons, p.msec))); + Qkbd => + m.offset = big 0; + s := ""; + s[0] = <-c.wm.kbd; + srv.replydirect(styxservers->readstr(m, s)); + Qwinname => + m.offset = big 0; + srv.replydirect(styxservers->readstr(m, c.iname)); + * => + srv.replydirect(ref Rmsg.Error(m.tag, "what was i thinking1?")); + } + Write => + if(c == nil) + srv.replydirect(ref Rmsg.Error(m.tag, "connection is dead")); + case path & Mask { + Qwmctl => + if(sys->write(c.wm.connfd, m.data, len m.data) == -1){ + srv.replydirect(ref Rmsg.Error(m.tag, sys->sprint("%r"))); + break; + } + if(len m.data > 0 && int m.data[0] == '!'){ + i := <-c.wm.images; + if(i == nil) + i = <-c.wm.images; + c.iname = nameimage(c, i); + } + srv.replydirect(ref Rmsg.Write(m.tag, len m.data)); + * => + srv.replydirect(ref Rmsg.Error(m.tag, "what was i thinking2?")); + } + Open => + if(c == nil && path != Qclone) + srv.replydirect(ref Rmsg.Error(m.tag, "connection is dead")); + err: string; + q := qid(path); + case path & Mask { + Qclone => + cch := chan of (ref Conn, string); + makeconn <-= cch; + (c, err) = <-cch; + if(c != nil) + q = qid(Qwmctl | (c.n << Shift)); + Qptr => + if(sys->fprint(c.wm.connfd, "start ptr") == -1) + err = sys->sprint("%r"); + Qkbd => + if(sys->fprint(c.wm.connfd, "start kbd") == -1) + err = sys->sprint("%r"); + Qwmctl => + ; + Qwinname => + ; + * => + err = "what was i thinking3?"; + } + if(err != nil) + srv.replydirect(ref Rmsg.Error(m.tag, err)); + else{ + srv.replydirect(ref Rmsg.Open(m.tag, q, 0)); + fid.open(m.mode, q); + } + Clunk => + case path & Mask { + Qwmctl => + if(c != nil) + delconn <-= c; + } + * => + srv.replydirect(ref Rmsg.Error(gm.tag, "oh dear")); + } + pidregister <-= (pid, -1); + reqdone <-= req; + } +} + +qid(path: int): Sys->Qid +{ + return dirgen(path).t0.qid; +} + +replyproc(c: chan of ref Rmsg, replydone: chan of chan of ref Rmsg) +{ + # hmm, this could still send a reply out-of-order with a flush + while((m := <-c) != nil){ + srv.replydirect(m); + replydone <-= c; + } +} + +# deal with reply messages coming from styxservers. +replymarshal(c: chan of ref Styx->Rmsg) +{ + replypool: list of chan of ref Rmsg; + n := 0; + replydone := chan of chan of ref Rmsg; + for(;;) alt{ + m := <-c => + c: chan of ref Rmsg; + if(replypool == nil){ + c = chan of ref Rmsg; + spawn replyproc(c, replydone); + }else{ + (c, replypool) = (hd replypool, tl replypool); + n--; + } + c <-= m; + replypool = <-replydone :: replypool => + if(++n > Maxreplyidle){ + hd replypool <-= nil; + replypool = tl replypool; + n--; + } + } +} + +navigator(navops: chan of ref Navop) +{ + while((m := <-navops) != nil){ + path := int m.path; + pick n := m { + Stat => + n.reply <-= dirgen(int n.path); + Walk => + name := n.name; + case path & Mask { + Qdir => + dp := path & ~Mask; + case name { + ".." => + path = Qroot; + "wmctl" => + path = Qwmctl | dp; + "pointer" => + path = Qptr | dp; + "keyboard" => + path = Qkbd | dp; + "winname" => + path = Qwinname | dp; + * => + path = Qerror; + } + Qroot => + case name{ + "clone" => + path = Qclone; + * => + x := int name; + path = Qerror; + if(string x == name){ + for(i := 0; i < nconns; i++) + if(conns[i].n == x){ + path = (x << Shift) | Qdir; + break; + } + } + } + } + n.reply <-= dirgen(path); + Readdir => + err := ""; + d: array of int; + case path & Mask { + Qdir => + d = array[] of {Qwmctl, Qptr, Qkbd, Qwinname}; + for(i := 0; i < len d; i++) + d[i] |= path & ~Mask; + Qroot => + d = array[nconns + 1] of int; + d[0] = Qclone; + for(i := 0; i < nconns; i++) + d[i + 1] = (conns[i].n<<Shift) | Qdir; + } + if(d == nil){ + n.reply <-= (nil, Enotdir); + break; + } + for (i := n.offset; i < len d; i++) + n.reply <-= dirgen(d[i]); + n.reply <-= (nil, nil); + } + } +} + +dirgen(path: int): (ref Sys->Dir, string) +{ + name: string; + perm: int; + case path & Mask { + Qroot => + name = "."; + perm = 8r555|Sys->DMDIR; + Qdir => + name = string (path >> Shift); + perm = 8r555|Sys->DMDIR; + Qclone => + name = "clone"; + perm = 8r666; + Qwmctl => + name = "wmctl"; + perm = 8r666; + Qptr => + name = "pointer"; + perm = 8r444; + Qkbd => + name = "keyboard"; + perm = 8r444; + Qwinname => + name = "winname"; + perm = 8r444; + * => + return (nil, Enotfound); + } + return (dir(path, name, perm), nil); +} + +dir(path: int, name: string, perm: int): ref Sys->Dir +{ + d := ref sys->zerodir; + d.qid.path = big path; + if(perm & Sys->DMDIR) + d.qid.qtype = Sys->QTDIR; + d.mode = perm; + d.name = name; + d.uid = user; + d.gid = user; + return d; +} + +flushproc(flush: chan of (int, int, chan of int)) +{ + a: array of (int, int); # (pid, tag) + n := 0; + for(;;)alt{ + (pid, tag) := <-pidregister => + if(tag == -1){ + for(i := 0; i < n; i++) + if(a[i].t0 == pid) + break; + n--; + if(i < n) + a[i] = a[n]; + }else{ + if(n >= len a){ + na := array[n + 5] of (int, int); + na[0:] = a; + a = na; + } + a[n++] = (pid, tag); + } + (tag, oldtag, done) := <-flush => + for(i := 0; i < n; i++) + if(a[i].t1 == oldtag){ + spawn doflush(tag, a[i].t0, done); + break; + } + if(i == n) + spawn doflush(tag, -1, done); + } +} + +doflush(tag: int, pid: int, done: chan of int) +{ + if(pid != -1){ + kill(pid, "kill"); + pidregister <-= (pid, -1); + } + srv.replydirect(ref Rmsg.Flush(tag)); + done <-= 1; +} + +# return number of characters from s that will fit into +# max bytes when encoded as utf-8. +fullutf(s: string, max: int): int +{ + Bit1: con 7; + Bitx: con 6; + Bit2: con 5; + Bit3: con 4; + Bit4: con 3; + Rune1: con (1<<(Bit1+0*Bitx))-1; # 0000 0000 0111 1111 + Rune2: con (1<<(Bit2+1*Bitx))-1; # 0000 0111 1111 1111 + Rune3: con (1<<(Bit3+2*Bitx))-1; # 1111 1111 1111 1111 + nb := 0; + for(i := 0; i < len s; i++){ + c := s[i]; + if(c <= Rune1) + nb += 1; + else if(c <= Rune2) + nb += 2; + else + nb += 3; + if(nb > max) + break; + } + return i; +} + +kill(pid: int, note: string): int +{ + fd := sys->open("/prog/"+string pid+"/ctl", Sys->OWRITE); + if(fd == nil || sys->fprint(fd, "%s", note) < 0) + return -1; + return 0; +} diff --git a/appl/cmd/wmimport.b b/appl/cmd/wmimport.b new file mode 100644 index 00000000..2a0d3f3b --- /dev/null +++ b/appl/cmd/wmimport.b @@ -0,0 +1,64 @@ +implement Wmimport; + +# +# Copyright © 2003 Vita Nuova Holdings Limited. +# + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; +include "arg.m"; +include "wmlib.m"; +include "sh.m"; + +# turn wmexport namespace into a Draw->Context. +# usage: wmimport [-d /dev/draw] [-w /mnt/wm] cmd [arg...] + +Wmimport: module { + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + draw = load Draw Draw->PATH; + wmlib := load Wmlib Wmlib->PATH; + wmlib->init(); + sh := load Sh Sh->PATH; + arg := load Arg Arg->PATH; + + devdraw := "/dev"; + mntwm := "/mnt/wm"; + arg->init(argv); + arg->setusage("wmimport [-d /dev] [-w /mnt/wm] cmd [arg...]"); + while((opt := arg->opt()) != 0){ + case opt{ + 'd' => + devdraw = arg->earg(); + 'w' => + mntwm = arg->earg(); + * => + arg->usage(); + } + } + argv = arg->argv(); + if(argv == nil) + arg->usage(); + arg = nil; + (ok, nil) := sys->stat(mntwm + "/clone"); + if(ok == -1){ + sys->fprint(sys->fildes(2), "wmimport: no wm at %s\n", mntwm); + raise "fail:no wm"; + } + (ctxt, err) := wmlib->importdrawcontext(devdraw, mntwm); + if(ctxt == nil){ + sys->fprint(sys->fildes(2), "wmimport: remote connect failed; %s\n", err); + raise "fail:error"; + } + + e := sh->run(ctxt, argv); + if(e != nil) + raise "fail:" + e; +} + diff --git a/appl/cmd/xargs.b b/appl/cmd/xargs.b new file mode 100644 index 00000000..abfe8cf1 --- /dev/null +++ b/appl/cmd/xargs.b @@ -0,0 +1,86 @@ +# apply cmd to args list read from stdin +# obc +implement Xargs; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +Xargs: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +stderr: ref Sys->FD; + +usage() +{ + sys->fprint(stderr, "Usage: xargs command [command args] <[list of last command arg]\n"); +} + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if(bufio == nil){ + sys->fprint(stderr, "xargs: can't load Bufio: %r\n"); + exit; + } + if(args != nil) + args = tl args; + if (args == nil) { + usage(); + return; + } + cmd := hd args; + args = tl args; + if(len cmd < 4 || cmd[len cmd -4:]!=".dis") + cmd += ".dis"; + sh := load Command cmd; + if (sh == nil){ + cmd = "/dis/"+cmd; + sh = load Command cmd; + } + if (sh == nil){ + sys->fprint(stderr, "xargs: can't load %s: %r\n", cmd); + exit; + } + + stdin := sys->fildes(0); + if(stdin == nil){ + sys->fprint(stderr, "xargs: no standard input\n"); + exit; + } + b := bufio->fopen(stdin, Bufio->OREAD); + while((t := b.gets('\n')) != nil){ + (nil, rargs) := sys->tokenize(t, " \t\n"); + if (rargs == nil) + continue; + if (args == nil) + rargs = cmd :: rargs; + else + rargs = append(cmd :: args, rargs); + sh->init(ctxt, rargs); # BUG: process environment? + } +} + +reverse[T](l: list of T): list of T +{ + t: list of T; + for(; l != nil; l = tl l) + t = hd l :: t; + return t; +} + +append(h, t: list of string) : list of string +{ + r := reverse(h); + for(; r != nil; r = tl r) + t = hd r :: t; + return t; +} diff --git a/appl/cmd/xd.b b/appl/cmd/xd.b new file mode 100644 index 00000000..fa032550 --- /dev/null +++ b/appl/cmd/xd.b @@ -0,0 +1,316 @@ +implement Xd; + +# +# based on Plan9 xd +# + +include "sys.m"; +include "draw.m"; +include "bufio.m"; + +Xd: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +sys : Sys; +bufio : Bufio; +Iobuf : import bufio; +stdin, stdout, stderr : ref Sys->FD; + +wbytes := array [] of { + 1, + 2, + 4, + 8, +}; +fmtchars : con "odx"; +fmtbases := array [] of { + 8, + 10, + 16, +}; +fwidths := array [] of { + 3, # 1o + 3, # 1d + 2, # 1x + 6, # 2o + 5, # 2d + 4, # 2x + 11, # 4o + 10, # 4d + 8, # 4x + 22, # 8o + 20, # 8d + 16, # 8x +}; + +bytepos := array [16] of { * => 0 }; + +formats := array [10] of (int, int, int); # (nbytes, base, fieldwidth) +nformats := 0; +addrbase := 16; +repeats := 0; +swab := 0; +flush := 0; +addr := big 0; +output : ref Iobuf; +pad : string; + + +init(nil : ref Draw->Context, argv : list of string) +{ + sys = load Sys Sys->PATH; + stdin = sys->fildes(0); + stdout = sys->fildes(1); + stderr = sys->fildes(2); + + bufio = load Bufio Bufio->PATH; + if (bufio == nil) { + sys->fprint(stderr, "cannot load bufio: %r\n"); + raise "fail:init"; + } + output = bufio->fopen(stdout, Sys->OWRITE); + if (argv == nil) + raise "fail:bad argv"; + + pad = string array [32] of { * => byte ' ' }; + + for (argv = tl argv; argv != nil; argv = tl argv) { + arg := hd argv; + if (arg == nil) + continue; + if (arg[0] != '-') + break; + + if (len arg == 2) { + case arg[1] { + 'c' => + addformat(0, 256); + 'r' => + repeats = 1; + 's' => + swab = 1; + 'u' => + flush = 1; + * => + usage(); + } + continue; + } + # XXX should allow -x1, -x + if (len arg == 3) { + n := 0; + baseix := strchr(fmtchars,arg[2]); + if (baseix == -1) + usage(); + case arg[1] { + 'a' => + addrbase = fmtbases[baseix]; + continue; + 'b' or '1' => n = 0; + 'w' or '2' => n = 1; + 'l' or '4' => n = 2; + 'v' or '8' => n = 3; + * => + usage(); + } + addformat(n, baseix); + continue; + } + usage(); + } + if (nformats == 0) + addformat(2, 2); # "4x" + + if (argv == nil) + dump(nil, 0); + else if (tl argv == nil) + dump(hd argv, 0); + else { + for (; argv != nil; argv = tl argv) { + dump(hd argv, 1); + } + } +} + +usage() +{ + sys->fprint(stderr, "usage: xd [-u] [-r] [-s] [-a{odx}] [-c|{b1w2l4v8}{odx}] ... file ...\n"); + raise "fail:usage"; +} + +strchr(s : string, ch : int) : int +{ + for (ix := 0; ix < len s; ix++) + if (s[ix] == ch) + return ix; + return -1; +} + +addformat(widix, baseix : int) +{ + nbytes := wbytes[widix]; + if (nformats >= len formats) { + sys->fprint(stderr, "xd: too many formats\n"); + raise "fail:error"; + } + fw : int; + if (baseix == 256) { + # special -c case + formats[nformats++] = (nbytes, 256, 2); + fw = 2; + } else { + fw = fwidths[baseix + (widix *len fmtbases)]; + formats[nformats++] = (nbytes, fmtbases[baseix], fw); + } + bpos := 0; + for (ix := 0; ix < 16; ix += nbytes) { + if (bytepos[ix] >= bpos) + bpos = bytepos[ix]; + else { + d := bpos - bytepos[ix]; + for (dix := ix; dix < 16; dix++) + bytepos[dix] += d; + } + bpos += fw + 1; + } +} + +dump(path : string, title : int) +{ + input := bufio->fopen(stdin, Sys->OREAD); + zeros := array [16] of {* => byte 0}; + + if (path != nil) { + input = bufio->open(path, Sys->OREAD); + if (input == nil) { + sys->fprint(stderr, "xd: cannot open %s: %r\n", path); + raise "fail:cannot open"; + } + } + + if (title) { + output.puts(path); + output.putc('\n'); + } + + addr = big 0; + star := 0; + obuf: array of byte; + + for (;;) { + n := 0; + buf := array [16] of byte; + while (n < 16 && (r := input.read(buf[n:], 16 - n)) > 0) + n += r; + if (n < 16) + buf[n:] = zeros[n:]; + if (swab) + doswab(buf); + if (n == 16 && repeats) { + if (obuf != nil && buf[0]==obuf[0]) { + for (i := 0; i < 16; i++) + if (obuf[i] != buf[i]) + break; + if (i == 16) { + addr += big 16; + if (star == 0) { + star++; + output.puts("*\n"); + } + continue; + } + } + obuf = buf; + star = 0; + } + for (fmt := 0; fmt < nformats; fmt++) { + if (fmt == 0) + output.puts(big2str(addr, 7, addrbase, '0')); + else + output.puts(big2str(addr, 7, addrbase, ' ')); + output.putc(' '); + (w, b, fw) := formats[fmt]; + pdata(fw, w, b, n, buf); + output.putc('\n'); + if (flush) + output.flush(); + } + addr += big n; + if (n < 16) { + output.puts(big2str(addr, 7, addrbase, '0')); + output.putc('\n'); + if (flush) + output.flush(); + break; + } + } + output.flush(); +} + +hexchars : con "0123456789abcdef"; + +big2str(b : big, minw, base, padc : int) : string +{ + s := ""; + do { + d := int (b % big base); + s[len s] = hexchars[d]; + b /= big base; + } while (b > big 0); + t := ""; + if (len s < minw) + t = string array [minw] of { * => byte padc }; + else + t = s; + for (i := len s - 1; i >= 0; i--) + t[len t - 1 - i] = s[i]; + return t; +} + +pdata(fw, n, base, dlen : int, data : array of byte) +{ + nout := 0; + text := ""; + + for (i := 0; i < dlen; i += n) { + if (i != 0) { + padlen := bytepos[i] - nout; + output.puts(pad[0:padlen]); + nout += padlen; + } + if (base == 256) { + # special -c case + ch := int data[i]; + case ch { + '\t' => text = "\\t"; + '\r' => text = "\\r"; + '\n' => text = "\\n"; + '\b' => text = "\\b"; + * => + if (ch >= 16r7f || ' ' > ch) + text = sys->sprint("%.2x", ch); + else + text = sys->sprint("%c", ch); + } + } else { + v := big data[i]; + for (ix := 1; ix < n; ix++) + v = (v << 8) + big data[i+ix]; + text = big2str(v, fw, base, '0'); + } + output.puts(text); + nout += len text; + } +} + +doswab(b : array of byte) +{ + ix := 0; + for (i := 0; i < 4; i++) { + (b[ix], b[ix+3]) = (b[ix+3], b[ix]); + (b[ix+1], b[ix+2]) = (b[ix+2], b[ix+1]); + ix += 4; + } +} diff --git a/appl/cmd/xmount.b b/appl/cmd/xmount.b new file mode 100644 index 00000000..6d86c939 --- /dev/null +++ b/appl/cmd/xmount.b @@ -0,0 +1,231 @@ +implement Mount; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "keyring.m"; +include "security.m"; +include "arg.m"; +include "sh.m"; +include "styxconv.m"; + styxconv: Styxconv; + +Mount: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +vflag := 0; +doauth := 1; + +usage() +{ + sys->fprint(sys->fildes(2), "Usage: mount [-a|-b] [-cA] [-C cryptoalg] [-f keyfile] net!addr|file|{command} mountpoint [spec]\n"); + raise "fail:usage"; +} + +fail(status, msg: string) +{ + sys->fprint(sys->fildes(2), "mount: %s\n", msg); + raise "fail:"+status; +} + +nomod(mod: string) +{ + fail("load", sys->sprint("can't load %s: %r", mod)); +} + +init(ctxt: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + + vflag = 0; + unauth := 0; + alg := "none"; + keyfile: string; + spec: string; + + arg := load Arg Arg->PATH; + if(arg == nil) + nomod(Arg->PATH); + styxconv = load Styxconv Styxconv->PATH; + if(styxconv == nil) + nomod(Styxconv->PATH); + + arg->init(args); + styxconv->init(); + + flags := 0; + while((o := arg->opt()) != 0) + case o { + 'a' => + flags |= Sys->MAFTER; + 'b' => + flags |= Sys->MBEFORE; + 'c' => + flags |= Sys->MCREATE; + 'C' => + alg = arg->arg(); + if(alg == nil) + usage(); + 'f' => + keyfile = arg->arg(); + if(keyfile == nil) + usage(); + 'A' => + doauth = 0; + 'v' => + vflag = 1; + 'u' => + unauth = 1; # temporary, undocumented option for testing + * => + usage(); + } + args = arg->argv(); + arg = nil; + if(len args != 2){ + if(len args != 3) + usage(); + spec = hd tl tl args; + } + addr := hd args; + mountpoint := hd tl args; + + # open stream + fd := do_connect(ctxt, addr); + + # authenticate if necessary + if (doauth) + fd = do_auth(keyfile, alg, fd, addr, unauth); + + p := array[2] of ref Sys->FD; + if(sys->pipe(p) < 0) + fail("can't create pipe", sys->sprint("can't create pipe: %r")); + pidch := chan of int; + spawn styxconv->styxconv(p[1], fd, pidch); + p[1] = nil; + <- pidch; + ok := sys->mount(p[0], nil, mountpoint, flags, spec); + p[0] = nil; + if(ok < 0) + fail("mount failed", sys->sprint("mount failed: %r")); + +} + +# either make network connection or open file +do_connect(ctxt: ref Draw->Context, dest: string): ref Sys->FD +{ + if(dest != nil && dest[0] == '{' && dest[len dest - 1] == '}'){ + doauth = 0; + return popen(ctxt, dest :: nil); + } + (n, nil) := sys->tokenize(dest, "!"); + if(n == 1){ + fd := sys->open(dest, Sys->ORDWR); + if(fd != nil) + return fd; + if(dest[0] == '/') + fail("open failed", sys->sprint("can't open %s: %r", dest)); + } + (ok, c) := sys->dial(netmkaddr(dest, "net", "styx"), nil); + if(ok < 0) + fail("dial failed", sys->sprint("can't dial %s: %r", dest)); + return c.dfd; +} + +popen(ctxt: ref Draw->Context, argv: list of string): ref Sys->FD +{ + sh := load Sh Sh->PATH; + if(sh == nil) + nomod(Sh->PATH); + sync := chan of int; + fds := array[2] of ref Sys->FD; + sys->pipe(fds); + spawn runcmd(sh, ctxt, argv, fds[0], sync); + <-sync; + return fds[1]; +} + +runcmd(sh: Sh, ctxt: ref Draw->Context, argv: list of string, stdin: ref Sys->FD, + sync: chan of int) +{ + sys->pctl(Sys->FORKFD, nil); + sys->dup(stdin.fd, 0); + stdin = nil; + sync <-= 0; + sh->run(ctxt, argv); +} + +# authenticate if necessary +do_auth(keyfile, alg: string, dfd: ref Sys->FD, addr: string, unauth: int): ref Sys->FD +{ + cert : string; + + kr := load Keyring Keyring->PATH; + if(kr == nil) + nomod(Keyring->PATH); + + kd := "/usr/" + user() + "/keyring/"; + if (keyfile == nil) { + cert = kd + netmkaddr(addr, "tcp", ""); + (ok, nil) := sys->stat(cert); + if (ok < 0) + cert = kd + "default"; + } + else if (len keyfile > 0 && keyfile[0] != '/') + cert = kd + keyfile; + else + cert = keyfile; + ai := kr->readauthinfo(cert); + if (ai == nil){ + if(!unauth) + fail("readauthinfo failed", sys->sprint("cannot read %s: %r", cert)); + sys->fprint(sys->fildes(2), "mount: can't read %s (%r): trying mount as `nobody'\n", cert); + } + + au := load Auth Auth->PATH; + if(au == nil) + nomod(Auth->PATH); + + err := au->init(); + if(err != nil) + fail("auth init failed", sys->sprint("cannot init Auth: %s", err)); + + fd: ref Sys->FD; + (fd, err) = au->client(alg, ai, dfd); + if(fd == nil) + fail("auth failed", sys->sprint("authentication failed: %s", err)); + if(vflag) + sys->print("remote username is %s\n", err); + + return fd; +} + +user(): string +{ + fd := sys->open("/dev/user", sys->OREAD); + if(fd == nil) + return ""; + + buf := array[Sys->NAMEMAX] of byte; + n := sys->read(fd, buf, len buf); + if(n < 0) + return ""; + + return string buf[0:n]; +} + +netmkaddr(addr, net, svc: string): string +{ + if(net == nil) + net = "net"; + (n, l) := sys->tokenize(addr, "!"); + if(n <= 1){ + if(svc== nil) + return sys->sprint("%s!%s", net, addr); + return sys->sprint("%s!%s!%s", net, addr, svc); + } + if(svc == nil || n > 2) + return addr; + return sys->sprint("%s!%s", addr, svc); +} diff --git a/appl/cmd/yacc.b b/appl/cmd/yacc.b new file mode 100644 index 00000000..97ef87cf --- /dev/null +++ b/appl/cmd/yacc.b @@ -0,0 +1,2810 @@ +implement Yacc; + +include "sys.m"; + sys: Sys; + print, fprint, sprint: import sys; + UTFmax: import Sys; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "draw.m"; + +Yacc: module +{ + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +Arg: adt +{ + argv: list of string; + c: int; + opts: string; + + init: fn(argv: list of string): ref Arg; + opt: fn(arg: self ref Arg): int; + arg: fn(arg: self ref Arg): string; +}; + +PARSER: con "/lib/yaccpar"; +OFILE: con "tab.b"; +FILEU: con "output"; +FILED: con "tab.m"; +FILEDEBUG: con "debug"; + +# the following are adjustable +# according to memory size +ACTSIZE: con 30000; +NSTATES: con 2000; +TEMPSIZE: con 2000; + +SYMINC: con 50; # increase for non-term or term +RULEINC: con 50; # increase for max rule length prodptr[i] +PRODINC: con 100; # increase for productions prodptr +WSETINC: con 50; # increase for working sets wsets +STATEINC: con 200; # increase for states statemem + +NAMESIZE: con 50; +NTYPES: con 63; +ISIZE: con 400; + +PRIVATE: con 16rE000; # unicode private use + +# relationships which must hold: +# TEMPSIZE >= NTERMS + NNONTERM + 1 +# TEMPSIZE >= NSTATES +# + +NTBASE: con 8r10000; +ERRCODE: con 8190; +ACCEPTCODE: con 8191; +YYLEXUNK: con 3; +TOKSTART: con 4; #index of first defined token + +# no, left, right, binary assoc. +NOASC, LASC, RASC, BASC: con iota; + +# flags for state generation +DONE, MUSTDO, MUSTLOOKAHEAD: con iota; + +# flags for a rule having an action, and being reduced +ACTFLAG: con 16r4; +REDFLAG: con 16r8; + +# output parser flags +YYFLAG1: con -1000; + +# parse tokens +IDENTIFIER, MARK, TERM, LEFT, RIGHT, BINARY, PREC, LCURLY, IDENTCOLON, NUMBER, START, TYPEDEF, TYPENAME, MODULE: con PRIVATE+iota; + +ENDFILE: con 0; + +EMPTY: con 1; +WHOKNOWS: con 0; +OK: con 1; +NOMORE: con -1000; + +# macros for getting associativity and precedence levels +ASSOC(i: int): int +{ + return i & 3; +} + +PLEVEL(i: int): int +{ + return (i >> 4) & 16r3f; +} + +TYPE(i: int): int +{ + return (i >> 10) & 16r3f; +} + +# macros for setting associativity and precedence levels +SETASC(i, j: int): int +{ + return i | j; +} + +SETPLEV(i, j: int): int +{ + return i | (j << 4); +} + +SETTYPE(i, j: int): int +{ + return i | (j << 10); +} + +# I/O descriptors +stderr: ref Sys->FD; +fdefine: ref Iobuf; # file for module definition +fdebug: ref Iobuf; # y.debug for strings for debugging +ftable: ref Iobuf; # y.tab.c file +finput: ref Iobuf; # input file +foutput: ref Iobuf; # y.output file + +CodeData, CodeMod, CodeAct: con iota; +NCode: con 8192; + +Code: adt +{ + kind: int; + data: array of byte; + ndata: int; + next: cyclic ref Code; +}; + +codehead: ref Code; +codetail: ref Code; + +modname: string; # name of module +suppressmod: int; # suppress module definition +stacksize := 200; + +# communication variables between various I/O routines +infile: string; # input file name +numbval: int; # value of an input number +tokname: string; # input token name, slop for runes and 0 + +# structure declarations +Lkset: type array of int; + +Pitem: adt +{ + prod: array of int; + off: int; # offset within the production + first: int; # first term or non-term in item + prodno: int; # production number for sorting +}; + +Item: adt +{ + pitem: Pitem; + look: Lkset; +}; + +Symb: adt +{ + name: string; + value: int; +}; + +Wset: adt +{ + pitem: Pitem; + flag: int; + ws: Lkset; +}; + + # storage of names + +parser := PARSER; +yydebug: string; + + # storage of types +ntypes: int; # number of types defined +typeset := array[NTYPES] of string; # pointers to type tags + + # token information + +ntokens := 0; # number of tokens +tokset: array of Symb; +toklev: array of int; # vector with the precedence of the terminals + + # nonterminal information + +nnonter := -1; # the number of nonterminals +nontrst: array of Symb; +start: int; # start symbol + + # state information + +nstate := 0; # number of states +pstate := array[NSTATES+2] of int; # index into statemem to the descriptions of the states +statemem : array of Item; +tystate := array[NSTATES] of int; # contains type information about the states +tstates : array of int; # states generated by terminal gotos +ntstates : array of int; # states generated by nonterminal gotos +mstates := array[NSTATES] of {* => 0}; # chain of overflows of term/nonterm generation lists +lastred: int; # number of last reduction of a state +defact := array[NSTATES] of int; # default actions of states + + # lookahead set information + +lkst: array of Lkset; +nolook := 0; # flag to turn off lookahead computations +tbitset := 0; # size of lookahead sets +clset: Lkset; # temporary storage for lookahead computations + + # working set information + +wsets: array of Wset; +cwp: int; + + # storage for action table + +amem: array of int; # action table storage +memp: int; # next free action table position +indgo := array[NSTATES] of int; # index to the stored goto table + + # temporary vector, indexable by states, terms, or ntokens + +temp1 := array[TEMPSIZE] of int; # temporary storage, indexed by terms + ntokens or states +lineno := 1; # current input line number +fatfl := 1; # if on, error is fatal +nerrors := 0; # number of errors + + # assigned token type values +extval := 0; + +ytabc := OFILE; # name of y.tab.c + + # grammar rule information + +nprod := 1; # number of productions +prdptr: array of array of int; # pointers to descriptions of productions +levprd: array of int; # precedence levels for the productions +rlines: array of int; # line number for this rule + + + # statistics collection variables + +zzgoent := 0; +zzgobest := 0; +zzacent := 0; +zzexcp := 0; +zzclose := 0; +zzrrconf := 0; +zzsrconf := 0; +zzstate := 0; + + # optimizer arrays +yypgo: array of array of int; +optst: array of array of int; +ggreed: array of int; +pgo: array of int; + +maxspr: int; # maximum spread of any entry +maxoff: int; # maximum offset into a array +maxa: int; + + # storage for information about the nonterminals + +pres: array of array of array of int; # vector of pointers to productions yielding each nonterminal +pfirst: array of Lkset; +pempty: array of int; # vector of nonterminals nontrivially deriving e + # random stuff picked out from between functions + +indebug := 0; # debugging flag for cpfir +pidebug := 0; # debugging flag for putitem +gsdebug := 0; # debugging flag for stagen +cldebug := 0; # debugging flag for closure +pkdebug := 0; # debugging flag for apack +g2debug := 0; # debugging for go2gen +adb := 0; # debugging for callopt + +Resrv : adt +{ + name: string; + value: int; +}; + +resrv := array[] of { + Resrv("binary", BINARY), + Resrv("module", MODULE), + Resrv("left", LEFT), + Resrv("nonassoc", BINARY), + Resrv("prec", PREC), + Resrv("right", RIGHT), + Resrv("start", START), + Resrv("term", TERM), + Resrv("token", TERM), + Resrv("type", TYPEDEF),}; + +zznewstate := 0; + +init(nil: ref Draw->Context, argv: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + + stderr = sys->fildes(2); + + setup(argv); # initialize and read productions + + tbitset = (ntokens+32)/32; + cpres(); # make table of which productions yield a given nonterminal + cempty(); # make a table of which nonterminals can match the empty string + cpfir(); # make a table of firsts of nonterminals + + stagen(); # generate the states + + yypgo = array[nnonter+1] of array of int; + optst = array[nstate] of array of int; + output(); # write the states and the tables + go2out(); + + hideprod(); + summary(); + + callopt(); + + others(); + + if(fdefine != nil) + fdefine.close(); + if(fdebug != nil) + fdebug.close(); + if(ftable != nil) + ftable.close(); + if(foutput != nil) + foutput.close(); +} + +setup(argv: list of string) +{ + j, ty: int; + + ytab := 0; + vflag := 0; + dflag := 0; + stem := 0; + stemc := "y"; + foutput = nil; + fdefine = nil; + fdebug = nil; + arg := Arg.init(argv); + while(c := arg.opt()){ + case c{ + 'v' or 'V' => + vflag++; + 'D' => + yydebug = arg.arg(); + 'd' => + dflag++; + 'n' => + stacksize = int arg.arg(); + 'o' => + ytab++; + ytabc = arg.arg(); + 's' => + stem++; + stemc = arg.arg(); + 'm' => + suppressmod++; + * => + usage(); + } + } + argv = arg.argv; + if(len argv != 1) + usage(); + if (suppressmod && dflag) { + sys->fprint(stderr, "yacc: -m and -d are exclusive\n"); + usage(); + } + if (stacksize < 1) { + sys->fprint(stderr, "yacc: stack size too small\n"); + usage(); + } + infile = hd argv; + finput = bufio->open(infile, Bufio->OREAD); + if(finput == nil) + error("cannot open '"+infile+"'"); + + openup(stemc, dflag, vflag, ytab, ytabc); + + defin(0, "$end"); + extval = PRIVATE; # tokens start in unicode 'private use' + defin(0, "error"); + defin(1, "$accept"); + defin(0, "$unk"); + i := 0; + + for(t := gettok(); t != MARK && t != ENDFILE; ) + case t { + ';' => + t = gettok(); + + START => + if(gettok() != IDENTIFIER) + error("bad %%start construction"); + start = chfind(1, tokname); + t = gettok(); + + TYPEDEF => + if(gettok() != TYPENAME) + error("bad syntax in %%type"); + ty = numbval; + for(;;) { + t = gettok(); + case t { + IDENTIFIER => + if((t=chfind(1, tokname)) < NTBASE) { + j = TYPE(toklev[t]); + if(j != 0 && j != ty) + error("type redeclaration of token "+ + tokset[t].name); + else + toklev[t] = SETTYPE(toklev[t], ty); + } else { + j = nontrst[t-NTBASE].value; + if(j != 0 && j != ty) + error("type redeclaration of nonterminal "+ + nontrst[t-NTBASE].name); + else + nontrst[t-NTBASE].value = ty; + } + continue; + ',' => + continue; + ';' => + t = gettok(); + } + break; + } + + MODULE => + cpymodule(); + t = gettok(); + + LEFT or BINARY or RIGHT or TERM => + # nonzero means new prec. and assoc. + lev := t-TERM; + if(lev) + i++; + ty = 0; + + # get identifiers so defined + t = gettok(); + + # there is a type defined + if(t == TYPENAME) { + ty = numbval; + t = gettok(); + } + for(;;) { + case t { + ',' => + t = gettok(); + continue; + + ';' => + break; + + IDENTIFIER => + j = chfind(0, tokname); + if(j >= NTBASE) + error(tokname+" defined earlier as nonterminal"); + if(lev) { + if(ASSOC(toklev[j])) + error("redeclaration of precedence of "+tokname); + toklev[j] = SETASC(toklev[j], lev); + toklev[j] = SETPLEV(toklev[j], i); + } + if(ty) { + if(TYPE(toklev[j])) + error("redeclaration of type of "+tokname); + toklev[j] = SETTYPE(toklev[j],ty); + } + t = gettok(); + if(t == NUMBER) { + tokset[j].value = numbval; + t = gettok(); + } + continue; + } + break; + } + + LCURLY => + cpycode(); + t = gettok(); + + * => + error("syntax error"); + } + if(t == ENDFILE) + error("unexpected EOF before %%"); + if(modname == nil) + error("missing %module specification"); + + moreprod(); + prdptr[0] = array[4] of { + NTBASE, # added production + start, # if start is 0, we will overwrite with the lhs of the first rule + 1, + 0 + }; + nprod = 1; + curprod := array[RULEINC] of int; + t = gettok(); + if(t != IDENTCOLON) + error("bad syntax on first rule"); + + if(!start) + prdptr[0][1] = chfind(1, tokname); + + # read rules + # put into prdptr array in the format + # target + # followed by id's of terminals and non-terminals + # followd by -nprod + while(t != MARK && t != ENDFILE) { + mem := 0; + # process a rule + rlines[nprod] = lineno; + if(t == '|') + curprod[mem++] = prdptr[nprod-1][0]; + else if(t == IDENTCOLON) { + curprod[mem] = chfind(1, tokname); + if(curprod[mem] < NTBASE) + error("token illegal on LHS of grammar rule"); + mem++; + } else + error("illegal rule: missing semicolon or | ?"); + + # read rule body + t = gettok(); + + for(;;){ + while(t == IDENTIFIER) { + curprod[mem] = chfind(1, tokname); + if(curprod[mem] < NTBASE) + levprd[nprod] = toklev[curprod[mem]]; + mem++; + if(mem >= len curprod){ + ncurprod := array[mem+RULEINC] of int; + ncurprod[0:] = curprod; + curprod = ncurprod; + } + t = gettok(); + } + if(t == PREC) { + if(gettok() != IDENTIFIER) + error("illegal %%prec syntax"); + j = chfind(2, tokname); + if(j >= NTBASE) + error("nonterminal "+nontrst[j-NTBASE].name+" illegal after %%prec"); + levprd[nprod] = toklev[j]; + t = gettok(); + } + if(t != '=') + break; + levprd[nprod] |= ACTFLAG; + addcode(CodeAct, "\n"+string nprod+"=>"); + cpyact(curprod, mem); + + # action within rule... + if((t=gettok()) == IDENTIFIER) { + # make it a nonterminal + j = chfind(1, "$$"+string nprod); + + # + # the current rule will become rule number nprod+1 + # enter null production for action + # + prdptr[nprod] = array[2] of {j, -nprod}; + + # update the production information + nprod++; + moreprod(); + levprd[nprod] = levprd[nprod-1] & ~ACTFLAG; + levprd[nprod-1] = ACTFLAG; + rlines[nprod] = lineno; + + # make the action appear in the original rule + curprod[mem++] = j; + if(mem >= len curprod){ + ncurprod := array[mem+RULEINC] of int; + ncurprod[0:] = curprod; + curprod = ncurprod; + } + } + } + + while(t == ';') + t = gettok(); + curprod[mem++] = -nprod; + + # check that default action is reasonable + if(ntypes && !(levprd[nprod]&ACTFLAG) && nontrst[curprod[0]-NTBASE].value) { + # no explicit action, LHS has value + + tempty := curprod[1]; + if(tempty < 0) + error("must return a value, since LHS has a type"); + else + if(tempty >= NTBASE) + tempty = nontrst[tempty-NTBASE].value; + else + tempty = TYPE(toklev[tempty]); + if(tempty != nontrst[curprod[0]-NTBASE].value) + error("default action causes potential type clash"); + else{ + addcodec(CodeAct, '\n'); + addcode(CodeAct, string nprod); + addcode(CodeAct, "=>\nyyval."); + addcode(CodeAct, typeset[tempty]); + addcode(CodeAct, " = yys[yyp+1].yyv."); + addcode(CodeAct, typeset[tempty]); + addcodec(CodeAct, ';'); + } + } + moreprod(); + prdptr[nprod] = array[mem] of int; + prdptr[nprod][0:] = curprod[:mem]; + nprod++; + moreprod(); + levprd[nprod] = 0; + } + + # + # end of all rules + # dump out the prefix code + # + ftable.puts("implement "); + ftable.puts(modname); + ftable.puts(";\n"); + + dumpcode(CodeMod); + dumpmod(); + dumpcode(CodeAct); + + ftable.puts("YYEOFCODE: con 1;\n"); + ftable.puts("YYERRCODE: con 2;\n"); + ftable.puts("YYMAXDEPTH: con " + string stacksize + ";\n"); # was 150 + #ftable.puts("yyval: YYSTYPE;\n"); + + # + # copy any postfix code + # + if(t == MARK) { + ftable.puts("\n#line\t"); + ftable.puts(string lineno); + ftable.puts("\t\""); + ftable.puts(infile); + ftable.puts("\"\n"); + while((c=finput.getc()) != Bufio->EOF) + ftable.putc(c); + } + finput.close(); +} + +# +# allocate enough room to hold another production +# +moreprod() +{ + n := len prdptr; + if(nprod < n) + return; + n += PRODINC; + aprod := array[n] of array of int; + aprod[0:] = prdptr; + prdptr = aprod; + + alevprd := array[n] of int; + alevprd[0:] = levprd; + levprd = alevprd; + + arlines := array[n] of int; + arlines[0:] = rlines; + rlines = arlines; +} + +# +# define s to be a terminal if t=0 +# or a nonterminal if t=1 +# +defin(nt: int, s: string): int +{ + val := 0; + if(nt) { + nnonter++; + if(nnonter >= len nontrst){ + anontrst := array[nnonter + SYMINC] of Symb; + anontrst[0:] = nontrst; + nontrst = anontrst; + } + nontrst[nnonter] = Symb(s, 0); + return NTBASE + nnonter; + } + + # must be a token + ntokens++; + if(ntokens >= len tokset){ + atokset := array[ntokens + SYMINC] of Symb; + atokset[0:] = tokset; + tokset = atokset; + + atoklev := array[ntokens + SYMINC] of int; + atoklev[0:] = toklev; + toklev = atoklev; + } + tokset[ntokens].name = s; + toklev[ntokens] = 0; + + # establish value for token + # single character literal + if(s[0] == ' ' && len s == 1+1){ + val = s[1]; + }else if(s[0] == ' ' && s[1] == '\\') { # escape sequence + if(len s == 2+1) { + # single character escape sequence + case s[2] { + '\'' => val = '\''; + '"' => val = '"'; + '\\' => val = '\\'; + 'a' => val = '\a'; + 'b' => val = '\b'; + 'n' => val = '\n'; + 'r' => val = '\r'; + 't' => val = '\t'; + 'v' => val = '\v'; + * => + error("invalid escape "+s[1:3]); + } + }else if(s[2] == 'u' && len s == 2+1+4) { # \unnnn sequence + val = 0; + s = s[3:]; + while(s != ""){ + c := s[0]; + if(c >= '0' && c <= '9') + c -= '0'; + else if(c >= 'a' && c <= 'f') + c -= 'a' - 10; + else if(c >= 'A' && c <= 'F') + c -= 'A' - 10; + else + error("illegal \\unnnn construction"); + val = val * 16 + c; + s = s[1:]; + } + if(val == 0) + error("'\\u0000' is illegal"); + }else + error("unknown escape"); + }else + val = extval++; + + tokset[ntokens].value = val; + return ntokens; +} + +peekline := 0; +gettok(): int +{ + i, match, c: int; + + tokname = ""; + for(;;){ + reserve := 0; + lineno += peekline; + peekline = 0; + c = finput.getc(); + while(c == ' ' || c == '\n' || c == '\t' || c == '\v' || c == '\r') { + if(c == '\n') + lineno++; + c = finput.getc(); + } + + # skip comment + if(c != '#') + break; + lineno += skipcom(); + } + case c { + Bufio->EOF => + return ENDFILE; + + '{' => + finput.ungetc(); + return '='; + + '<' => + # get, and look up, a type name (union member name) + i = 0; + while((c=finput.getc()) != '>' && c != Bufio->EOF && c != '\n') + tokname[i++] = c; + if(c != '>') + error("unterminated < ... > clause"); + for(i=1; i<=ntypes; i++) + if(typeset[i] == tokname) { + numbval = i; + return TYPENAME; + } + ntypes++; + numbval = ntypes; + typeset[numbval] = tokname; + return TYPENAME; + + '"' or '\'' => + match = c; + tokname[0] = ' '; + i = 1; + for(;;) { + c = finput.getc(); + if(c == '\n' || c == Bufio->EOF) + error("illegal or missing ' or \"" ); + if(c == '\\') { + tokname[i++] = '\\'; + c = finput.getc(); + } else if(c == match) + return IDENTIFIER; + tokname[i++] = c; + } + + '%' => + case c = finput.getc(){ + '%' => return MARK; + '=' => return PREC; + '{' => return LCURLY; + } + + getword(c); + # find a reserved word + for(c=0; c < len resrv; c++) + if(tokname == resrv[c].name) + return resrv[c].value; + error("invalid escape, or illegal reserved word: "+tokname); + + '0' to '9' => + numbval = c - '0'; + while(isdigit(c = finput.getc())) + numbval = numbval*10 + c-'0'; + finput.ungetc(); + return NUMBER; + + * => + if(isword(c) || c=='.' || c=='$') + getword(c); + else + return c; + } + + # look ahead to distinguish IDENTIFIER from IDENTCOLON + c = finput.getc(); + while(c == ' ' || c == '\t'|| c == '\n' || c == '\v' || c == '\r' || c == '#') { + if(c == '\n') + peekline++; + # look for comments + if(c == '#') + peekline += skipcom(); + c = finput.getc(); + } + if(c == ':') + return IDENTCOLON; + finput.ungetc(); + return IDENTIFIER; +} + +getword(c: int) +{ + i := 0; + while(isword(c) || isdigit(c) || c == '_' || c=='.' || c=='$') { + tokname[i++] = c; + c = finput.getc(); + } + finput.ungetc(); +} + +# +# determine the type of a symbol +# +fdtype(t: int): int +{ + v : int; + s: string; + + if(t >= NTBASE) { + v = nontrst[t-NTBASE].value; + s = nontrst[t-NTBASE].name; + } else { + v = TYPE(toklev[t]); + s = tokset[t].name; + } + if(v <= 0) + error("must specify type for "+s); + return v; +} + +chfind(t: int, s: string): int +{ + if(s[0] == ' ') + t = 0; + for(i:=0; i<=ntokens; i++) + if(s == tokset[i].name) + return i; + for(i=0; i<=nnonter; i++) + if(s == nontrst[i].name) + return NTBASE+i; + + # cannot find name + if(t > 1) + error(s+" should have been defined earlier"); + return defin(t, s); +} + +# +# saves module definition in Code +# +cpymodule() +{ + if(gettok() != IDENTIFIER) + error("bad %%module construction"); + if(modname != nil) + error("duplicate %%module construction"); + modname = tokname; + + level := 0; + for(;;) { + if((c:=finput.getc()) == Bufio->EOF) + error("EOF encountered while processing %%module"); + case c { + '\n' => + lineno++; + '{' => + level++; + if(level == 1) + continue; + '}' => + level--; + + # we are finished copying + if(level == 0) + return; + } + addcodec(CodeMod, c); + } + if(codehead == nil || codetail.kind != CodeMod) + addcodec(CodeMod, '\n'); # ensure we add something +} + +# +# saves code between %{ and %} +# +cpycode() +{ + c := finput.getc(); + if(c == '\n') { + c = finput.getc(); + lineno++; + } + addcode(CodeData, "\n#line\t" + string lineno + "\t\"" + infile + "\"\n"); + while(c != Bufio->EOF) { + if(c == '%') { + if((c=finput.getc()) == '}') + return; + addcodec(CodeData, '%'); + } + addcodec(CodeData, c); + if(c == '\n') + lineno++; + c = finput.getc(); + } + error("eof before %%}"); +} + +addcode(k: int, s: string) +{ + for(i := 0; i < len s; i++) + addcodec(k, s[i]); +} + +addcodec(k, c: int) +{ + if(codehead == nil + || k != codetail.kind + || codetail.ndata >= NCode){ + cd := ref Code(k, array[NCode+UTFmax] of byte, 0, nil); + if(codehead == nil) + codehead = cd; + else + codetail.next = cd; + codetail = cd; + } + + codetail.ndata += sys->char2byte(c, codetail.data, codetail.ndata); +} + +dumpcode(til: int) +{ + for(; codehead != nil; codehead = codehead.next){ + if(codehead.kind == til) + return; + if(ftable.write(codehead.data, codehead.ndata) != codehead.ndata) + error("can't write output file"); + } +} + +# +# write out the module declaration and any token info +# +dumpmod() +{ + if(fdefine != nil) { + fdefine.puts(modname); + fdefine.puts(": module {\n"); + } + if (!suppressmod) { + ftable.puts(modname); + ftable.puts(": module {\n"); + } + + for(; codehead != nil; codehead = codehead.next){ + if(codehead.kind != CodeMod) + break; + if(ftable.write(codehead.data, codehead.ndata) != codehead.ndata) + error("can't write output file"); + if(fdefine != nil && fdefine.write(codehead.data, codehead.ndata) != codehead.ndata) + error("can't write define file"); + } + + for(i:=TOKSTART; i<=ntokens; i++) { + # non-literals + c := tokset[i].name[0]; + if(c != ' ' && c != '$') { + s := tokset[i].name+": con "+string tokset[i].value+";\n"; + ftable.puts(s); + if(fdefine != nil) + fdefine.puts(s); + } + } + + if(fdefine != nil) + fdefine.puts("};\n"); + if (!suppressmod) + ftable.puts("\n};\n"); + + if(fdebug != nil) { + fdebug.puts("yytoknames = array[] of {\n"); + for(i=1; i<=ntokens; i++) { + if(tokset[i].name != nil) + fdebug.puts("\t\""+chcopy(tokset[i].name)+"\",\n"); + else + fdebug.puts("\t\"\",\n"); + } + fdebug.puts("};\n"); + } +} + +# +# skip over comments +# skipcom is called after reading a '#' +# +skipcom(): int +{ + c := finput.getc(); + while(c != Bufio->EOF) { + if(c == '\n') + return 1; + c = finput.getc(); + } + error("EOF inside comment"); + return 0; +} + +# +# copy limbo action to the next ; or closing } +# +cpyact(curprod: array of int, max: int) +{ + addcode(CodeAct, "\n#line\t"); + addcode(CodeAct, string lineno); + addcode(CodeAct, "\t\""); + addcode(CodeAct, infile); + addcode(CodeAct, "\"\n"); + + brac := 0; + +loop: for(;;){ + c := finput.getc(); + swt: case c { + ';' => + if(brac == 0) { + addcodec(CodeAct, c); + return; + } + + '{' => + brac++; + + '$' => + s := 1; + tok := -1; + c = finput.getc(); + + # type description + if(c == '<') { + finput.ungetc(); + if(gettok() != TYPENAME) + error("bad syntax on $<ident> clause"); + tok = numbval; + c = finput.getc(); + } + if(c == '$') { + addcode(CodeAct, "yyval"); + + # put out the proper tag... + if(ntypes) { + if(tok < 0) + tok = fdtype(curprod[0]); + addcode(CodeAct, "."+typeset[tok]); + } + continue loop; + } + if(c == '-') { + s = -s; + c = finput.getc(); + } + j := 0; + if(isdigit(c)) { + while(isdigit(c)) { + j = j*10 + c-'0'; + c = finput.getc(); + } + finput.ungetc(); + j = j*s; + if(j >= max) + error("Illegal use of $" + string j); + }else if(isword(c) || c == '_' || c == '.') { + # look for $name + finput.ungetc(); + if(gettok() != IDENTIFIER) + error("$ must be followed by an identifier"); + tokn := chfind(2, tokname); + fnd := -1; + if((c = finput.getc()) != '@') + finput.ungetc(); + else if(gettok() != NUMBER) + error("@ must be followed by number"); + else + fnd = numbval; + for(j=1; j<max; j++){ + if(tokn == curprod[j]) { + fnd--; + if(fnd <= 0) + break; + } + } + if(j >= max) + error("$name or $name@number not found"); + }else{ + addcodec(CodeAct, '$'); + if(s < 0) + addcodec(CodeAct, '-'); + finput.ungetc(); + continue loop; + } + addcode(CodeAct, "yys[yypt-" + string(max-j-1) + "].yyv"); + + # put out the proper tag + if(ntypes) { + if(j <= 0 && tok < 0) + error("must specify type of $" + string j); + if(tok < 0) + tok = fdtype(curprod[j]); + addcodec(CodeAct, '.'); + addcode(CodeAct, typeset[tok]); + } + continue loop; + + '}' => + brac--; + if(brac) + break; + addcodec(CodeAct, c); + return; + + '#' => + # a comment + addcodec(CodeAct, c); + c = finput.getc(); + while(c != Bufio->EOF) { + if(c == '\n') { + lineno++; + break swt; + } + addcodec(CodeAct, c); + c = finput.getc(); + } + error("EOF inside comment"); + + '\''or '"' => + # character string or constant + match := c; + addcodec(CodeAct, c); + while(c = finput.getc()) { + if(c == '\\') { + addcodec(CodeAct, c); + c = finput.getc(); + if(c == '\n') + lineno++; + } else if(c == match) + break swt; + if(c == '\n') + error("newline in string or char const."); + addcodec(CodeAct, c); + } + error("EOF in string or character constant"); + + Bufio->EOF => + error("action does not terminate"); + + '\n' => + lineno++; + } + + addcodec(CodeAct, c); + } +} + +openup(stem: string, dflag, vflag, ytab: int, ytabc: string) +{ + buf: string; + if(vflag) { + buf = stem + "." + FILEU; + foutput = bufio->create(buf, Bufio->OWRITE, 8r666); + if(foutput == nil) + error("can't create " + buf); + } + if(yydebug != nil) { + buf = stem + "." + FILEDEBUG; + fdebug = bufio->create(buf, Bufio->OWRITE, 8r666); + if(fdebug == nil) + error("can't create " + buf); + } + if(dflag) { + buf = stem + "." + FILED; + fdefine = bufio->create(buf, Bufio->OWRITE, 8r666); + if(fdefine == nil) + error("can't create " + buf); + } + if(ytab == 0) + buf = stem + "." + OFILE; + else + buf = ytabc; + ftable = bufio->create(buf, Bufio->OWRITE, 8r666); + if(ftable == nil) + error("can't create file " + buf); +} + +# +# return a pointer to the name of symbol i +# +symnam(i: int): string +{ + s: string; + if(i >= NTBASE) + s = nontrst[i-NTBASE].name; + else + s = tokset[i].name; + if(s[0] == ' ') + s = s[1:]; + return s; +} + +# +# write out error comment +# +error(s: string) +{ + nerrors++; + fprint(stderr, "yacc: fatal error: %s, %s:%d\n", s, infile, lineno); + if(!fatfl) + return; + summary(); + raise "fail:error"; +} + +# +# set elements 0 through n-1 to c +# +aryfil(v: array of int, n, c: int) +{ + for(i:=0; i<n; i++) + v[i] = c; +} + +# +# compute an array with the beginnings of productions yielding given nonterminals +# The array pres points to these lists +# the array pyield has the lists: the total size is only NPROD+1 +# +cpres() +{ + pres = array[nnonter+1] of array of array of int; + curres := array[nprod] of array of int; + for(i:=0; i<=nnonter; i++) { + n := 0; + c := i+NTBASE; + fatfl = 0; # make undefined symbols nonfatal + for(j:=0; j<nprod; j++) + if(prdptr[j][0] == c) + curres[n++] = prdptr[j][1:]; + if(n == 0) + error("nonterminal " + nontrst[i].name + " not defined!"); + else{ + pres[i] = array[n] of array of int; + pres[i][0:] = curres[:n]; + } + } + fatfl = 1; + if(nerrors) { + summary(); + raise "fail:error"; + } +} + +dumppres() +{ + for(i := 0; i <= nnonter; i++){ + print("nonterm %d\n", i); + curres := pres[i]; + for(j := 0; j < len curres; j++){ + print("\tproduction %d:", j); + prd := curres[j]; + for(k := 0; k < len prd; k++) + print(" %d", prd[k]); + print("\n"); + } + } +} + +# +# mark nonterminals which derive the empty string +# also, look for nonterminals which don't derive any token strings +# +cempty() +{ + i, p, np: int; + prd: array of int; + + pempty = array[nnonter+1] of int; + + # first, use the array pempty to detect productions that can never be reduced + # set pempty to WHONOWS + aryfil(pempty, nnonter+1, WHOKNOWS); + + # now, look at productions, marking nonterminals which derive something +more: for(;;){ + for(i=0; i<nprod; i++) { + prd = prdptr[i]; + if(pempty[prd[0] - NTBASE]) + continue; + np = len prd - 1; + for(p = 1; p < np; p++) + if(prd[p] >= NTBASE && pempty[prd[p]-NTBASE] == WHOKNOWS) + break; + # production can be derived + if(p == np) { + pempty[prd[0]-NTBASE] = OK; + continue more; + } + } + break; + } + + # now, look at the nonterminals, to see if they are all OK + for(i=0; i<=nnonter; i++) { + # the added production rises or falls as the start symbol ... + if(i == 0) + continue; + if(pempty[i] != OK) { + fatfl = 0; + error("nonterminal " + nontrst[i].name + " never derives any token string"); + } + } + + if(nerrors) { + summary(); + raise "fail:error"; + } + + # now, compute the pempty array, to see which nonterminals derive the empty string + # set pempty to WHOKNOWS + aryfil(pempty, nnonter+1, WHOKNOWS); + + # loop as long as we keep finding empty nonterminals + +again: for(;;){ + next: for(i=1; i<nprod; i++) { + # not known to be empty + prd = prdptr[i]; + if(pempty[prd[0]-NTBASE] != WHOKNOWS) + continue; + np = len prd - 1; + for(p = 1; p < np; p++) + if(prd[p] < NTBASE || pempty[prd[p]-NTBASE] != EMPTY) + continue next; + + # we have a nontrivially empty nonterminal + pempty[prd[0]-NTBASE] = EMPTY; + # got one ... try for another + continue again; + } + return; + } +} + +dumpempty() +{ + for(i := 0; i <= nnonter; i++) + if(pempty[i] == EMPTY) + print("non-term %d %s matches empty\n", i, symnam(i+NTBASE)); +} + +# +# compute an array with the first of nonterminals +# +cpfir() +{ + s, n, p, np, ch: int; + curres: array of array of int; + prd: array of int; + + wsets = array[nnonter+WSETINC] of Wset; + pfirst = array[nnonter+1] of Lkset; + for(i:=0; i<=nnonter; i++) { + wsets[i].ws = mkset(); + pfirst[i] = mkset(); + curres = pres[i]; + n = len curres; + # initially fill the sets + for(s = 0; s < n; s++) { + prd = curres[s]; + np = len prd - 1; + for(p = 0; p < np; p++) { + ch = prd[p]; + if(ch < NTBASE) { + setbit(pfirst[i], ch); + break; + } + if(!pempty[ch-NTBASE]) + break; + } + } + } + + # now, reflect transitivity + changes := 1; + while(changes) { + changes = 0; + for(i=0; i<=nnonter; i++) { + curres = pres[i]; + n = len curres; + for(s = 0; s < n; s++) { + prd = curres[s]; + np = len prd - 1; + for(p = 0; p < np; p++) { + ch = prd[p] - NTBASE; + if(ch < 0) + break; + changes |= setunion(pfirst[i], pfirst[ch]); + if(!pempty[ch]) + break; + } + } + } + } + + if(!indebug) + return; + if(foutput != nil){ + for(i=0; i<=nnonter; i++) { + foutput.putc('\n'); + foutput.puts(nontrst[i].name); + foutput.puts(": "); + prlook(pfirst[i]); + foutput.putc(' '); + foutput.puts(string pempty[i]); + foutput.putc('\n'); + } + } +} + +# +# generate the states +# +stagen() +{ + # initialize + nstate = 0; + tstates = array[ntokens+1] of {* => 0}; # states generated by terminal gotos + ntstates = array[nnonter+1] of {* => 0};# states generated by nonterminal gotos + amem = array[ACTSIZE] of {* => 0}; + memp = 0; + + clset = mkset(); + pstate[0] = pstate[1] = 0; + aryfil(clset, tbitset, 0); + putitem(Pitem(prdptr[0], 0, 0, 0), clset); + tystate[0] = MUSTDO; + nstate = 1; + pstate[2] = pstate[1]; + + # + # now, the main state generation loop + # first pass generates all of the states + # later passes fix up lookahead + # could be sped up a lot by remembering + # results of the first pass rather than recomputing + # + first := 1; + for(more := 1; more; first = 0){ + more = 0; + for(i:=0; i<nstate; i++) { + if(tystate[i] != MUSTDO) + continue; + + tystate[i] = DONE; + aryfil(temp1, nnonter+1, 0); + + # take state i, close it, and do gotos + closure(i); + + # generate goto's + for(p:=0; p<cwp; p++) { + pi := wsets[p]; + if(pi.flag) + continue; + wsets[p].flag = 1; + c := pi.pitem.first; + if(c <= 1) { + if(pstate[i+1]-pstate[i] <= p) + tystate[i] = MUSTLOOKAHEAD; + continue; + } + # do a goto on c + putitem(wsets[p].pitem, wsets[p].ws); + for(q:=p+1; q<cwp; q++) { + # this item contributes to the goto + if(c == wsets[q].pitem.first) { + putitem(wsets[q].pitem, wsets[q].ws); + wsets[q].flag = 1; + } + } + + if(c < NTBASE) + state(c); # register new state + else + temp1[c-NTBASE] = state(c); + } + + if(gsdebug && foutput != nil) { + foutput.puts(string i + ": "); + for(j:=0; j<=nnonter; j++) + if(temp1[j]) + foutput.puts(nontrst[j].name + " " + string temp1[j] + ", "); + foutput.putc('\n'); + } + + if(first) + indgo[i] = apack(temp1[1:], nnonter-1) - 1; + + more++; + } + } +} + +# +# generate the closure of state i +# +closure(i: int) +{ + zzclose++; + + # first, copy kernel of state i to wsets + cwp = 0; + q := pstate[i+1]; + for(p:=pstate[i]; p<q; p++) { + wsets[cwp].pitem = statemem[p].pitem; + wsets[cwp].flag = 1; # this item must get closed + wsets[cwp].ws[0:] = statemem[p].look; + cwp++; + } + + # now, go through the loop, closing each item + work := 1; + while(work) { + work = 0; + for(u:=0; u<cwp; u++) { + if(wsets[u].flag == 0) + continue; + # dot is before c + c := wsets[u].pitem.first; + if(c < NTBASE) { + wsets[u].flag = 0; + # only interesting case is where . is before nonterminal + continue; + } + + # compute the lookahead + aryfil(clset, tbitset, 0); + + # find items involving c + for(v:=u; v<cwp; v++) { + if(wsets[v].flag != 1 + || wsets[v].pitem.first != c) + continue; + pi := wsets[v].pitem.prod; + ipi := wsets[v].pitem.off + 1; + + wsets[v].flag = 0; + if(nolook) + continue; + while((ch := pi[ipi++]) > 0) { + # terminal symbol + if(ch < NTBASE) { + setbit(clset, ch); + break; + } + # nonterminal symbol + setunion(clset, pfirst[ch-NTBASE]); + if(!pempty[ch-NTBASE]) + break; + } + if(ch <= 0) + setunion(clset, wsets[v].ws); + } + + # + # now loop over productions derived from c + # + curres := pres[c - NTBASE]; + n := len curres; + # initially fill the sets + nexts: for(s := 0; s < n; s++) { + prd := curres[s]; + # + # put these items into the closure + # is the item there + # + for(v=0; v<cwp; v++) { + # yes, it is there + if(wsets[v].pitem.off == 0 + && wsets[v].pitem.prod == prd) { + if(!nolook && setunion(wsets[v].ws, clset)) + wsets[v].flag = work = 1; + continue nexts; + } + } + + # not there; make a new entry + if(cwp >= len wsets){ + awsets := array[cwp + WSETINC] of Wset; + awsets[0:] = wsets; + wsets = awsets; + } + wsets[cwp].pitem = Pitem(prd, 0, prd[0], -prd[len prd-1]); + wsets[cwp].flag = 1; + wsets[cwp].ws = mkset(); + if(!nolook) { + work = 1; + wsets[cwp].ws[0:] = clset; + } + cwp++; + } + } + } + + # have computed closure; flags are reset; return + if(cldebug && foutput != nil) { + foutput.puts("\nState " + string i + ", nolook = " + string nolook + "\n"); + for(u:=0; u<cwp; u++) { + if(wsets[u].flag) + foutput.puts("flag set!\n"); + wsets[u].flag = 0; + foutput.putc('\t'); + foutput.puts(writem(wsets[u].pitem)); + prlook(wsets[u].ws); + foutput.putc('\n'); + } + } +} + +# +# sorts last state,and sees if it equals earlier ones. returns state number +# +state(c: int): int +{ + zzstate++; + p1 := pstate[nstate]; + p2 := pstate[nstate+1]; + if(p1 == p2) + return 0; # null state + # sort the items + k, l: int; + for(k = p1+1; k < p2; k++) { # make k the biggest + for(l = k; l > p1; l--) { + if(statemem[l].pitem.prodno < statemem[l-1].pitem.prodno + || statemem[l].pitem.prodno == statemem[l-1].pitem.prodno + && statemem[l].pitem.off < statemem[l-1].pitem.off) { + s := statemem[l]; + statemem[l] = statemem[l-1]; + statemem[l-1] = s; + }else + break; + } + } + + size1 := p2 - p1; # size of state + + if(c >= NTBASE) + i := ntstates[c-NTBASE]; + else + i = tstates[c]; + +look: for(; i != 0; i = mstates[i]) { + # get ith state + q1 := pstate[i]; + q2 := pstate[i+1]; + size2 := q2 - q1; + if(size1 != size2) + continue; + k = p1; + for(l = q1; l < q2; l++) { + if(statemem[l].pitem.prod != statemem[k].pitem.prod + || statemem[l].pitem.off != statemem[k].pitem.off) + continue look; + k++; + } + + # found it + pstate[nstate+1] = pstate[nstate]; # delete last state + # fix up lookaheads + if(nolook) + return i; + k = p1; + for(l = q1; l < q2; l++) { + if(setunion(statemem[l].look, statemem[k].look)) + tystate[i] = MUSTDO; + k++; + } + return i; + } + # state is new + zznewstate++; + if(nolook) + error("yacc state/nolook error"); + pstate[nstate+2] = p2; + if(nstate+1 >= NSTATES) + error("too many states"); + if(c >= NTBASE) { + mstates[nstate] = ntstates[c-NTBASE]; + ntstates[c-NTBASE] = nstate; + } else { + mstates[nstate] = tstates[c]; + tstates[c] = nstate; + } + tystate[nstate] = MUSTDO; + return nstate++; +} + +putitem(p: Pitem, set: Lkset) +{ + p.off++; + p.first = p.prod[p.off]; + + if(pidebug && foutput != nil) + foutput.puts("putitem(" + writem(p) + "), state " + string nstate + "\n"); + j := pstate[nstate+1]; + if(j >= len statemem){ + asm := array[j + STATEINC] of Item; + asm[0:] = statemem; + statemem = asm; + } + statemem[j].pitem = p; + if(!nolook){ + s := mkset(); + s[0:] = set; + statemem[j].look = s; + } + j++; + pstate[nstate+1] = j; +} + +# +# creates output string for item pointed to by pp +# +writem(pp: Pitem): string +{ + i: int; + p := pp.prod; + q := chcopy(nontrst[prdptr[pp.prodno][0]-NTBASE].name) + ": "; + npi := pp.off; + pi := p == prdptr[pp.prodno]; + for(;;){ + c := ' '; + if(pi == npi) + c = '.'; + q[len q] = c; + i = p[pi++]; + if(i <= 0) + break; + q += chcopy(symnam(i)); + } + + # an item calling for a reduction + i = p[npi]; + if(i < 0) + q += " (" + string -i + ")"; + return q; +} + +# +# pack state i from temp1 into amem +# +apack(p: array of int, n: int): int +{ + # + # we don't need to worry about checking because + # we will only look at entries known to be there... + # eliminate leading and trailing 0's + # + off := 0; + for(pp := 0; pp <= n && p[pp] == 0; pp++) + off--; + # no actions + if(pp > n) + return 0; + for(; n > pp && p[n] == 0; n--) + ; + p = p[pp:n+1]; + + # now, find a place for the elements from p to q, inclusive + r := len amem - len p; +nextk: for(rr := 0; rr <= r; rr++) { + qq := rr; + for(pp = 0; pp < len p; pp++) { + if(p[pp] != 0) + if(p[pp] != amem[qq] && amem[qq] != 0) + continue nextk; + qq++; + } + + # we have found an acceptable k + if(pkdebug && foutput != nil) + foutput.puts("off = " + string(off+rr) + ", k = " + string rr + "\n"); + qq = rr; + for(pp = 0; pp < len p; pp++) { + if(p[pp]) { + if(qq > memp) + memp = qq; + amem[qq] = p[pp]; + } + qq++; + } + if(pkdebug && foutput != nil) { + for(pp = 0; pp <= memp; pp += 10) { + foutput.putc('\t'); + for(qq = pp; qq <= pp+9; qq++) + foutput.puts(string amem[qq] + " "); + foutput.putc('\n'); + } + } + return off + rr; + } + error("no space in action table"); + return 0; +} + +# +# print the output for the states +# +output() +{ + c, u, v: int; + + ftable.puts("yyexca := array[] of {"); + if(fdebug != nil) + fdebug.puts("yystates = array [] of {\n"); + + noset := mkset(); + + # output the stuff for state i + for(i:=0; i<nstate; i++) { + nolook = tystate[i]!=MUSTLOOKAHEAD; + closure(i); + + # output actions + nolook = 1; + aryfil(temp1, ntokens+nnonter+1, 0); + for(u=0; u<cwp; u++) { + c = wsets[u].pitem.first; + if(c > 1 && c < NTBASE && temp1[c] == 0) { + for(v=u; v<cwp; v++) + if(c == wsets[v].pitem.first) + putitem(wsets[v].pitem, noset); + temp1[c] = state(c); + } else + if(c > NTBASE && temp1[(c -= NTBASE) + ntokens] == 0) + temp1[c+ntokens] = amem[indgo[i]+c]; + } + if(i == 1) + temp1[1] = ACCEPTCODE; + + # now, we have the shifts; look at the reductions + lastred = 0; + for(u=0; u<cwp; u++) { + c = wsets[u].pitem.first; + + # reduction + if(c > 0) + continue; + lastred = -c; + us := wsets[u].ws; + for(k:=0; k<=ntokens; k++) { + if(!bitset(us, k)) + continue; + if(temp1[k] == 0) + temp1[k] = c; + else + if(temp1[k] < 0) { # reduce/reduce conflict + if(foutput != nil) + foutput.puts( + "\n" + string i + ": reduce/reduce conflict (red'ns " + + string -temp1[k] + " and " + string lastred + " ) on " + symnam(k)); + if(-temp1[k] > lastred) + temp1[k] = -lastred; + zzrrconf++; + } else + # potential shift/reduce conflict + precftn(lastred, k, i); + } + } + wract(i); + } + + if(fdebug != nil) + fdebug.puts("};\n"); + ftable.puts("};\n"); + ftable.puts("YYNPROD: con " + string nprod + ";\n"); + ftable.puts("YYPRIVATE: con " + string PRIVATE + ";\n"); + ftable.puts("yytoknames: array of string;\n"); + ftable.puts("yystates: array of string;\n"); + if(yydebug != nil){ + ftable.puts("include \"y.debug\";\n"); + ftable.puts("yydebug: con " + yydebug + ";\n"); + }else{ + ftable.puts("yydebug: con 0;\n"); + } +} + +# +# decide a shift/reduce conflict by precedence. +# r is a rule number, t a token number +# the conflict is in state s +# temp1[t] is changed to reflect the action +# +precftn(r, t, s: int) +{ + action: int; + + lp := levprd[r]; + lt := toklev[t]; + if(PLEVEL(lt) == 0 || PLEVEL(lp) == 0) { + + # conflict + if(foutput != nil) + foutput.puts( + "\n" + string s + ": shift/reduce conflict (shift " + + string temp1[t] + "(" + string PLEVEL(lt) + "), red'n " + + string r + "(" + string PLEVEL(lp) + ")) on " + symnam(t)); + zzsrconf++; + return; + } + if(PLEVEL(lt) == PLEVEL(lp)) + action = ASSOC(lt); + else if(PLEVEL(lt) > PLEVEL(lp)) + action = RASC; # shift + else + action = LASC; # reduce + case action{ + BASC => # error action + temp1[t] = ERRCODE; + LASC => # reduce + temp1[t] = -r; + } +} + +# +# output state i +# temp1 has the actions, lastred the default +# +wract(i: int) +{ + p, p1: int; + + # find the best choice for lastred + lastred = 0; + ntimes := 0; + for(j:=0; j<=ntokens; j++) { + if(temp1[j] >= 0) + continue; + if(temp1[j]+lastred == 0) + continue; + # count the number of appearances of temp1[j] + count := 0; + tred := -temp1[j]; + levprd[tred] |= REDFLAG; + for(p=0; p<=ntokens; p++) + if(temp1[p]+tred == 0) + count++; + if(count > ntimes) { + lastred = tred; + ntimes = count; + } + } + + # + # for error recovery, arrange that, if there is a shift on the + # error recovery token, `error', that the default be the error action + # + if(temp1[2] > 0) + lastred = 0; + + # clear out entries in temp1 which equal lastred + # count entries in optst table + n := 0; + for(p=0; p<=ntokens; p++) { + p1 = temp1[p]; + if(p1+lastred == 0) + temp1[p] = p1 = 0; + if(p1 > 0 && p1 != ACCEPTCODE && p1 != ERRCODE) + n++; + } + + wrstate(i); + defact[i] = lastred; + flag := 0; + os := array[n*2] of int; + n = 0; + for(p=0; p<=ntokens; p++) { + if((p1=temp1[p]) != 0) { + if(p1 < 0) { + p1 = -p1; + } else if(p1 == ACCEPTCODE) { + p1 = -1; + } else if(p1 == ERRCODE) { + p1 = 0; + } else { + os[n++] = p; + os[n++] = p1; + zzacent++; + continue; + } + if(flag++ == 0) + ftable.puts("-1, " + string i + ",\n"); + ftable.puts("\t" + string p + ", " + string p1 + ",\n"); + zzexcp++; + } + } + if(flag) { + defact[i] = -2; + ftable.puts("\t-2, " + string lastred + ",\n"); + } + optst[i] = os; +} + +# +# writes state i +# +wrstate(i: int) +{ + j0, j1, u: int; + pp, qq: int; + + if(fdebug != nil) { + if(lastred) { + fdebug.puts(" nil, #" + string i + "\n"); + } else { + fdebug.puts(" \""); + qq = pstate[i+1]; + for(pp=pstate[i]; pp<qq; pp++){ + fdebug.puts(writem(statemem[pp].pitem)); + fdebug.puts("\\n"); + } + if(tystate[i] == MUSTLOOKAHEAD) + for(u = pstate[i+1] - pstate[i]; u < cwp; u++) + if(wsets[u].pitem.first < 0){ + fdebug.puts(writem(wsets[u].pitem)); + fdebug.puts("\\n"); + } + fdebug.puts("\", #" + string i + "/\n"); + } + } + if(foutput == nil) + return; + foutput.puts("\nstate " + string i + "\n"); + qq = pstate[i+1]; + for(pp=pstate[i]; pp<qq; pp++){ + foutput.putc('\t'); + foutput.puts(writem(statemem[pp].pitem)); + foutput.putc('\n'); + } + if(tystate[i] == MUSTLOOKAHEAD) { + # print out empty productions in closure + for(u = pstate[i+1] - pstate[i]; u < cwp; u++) { + if(wsets[u].pitem.first < 0) { + foutput.putc('\t'); + foutput.puts(writem(wsets[u].pitem)); + foutput.putc('\n'); + } + } + } + + # check for state equal to another + for(j0=0; j0<=ntokens; j0++) + if((j1=temp1[j0]) != 0) { + foutput.puts("\n\t" + symnam(j0) + " "); + # shift, error, or accept + if(j1 > 0) { + if(j1 == ACCEPTCODE) + foutput.puts("accept"); + else if(j1 == ERRCODE) + foutput.puts("error"); + else + foutput.puts("shift "+string j1); + } else + foutput.puts("reduce " + string -j1 + " (src line " + string rlines[-j1] + ")"); + } + + # output the final production + if(lastred) + foutput.puts("\n\t. reduce " + string lastred + " (src line " + string rlines[lastred] + ")\n\n"); + else + foutput.puts("\n\t. error\n\n"); + + # now, output nonterminal actions + j1 = ntokens; + for(j0 = 1; j0 <= nnonter; j0++) { + j1++; + if(temp1[j1]) + foutput.puts("\t" + symnam(j0+NTBASE) + " goto " + string temp1[j1] + "\n"); + } +} + +# +# output the gotos for the nontermninals +# +go2out() +{ + for(i := 1; i <= nnonter; i++) { + go2gen(i); + + # find the best one to make default + best := -1; + times := 0; + + # is j the most frequent + for(j := 0; j < nstate; j++) { + if(tystate[j] == 0) + continue; + if(tystate[j] == best) + continue; + + # is tystate[j] the most frequent + count := 0; + cbest := tystate[j]; + for(k := j; k < nstate; k++) + if(tystate[k] == cbest) + count++; + if(count > times) { + best = cbest; + times = count; + } + } + + # best is now the default entry + zzgobest += times-1; + n := 0; + for(j = 0; j < nstate; j++) + if(tystate[j] != 0 && tystate[j] != best) + n++; + goent := array[2*n+1] of int; + n = 0; + for(j = 0; j < nstate; j++) + if(tystate[j] != 0 && tystate[j] != best) { + goent[n++] = j; + goent[n++] = tystate[j]; + zzgoent++; + } + + # now, the default + if(best == -1) + best = 0; + zzgoent++; + goent[n] = best; + yypgo[i] = goent; + } +} + +# +# output the gotos for nonterminal c +# +go2gen(c: int) +{ + i, cc, p, q: int; + + # first, find nonterminals with gotos on c + aryfil(temp1, nnonter+1, 0); + temp1[c] = 1; + work := 1; + while(work) { + work = 0; + for(i=0; i<nprod; i++) { + # cc is a nonterminal with a goto on c + cc = prdptr[i][1]-NTBASE; + if(cc >= 0 && temp1[cc] != 0) { + # thus, the left side of production i does too + cc = prdptr[i][0]-NTBASE; + if(temp1[cc] == 0) { + work = 1; + temp1[cc] = 1; + } + } + } + } + + # now, we have temp1[c] = 1 if a goto on c in closure of cc + if(g2debug && foutput != nil) { + foutput.puts(nontrst[c].name); + foutput.puts(": gotos on "); + for(i=0; i<=nnonter; i++) + if(temp1[i]){ + foutput.puts(nontrst[i].name); + foutput.putc(' '); + } + foutput.putc('\n'); + } + + # now, go through and put gotos into tystate + aryfil(tystate, nstate, 0); + for(i=0; i<nstate; i++) { + q = pstate[i+1]; + for(p=pstate[i]; p<q; p++) { + if((cc = statemem[p].pitem.first) >= NTBASE) { + # goto on c is possible + if(temp1[cc-NTBASE]) { + tystate[i] = amem[indgo[i]+c]; + break; + } + } + } + } +} + +# +# in order to free up the mem and amem arrays for the optimizer, +# and still be able to output yyr1, etc., after the sizes of +# the action array is known, we hide the nonterminals +# derived by productions in levprd. +# +hideprod() +{ + j := 0; + levprd[0] = 0; + for(i:=1; i<nprod; i++) { + if(!(levprd[i] & REDFLAG)) { + j++; + if(foutput != nil) { + foutput.puts("Rule not reduced: "); + foutput.puts(writem(Pitem(prdptr[i], 0, 0, i))); + foutput.putc('\n'); + } + } + levprd[i] = prdptr[i][0] - NTBASE; + } + if(j) + print("%d rules never reduced\n", j); +} + +callopt() +{ + j, k, p, q: int; + v: array of int; + + pgo = array[nnonter+1] of int; + pgo[0] = 0; + maxoff = 0; + maxspr = 0; + for(i := 0; i < nstate; i++) { + k = 32000; + j = 0; + v = optst[i]; + q = len v; + for(p = 0; p < q; p += 2) { + if(v[p] > j) + j = v[p]; + if(v[p] < k) + k = v[p]; + } + # nontrivial situation + if(k <= j) { + # j is now the range +# j -= k; # call scj + if(k > maxoff) + maxoff = k; + } + tystate[i] = q + 2*j; + if(j > maxspr) + maxspr = j; + } + + # initialize ggreed table + ggreed = array[nnonter+1] of int; + for(i = 1; i <= nnonter; i++) { + ggreed[i] = 1; + j = 0; + + # minimum entry index is always 0 + v = yypgo[i]; + q = len v - 1; + for(p = 0; p < q ; p += 2) { + ggreed[i] += 2; + if(v[p] > j) + j = v[p]; + } + ggreed[i] = ggreed[i] + 2*j; + if(j > maxoff) + maxoff = j; + } + + # now, prepare to put the shift actions into the amem array + for(i = 0; i < ACTSIZE; i++) + amem[i] = 0; + maxa = 0; + for(i = 0; i < nstate; i++) { + if(tystate[i] == 0 && adb > 1) + ftable.puts("State " + string i + ": null\n"); + indgo[i] = YYFLAG1; + } + while((i = nxti()) != NOMORE) + if(i >= 0) + stin(i); + else + gin(-i); + + # print amem array + if(adb > 2) + for(p = 0; p <= maxa; p += 10) { + ftable.puts(string p + " "); + for(i = 0; i < 10; i++) + ftable.puts(string amem[p+i] + " "); + ftable.putc('\n'); + } + + aoutput(); + osummary(); +} + +# +# finds the next i +# +nxti(): int +{ + max := 0; + maxi := 0; + for(i := 1; i <= nnonter; i++) + if(ggreed[i] >= max) { + max = ggreed[i]; + maxi = -i; + } + for(i = 0; i < nstate; i++) + if(tystate[i] >= max) { + max = tystate[i]; + maxi = i; + } + if(max == 0) + return NOMORE; + return maxi; +} + +gin(i: int) +{ + s: int; + + # enter gotos on nonterminal i into array amem + ggreed[i] = 0; + + q := yypgo[i]; + nq := len q - 1; + # now, find amem place for it +nextgp: for(p := 0; p < ACTSIZE; p++) { + if(amem[p]) + continue; + for(r := 0; r < nq; r += 2) { + s = p + q[r] + 1; + if(s > maxa){ + maxa = s; + if(maxa >= ACTSIZE) + error("a array overflow"); + } + if(amem[s]) + continue nextgp; + } + # we have found amem spot + amem[p] = q[nq]; + if(p > maxa) + maxa = p; + for(r = 0; r < nq; r += 2) { + s = p + q[r] + 1; + amem[s] = q[r+1]; + } + pgo[i] = p; + if(adb > 1) + ftable.puts("Nonterminal " + string i + ", entry at " + string pgo[i] + "\n"); + return; + } + error("cannot place goto " + string i + "\n"); +} + +stin(i: int) +{ + s: int; + + tystate[i] = 0; + + # enter state i into the amem array + q := optst[i]; + nq := len q; + # find an acceptable place +nextn: for(n := -maxoff; n < ACTSIZE; n++) { + flag := 0; + for(r := 0; r < nq; r += 2) { + s = q[r] + n; + if(s < 0 || s > ACTSIZE) + continue nextn; + if(amem[s] == 0) + flag++; + else if(amem[s] != q[r+1]) + continue nextn; + } + + # check the position equals another only if the states are identical + for(j:=0; j<nstate; j++) { + if(indgo[j] == n) { + + # we have some disagreement + if(flag) + continue nextn; + if(nq == len optst[j]) { + + # states are equal + indgo[i] = n; + if(adb > 1) + ftable.puts("State " + string i + ": entry at " + + string n + " equals state " + string j + "\n"); + return; + } + + # we have some disagreement + continue nextn; + } + } + + for(r = 0; r < nq; r += 2) { + s = q[r] + n; + if(s > maxa) + maxa = s; + if(amem[s] != 0 && amem[s] != q[r+1]) + error("clobber of a array, pos'n " + string s + ", by " + string q[r+1] + ""); + amem[s] = q[r+1]; + } + indgo[i] = n; + if(adb > 1) + ftable.puts("State " + string i + ": entry at " + string indgo[i] + "\n"); + return; + } + error("Error; failure to place state " + string i + "\n"); +} + +# +# this version is for limbo +# write out the optimized parser +# +aoutput() +{ + ftable.puts("YYLAST:\tcon "+string (maxa+1)+";\n"); + arout("yyact", amem, maxa+1); + arout("yypact", indgo, nstate); + arout("yypgo", pgo, nnonter+1); +} + +# +# put out other arrays, copy the parsers +# +others() +{ + finput = bufio->open(parser, Bufio->OREAD); + if(finput == nil) + error("cannot find parser " + parser); + arout("yyr1", levprd, nprod); + aryfil(temp1, nprod, 0); + + # + #yyr2 is the number of rules for each production + # + for(i:=1; i<nprod; i++) + temp1[i] = len prdptr[i] - 2; + arout("yyr2", temp1, nprod); + + aryfil(temp1, nstate, -1000); + for(i=0; i<=ntokens; i++) + for(j:=tstates[i]; j!=0; j=mstates[j]) + temp1[j] = i; + for(i=0; i<=nnonter; i++) + for(j=ntstates[i]; j!=0; j=mstates[j]) + temp1[j] = -i; + arout("yychk", temp1, nstate); + arout("yydef", defact, nstate); + + # put out token translation tables + # table 1 has 0-256 + aryfil(temp1, 256, 0); + c := 0; + for(i=1; i<=ntokens; i++) { + j = tokset[i].value; + if(j >= 0 && j < 256) { + if(temp1[j]) { + print("yacc bug -- cant have 2 different Ts with same value\n"); + print(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name); + nerrors++; + } + temp1[j] = i; + if(j > c) + c = j; + } + } + for(i = 0; i <= c; i++) + if(temp1[i] == 0) + temp1[i] = YYLEXUNK; + arout("yytok1", temp1, c+1); + + # table 2 has PRIVATE-PRIVATE+256 + aryfil(temp1, 256, 0); + c = 0; + for(i=1; i<=ntokens; i++) { + j = tokset[i].value - PRIVATE; + if(j >= 0 && j < 256) { + if(temp1[j]) { + print("yacc bug -- cant have 2 different Ts with same value\n"); + print(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name); + nerrors++; + } + temp1[j] = i; + if(j > c) + c = j; + } + } + arout("yytok2", temp1, c+1); + + # table 3 has everything else + ftable.puts("yytok3 := array[] of {\n"); + c = 0; + for(i=1; i<=ntokens; i++) { + j = tokset[i].value; + if(j >= 0 && j < 256) + continue; + if(j >= PRIVATE && j < 256+PRIVATE) + continue; + + ftable.puts(sprint("%4d,%4d,", j, i)); + c++; + if(c%5 == 0) + ftable.putc('\n'); + } + ftable.puts(sprint("%4d\n};\n", 0)); + + # copy parser text + while((c=finput.getc()) != Bufio->EOF) { + if(c == '$') { + if((c = finput.getc()) != 'A') + ftable.putc('$'); + else { # copy actions + if(codehead == nil) + ftable.puts("* => ;"); + else + dumpcode(-1); + c = finput.getc(); + } + } + ftable.putc(c); + } + ftable.close(); +} + +arout(s: string, v: array of int, n: int) +{ + ftable.puts(s+" := array[] of {"); + for(i := 0; i < n; i++) { + if(i%10 == 0) + ftable.putc('\n'); + ftable.puts(sprint("%4d", v[i])); + ftable.putc(','); + } + ftable.puts("\n};\n"); +} + +# +# output the summary on y.output +# +summary() +{ + if(foutput != nil) { + foutput.puts("\n" + string ntokens + " terminals, " + string(nnonter + 1) + " nonterminals\n"); + foutput.puts("" + string nprod + " grammar rules, " + string nstate + "/" + string NSTATES + " states\n"); + foutput.puts("" + string zzsrconf + " shift/reduce, " + string zzrrconf + " reduce/reduce conflicts reported\n"); + foutput.puts("" + string len wsets + " working sets used\n"); + foutput.puts("memory: parser " + string memp + "/" + string ACTSIZE + "\n"); + foutput.puts(string (zzclose - 2*nstate) + " extra closures\n"); + foutput.puts(string zzacent + " shift entries, " + string zzexcp + " exceptions\n"); + foutput.puts(string zzgoent + " goto entries\n"); + foutput.puts(string zzgobest + " entries saved by goto default\n"); + } + if(zzsrconf != 0 || zzrrconf != 0) { + print("\nconflicts: "); + if(zzsrconf) + print("%d shift/reduce", zzsrconf); + if(zzsrconf && zzrrconf) + print(", "); + if(zzrrconf) + print("%d reduce/reduce", zzrrconf); + print("\n"); + } + if(fdefine != nil) + fdefine.close(); +} + +# +# write optimizer summary +# +osummary() +{ + if(foutput == nil) + return; + i := 0; + for(p := maxa; p >= 0; p--) + if(amem[p] == 0) + i++; + + foutput.puts("Optimizer space used: output " + string (maxa+1) + "/" + string ACTSIZE + "\n"); + foutput.puts(string(maxa+1) + " table entries, " + string i + " zero\n"); + foutput.puts("maximum spread: " + string maxspr + ", maximum offset: " + string maxoff + "\n"); +} + +# +# copies and protects "'s in q +# +chcopy(q: string): string +{ + s := ""; + j := 0; + for(i := 0; i < len q; i++) { + if(q[i] == '"') { + s += q[j:i] + "\\"; + j = i; + } + } + return s + q[j:i]; +} + +usage() +{ + fprint(stderr, "usage: yacc [-vdm] [-Dn] [-o output] [-s stem] file\n"); + raise "fail:usage"; +} + +bitset(set: Lkset, bit: int): int +{ + return set[bit>>5] & (1<<(bit&31)); +} + +setbit(set: Lkset, bit: int): int +{ + return set[bit>>5] |= (1<<(bit&31)); +} + +mkset(): Lkset +{ + return array[tbitset] of {* => 0}; +} + +# +# set a to the union of a and b +# return 1 if b is not a subset of a, 0 otherwise +# +setunion(a, b: array of int): int +{ + sub := 0; + for(i:=0; i<tbitset; i++) { + x := a[i]; + y := x | b[i]; + a[i] = y; + if(y != x) + sub = 1; + } + return sub; +} + +prlook(p: Lkset) +{ + if(p == nil){ + foutput.puts("\tNULL"); + return; + } + foutput.puts(" { "); + for(j:=0; j<=ntokens; j++){ + if(bitset(p, j)){ + foutput.puts(symnam(j)); + foutput.putc(' '); + } + } + foutput.putc('}'); +} + +# +# utility routines +# +isdigit(c: int): int +{ + return c >= '0' && c <= '9'; +} + +isword(c: int): int +{ + return c >= 16ra0 || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; +} + +mktemp(t: string): string +{ + return t; +} + +# +# arg processing +# +Arg.init(argv: list of string): ref Arg +{ + if(argv != nil) + argv = tl argv; + return ref Arg(argv, 0, ""); +} + +Arg.opt(arg: self ref Arg): int +{ + opts := arg.opts; + if(opts != ""){ + arg.c = opts[0]; + arg.opts = opts[1:]; + return arg.c; + } + argv := arg.argv; + if(argv == nil) + return arg.c = 0; + opts = hd argv; + if(len opts < 2 || opts[0] != '-') + return arg.c = 0; + arg.argv = tl argv; + if(opts == "--") + return arg.c = 0; + arg.opts = opts[2:]; + return arg.c = opts[1]; +} + +Arg.arg(arg: self ref Arg): string +{ + s := arg.opts; + arg.opts = ""; + if(s != "") + return s; + argv := arg.argv; + if(argv == nil) + return ""; + arg.argv = tl argv; + return hd argv; +} diff --git a/appl/cmd/zeros.b b/appl/cmd/zeros.b new file mode 100644 index 00000000..9708fca3 --- /dev/null +++ b/appl/cmd/zeros.b @@ -0,0 +1,68 @@ +implement Zeros; + +include "sys.m"; + sys: Sys; +include "arg.m"; + arg: Arg; +include "string.m"; + str: String; +include "keyring.m"; +include "security.m"; + random: Random; + +include "draw.m"; + +Zeros: module +{ + init: fn(nil: ref Draw->Context, argv: list of string); +}; + +init(nil: ref Draw->Context, argv: list of string) +{ + z: array of byte; + i: int; + sys = load Sys Sys->PATH; + arg = load Arg Arg->PATH; + str = load String String->PATH; + + if(sys == nil || arg == nil) + return; + + bs := 0; + n := 0; + val := 0; + rflag := 0; + arg->init(argv); + while ((c := arg->opt()) != 0) + case c { + 'r' => rflag = 1; + 'v' => (val, nil) = str->toint(arg->arg(), 16); + * => raise sys->sprint("fail:unknown option (%c)\n", c); + } + argv = arg->argv(); + if(len argv >= 1) + bs = int hd argv; + else + bs = 1; + if (len argv >= 2) + n = int hd tl argv; + else + n = 1; + if(bs == 0 || n == 0) { + sys->fprint(sys->fildes(2), "usage: zeros [-r] [-v value] blocksize [number]\n"); + raise "fail:usage"; + } + if (rflag) { + random = load Random Random->PATH; + if (random == nil) + raise "fail:no security module\n"; + z = random->randombuf(random->NotQuiteRandom, bs); + } + else { + z = array[bs] of byte; + for(i=0;i<bs;i++) + z[i] = byte val; + } + for(i=0;i<n;i++) + sys->write(sys->fildes(1), z, bs); +} |
