1implement Sh; 2 3include "sys.m"; 4 sys: Sys; 5 FD: import Sys; 6 7include "draw.m"; 8 Context: import Draw; 9 10include "filepat.m"; 11 filepat: Filepat; 12 nofilepat := 0; # true if load Filepat has failed. 13 14include "bufio.m"; 15 bufio: Bufio; 16 Iobuf: import bufio; 17 18include "env.m"; 19 env: Env; 20 21stdin: ref FD; 22stderr: ref FD; 23waitfd: ref FD; 24 25Quoted: con '\uFFF0'; 26stringQuoted: con "\uFFF0"; 27 28Sh: module 29{ 30 init: fn(ctxt: ref Context, argv: list of string); 31}; 32 33Command: adt 34{ 35 args: list of string; 36 inf, outf: string; 37 append: int; 38}; 39 40Async, Seq: con iota; 41 42Pipeline: adt 43{ 44 cmds: list of ref Command; 45 term: int; 46}; 47 48usage() 49{ 50 sys->fprint(stderr, "Usage: sh [-n] [-c cmd] [file]\n"); 51} 52 53init(ctxt: ref Context, argv: list of string) 54{ 55 n: int; 56 arg: list of string; 57 buf := array[1024] of byte; 58 59 sys = load Sys Sys->PATH; 60 env = load Env Env->PATH; 61 62 stderr = sys->fildes(2); 63 64 waitfd = sys->open("#p/"+string sys->pctl(0, nil)+"/wait", sys->OREAD); 65 if(waitfd == nil){ 66 sys->fprint(stderr, "sh: open wait: %r\n"); 67 return; 68 } 69 70 eflag := nflag := lflag := 0; 71 cmd: string; 72 if(argv != nil) 73 argv = tl argv; 74 for(; argv != nil && len hd argv && (hd argv)[0]=='-'; argv = tl argv) 75 case hd argv { 76 "-e" => 77 eflag = 1; 78 "-n" => 79 nflag = 1; 80 "-l" => 81 lflag = 1; 82 "-c" => 83 argv = tl argv; 84 if(len argv != 1){ 85 usage(); 86 return; 87 } 88 cmd = hd argv; 89 * => 90 usage(); 91 return; 92 } 93 94 if (lflag) 95 startup(ctxt); 96 97 if(eflag == 0) 98 sys->pctl(sys->FORKENV, nil); 99 if(nflag == 0) 100 sys->pctl(sys->FORKNS, nil); 101 if(cmd != nil){ 102 arg = tokenize(cmd+"\n"); 103 if(arg != nil) 104 runit(ctxt, parseit(arg)); 105 return; 106 } 107 if(argv != nil){ 108 script(ctxt, hd argv); 109 return; 110 } 111 112 stdin = sys->fildes(0); 113 114 prompt := sysname() + "$ "; 115 for(;;){ 116 sys->print("%s", prompt); 117 n = sys->read(stdin, buf, len buf); 118 if(n <= 0) 119 break; 120 arg = tokenize(string buf[0:n]); 121 if(arg != nil) 122 runit(ctxt, parseit(arg)); 123 } 124} 125 126rev(arg: list of string): list of string 127{ 128 ret: list of string; 129 130 while(arg != nil){ 131 ret = hd arg :: ret; 132 arg = tl arg; 133 } 134 return ret; 135} 136 137waitfor(pid: int) 138{ 139 if(pid <= 0) 140 return; 141 buf := array[sys->WAITLEN] of byte; 142 status := ""; 143 for(;;){ 144 n := sys->read(waitfd, buf, len buf); 145 if(n < 0){ 146 sys->fprint(stderr, "sh: read wait: %r\n"); 147 return; 148 } 149 status = string buf[0:n]; 150 if(status[len status-1] != ':') 151 sys->fprint(stderr, "%s\n", status); 152 who := int status; 153 if(who != 0){ 154 if(who == pid) 155 return; 156 } 157 } 158} 159 160mkprog(ctxt: ref Context, arg: list of string, infd, outfd: ref Sys->FD, waitpid: chan of int) 161{ 162 fds := list of {0, 1, 2}; 163 if(infd != nil) 164 fds = infd.fd :: fds; 165 if(outfd != nil) 166 fds = outfd.fd :: fds; 167 pid := sys->pctl(sys->NEWFD, fds); 168 console := sys->fildes(2); 169 170 if(infd != nil){ 171 sys->dup(infd.fd, 0); 172 infd = nil; 173 } 174 if(outfd != nil){ 175 sys->dup(outfd.fd, 1); 176 outfd = nil; 177 } 178 179 waitpid <-= pid; 180 181 if(pid < 0 || arg == nil) 182 return; 183 184 { 185 exec(ctxt, arg, console); 186 }exception{ 187 "fail:*" => 188 #sys->fprint(console, "%s:%s\n", hd arg, e.name[5:]); 189 exit; 190 "write on closed pipe" => 191 #sys->fprint(console, "%s: %s\n", hd arg, e.name); 192 exit; 193 } 194} 195 196exec(ctxt: ref Context, args: list of string, console: ref Sys->FD) 197{ 198 if (args == nil) 199 return; 200 cmd := hd args; 201 file := cmd; 202 203 if(len file<4 || file[len file-4:]!=".dis") 204 file += ".dis"; 205 206 c := load Sh file; 207 if(c == nil) { 208 err := sys->sprint("%r"); 209 if(err != "permission denied" && err != "access permission denied" && file[0]!='/' && file[0:2]!="./"){ 210 c = load Sh "/dis/"+file; 211 if(c == nil) 212 err = sys->sprint("%r"); 213 } 214 if(c == nil){ 215 sys->fprint(console, "%s: %s\n", cmd, err); 216 return; 217 } 218 } 219 220 c->init(ctxt, args); 221} 222 223script(ctxt: ref Context, src: string) 224{ 225 bufio = load Bufio Bufio->PATH; 226 if(bufio == nil){ 227 sys->fprint(stderr, "sh: load bufio: %r\n"); 228 return; 229 } 230 231 f := bufio->open(src, Bufio->OREAD); 232 if(f == nil){ 233 sys->fprint(stderr, "sh: open %s: %r\n", src); 234 return; 235 } 236 for(;;){ 237 s := f.gets('\n'); 238 if(s == nil) 239 break; 240 arg := tokenize(s); 241 if(arg != nil) 242 runit(ctxt, parseit(arg)); 243 } 244} 245 246sysname(): string 247{ 248 fd := sys->open("#c/sysname", sys->OREAD); 249 if(fd == nil) 250 return "anon"; 251 buf := array[128] of byte; 252 n := sys->read(fd, buf, len buf); 253 if(n < 0) 254 return "anon"; 255 return string buf[0:n]; 256} 257 258# Lexer. 259 260tokenize(s: string): list of string 261{ 262 tok: list of string; 263 token := ""; 264 instring := 0; 265 266loop: 267 for(i:=0; i<len s; i++) { 268 if(instring) { 269 if(s[i] != '\'') 270 token = addchar(token, s[i]); 271 else if(i == len s-1 || s[i+1] != '\'') { 272 if(i == len s-1 || s[i+1] == ' ' || s[i+1] == '\t' || s[i+1] == '\n'){ 273 tok = token :: tok; 274 token = ""; 275 } 276 instring = 0; 277 } else { 278 token[len token] = '\''; 279 i++; 280 } 281 continue; 282 } 283 case s[i] { 284 ' ' or '\t' or '\n' or '#' or 285 '\'' or '|' or '&' or ';' or 286 '>' or '<' or '\r' => 287 if(token != "" && s[i]!='\''){ 288 tok = token :: tok; 289 token = ""; 290 } 291 case s[i] { 292 '#' => 293 break loop; 294 '\'' => 295 instring = 1; 296 '>' => 297 ss := ""; 298 ss[0] = s[i]; 299 if(i<len s-1 && s[i+1]==s[i]) 300 ss[1] = s[i++]; 301 tok = ss :: tok; 302 '|' or '&' or ';' or '<' => 303 ss := ""; 304 ss[0] = s[i]; 305 tok = ss :: tok; 306 } 307 * => 308 token[len token] = s[i]; 309 } 310 } 311 if(instring){ 312 sys->fprint(stderr, "sh: unmatched quote\n"); 313 return nil; 314 } 315 return rev(tok); 316} 317 318ismeta(char: int): int 319{ 320 case char { 321 '*' or '[' or '?' or 322 '#' or '\'' or '|' or 323 '&' or ';' or '>' or 324 '<' => 325 return 1; 326 } 327 return 0; 328} 329 330addchar(token: string, char: int): string 331{ 332 if(ismeta(char) && (len token==0 || token[0]!=Quoted)) 333 token = stringQuoted + token; 334 token[len token] = char; 335 return token; 336} 337 338# Parser. 339 340getcommand(words: list of string): (ref Command, list of string) 341{ 342 args: list of string; 343 word: string; 344 si, so: string; 345 append := 0; 346 347gather: 348 do { 349 word = hd words; 350 351 case word { 352 ">" or ">>" => 353 if(so != nil) 354 return (nil, nil); 355 356 words = tl words; 357 358 if(words == nil) 359 return (nil, nil); 360 361 so = hd words; 362 if(len so>0 && so[0]==Quoted) 363 so = so[1:]; 364 if(word == ">>") 365 append = 1; 366 "<" => 367 if(si != nil) 368 return (nil, nil); 369 370 words = tl words; 371 372 if(words == nil) 373 return (nil, nil); 374 375 si = hd words; 376 if(len si>0 && si[0]==Quoted) 377 si = si[1:]; 378 "|" or ";" or "&" => 379 break gather; 380 * => 381 files := doexpand(word); 382 while(files != nil){ 383 args = hd files :: args; 384 files = tl files; 385 } 386 } 387 388 words = tl words; 389 } while (words != nil); 390 391 return (ref Command(rev(args), si, so, append), words); 392} 393 394doexpand(file: string): list of string 395{ 396 if(file == nil) 397 return file :: nil; 398 if(len file>0 && file[0]==Quoted) 399 return file[1:] :: nil; 400 if (nofilepat) 401 return file :: nil; 402 for(i:=0; i<len file; i++) 403 if(file[i]=='*' || file[i]=='[' || file[i]=='?'){ 404 if(filepat == nil) { 405 if ((filepat = load Filepat Filepat->PATH) == nil) { 406 sys->fprint(stderr, "sh: warning: cannot load %s: %r\n", 407 Filepat->PATH); 408 nofilepat = 1; 409 return file :: nil; 410 } 411 } 412 files := filepat->expand(file); 413 if(files != nil) 414 return files; 415 break; 416 } 417 return file :: nil; 418} 419 420revc(arg: list of ref Command): list of ref Command 421{ 422 ret: list of ref Command; 423 while(arg != nil) { 424 ret = hd arg :: ret; 425 arg = tl arg; 426 } 427 return ret; 428} 429 430getpipe(words: list of string): (ref Pipeline, list of string) 431{ 432 cmds: list of ref Command; 433 cur: ref Command; 434 word: string; 435 436 term := Seq; 437gather: 438 while(words != nil) { 439 word = hd words; 440 441 if(word == "|") 442 return (nil, nil); 443 444 (cur, words) = getcommand(words); 445 446 if(cur == nil) 447 return (nil, nil); 448 449 cmds = cur :: cmds; 450 451 if(words == nil) 452 break gather; 453 454 word = hd words; 455 words = tl words; 456 457 case word { 458 ";" => 459 break gather; 460 "&" => 461 term = Async; 462 break gather; 463 "|" => 464 continue gather; 465 } 466 return (nil, nil); 467 } 468 469 if(word == "|") 470 return (nil, nil); 471 472 return (ref Pipeline(revc(cmds), term), words); 473} 474 475revp(arg: list of ref Pipeline): list of ref Pipeline 476{ 477 ret: list of ref Pipeline; 478 479 while(arg != nil) { 480 ret = hd arg :: ret; 481 arg = tl arg; 482 } 483 return ret; 484} 485 486parseit(words: list of string): list of ref Pipeline 487{ 488 ret: list of ref Pipeline; 489 cur: ref Pipeline; 490 491 while(words != nil) { 492 (cur, words) = getpipe(words); 493 if(cur == nil){ 494 sys->fprint(stderr, "sh: syntax error\n"); 495 return nil; 496 } 497 ret = cur :: ret; 498 } 499 return revp(ret); 500} 501 502# Runner. 503 504runpipeline(ctx: ref Context, pipeline: ref Pipeline) 505{ 506 if(pipeline.term == Async) 507 sys->pctl(sys->NEWPGRP, nil); 508 pid := startpipeline(ctx, pipeline); 509 if(pid < 0) 510 return; 511 if(pipeline.term == Seq) 512 waitfor(pid); 513} 514 515startpipeline(ctx: ref Context, pipeline: ref Pipeline): int 516{ 517 pid := 0; 518 cmds := pipeline.cmds; 519 first := 1; 520 inpipe, outpipe: ref Sys->FD; 521 while(cmds != nil) { 522 last := tl cmds == nil; 523 cmd := hd cmds; 524 525 infd: ref Sys->FD; 526 if(!first) 527 infd = inpipe; 528 else if(cmd.inf != nil){ 529 infd = sys->open(cmd.inf, Sys->OREAD); 530 if(infd == nil){ 531 sys->fprint(stderr, "sh: can't open %s: %r\n", cmd.inf); 532 return -1; 533 } 534 } 535 536 outfd: ref Sys->FD; 537 if(!last){ 538 fds := array[2] of ref Sys->FD; 539 if(sys->pipe(fds) < 0){ 540 sys->fprint(stderr, "sh: can't make pipe: %r\n"); 541 return -1; 542 } 543 outpipe = fds[0]; 544 outfd = fds[1]; 545 fds = nil; 546 }else if(cmd.outf != nil){ 547 if(cmd.append){ 548 outfd = sys->open(cmd.outf, Sys->OWRITE); 549 if(outfd != nil) 550 sys->seek(outfd, big 0, Sys->SEEKEND); 551 } 552 if(outfd == nil) 553 outfd = sys->create(cmd.outf, Sys->OWRITE, 8r666); 554 if(outfd == nil){ 555 sys->fprint(stderr, "sh: can't open %s: %r\n", cmd.outf); 556 return -1; 557 } 558 } 559 560 rpid := chan of int; 561 spawn mkprog(ctx, cmd.args, infd, outfd, rpid); 562 pid = <-rpid; 563 infd = nil; 564 outfd = nil; 565 566 inpipe = outpipe; 567 outpipe = nil; 568 569 first = 0; 570 cmds = tl cmds; 571 } 572 return pid; 573} 574 575runit(ctx: ref Context, pipes: list of ref Pipeline) 576{ 577 while(pipes != nil) { 578 pipeline := hd pipes; 579 pipes = tl pipes; 580 if(pipeline.term == Seq) 581 runpipeline(ctx, pipeline); 582 else 583 spawn runpipeline(ctx, pipeline); 584 } 585} 586 587strchr(s: string, c: int): int 588{ 589 ln := len s; 590 for (i := 0; i < ln; i++) 591 if (s[i] == c) 592 return i; 593 return -1; 594} 595 596# PROFILE: con "/lib/profile"; 597PROFILE: con "/lib/infernoinit"; 598 599startup(ctxt: ref Context) 600{ 601 if (env == nil) 602 return; 603 # if (env->getenv("home") != nil) 604 # return; 605 home := gethome(); 606 env->setenv("home", home); 607 escript(ctxt, PROFILE); 608 escript(ctxt, home + PROFILE); 609} 610 611escript(ctxt: ref Context, file: string) 612{ 613 fd := sys->open(file, Sys->OREAD); 614 if (fd != nil) 615 script(ctxt, file); 616} 617 618gethome(): string 619{ 620 fd := sys->open("/dev/user", sys->OREAD); 621 if(fd == nil) 622 return "/"; 623 buf := array[128] of byte; 624 n := sys->read(fd, buf, len buf); 625 if(n < 0) 626 return "/"; 627 return "/usr/" + string buf[0:n]; 628} 629