implement Sh; include "sys.m"; sys: Sys; FD: import Sys; include "draw.m"; Context: import Draw; include "filepat.m"; filepat: Filepat; include "bufio.m"; bufio: Bufio; Iobuf: import bufio; stdin: ref FD; stderr: ref FD; waitfd: ref FD; Quoted: con '\uFFF0'; stringQuoted: con "\uFFF0"; Sh: module { init: fn(ctxt: ref Context, argv: list of string); }; Command: adt { args: list of string; inf, outf: string; append: int; }; Async, Seq: con iota; Pipeline: adt { cmds: list of ref Command; term: int; }; init(ctxt: ref Context, argv: list of string) { n: int; arg: list of string; buf := array[1024] of byte; 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, "sh: open wait: %r\n"); return; } if(argv != nil){ argv = tl argv; if(argv != nil){ if(hd argv == "-n") argv = tl argv; else sys->pctl(sys->FORKNS, nil); } if(argv != nil){ script(ctxt, hd argv); return; } } stdin = sys->fildes(0); prompt := sysname() + "$ "; for(;;){ sys->print("%s", prompt); n = sys->read(stdin, buf, len buf); if(n <= 0) break; arg = tokenize(string buf[0:n]); if(arg != nil) runit(ctxt, parseit(arg)); } } rev(arg: list of string): list of string { ret: list of string; while(arg != nil){ ret = hd arg :: ret; arg = tl arg; } return ret; } waitfor(pid: int) { if(pid <= 0) return; 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; } } } mkprog(ctxt: ref Context, arg: list of string, infd, outfd: ref Sys->FD, waitpid: chan of int) { fds := list of {0, 1, 2}; if(infd != nil) fds = infd.fd :: fds; if(outfd != nil) fds = outfd.fd :: fds; pid := sys->pctl(sys->NEWFD, fds); console := sys->fildes(2); if(infd != nil){ sys->dup(infd.fd, 0); infd = nil; } if(outfd != nil){ sys->dup(outfd.fd, 1); outfd = nil; } waitpid <-= pid; if(pid < 0 || arg == nil) return; e := ref Sys->Exception; if(sys->rescue("fail:*", e) == Sys->EXCEPTION){ sys->fprint(console, "%s:%s\n", hd arg, e.name[5:]); exit; } if(sys->rescue("write on closed pipe", e) == Sys->EXCEPTION){ sys->fprint(console, "%s: %s\n", hd arg, e.name); exit; } exec(ctxt, arg, console); } exec(ctxt: ref Context, args: list of string, console: ref Sys->FD) { cmd := hd args; file := cmd; if(len file<4 || file[len file-4:]!=".dis") file += ".dis"; c := load Sh file; if(c == nil) { err := sys->sprint("%r"); if(err == "file does not exist" && file[0]!='/' && file[0:2]!="./"){ c = load Sh "/dis/"+file; if(c == nil) err = sys->sprint("%r"); } if(c == nil){ sys->fprint(console, "%s: %s\n", cmd, err); return; } } c->init(ctxt, args); } script(ctxt: ref Context, src: string) { bufio = load Bufio Bufio->PATH; if(bufio == nil){ sys->fprint(stderr, "sh: load bufio: %r\n"); return; } f := bufio->open(src, Bufio->OREAD); if(f == nil){ sys->fprint(stderr, "sh: open %s: %r\n", src); return; } for(;;){ s := f.gets('\n'); if(s == nil) break; arg := tokenize(s); if(arg != nil) runit(ctxt, parseit(arg)); } } sysname(): string { fd := sys->open("#c/sysname", sys->OREAD); if(fd == nil) return "anon"; buf := array[128] of byte; n := sys->read(fd, buf, len buf); if(n < 0) return "anon"; return string buf[0:n]; } # Lexer. tokenize(s: string): list of string { tok: list of string; token := ""; instring := 0; loop: for(i:=0; i' or '<' or '\r' => if(token != "" && s[i]!='\''){ tok = token :: tok; token = ""; } case s[i] { '#' => break loop; '\'' => instring = 1; '>' => ss := ""; ss[0] = s[i]; if(i ss := ""; ss[0] = s[i]; tok = ss :: tok; } * => token[len token] = s[i]; } } if(instring){ sys->fprint(stderr, "sh: unmatched quote\n"); return nil; } return rev(tok); } ismeta(char: int): int { case char { '*' or '[' or '?' or '#' or '\'' or '|' or '&' or ';' or '>' or '<' => return 1; } return 0; } addchar(token: string, char: int): string { if(ismeta(char) && (len token==0 || token[0]!=Quoted)) token = stringQuoted + token; token[len token] = char; return token; } # Parser. getcommand(words: list of string): (ref Command, list of string) { args: list of string; word: string; si, so: string; append := 0; gather: do { word = hd words; case word { ">" or ">>" => if(so != nil) return (nil, nil); words = tl words; if(words == nil) return (nil, nil); so = hd words; if(len so>0 && so[0]==Quoted) so = so[1:]; if(word == ">>") append = 1; "<" => if(si != nil) return (nil, nil); words = tl words; if(words == nil) return (nil, nil); si = hd words; if(len si>0 && si[0]==Quoted) si = si[1:]; "|" or ";" or "&" => break gather; * => files := doexpand(word); while(files != nil){ args = hd files :: args; files = tl files; } } words = tl words; } while (words != nil); return (ref Command(rev(args), si, so, append), words); } doexpand(file: string): list of string { if(len file>0 && file[0]==Quoted) return file[1:] :: nil; for(i:=0; iPATH; files := filepat->expand(file); if(files != nil) return files; break; } return file :: nil; } revc(arg: list of ref Command): list of ref Command { ret: list of ref Command; while(arg != nil) { ret = hd arg :: ret; arg = tl arg; } return ret; } getpipe(words: list of string): (ref Pipeline, list of string) { cmds: list of ref Command; cur: ref Command; word: string; term := Seq; gather: while(words != nil) { word = hd words; if(word == "|") return (nil, nil); (cur, words) = getcommand(words); if(cur == nil) return (nil, nil); cmds = cur :: cmds; if(words == nil) break gather; word = hd words; words = tl words; case word { ";" => break gather; "&" => term = Async; break gather; "|" => continue gather; } return (nil, nil); } if(word == "|") return (nil, nil); return (ref Pipeline(revc(cmds), term), words); } revp(arg: list of ref Pipeline): list of ref Pipeline { ret: list of ref Pipeline; while(arg != nil) { ret = hd arg :: ret; arg = tl arg; } return ret; } parseit(words: list of string): list of ref Pipeline { ret: list of ref Pipeline; cur: ref Pipeline; while(words != nil) { (cur, words) = getpipe(words); if(cur == nil){ sys->fprint(stderr, "sh: syntax error\n"); return nil; } ret = cur :: ret; } return revp(ret); } # Runner. runpipeline(ctx: ref Context, pipeline: ref Pipeline) { if(pipeline.term == Async) sys->pctl(sys->NEWPGRP, nil); pid := startpipeline(ctx, pipeline); if(pid < 0) return; if(pipeline.term == Seq) waitfor(pid); } startpipeline(ctx: ref Context, pipeline: ref Pipeline): int { pid := 0; cmds := pipeline.cmds; first := 1; inpipe, outpipe: ref Sys->FD; while(cmds != nil) { last := tl cmds == nil; cmd := hd cmds; infd: ref Sys->FD; if(!first) infd = inpipe; else if(cmd.inf != nil){ infd = sys->open(cmd.inf, Sys->OREAD); if(infd == nil){ sys->fprint(stderr, "sh: can't open %s: %r\n", cmd.inf); return -1; } } outfd: ref Sys->FD; if(!last){ fds := array[2] of ref Sys->FD; if(sys->pipe(fds) < 0){ sys->fprint(stderr, "sh: can't make pipe: %r\n"); return -1; } outpipe = fds[0]; outfd = fds[1]; fds = nil; }else if(cmd.outf != nil){ if(cmd.append){ outfd = sys->open(cmd.outf, Sys->OWRITE); if(outfd != nil) sys->seek(outfd, 0, Sys->SEEKEND); } if(outfd == nil) outfd = sys->create(cmd.outf, Sys->OWRITE, 8r666); if(outfd == nil){ sys->fprint(stderr, "sh: can't open %s: %r\n", cmd.outf); return -1; } } rpid := chan of int; spawn mkprog(ctx, cmd.args, infd, outfd, rpid); pid = <-rpid; infd = nil; outfd = nil; inpipe = outpipe; outpipe = nil; first = 0; cmds = tl cmds; } return pid; } runit(ctx: ref Context, pipes: list of ref Pipeline) { while(pipes != nil) { pipeline := hd pipes; pipes = tl pipes; if(pipeline.term == Seq) runpipeline(ctx, pipeline); else spawn runpipeline(ctx, pipeline); } }