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.mDUP: 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"; LIBSHELLRC: con "/lib/sh/profile";BUILTINPATH: con "/dis/sh"; DEBUG: con 0; ENVSEP: con 0; # word seperator in external environmentENVHASHSIZE: con 7; # XXX profile usage of this...OAPPEND: con 16r80000; # make sure this doesn't clash with O* constants in sys.mOMASK: 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 (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 failurestatus(e); }} run(drawctxt: ref Draw->Context, argv: list of string): string{ initialise(); { return Context.new(drawctxt).run(stringlist2list(argv), 0); } exception e { "fail:*" => return failurestatus(e); }} 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 { "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 = failurestatus(e2); } } 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 { "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;} pipecmd(ctxt: ref Context, cmd: list of ref Listnode, redir: ref Redir): ref Sys->FD{ if(redir.fd2 != -1 || (redir.rtype & OAPPEND)) ctxt.fail("bad redir", "sh: bad redirection"); r := *redir; case redir.rtype {Lines 1–500 of 2876. Load more
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"; 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 (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 failurestatus(e); } } run(drawctxt: ref Draw->Context, argv: list of string): string { initialise(); { return Context.new(drawctxt).run(stringlist2list(argv), 0); } exception e { "fail:*" => return failurestatus(e); } } 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 { "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 = failurestatus(e2); } } 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 { "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; } pipecmd(ctxt: ref Context, cmd: list of ref Listnode, redir: ref Redir): ref Sys->FD { if(redir.fd2 != -1 || (redir.rtype & OAPPEND)) ctxt.fail("bad redir", "sh: bad redirection"); r := *redir; case redir.rtype {