diff options
Diffstat (limited to 'appl/cmd')
| -rw-r--r-- | appl/cmd/ar.b | 856 | ||||
| -rw-r--r-- | appl/cmd/cddb.b | 257 | ||||
| -rw-r--r-- | appl/cmd/lookman.b | 250 | ||||
| -rw-r--r-- | appl/cmd/man2html.b | 1328 | ||||
| -rw-r--r-- | appl/cmd/shutdown.b | 72 |
5 files changed, 2441 insertions, 322 deletions
diff --git a/appl/cmd/ar.b b/appl/cmd/ar.b new file mode 100644 index 00000000..8ef237b8 --- /dev/null +++ b/appl/cmd/ar.b @@ -0,0 +1,856 @@ +implement Ar; + +# +# ar - portable (ascii) format version +# + +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; + +Ar: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +ARMAG: con "!<arch>\n"; +SARMAG: con len ARMAG; +ARFMAG0: con byte '`'; +ARFMAG1: con byte '\n'; +SARNAME: con 16; # ancient limit + +# +# printable archive header +# name[SARNAME] date[12] uid[6] gid[6] mode[8] size[10] fmag[2] +# +Oname: con 0; +Lname: con SARNAME; +Odate: con Oname+Lname; +Ldate: con 12; +Ouid: con Odate+Ldate; +Luid: con 6; +Ogid: con Ouid+Luid; +Lgid: con 6; +Omode: con Ogid+Lgid; +Lmode: con 8; +Osize: con Omode+Lmode; +Lsize: con 10; +Ofmag: con Osize+Lsize; +Lfmag: con 2; +SAR_HDR: con Ofmag+Lfmag; # 60 + +# +# The algorithm uses up to 3 temp files. The "pivot contents" is the +# archive contents specified by an a, b, or i option. The temp files are +# astart - contains existing contentss up to and including the pivot contents. +# amiddle - contains new files moved or inserted behind the pivot. +# aend - contains the existing contentss that follow the pivot contents. +# When all contentss have been processed, function 'install' streams the +# temp files, in order, back into the archive. +# + +Armember: adt { # one per archive contents + name: string; # trimmed + length: int; + date: int; + uid: int; + gid: int; + mode: int; + size: int; + contents: array of byte; + fd: ref Sys->FD; # if contents is nil and fd is not nil, fd has contents + next: cyclic ref Armember; + + new: fn(name: string, fd: ref Sys->FD): ref Armember; + rdhdr: fn(b: ref Iobuf): ref Armember; + read: fn(m: self ref Armember, b: ref Iobuf): int; + wrhdr: fn(m: self ref Armember, fd: ref Sys->FD); + write: fn(m: self ref Armember, fd: ref Sys->FD); + skip: fn(m: self ref Armember, b: ref Iobuf); + replace: fn(m: self ref Armember, name: string, fd: ref Sys->FD); + copyout: fn(m: self ref Armember, b: ref Iobuf, destfd: ref Sys->FD); +}; + +Arfile: adt { # one per tempfile + fd: ref Sys->FD; # paging file descriptor, nil if none allocated + + head: ref Armember; + tail: ref Armember; + + new: fn(): ref Arfile; + copy: fn(ar: self ref Arfile, b: ref Iobuf, mem: ref Armember); + insert: fn(ar: self ref Arfile, mem: ref Armember); + stream: fn(ar: self ref Arfile, fd: ref Sys->FD); + page: fn(ar: self ref Arfile): int; +}; + +File: adt { + name: string; + trimmed: string; + found: int; +}; + +man := "mrxtdpq"; +opt := "uvnbailo"; + +aflag := 0; +bflag := 0; +cflag := 0; +oflag := 0; +uflag := 0; +vflag := 0; + +pivotname: string; +bout: ref Iobuf; +stderr: ref Sys->FD; +parts: array of ref Arfile; + +comfun: ref fn(a: string, f: array of ref File); + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + bufio = load Bufio Bufio->PATH; + daytime = load Daytime Daytime->PATH; + str = load String String->PATH; + + stderr = sys->fildes(2); + bout = bufio->fopen(sys->fildes(1), Sys->OWRITE); + if(len args < 3) + usage(); + args = tl args; + s := hd args; args = tl args; + for(i := 0; i < len s; i++){ + case s[i] { + 'a' => aflag = 1; + 'b' => bflag = 1; + 'c' => cflag = 1; + 'd' => setcom(dcmd); + 'i' => bflag = 1; + 'l' => ; # ignored + 'm' => setcom(mcmd); + 'o' => oflag = 1; + 'p' => setcom(pcmd); + 'q' => setcom(qcmd); + 'r' => setcom(rcmd); + 't' => setcom(tcmd); + 'u' => uflag = 1; + 'v' => vflag = 1; + 'x' => setcom(xcmd); + * => + sys->fprint(stderr, "ar: bad option `%c'\n", s[i]); + usage(); + } + } + if(aflag && bflag){ + sys->fprint(stderr, "ar: only one of 'a' and 'b' can be specified\n"); + usage(); + } + if(aflag || bflag){ + pivotname = trim(hd args); args = tl args; + if(len args < 2) + usage(); + } + if(comfun == nil){ + if(uflag == 0){ + sys->fprint(stderr, "ar: one of [%s] must be specified\n", man); + usage(); + } + setcom(rcmd); + } + cp := hd args; args = tl args; + files := array[len args] of ref File; + for(i = 0; args != nil; args = tl args) + files[i++] = ref File(hd args, trim(hd args), 0); + comfun(cp, files); # do the command + for(i = 0; i < len files; i++) + if(!files[i].found){ + sys->fprint(stderr, "ar: %s not found\n", files[i].name); + cp = "error"; + } + bout.flush(); + if(cp != nil) + raise "fail:"+cp; +} + +# +# select a command +# +setcom(fun: ref fn(s: string, f: array of ref File)) +{ + if(comfun != nil){ + sys->fprint(stderr, "ar: only one of [%s] allowed\n", man); + usage(); + } + comfun = fun; +} + +# +# perform the 'r' and 'u' commands +# +rcmd(arname: string, files: array of ref File) +{ + bar := openar(arname, Sys->ORDWR, 1); + parts = array[2] of {Arfile.new(), nil}; + ap := parts[0]; + if(bar != nil){ + while((mem := Armember.rdhdr(bar)) != nil){ + if(bamatch(mem.name, pivotname)) # check for pivot + ap = parts[1] = Arfile.new(); + f := match(files, mem.name); + if(f == nil){ + ap.copy(bar, mem); + continue; + } + f.found = 1; + dfd := sys->open(f.name, Sys->OREAD); + if(dfd == nil){ + if(len files > 0) + sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name); + ap.copy(bar, mem); + continue; + } + if(uflag){ + (ok, d) := sys->fstat(dfd); + if(ok < 0 || d.mtime <= mem.date){ + if(ok < 0) + sys->fprint(stderr, "ar: cannot stat %s: %r\n", f.name); + ap.copy(bar, mem); + continue; + } + } + mem.skip(bar); + mesg('r', f.name); + mem.replace(f.name, dfd); + ap.insert(mem); + dfd = nil; + } + } + # copy in remaining files named on command line + for(i := 0; i < len files; i++){ + f := files[i]; + if(f.found) + continue; + f.found = 1; + dfd := sys->open(f.name, Sys->OREAD); + if(dfd != nil){ + mesg('a', f.name); + parts[0].insert(Armember.new(f.trimmed, dfd)); + }else + sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name); + } + if(bar == nil && !cflag) + install(arname, parts, 1); # issue 'creating' msg + else + install(arname, parts, 0); +} + +dcmd(arname: string, files: array of ref File) +{ + if(len files == 0) + return; + changed := 0; + parts = array[] of {Arfile.new()}; + bar := openar(arname, Sys->ORDWR, 0); + while((mem := Armember.rdhdr(bar)) != nil){ + if(match(files, mem.name) != nil){ + mesg('d', mem.name); + mem.skip(bar); + changed = 1; + }else + parts[0].copy(bar, mem); + mem = nil; # conserves memory + } + if(changed) + install(arname, parts, 0); +} + +xcmd(arname: string, files: array of ref File) +{ + bar := openar(arname, Sys->OREAD, 0); + i := 0; + while((mem := Armember.rdhdr(bar)) != nil){ + if((f := match(files, mem.name)) != nil){ + f.found = 1; + fd := sys->create(f.name, Sys->OWRITE, mem.mode & 8r777); + if(fd == nil){ + sys->fprint(stderr, "ar: cannot create %s: %r\n", f.name); + mem.skip(bar); + }else{ + mesg('x', f.name); + mem.copyout(bar, fd); + if(oflag){ + dx := sys->nulldir; + dx.atime = mem.date; + dx.mtime = mem.date; + if(sys->fwstat(fd, dx) < 0) + sys->fprint(stderr, "ar: can't set times on %s: %r", f.name); + } + fd = nil; + mem = nil; + } + if(len files > 0 && ++i >= len files) + break; + }else + mem.skip(bar); + } +} + +pcmd(arname: string, files: array of ref File) +{ + bar := openar(arname, Sys->OREAD, 0); + i := 0; + while((mem := Armember.rdhdr(bar)) != nil){ + if((f := match(files, mem.name)) != nil){ + if(vflag) + sys->print("\n<%s>\n\n", f.name); + mem.copyout(bar, sys->fildes(1)); + if(len files > 0 && ++i >= len files) + break; + }else + mem.skip(bar); + mem = nil; # we no longer need the contents + } +} + +mcmd(arname: string, files: array of ref File) +{ + if(len files == 0) + return; + parts = array[3] of {Arfile.new(), Arfile.new(), nil}; + bar := openar(arname, Sys->ORDWR, 0); + ap := parts[0]; + while((mem := Armember.rdhdr(bar)) != nil){ + if(bamatch(mem.name, pivotname)) + ap = parts[2] = Arfile.new(); + if((f := match(files, mem.name)) != nil){ + mesg('m', f.name); + parts[1].copy(bar, mem); + }else + ap.copy(bar, mem); + } + if(pivotname != nil && parts[2] == nil) + sys->fprint(stderr, "ar: %s not found - files moved to end\n", pivotname); + install(arname, parts, 0); +} + +tcmd(arname: string, files: array of ref File) +{ + bar := openar(arname, Sys->OREAD, 0); + while((mem := Armember.rdhdr(bar)) != nil){ + if((f := match(files, mem.name)) != nil){ + longls := ""; + if(vflag) + longls = longtext(mem)+" "; + bout.puts(longls+f.trimmed+"\n"); + } + mem.skip(bar); + mem = nil; + } +} + +qcmd(arname: string, files: array of ref File) +{ + if(aflag || bflag){ + sys->fprint(stderr, "ar: abi not allowed with q\n"); + raise "fail:usage"; + } + fd := openrawar(arname, Sys->ORDWR, 1); + if(fd == nil){ + if(!cflag) + sys->fprint(stderr, "ar: creating %s\n", arname); + fd = arcreate(arname); + } + # leave note group behind when writing archive; i.e. sidestep interrupts + sys->seek(fd, big 0, 2); # append + for(i := 0; i < len files; i++){ + f := files[i]; + f.found = 1; + dfd := sys->open(f.name, Sys->OREAD); + if(dfd != nil){ + mesg('q', f.name); + mem := Armember.new(f.trimmed, dfd); + if(mem != nil){ + mem.write(fd); + mem = nil; + } + }else + sys->fprint(stderr, "ar: cannot open %s: %r\n", f.name); + } +} + +# +# open an archive and validate its header +# +openrawar(arname: string, mode: int, errok: int): ref Sys->FD +{ + fd := sys->open(arname, mode); + if(fd == nil){ + if(!errok){ + sys->fprint(stderr, "ar: cannot open %s: %r\n", arname); + raise "fail:error"; + } + return nil; + } + mbuf := array[SARMAG] of byte; + if(sys->read(fd, mbuf, SARMAG) != SARMAG || string mbuf != ARMAG){ + sys->fprint(stderr, "ar: %s not in archive format\n", arname); + raise "fail:error"; + } + return fd; +} + +openar(arname: string, mode: int, errok: int): ref Iobuf +{ + fd := openrawar(arname, mode, errok); + if(fd == nil) + return nil; + bfd := bufio->fopen(fd, mode); + bfd.seek(big SARMAG, 0); + return bfd; +} + +# +# create an archive and set its header +# +arcreate(arname: string): ref Sys->FD +{ + fd := sys->create(arname, Sys->OWRITE, 8r666); + if(fd == nil){ + sys->fprint(stderr, "ar: cannot create %s: %r\n", arname); + raise "fail:create"; + } + a := array of byte ARMAG; + mustwrite(fd, a, len a); + return fd; +} + +# +# error handling +# +wrerr() +{ + sys->fprint(stderr, "ar: write error: %r\n"); + raise "fail:write error"; +} + +rderr() +{ + sys->fprint(stderr, "ar: read error: %r\n"); + raise "fail:read error"; +} + +phaseerr(offset: big) +{ + sys->fprint(stderr, "ar: phase error at offset %bd\n", offset); + raise "fail:phase error"; +} + +usage() +{ + sys->fprint(stderr, "usage: ar [%s][%s] archive files ...\n", opt, man); + raise "fail:usage"; +} + +# +# concatenate the several sequences of members into one archive +# +install(arname: string, seqs: array of ref Arfile, createflag: int) +{ + # leave process group behind when copying back; i.e. sidestep interrupts + sys->pctl(Sys->NEWPGRP, nil); + + if(createflag) + sys->fprint(stderr, "ar: creating %s\n", arname); + fd := arcreate(arname); + for(i := 0; i < len seqs; i++) + if((ap := seqs[i]) != nil) + ap.stream(fd); +} + +# +# return the command line File matching a given name +# +match(files: array of ref File, file: string): ref File +{ + if(len files == 0) + return ref File(file, file, 0); # empty list always matches + for(i := 0; i < len files; i++) + if(!files[i].found && files[i].trimmed == file){ + files[i].found = 1; + return files[i]; + } + return nil; +} + +# +# is `file' the pivot member's name and is the archive positioned +# at the correct point wrt after or before options? return true if so. +# +state := 0; + +bamatch(file: string, pivot: string): int +{ + case state { + 0 => # looking for position file + if(aflag){ + if(file == pivot) + state = 1; + }else if(bflag){ + if(file == pivot){ + state = 2; # found + return 1; + } + } + 1 => # found - after previous file + state = 2; + return 1; + 2 => # already found position file + ; + } + return 0; +} + +# +# output a message, if 'v' option was specified +# +mesg(c: int, file: string) +{ + if(vflag) + bout.puts(sys->sprint("%c - %s\n", c, file)); +} + +# +# return just the file name +# +trim(s: string): string +{ + for(j := len s; j > 0 && s[j-1] == '/';) + j--; + k := 0; + for(i := 0; i < j; i++) + if(s[i] == '/') + k = i+1; + return s[k: j]; +} + +longtext(mem: ref Armember): string +{ + s := modes(mem.mode); + s += sys->sprint(" %3d/%1d", mem.uid, mem.gid); + s += sys->sprint(" %7ud", mem.size); + t := daytime->text(daytime->local(mem.date)); + return s+sys->sprint(" %-12.12s %-4.4s ", t[4:], t[24:]); +} + +mtab := array[] of { + "---", "--x", "-w-", "-wx", + "r--", "r-x", "rw-", "rwx" +}; + +modes(mode: int): string +{ + return mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; +} + +# +# read the header for the next archive contents +# +Armember.rdhdr(b: ref Iobuf): ref Armember +{ + buf := array[SAR_HDR] of byte; + if((n := b.read(buf, len buf)) != len buf){ + if(n == 0) + return nil; + if(n > 0) + sys->werrstr("unexpected end-of-file"); + rderr(); + } + mem := ref Armember; + for(i := Oname+Lname; i > Oname; i--) + if(buf[i-1] != byte '/' && buf[i-1] != byte ' ') + break; + mem.name = string buf[Oname:i]; + mem.date = intof(buf[Odate: Odate+Ldate], 10); + mem.uid = intof(buf[Ouid: Ouid+Luid], 10); + mem.gid = intof(buf[Ogid: Ogid+Lgid], 10); + mem.mode = intof(buf[Omode: Omode+Lmode], 8); + mem.size = intof(buf[Osize: Osize+Lsize], 10); + if(buf[Ofmag] != ARFMAG0 || buf[Ofmag+1] != ARFMAG1) + phaseerr(b.offset()-big SAR_HDR); + return mem; +} + +intof(a: array of byte, base: int): int +{ + for(i := len a; i > 0; i--) + if(a[i-1] != byte ' '){ + a = a[0:i]; + break; + } + (n, s) := str->toint(string a, base); + if(s != nil){ + sys->fprint(stderr, "ar: invalid integer in archive member's header: %q\n", string a); + raise "fail:error"; + } + return n; +} + +Armember.wrhdr(mem: self ref Armember, fd: ref Sys->FD) +{ + b := array[SAR_HDR] of {* => byte ' '}; + nm := array of byte mem.name; + if(len nm > Lname) + nm = nm[0:Lname]; + b[Oname:] = nm; + b[Odate:] = sys->aprint("%-12ud", mem.date); + b[Ouid:] = sys->aprint("%-6d", 0); + b[Ogid:] = sys->aprint("%-6d", 0); + b[Omode:] = sys->aprint("%-8uo", mem.mode); + b[Osize:] = sys->aprint("%-10ud", mem.size); + b[Ofmag] = ARFMAG0; + b[Ofmag+1] = ARFMAG1; + mustwrite(fd, b, len b); +} + +# +# make a new member from the given file, with the file's contents +# +Armember.new(name: string, fd: ref Sys->FD): ref Armember +{ + mem := ref Armember; + mem.replace(name, fd); + return mem; +} + +# +# replace the contents of an existing member +# +Armember.replace(mem: self ref Armember, name: string, fd: ref Sys->FD) +{ + (ok, d) := sys->fstat(fd); + if(ok < 0){ + sys->fprint(stderr, "ar: cannot stat %s: %r\n", name); + raise "fail:no stat"; + } + mem.name = trim(name); + mem.date = d.mtime; + mem.uid = 0; + mem.gid = 0; + mem.mode = d.mode & 8r777; + mem.size = int d.length; + if(big mem.size != d.length){ + sys->fprint(stderr, "ar: file %s too big\n", name); + raise "fail:error"; + } + mem.fd = fd; + mem.contents = nil; # will be copied across from fd when needed +} + +# +# read the contents of an archive member +# +Armember.read(mem: self ref Armember, b: ref Iobuf): int +{ + if(mem.contents != nil) + return len mem.contents; + mem.contents = buffer(mem.size + (mem.size&1)); + n := b.read(mem.contents, len mem.contents); + if(n != len mem.contents){ + if(n >= 0) + sys->werrstr("unexpected end-of-file"); + rderr(); + } + return n; +} + +mustwrite(fd: ref Sys->FD, buf: array of byte, n: int) +{ + if(sys->write(fd, buf, n) != n) + wrerr(); +} + +# +# write an archive member to ofd, including header +# +Armember.write(mem: self ref Armember, ofd: ref Sys->FD) +{ + mem.wrhdr(ofd); + if(mem.contents != nil){ + mustwrite(ofd, mem.contents, len mem.contents); + return; + } + if(mem.fd == nil) + raise "ar: write nil fd"; + buf := array[Sys->ATOMICIO] of byte; # could be bigger + for(nr := mem.size; nr > 0;){ + n := nr; + if(n > len buf) + n = len buf; + n = sys->read(mem.fd, buf, n); + if(n <= 0){ + if(n == 0) + sys->werrstr("unexpected end-of-file"); + rderr(); + } + mustwrite(ofd, buf, n); + nr -= n; + } + if(mem.size & 1) + mustwrite(ofd, array[] of {byte '\n'}, 1); +} + +# +# seek past the current member's contents in b +# +Armember.skip(mem: self ref Armember, b: ref Iobuf) +{ + b.seek(big(mem.size + (mem.size&1)), 1); +} + +# +# copy a member's contents from memory or directly from an archive to another file +# +Armember.copyout(mem: self ref Armember, b: ref Iobuf, ofd: ref Sys->FD) +{ + if(mem.contents != nil){ + mustwrite(ofd, mem.contents, len mem.contents); + return; + } + buf := array[Sys->ATOMICIO] of byte; # could be bigger + for(nr := mem.size; nr > 0;){ + n := nr; + if(n > len buf) + n = len buf; + n = b.read(buf, n); + if(n <= 0){ + if(n == 0) + sys->werrstr("unexpected end-of-file"); + rderr(); + } + mustwrite(ofd, buf, n); + nr -= n; + } + if(mem.size & 1) + b.getc(); +} + +# +# Temp file I/O subsystem. We attempt to cache all three temp files in +# core. When we run out of memory we spill to disk. +# The I/O model assumes that temp files: +# 1) are only written on the end +# 2) are only read from the beginning +# 3) are only read after all writing is complete. +# The architecture uses one control block per temp file. Each control +# block anchors a chain of buffers, each containing an archive contents. +# +Arfile.new(): ref Arfile +{ + return ref Arfile; +} + +# +# copy the contents of mem at b into the temporary +# +Arfile.copy(ap: self ref Arfile, b: ref Iobuf, mem: ref Armember) +{ + mem.read(b); + ap.insert(mem); +} + +# +# insert a contents buffer into the contents chain +# +Arfile.insert(ap: self ref Arfile, mem: ref Armember) +{ + mem.next = nil; + if(ap.head == nil) + ap.head = mem; + else + ap.tail.next = mem; + ap.tail = mem; +} + +# +# stream the contents in a temp file to the file referenced by 'fd'. +# +Arfile.stream(ap: self ref Arfile, fd: ref Sys->FD) +{ + if(ap.fd != nil){ # copy prefix from disk + buf := array[Sys->ATOMICIO] of byte; + sys->seek(ap.fd, big 0, 0); + while((n := sys->read(ap.fd, buf, len buf)) > 0) + mustwrite(fd, buf, n); + if(n < 0) + rderr(); + ap.fd = nil; + } + # dump the in-core buffers, which always follow the contents in the temp file + for(mem := ap.head; mem != nil; mem = mem.next) + mem.write(fd); +} + +# +# spill a member's contents to disk +# + +totalmem := 0; +warned := 0; +tn := 0; + +Arfile.page(ap: self ref Arfile): int +{ + mem := ap.head; + if(ap.fd == nil && !warned){ + pid := sys->pctl(0, nil); + for(i := 0;; i++){ + name := sys->sprint("/tmp/art%d.%d.%d", pid, tn, i); + ap.fd = sys->create(name, Sys->OEXCL | Sys->ORDWR | Sys->ORCLOSE, 8r600); + if(ap.fd != nil) + break; + if(i >= 20){ + warned =1; + sys->fprint(stderr,"ar: warning: can't create temp file %s: %r\n", name); + return 0; # we'll simply use the memory + } + } + tn++; + } + mem.write(ap.fd); + ap.head = mem.next; + if(ap.tail == mem) + ap.tail = mem.next; + totalmem -= len mem.contents; + return 1; +} + +# +# account for the space taken by a contents's contents, +# pushing earlier contentss to disk to keep the space below a +# reasonable level +# + +buffer(n: int): array of byte +{ +Flush: + while(totalmem + n > 1024*1024){ + for(i := 0; i < len parts; i++) + if(parts[i] != nil && parts[i].page()) + continue Flush; + break; + } + totalmem += n; + return array[n] of byte; +} diff --git a/appl/cmd/cddb.b b/appl/cmd/cddb.b new file mode 100644 index 00000000..6265ba24 --- /dev/null +++ b/appl/cmd/cddb.b @@ -0,0 +1,257 @@ +implement Cddb; + +# this is a near transliteration of Plan 9 source, and 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 "string.m"; + str: String; + +include "arg.m"; + +Cddb: module +{ + init: fn(nil: ref Draw->Context, nil: list of string); +}; + +server := "freedb.freedb.org"; +debug := 0; +tflag := 0; +Tflag := 0; + +Track: adt { + n: int; + title: string; +}; + +Toc: adt { + diskid: int; + ntrack: int; + title: string; + track: array of Track; +}; + +DPRINT(fd: int, s: string) +{ + if(debug) + sys->fprint(sys->fildes(fd), "%s", s); +} + +dumpcddb(t: ref Toc) +{ + sys->print("title %s\n", t.title); + for(i:=0; i<t.ntrack; i++){ + if(tflag){ + n := t.track[i+1].n; + if(i == t.ntrack-1) + n *= 75; + s := (n - t.track[i].n)/75; + sys->print("%d\t%s\t%d:%2.2d\n", i+1, t.track[i].title, s/60, s%60); + } + else + sys->print("%d\t%s\n", i+1, t.track[i].title); + } + if(Tflag){ + s := t.track[i].n; + sys->print("Total time: %d:%2.2d\n", s/60, s%60); + } +} + +cddbfilltoc(t: ref Toc): int +{ + (ok, conn) := sys->dial(netmkaddr(server, "tcp", "888"), nil); + if(ok < 0) { + sys->fprint(sys->fildes(2), "cddb: cannot dial %s: %r\n", server); + return -1; + } + bin := bufio->fopen(conn.dfd, Bufio->OREAD); + + if((p:=getline(bin)) == nil || atoi(p)/100 != 2) + return died(p); + + sys->fprint(conn.dfd, "cddb hello gre plan9 9cd 1.0\r\n"); + if((p = getline(bin)) == nil || atoi(p)/100 != 2) + return died(p); + + # + # Protocol level 6 is the same as level 5 except that + # the character set is now UTF-8 instead of ISO-8859-1. + # + sys->fprint(conn.dfd, "proto 6\r\n"); + if((p = getline(bin)) == nil || atoi(p)/100 != 2) + return died(p); + DPRINT(2, sys->sprint("%s\n", p)); + + sys->fprint(conn.dfd, "cddb query %8.8ux %d", t.diskid, t.ntrack); + DPRINT(2, sys->sprint("cddb query %8.8ux %d", t.diskid, t.ntrack)); + for(i:=0; i<t.ntrack; i++) { + sys->fprint(conn.dfd, " %d", t.track[i].n); + DPRINT(2, sys->sprint(" %d", t.track[i].n)); + } + sys->fprint(conn.dfd, " %d\r\n", t.track[t.ntrack].n); + DPRINT(2, sys->sprint(" %d\r\n", t.track[t.ntrack].n)); + + if((p = getline(bin)) == nil || atoi(p)/100 != 2) + return died(p); + DPRINT(2, sys->sprint("cddb: %s\n", p)); + (nf, fl) := sys->tokenize(p, " \t\n\r"); + if(nf < 1) + return died(p); + + categ, id: string; + case atoi(hd fl) { + 200 => # exact match + if(nf < 3) + return died(p); + categ = hd tl fl; + id = hd tl tl fl; + 211 => # close matches + if((p = getline(bin)) == nil) + return died(nil); + if(p[0] == '.') # no close matches? + return died(nil); + + # accept first match + (nsf, f) := sys->tokenize(p, " \t\n\r"); + if(nsf < 2) + return died(p); + categ = hd f; + id = hd tl f; + + # snarf rest of buffer + while(p[0] != '.') { + if((p = getline(bin)) == nil) + return died(p); + DPRINT(2, sys->sprint("cddb: %s\n", p)); + } + 202 or # no match + * => + return died(p); + } + + t.title = ""; + for(i=0; i<t.ntrack; i++) + t.track[i].title = ""; + + # fetch results for this cd + sys->fprint(conn.dfd, "cddb read %s %s\r\n", categ, id); + do { + if((p = getline(bin)) == nil) + return died(nil); +DPRINT(2, sys->sprint("cddb %s\n", p)); + if(len p >= 7 && p[0:7] == "DTITLE=") + t.title += p[7:]; + else if(len p >= 6 && p[0:6] == "TTITLE"&& isdigit(p[6])) { + i = atoi(p[6:]); + if(i < t.ntrack) { + p = p[6:]; + while(p != nil && isdigit(p[0])) + p = p[1:]; + if(p != nil && p[0] == '=') + p = p[1:]; + t.track[i].title += p; + } + } + } while(p[0] != '.'); + + sys->fprint(conn.dfd, "quit\r\n"); + + return 0; +} + +getline(f: ref Iobuf): string +{ + p := f.gets('\n'); + while(p != nil && isspace(p[len p-1])) + p = p[0: len p-1]; + return p; +} + +isdigit(c: int): int +{ + return c>='0' && c <= '9'; +} + +isspace(c: int): int +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +died(p: string): int +{ + sys->fprint(sys->fildes(2), "cddb: error talking to server\n"); + if(p != nil){ + p = p[0:len p-1]; + sys->fprint(sys->fildes(2), "cddb: server says: %s\n", p); + } + return -1; +} + +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; + arg->init(args); + arg->setusage("cddb [-DTt] [-s server] query diskid n ..."); + while((o := arg->opt()) != 0) + case o { + 'D' => debug = 1; + 's' => server = arg->earg(); + 'T' => Tflag = 1; tflag = 1; + 't' => tflag = 1; + * => arg->usage(); + } + args = arg->argv(); + argc := len args; + if(argc < 3 || hd args != "query") + arg->usage(); + arg = nil; + + ntrack := atoi(hd tl tl args); + toc := ref Toc(str->toint(hd tl args, 16).t0, ntrack, nil, array[ntrack+1] of Track); + if(argc != 3+toc.ntrack+1){ + sys->fprint(sys->fildes(2), "cddb: argument count does not match given ntrack"); + raise "fail:error"; + } + args = tl tl tl args; + + for(i:=0; i<=toc.ntrack; i++){ # <=? + toc.track[i].n = atoi(hd args); + args = tl args; + } + + if(cddbfilltoc(toc) < 0) + raise "fail:whoops"; + + dumpcddb(toc); +} + +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); +} + +atoi(s: string): int +{ + return int s; +} diff --git a/appl/cmd/lookman.b b/appl/cmd/lookman.b deleted file mode 100644 index 53557c8b..00000000 --- a/appl/cmd/lookman.b +++ /dev/null @@ -1,250 +0,0 @@ -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/man2html.b b/appl/cmd/man2html.b new file mode 100644 index 00000000..9eda5940 --- /dev/null +++ b/appl/cmd/man2html.b @@ -0,0 +1,1328 @@ +implement Man2html; + +include "sys.m"; + stderr: ref Sys->FD; + sys: Sys; + print, fprint, sprint: import sys; + + +include "bufio.m"; + +include "draw.m"; + +include "daytime.m"; + dt: Daytime; + +include "string.m"; + str: String; + +Man2html: module +{ + init: fn(ctxt: ref Draw->Context, args: list of string); +}; + +Runeself: con 16r80; +false, true: con iota; + +Troffspec: adt { + name: string; + value: string; +}; + +tspec := array [] of { Troffspec + ("ff", "ff"), + ("fi", "fi"), + ("fl", "fl"), + ("Fi", "ffi"), + ("ru", "_"), + ("em", "­"), + ("14", "¼"), + ("12", "½"), + ("co", "©"), + ("de", "°"), + ("dg", "¡"), + ("fm", "´"), + ("rg", "®"), +# ("bu", "*"), + ("bu", "•"), + ("sq", "¤"), + ("hy", "-"), + ("pl", "+"), + ("mi", "-"), + ("mu", "×"), + ("di", "÷"), + ("eq", "="), + ("==", "=="), + (">=", ">="), + ("<=", "<="), + ("!=", "!="), + ("+-", "±"), + ("no", "¬"), + ("sl", "/"), + ("ap", "&"), + ("~=", "~="), + ("pt", "oc"), + ("gr", "GRAD"), + ("->", "->"), + ("<-", "<-"), + ("ua", "^"), + ("da", "v"), + ("is", "Integral"), + ("pd", "DIV"), + ("if", "oo"), + ("sr", "-/"), + ("sb", "(~"), + ("sp", "~)"), + ("cu", "U"), + ("ca", "(^)"), + ("ib", "(="), + ("ip", "=)"), + ("mo", "C"), + ("es", "Ø"), + ("aa", "´"), + ("ga", "`"), + ("ci", "O"), + ("L1", "Lucent"), + ("sc", "§"), + ("dd", "++"), + ("lh", "<="), + ("rh", "=>"), + ("lt", "("), + ("rt", ")"), + ("lc", "|"), + ("rc", "|"), + ("lb", "("), + ("rb", ")"), + ("lf", "|"), + ("rf", "|"), + ("lk", "|"), + ("rk", "|"), + ("bv", "|"), + ("ts", "s"), + ("br", "|"), + ("or", "|"), + ("ul", "_"), + ("rn", " "), + ("*p", "PI"), + ("**", "*"), +}; + + Entity: adt { + name: string; + value: int; + }; + Entities: array of Entity; + +Entities = array[] of { + Entity( "¡", '¡' ), + Entity( "¢", '¢' ), + Entity( "£", '£' ), + Entity( "¤", '¤' ), + Entity( "¥", '¥' ), + Entity( "¦", '¦' ), + Entity( "§", '§' ), + Entity( "¨", '¨' ), + Entity( "©", '©' ), + Entity( "ª", 'ª' ), + Entity( "«", '«' ), + Entity( "¬", '¬' ), + Entity( "­", '' ), + Entity( "®", '®' ), + Entity( "¯", '¯' ), + Entity( "°", '°' ), + Entity( "±", '±' ), + Entity( "²", '²' ), + Entity( "³", '³' ), + Entity( "´", '´' ), + Entity( "µ", 'µ' ), + Entity( "¶", '¶' ), + Entity( "·", '·' ), + Entity( "¸", '¸' ), + Entity( "¹", '¹' ), + Entity( "º", 'º' ), + Entity( "»", '»' ), + Entity( "¼", '¼' ), + Entity( "½", '½' ), + Entity( "¾", '¾' ), + Entity( "¿", '¿' ), + Entity( "À", 'À' ), + Entity( "Á", 'Á' ), + Entity( "Â", 'Â' ), + Entity( "Ã", 'Ã' ), + Entity( "Ä", 'Ä' ), + Entity( "Å", 'Å' ), + Entity( "Æ", 'Æ' ), + Entity( "Ç", 'Ç' ), + Entity( "È", 'È' ), + Entity( "É", 'É' ), + Entity( "Ê", 'Ê' ), + Entity( "Ë", 'Ë' ), + Entity( "Ì", 'Ì' ), + Entity( "Í", 'Í' ), + Entity( "Î", 'Î' ), + Entity( "Ï", 'Ï' ), + Entity( "Ð", 'Ð' ), + Entity( "Ñ", 'Ñ' ), + Entity( "Ò", 'Ò' ), + Entity( "Ó", 'Ó' ), + Entity( "Ô", 'Ô' ), + Entity( "Õ", 'Õ' ), + Entity( "Ö", 'Ö' ), + Entity( "&215;", '×' ), + Entity( "Ø", 'Ø' ), + Entity( "Ù", 'Ù' ), + Entity( "Ú", 'Ú' ), + Entity( "Û", 'Û' ), + Entity( "Ü", 'Ü' ), + Entity( "Ý", 'Ý' ), + Entity( "Þ", 'Þ' ), + Entity( "ß", 'ß' ), + Entity( "à", 'à' ), + Entity( "á", 'á' ), + Entity( "â", 'â' ), + Entity( "ã", 'ã' ), + Entity( "ä", 'ä' ), + Entity( "å", 'å' ), + Entity( "æ", 'æ' ), + Entity( "ç", 'ç' ), + Entity( "è", 'è' ), + Entity( "é", 'é' ), + Entity( "ê", 'ê' ), + Entity( "ë", 'ë' ), + Entity( "ì", 'ì' ), + Entity( "í", 'í' ), + Entity( "î", 'î' ), + Entity( "ï", 'ï' ), + Entity( "ð", 'ð' ), + Entity( "ñ", 'ñ' ), + Entity( "ò", 'ò' ), + Entity( "ó", 'ó' ), + Entity( "ô", 'ô' ), + Entity( "õ", 'õ' ), + Entity( "ö", 'ö' ), + Entity( "&247;", '÷' ), + Entity( "ø", 'ø' ), + Entity( "ù", 'ù' ), + Entity( "ú", 'ú' ), + Entity( "û", 'û' ), + Entity( "ü", 'ü' ), + Entity( "ý", 'ý' ), + Entity( "þ", 'þ' ), + Entity( "ÿ", 'ÿ' ), # ÿ + + Entity( "&#SPACE;", ' ' ), + Entity( "&#RS;", '\n' ), + Entity( "&#RE;", '\r' ), + Entity( """, '"' ), + Entity( "&", '&' ), + Entity( "<", '<' ), + Entity( ">", '>' ), + + Entity( "CAP-DELTA", 'Δ' ), + Entity( "ALPHA", 'α' ), + Entity( "BETA", 'β' ), + Entity( "DELTA", 'δ' ), + Entity( "EPSILON", 'ε' ), + Entity( "THETA", 'θ' ), + Entity( "MU", 'μ' ), + Entity( "PI", 'π' ), + Entity( "TAU", 'τ' ), + Entity( "CHI", 'χ' ), + + Entity( "<-", '←' ), + Entity( "^", '↑' ), + Entity( "->", '→' ), + Entity( "v", '↓' ), + Entity( "!=", '≠' ), + Entity( "<=", '≤' ), + Entity( nil, 0 ), +}; + + +Hit: adt { + glob: string; + chap: string; + mtype: string; + page: string; +}; + +Lnone, Lordered, Lunordered, Ldef, Lother: con iota; # list types + +Chaps: adt { + name: string; + primary: int; +}; + +Types: adt { + name: string; + desc: string; +}; + + +# having two separate flags here allows for inclusion of old-style formatted pages +# under a new-style three-level tree +Oldstyle: adt { + names: int; # two-level directory tree? + fmt: int; # old internal formats: e.g., "B" font means "L"; name in .TH in all caps +}; + +Href: adt { + title: string; + chap: string; + mtype: string; + man: string; +}; + +# per-thread global data +Global: adt { + bufio: Bufio; + bin: ref Bufio->Iobuf; + bout: ref Bufio->Iobuf; + topname: string; # name of the top level categories in the manual + chaps: array of Chaps; # names of top-level partitions of this manual + types: array of Types; # names of second-level partitions + oldstyle: Oldstyle; + mantitle: string; + mandir: string; + thisone: Hit; # man page we're displaying + mtime: int; # last modification time of thisone + href: Href; # hrefs of components of this man page + hits: array of Hit; + nhits: int; + list_type: int; + pm: string; # proprietary marking + def_goobie: string; # deferred goobie + sop: int; # output at start of paragraph? + sol: int; # input at start of line? + broken: int; # output at a break? + fill: int; # in fill mode? + pre: int; # in PRE block? + example: int; # an example active? + ipd: int; # emit inter-paragraph distance? + indents: int; + hangingdt: int; + curfont: string; # current font + prevfont: string; # previous font + lastc: int; # previous char from input scanner + def_sm: int; # amount of deferred "make smaller" request + + mk_href_chap: fn(g: self ref Global, chap: string); + mk_href_man: fn(g: self ref Global, man: string, oldstyle: int); + mk_href_mtype: fn(g: self ref Global, chap, mtype: string); + dobreak: fn(g: self ref Global); + print: fn(g: self ref Global, s: string); + softbr: fn(g: self ref Global): string; + softp: fn(g: self ref Global): string; +}; + + +usage() +{ + sys->fprint(stderr, "Usage: man2html file [section]\n"); + raise "fail:usage"; +} + + +init(nil: ref Draw->Context, args: list of string) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + str = load String String->PATH; + dt = load Daytime Daytime->PATH; + g := Global_init(); + if(args != nil) + args = tl args; + if(args == nil) + usage(); + page := hd args; + args = tl args; + section := "1"; + if(args != nil) + section = hd args; + hit := Hit ("", "man", section, page); + domanpage(g, hit); + g.bufio->g.bout.flush(); +} + +# remove markup from a string +# doesn't handle nested/quoted delimiters +demark(s: string): string +{ + t: string; + clean := true; + for (i := 0; i < len s; i++) { + case s[i] { + '<' => + clean = false; + '>' => + clean = true; + * => + if (clean) + t[len t] = s[i]; + } + } + return t; +} + + +# +# Convert an individual man page to HTML and output. +# +domanpage(g: ref Global, man: Hit) +{ + file := man.page; + g.bin = g.bufio->open(file, Bufio->OREAD); + g.bout = g.bufio->fopen(sys->fildes(1), Bufio->OWRITE); + if (g.bin == nil) { + fprint(stderr, "Cannot open %s: %r\n", file); + return; + } + (err, info) := sys->fstat(g.bin.fd); + if (! err) { + g.mtime = info.mtime; + } + g.thisone = man; + while ((p := getnext(g)) != nil) { + c := p[0]; + if (c == '.' && g.sol) { + if (g.pre) { + g.print("</PRE>"); + g.pre = false; + } + dogoobie(g, false); + dohangingdt(g); + } else if (g.def_goobie != nil || g.def_sm != 0) { + g.bufio->g.bin.ungetc(); + dogoobie(g, true); + } else if (c == '\n') { + g.print(p); + dohangingdt(g); + } else + g.print(p); + } + if (g.pm != nil) { + g.print("<BR><BR><BR><FONT SIZE=-2><CENTER>\n"); + g.print(g.pm); + g.print("<BR></CENTER></FONT>\n"); + } + closeall(g, 0); + rev(g, g.bin); +} + +dogoobie(g: ref Global, deferred: int) +{ + # read line, translate special chars + line: string; + while ((token := getnext(g)) != "\n") { + if (token == nil) + return; + line += token; + } + + # parse into arguments + argl, rargl: list of string; # create reversed version, then invert + while ((line = str->drop(line, " \t")) != nil) + if (line[0] == '"') { + (token, line) = split(line[1:], '"'); + rargl = token :: rargl; + } else { + (token, line) = str->splitl(line, " \t"); + rargl = token :: rargl; + } + + if (rargl == nil && !deferred) + return; + for ( ; rargl != nil; rargl = tl rargl) + argl = hd rargl :: argl; + + def_sm := g.def_sm; + if (deferred && def_sm > 0) { + g.print(sprint("<FONT SIZE=-%d>", def_sm)); + if (g.def_goobie == nil) + argl = "dS" :: argl; # dS is our own local creation + } + + subgoobie(g, argl); + + if (deferred && def_sm > 0) { + g.def_sm = 0; + g.print("</FONT>"); + } +} + +subgoobie(g: ref Global, argl: list of string) +{ + if (g.def_goobie != nil) { + argl = g.def_goobie :: argl; + g.def_goobie = nil; + if (tl argl == nil) + return; + } + + # the command part is at most two characters, but may be concatenated with the first arg + cmd := hd argl; + argl = tl argl; + if (len cmd > 2) { + cmd = cmd[0:2]; + argl = cmd[2:] :: argl; + } + + case cmd { + + "B" or "I" or "L" or "R" => + font(g, cmd, argl); # "R" macro implicitly generated by deferred R* macros + + "BI" or "BL" or "BR" or + "IB" or "IL" or + "LB" or "LI" or + "RB" or "RI" or "RL" => + altfont(g, cmd[0:1], cmd[1:2], argl, true); + + "IR" or "LR" => + anchor(g, cmd[0:1], cmd[1:2], argl); # includes man page refs ("IR" is old style, "LR" is new) + + "dS" => + printargs(g, argl); + g.print("\n"); + + "1C" or "2C" or "DT" or "TF" => # ignore these + return; + + "P" or "PP" or "LP" => + g_PP(g); + + "EE" => g_EE(g); + "EX" => g_EX(g); + "HP" => g_HP_TP(g, 1); + "IP" => g_IP(g, argl); + "PD" => g_PD(g, argl); + "PM" => g_PM(g, argl); + "RE" => g_RE(g); + "RS" => g_RS(g); + "SH" => g_SH(g, argl); + "SM" => g_SM(g, argl); + "SS" => g_SS(g, argl); + "TH" => g_TH(g, argl); + "TP" => g_HP_TP(g, 3); + + "br" => g_br(g); + "sp" => g_sp(g, argl); + "ti" => g_br(g); + "nf" => g_nf(g); + "fi" => g_fi(g); + "ft" => g_ft(g, argl); + + * => return; # ignore unrecognized commands + } + +} + +g_br(g: ref Global) +{ + if (g.hangingdt != 0) { + g.print("<DD>"); + g.hangingdt = 0; + } else if (g.fill && ! g.broken) + g.print("<BR>\n"); + g.broken = true; +} + +g_EE(g: ref Global) +{ + g.print("</PRE>\n"); + g.fill = true; + g.broken = true; + g.example = false; +} + +g_EX(g: ref Global) +{ + g.print("<PRE>"); + if (! g.broken) + g.print("\n"); + g.sop = true; + g.fill = false; + g.broken = true; + g.example = true; +} + +g_fi(g: ref Global) +{ + if (g.fill) + return; + g.fill = true; + g.print("<P style=\"display: inline; white-space: normal\">\n"); + g.broken = true; + g.sop = true; +} + +g_ft(g: ref Global, argl: list of string) +{ + font: string; + arg: string; + + if (argl == nil) + arg = "P"; + else + arg = hd argl; + + if (g.curfont != nil) + g.print(sprint("</%s>", g.curfont)); + + case arg { + "2" or "I" => + font = "I"; + "3" or "B" => + font = "B"; + "5" or "L" => + font = "TT"; + "P" => + font = g.prevfont; + * => + font = nil; + } + g.prevfont = g.curfont; + g.curfont = font; + if (g.curfont != nil) + if (g.fill) + g.print(sprint("<%s>", g.curfont)); + else + g.print(sprint("<%s style=\"white-space: pre\">", g.curfont)); +} + +# level == 1 is a .HP; level == 3 is a .TP +g_HP_TP(g: ref Global, level: int) +{ + case g.list_type { + Ldef => + if (g.hangingdt != 0) + g.print("<DD>"); + g.print(g.softbr() + "<DT>"); + * => + closel(g); + g.list_type = Ldef; + g.print("<DL compact>\n" + g.softbr() + "<DT>"); + } + g.hangingdt = level; + g.broken = true; +} + +g_IP(g: ref Global, argl: list of string) +{ + case g.list_type { + + Lordered or Lunordered or Lother => + ; # continue with an existing list + + * => + # figure out the type of a new list and start it + closel(g); + arg := ""; + if (argl != nil) + arg = hd argl; + case arg { + "1" or "i" or "I" or "a" or "A" => + g.list_type = Lordered; + g.print(sprint("<OL type=%s>\n", arg)); + "*" or "•" or "•" => + g.list_type = Lunordered; + g.print("<UL type=disc>\n"); + "○" or "○"=> + g.list_type = Lunordered; + g.print("<UL type=circle>\n"); + "□" or "□" => + g.list_type = Lunordered; + g.print("<UL type=square>\n"); + * => + g.list_type = Lother; + g.print("<DL compact>\n"); + } + } + + # actually do this list item + case g.list_type { + Lother => + g.print(g.softp()); # make sure there's space before each list item + if (argl != nil) { + g.print("<DT>"); + printargs(g, argl); + } + g.print("\n<DD>"); + + Lordered or Lunordered => + g.print(g.softp() + "<LI>"); + } + g.broken = true; +} + +g_nf(g: ref Global) +{ + if (! g.fill) + return; + g.fill = false; + g.print("<PRE>\n"); + g.broken = true; + g.sop = true; + g.pre = true; +} + +g_PD(g: ref Global, argl: list of string) +{ + if (len argl == 1 && hd argl == "0") + g.ipd = false; + else + g.ipd = true; +} + +g_PM(g: ref Global, argl: list of string) +{ + code := "P"; + if (argl != nil) + code = hd argl; + case code { + * => # includes "1" and "P" + g.pm = "<B>Lucent Technologies - Proprietary</B>\n" + + "<BR>Use pursuant to Company Instructions.\n"; + "2" or "RS" => + g.pm = "<B>Lucent Technologies - Proprietary (Restricted)</B>\n" + + "<BR>Solely for authorized persons having a need to know\n" + + "<BR>pursuant to Company Instructions.\n"; + "3" or "RG" => + g.pm = "<B>Lucent Technologies - Proprietary (Registered)</B>\n" + + "<BR>Solely for authorized persons having a need to know\n" + + "<BR>and subject to cover sheet instructions.\n"; + "4" or "CP" => + g.pm = "SEE PROPRIETARY NOTICE ON COVER PAGE\n"; + "5" or "CR" => + g.pm = "Copyright xxxx Lucent Technologies\n" + # should fill in the year from the date register + "<BR>All Rights Reserved.\n"; + "6" or "UW" => + g.pm = "THIS DOCUMENT CONTAINS PROPRIETARY INFORMATION OF\n" + + "<BR>LUCENT TECHNOLOGIES INC. AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN\n" + + "<BR>ACCORDANCE WITH APPLICABLE AGREEMENTS.\n" + + "<BR>Unpublished & Not for Publication\n"; + } +} + +g_PP(g: ref Global) +{ + closel(g); + reset_font(g); + p := g.softp(); + if (p != nil) + g.print(p); + g.sop = true; + g.broken = true; +} + +g_RE(g: ref Global) +{ + g.print("</DL>\n"); + g.indents--; + g.broken = true; +} + +g_RS(g: ref Global) +{ + g.print("<DL>\n<DT><DD>"); + g.indents++; + g.broken = true; +} + +g_SH(g: ref Global, argl: list of string) +{ + closeall(g, 1); # .SH is top-level list item + if (g.example) + g_EE(g); + if (g.fill && ! g.sop) + g.print("<P>"); + g.print("<DT><H4>"); + printargs(g, argl); + g.print("</H4>\n"); + g.print("<DD>\n"); + g.sop = true; + g.broken = true; +} + +g_SM(g: ref Global, argl: list of string) +{ + g.def_sm++; # can't use def_goobie, lest we collide with a deferred font macro + if (argl == nil) + return; + g.print(sprint("<FONT SIZE=-%d>", g.def_sm)); + printargs(g, argl); + g.print("</FONT>\n"); + g.def_sm = 0; +} + +g_sp(g: ref Global, argl: list of string) +{ + if (g.sop && g.fill) + return; + count := 1; + if (argl != nil) { + rcount := real hd argl; + count = int rcount; # may be 0 (e.g., ".sp .5") + if (count == 0 && rcount > 0.0) + count = 1; # force whitespace for fractional lines + } + g.dobreak(); + for (i := 0; i < count; i++) + g.print(" <BR>\n"); + g.broken = true; + g.sop = count > 0; +} + +g_SS(g: ref Global, argl: list of string) +{ + closeall(g, 1); + g.indents++; + g.print(g.softp() + "<DL><DT><FONT SIZE=3><B>"); + printargs(g, argl); + g.print("</B></FONT>\n"); + g.print("<DD>\n"); + g.sop = true; + g.broken = true; +} + +g_TH(g: ref Global, argl: list of string) +{ + if (g.oldstyle.names && len argl > 2) + argl = hd argl :: hd tl argl :: nil; # ignore extra .TH args on pages in oldstyle trees + case len argl { + 0 => + g.oldstyle.fmt = true; + title(g, sprint("%s", g.href.title), false); + 1 => + g.oldstyle.fmt = true; + title(g, sprint("%s", hd argl), false); # any pages use this form? + 2 => + g.oldstyle.fmt = true; + g.thisone.page = hd argl; + g.thisone.mtype = hd tl argl; + g.mk_href_man(hd argl, true); + g.mk_href_mtype(nil, hd tl argl); + title(g, sprint("%s(%s)", g.href.man, g.href.mtype), false); + * => + g.oldstyle.fmt = false; + chap := hd tl tl argl; + g.mk_href_chap(chap); + g.mk_href_man(hd argl, false); + g.mk_href_mtype(chap, hd tl argl); + title(g, sprint("%s/%s/%s(%s)", g.href.title, g.href.chap, g.href.man, g.href.mtype), false); + } + g.print("[<a href=\"../index.html\">manual index</a>]"); + g.print("[<a href=\"INDEX.html\">section index</a>]<p>"); + g.print("<DL>\n"); # whole man page is just one big list + g.indents = 1; + g.sop = true; + g.broken = true; +} + +dohangingdt(g: ref Global) +{ + case g.hangingdt { + 3 => + g.hangingdt--; + 2 => + g.print("<DD>"); + g.hangingdt = 0; + g.broken = true; + } +} + +# close a list, if there's one active +closel(g: ref Global) +{ + case g.list_type { + Lordered => + g.print("</OL>\n"); + g.broken = true; + Lunordered => + g.print("</UL>\n"); + g.broken = true; + Lother or Ldef => + g.print("</DL>\n"); + g.broken = true; + } + g.list_type = Lnone; +} + +closeall(g: ref Global, level: int) +{ + closel(g); + reset_font(g); + while (g.indents > level) { + g.indents--; + g.print("</DL>\n"); + g.broken = true; + } +} + +# +# Show last revision date for a file. +# +rev(g: ref Global, filebuf: ref Bufio->Iobuf) +{ + if (g.mtime == 0) { + (err, info) := sys->fstat(filebuf.fd); + if (! err) + g.mtime = info.mtime; + } + if (g.mtime != 0) { + g.print("<P><TABLE width=\"100%\" border=0 cellpadding=10 cellspacing=0 bgcolor=\"#E0E0E0\">\n"); + g.print("<TR>"); + g.print(sprint("<TD align=left><FONT SIZE=-1>")); + g.print(sprint("%s(%s)", g.thisone.page, g.thisone.mtype)); + g.print("</FONT></TD>\n"); + g.print(sprint("<TD align=right><FONT SIZE=-1><I>Rev: %s</I></FONT></TD></TR></TABLE>\n", + dt->text(dt->gmt(g.mtime)))); + } +} + +# +# Some font alternation macros are references to other man pages; +# detect them (second arg contains balanced parens) and make them into hot links. +# +anchor(g: ref Global, f1, f2: string, argl: list of string) +{ + final := ""; + link := false; + if (len argl == 2) { + (s, e) := str->splitl(hd tl argl, ")"); + if (str->prefix("(", s) && e != nil) { + # emit href containing search for target first + # if numeric, do old style + link = true; + file := hd argl; + (chap, man) := split(httpunesc(file), '/'); + if (man == nil) { + # given no explicit chapter prefix, use current chapter + man = chap; + chap = g.thisone.chap; + } + mtype := s[1:]; + if (mtype == nil) + mtype = "-"; + (n, toks) := sys->tokenize(mtype, "."); # Fix section 10 + if (n > 1) mtype = hd toks; + g.print(sprint("<A href=\"../%s/%s.html\">", mtype, fixlink(man))); + + # + # now generate the name the user sees, with terminal punctuation + # moved after the closing </A>. + # + if (len e > 1) + final = e[1:]; + argl = hd argl :: s + ")" :: nil; + } + } + altfont(g, f1, f2, argl, false); + if (link) { + g.print("</A>"); + font(g, f2, final :: nil); + } else + g.print("\n"); +} + + +# +# Fix up a link +# + +fixlink(l: string): string +{ + ll := str->tolower(l); + if (ll == "copyright") ll = "1" + ll; + (a, b) := str->splitstrl(ll, "intro"); + if (len b == 5) ll = a + "0" + b; + return ll; +} + + +# +# output argl in font f +# +font(g: ref Global, f: string, argl: list of string) +{ + if (argl == nil) { + g.def_goobie = f; + return; + } + case f { + "L" => f = "TT"; + "R" => f = nil; + } + if (f != nil) # nil == default (typically Roman) + g.print(sprint("<%s>", f)); + printargs(g, argl); + if (f != nil) + g.print(sprint("</%s>", f)); + g.print("\n"); + g.prevfont = f; +} + +# +# output concatenated elements of argl, alternating between fonts f1 and f2 +# +altfont(g: ref Global, f1, f2: string, argl: list of string, newline: int) +{ + reset_font(g); + if (argl == nil) { + g.def_goobie = f1; + return; + } + case f1 { + "L" => f1 = "TT"; + "R" => f1 = nil; + } + case f2 { + "L" => f2 = "TT"; + "R" => f2 = nil; + } + f := f1; + for (; argl != nil; argl = tl argl) { + if (f != nil) + g.print(sprint("<%s>%s</%s>", f, hd argl, f)); + else + g.print(hd argl); + if (f == f1) + f = f2; + else + f = f1; + } + if (newline) + g.print("\n"); + g.prevfont = f; +} + +# not yet implemented +map_font(nil: ref Global, nil: string) +{ +} + +reset_font(g: ref Global) +{ + if (g.curfont != nil) { + g.print(sprint("</%s>", g.curfont)); + g.prevfont = g.curfont; + g.curfont = nil; + } +} + +printargs(g: ref Global, argl: list of string) +{ + for (; argl != nil; argl = tl argl) + if (tl argl != nil) + g.print(hd argl + " "); + else + g.print(hd argl); +} + +# any parameter can be nil +addhit(g: ref Global, chap, mtype, page: string) +{ + # g.print(sprint("Adding %s / %s (%s) . . .", chap, page, mtype)); # debug + # always keep a spare slot at the end + if (g.nhits >= len g.hits - 1) + g.hits = (array[len g.hits + 32] of Hit)[0:] = g.hits; + g.hits[g.nhits].glob = chap + " " + mtype + " " + page; + g.hits[g.nhits].chap = chap; + g.hits[g.nhits].mtype = mtype; + g.hits[g.nhits++].page = page; +} + +Global.dobreak(g: self ref Global) +{ + if (! g.broken) { + g.broken = true; + g.print("<BR>\n"); + } +} + +Global.print(g: self ref Global, s: string) +{ + g.bufio->g.bout.puts(s); + if (g.sop || g.broken) { + # first non-white space, non-HTML we print takes us past the start of the paragraph & line + # (or even white space, if we're in no-fill mode) + for (i := 0; i < len s; i++) { + case s[i] { + '<' => + while (++i < len s && s[i] != '>') + ; + continue; + ' ' or '\t' or '\n' => + if (g.fill) + continue; + } + g.sop = false; + g.broken = false; + break; + } + } +} + +Global.softbr(g: self ref Global): string +{ + if (g.broken) + return nil; + g.broken = true; + return "<BR>"; +} + +# provide a paragraph marker, unless we're already at the start of a section +Global.softp(g: self ref Global): string +{ + if (g.sop) + return nil; + else if (! g.ipd) + return "<BR>"; + if (g.fill) + return "<P>"; + else + return "<P style=\"white-space: pre\">"; +} + +# +# Get next logical character. Expand it with escapes. +# +getnext(g: ref Global): string +{ + iob := g.bufio; + Iobuf: import iob; + + font: string; + token: string; + bin := g.bin; + + g.sol = (g.lastc == '\n'); + + c := bin.getc(); + if (c < 0) + return nil; + g.lastc = c; + if (c >= Runeself) { + for (i := 0; i < len Entities; i++) + if (Entities[i].value == c) + return Entities[i].name; + return sprint("&#%d;", c); + } + case c { + '<' => + return "<"; + '>' => + return ">"; + '\\' => + c = bin.getc(); + if (c < 0) + return nil; + g.lastc = c; + case c { + + # chars to ignore + '|' or '&' or '^' => + return getnext(g); + + # ignore arg + 'k' => + nil = bin.getc(); + return getnext(g); + + # defined strings + '*' => + case bin.getc() { + 'R' => + return "®"; + } + return getnext(g); + + # special chars + '(' => + token[0] = bin.getc(); + token[1] = bin.getc(); + for (i := 0; i < len tspec; i++) + if (token == tspec[i].name) + return tspec[i].value; + return "¿"; + 'c' => + c = bin.getc(); + if (c < 0) + return nil; + else if (c == '\n') { + g.lastc = c; + g.sol = true; + token[0] = bin.getc(); + return token; + } + # DEBUG: should there be a "return xxx" here? + 'e' => + return "\\"; + 'f' => + g.lastc = c = bin.getc(); + if (c < 0) + return nil; + case c { + '2' or 'I' => + font = "I"; + '3' or 'B' => + font = "B"; + '5' or 'L' => + font = "TT"; + 'P' => + font = g.prevfont; + * => # includes '1' and 'R' + font = nil; + } +# There are serious problems with this. We don't know the fonts properly at this stage. +# g.prevfont = g.curfont; +# g.curfont = font; +# if (g.prevfont != nil) +# token = sprint("</%s>", g.prevfont); +# if (g.curfont != nil) +# token += sprint("<%s>", g.curfont); + if (token == nil) + return " "; # shouldn't happen - maybe a \fR inside a font macro - just do something! + return token; + 's' => + sign := '+'; + size := 0; + relative := false; + getsize: + for (;;) { + c = bin.getc(); + if (c < 0) + return nil; + case c { + '+' => + relative = true; + '-' => + sign = '-'; + relative = true; + '0' to '9' => + size = size * 10 + (c - '0'); + * => + bin.ungetc(); + break getsize; + } + g.lastc = c; + } + if (size == 0) + token = "</FONT>"; + else if (relative) + token = sprint("<FONT SIZE=%c%d>", sign, size); + else + token = sprint("<FONT SIZE=%d>", size); + return token; + } + } + token[0] = c; + return token; +} + +# +# Return strings before and after the left-most instance of separator; +# (s, nil) if no match or separator is last char in s. +# +split(s: string, sep: int): (string, string) +{ + for (i := 0; i < len s; i++) + if (s[i] == sep) + return (s[:i], s[i+1:]); # s[len s:] is a valid slice, with value == nil + return (s, nil); +} + +Global_init(): ref Global +{ + g := ref Global; + g.bufio = load Bufio Bufio->PATH; + g.chaps = array[20] of Chaps; + g.types = array[20] of Types; + g.mantitle = ""; + g.href.title = g.mantitle; # ?? + g.mtime = 0; + g.nhits = 0; + g.oldstyle.names = false; + g.oldstyle.fmt = false; + g.topname = "System"; + g.list_type = Lnone; + g.def_sm = 0; + g.hangingdt = 0; + g.indents = 0; + g.sop = true; + g.broken = true; + g.ipd = true; + g.fill = true; + g.example = false; + g.pre = false; + g.lastc = '\n'; + return g; +} + +Global.mk_href_chap(g: self ref Global, chap: string) +{ + if (chap != nil) + g.href.chap = sprint("<A href=\"%s/%s?man=*\"><B>%s</B></A>", g.mandir, chap, chap); +} + +Global.mk_href_man(g: self ref Global, man: string, oldstyle: int) +{ + rman := man; + if (oldstyle) + rman = str->tolower(man); # compensate for tradition of putting titles in all CAPS + g.href.man = sprint("<A href=\"%s?man=%s\"><B>%s</B></A>", g.mandir, rman, man); +} + +Global.mk_href_mtype(g: self ref Global, chap, mtype: string) +{ + g.href.mtype = sprint("<A href=\"%s/%s/%s\"><B>%s</B></A>", g.mandir, chap, mtype, mtype); +} + +# We assume that anything >= Runeself is already in UTF. +# +httpunesc(s: string): string +{ + t := ""; + for (i := 0; i < len s; i++) { + c := s[i]; + if (c == '&' && i + 1 < len s) { + (char, rem) := str->splitl(s[i+1:], ";"); + if (rem == nil) + break; # require the terminating ';' + if (char == nil) + continue; + if (char[0] == '#' && len char > 1) { + c = int char[1:]; + i += len char; + if (c < 256 && c >= 161) { + t[len t] = Entities[c-161].value; + continue; + } + } else { + for (j := 0; j < len Entities; j++) + if (Entities[j].name == char) + break; + if (j < len Entities) { + i += len char; + t[len t] = Entities[j].value; + continue; + } + } + } + t[len t] = c; + } + return t; +} + + + +title(g: ref Global, t: string, search: int) +{ + if(search) + ; # not yet used + g.print("<HTML><HEAD>\n"); + g.print(sprint("<TITLE>Inferno's %s</TITLE>\n", demark(t))); + g.print("</HEAD>\n"); + g.print("<BODY bgcolor=\"#FFFFFF\">\n"); + +} diff --git a/appl/cmd/shutdown.b b/appl/cmd/shutdown.b deleted file mode 100644 index 8eb7a86c..00000000 --- a/appl/cmd/shutdown.b +++ /dev/null @@ -1,72 +0,0 @@ -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; -} |
