9fans.net/go@v0.0.7/cmd/acme/internal/exec/exec.go (about) 1 // #include <u.h> 2 // #include <libc.h> 3 // #include <bio.h> 4 // #include <draw.h> 5 // #include <thread.h> 6 // #include <cursor.h> 7 // #include <mouse.h> 8 // #include <keyboard.h> 9 // #include <frame.h> 10 // #include <fcall.h> 11 // #include <plumb.h> 12 // #include <libsec.h> 13 // #include <9pclient.h> 14 // #include "dat.h" 15 // #include "fns.h" 16 17 package exec 18 19 import ( 20 "bufio" 21 "crypto/sha1" 22 "fmt" 23 "io" 24 "os" 25 "os/exec" 26 "runtime" 27 "strconv" 28 "strings" 29 "unicode/utf8" 30 31 "9fans.net/go/cmd/acme/internal/addr" 32 "9fans.net/go/cmd/acme/internal/alog" 33 "9fans.net/go/cmd/acme/internal/bufs" 34 "9fans.net/go/cmd/acme/internal/dump" 35 "9fans.net/go/cmd/acme/internal/edit" 36 "9fans.net/go/cmd/acme/internal/file" 37 "9fans.net/go/cmd/acme/internal/fileload" 38 "9fans.net/go/cmd/acme/internal/runes" 39 "9fans.net/go/cmd/acme/internal/ui" 40 "9fans.net/go/cmd/acme/internal/util" 41 "9fans.net/go/cmd/acme/internal/wind" 42 "9fans.net/go/cmd/internal/base" 43 "9fans.net/go/plan9" 44 "9fans.net/go/plan9/client" 45 ) 46 47 var Fsysmount = func([]rune, [][]rune) *base.Mntdir { return nil } 48 var Fsysdelid = func(*base.Mntdir) {} 49 var Xfidlog = func(*wind.Window, string) {} 50 51 var Cwait = make(chan Waitmsg) 52 53 type Waitmsg struct { 54 Proc *os.Process 55 Err error 56 } 57 58 /* 59 * These functions get called as: 60 * 61 * fn(et, t, argt, flag1, flag2, arg); 62 * 63 * Where the arguments are: 64 * 65 * et: the Text* in which the executing event (click) occurred 66 * t: the Text* containing the current selection (Edit, Cut, Snarf, Paste) 67 * argt: the Text* containing the argument for a 2-1 click. 68 * flag1: from Exectab entry 69 * flag2: from Exectab entry 70 * arg: the command line remainder (e.g., "x" if executing "Dump x") 71 */ 72 73 type Exectab struct { 74 name []rune 75 fn func(et, t, argt *wind.Text, flag1, flag2 bool, s []rune) 76 mark bool 77 flag1 bool 78 flag2 bool 79 } 80 81 var exectab = [30]Exectab{ 82 Exectab{[]rune("Abort"), doabort, false, XXX, XXX}, 83 Exectab{[]rune("Cut"), ui.XCut, true, true, true}, 84 Exectab{[]rune("Del"), del, false, false, XXX}, 85 Exectab{[]rune("Delcol"), delcol, false, XXX, XXX}, 86 Exectab{[]rune("Delete"), del, false, true, XXX}, 87 Exectab{[]rune("Dump"), dump_, false, true, XXX}, 88 Exectab{[]rune("Edit"), edit_, false, XXX, XXX}, 89 Exectab{[]rune("Exit"), xexit, false, XXX, XXX}, 90 Exectab{[]rune("Font"), ui.Fontx, false, XXX, XXX}, 91 Exectab{[]rune("Get"), Get, false, true, XXX}, 92 Exectab{[]rune("ID"), id, false, XXX, XXX}, 93 Exectab{[]rune("Incl"), incl, false, XXX, XXX}, 94 Exectab{[]rune("Indent"), indent, false, XXX, XXX}, 95 Exectab{[]rune("Kill"), xkill, false, XXX, XXX}, 96 Exectab{[]rune("Load"), dump_, false, false, XXX}, 97 Exectab{[]rune("Local"), local, false, XXX, XXX}, 98 Exectab{[]rune("Look"), look, false, XXX, XXX}, 99 Exectab{[]rune("New"), ui.New, false, XXX, XXX}, 100 Exectab{[]rune("Newcol"), newcol, false, XXX, XXX}, 101 Exectab{[]rune("Paste"), ui.XPaste, true, true, XXX}, 102 Exectab{[]rune("Put"), Put, false, XXX, XXX}, 103 Exectab{[]rune("Putall"), putall, false, XXX, XXX}, 104 Exectab{[]rune("Redo"), ui.XUndo, false, false, XXX}, 105 Exectab{[]rune("Send"), sendx, true, XXX, XXX}, 106 Exectab{[]rune("Snarf"), ui.XCut, false, true, false}, 107 Exectab{[]rune("Sort"), xsort, false, XXX, XXX}, 108 Exectab{[]rune("Tab"), tab, false, XXX, XXX}, 109 Exectab{[]rune("Undo"), ui.XUndo, false, true, XXX}, 110 Exectab{[]rune("Zerox"), zeroxx, false, XXX, XXX}, 111 } 112 113 func lookup(r []rune) *Exectab { 114 r = runes.SkipBlank(r) 115 if len(r) == 0 { 116 return nil 117 } 118 r = r[:len(r)-len(runes.SkipNonBlank(r))] 119 for i := range exectab { 120 e := &exectab[i] 121 if runes.Equal(r, e.name) { 122 return e 123 } 124 } 125 return nil 126 } 127 128 func isexecc(c rune) bool { 129 if runes.IsFilename(c) { 130 return true 131 } 132 return c == '<' || c == '|' || c == '>' 133 } 134 135 func Execute(t *wind.Text, aq0 int, aq1 int, external bool, argt *wind.Text) { 136 q0 := aq0 137 q1 := aq1 138 var c rune 139 if q1 == q0 { // expand to find word (actually file name) 140 // if in selection, choose selection 141 if t.Q1 > t.Q0 && t.Q0 <= q0 && q0 <= t.Q1 { 142 q0 = t.Q0 143 q1 = t.Q1 144 } else { 145 for q1 < t.Len() && func() bool { c = t.RuneAt(q1); return isexecc(c) }() && c != ':' { 146 q1++ 147 } 148 for q0 > 0 && func() bool { c = t.RuneAt(q0 - 1); return isexecc(c) }() && c != ':' { 149 q0-- 150 } 151 if q1 == q0 { 152 return 153 } 154 } 155 } 156 r := make([]rune, q1-q0) 157 t.File.Read(q0, r) 158 e := lookup(r) 159 var a, aa *string 160 var n int 161 if !external && t.W != nil && t.W.External { 162 f := 0 163 if e != nil { 164 f |= 1 165 } 166 if q0 != aq0 || q1 != aq1 { 167 t.File.Read(aq0, r[:aq1-aq0]) 168 f |= 2 169 } 170 aa = getbytearg(argt, true, true, &a) 171 if a != nil { 172 if len(*a) > wind.EVENTSIZE { // too big; too bad 173 alog.Printf("argument string too long\n") 174 return 175 } 176 f |= 8 177 } 178 c = 'x' 179 if t.What == wind.Body { 180 c = 'X' 181 } 182 n = aq1 - aq0 183 if n <= wind.EVENTSIZE { 184 r := r 185 if len(r) > n { 186 r = r[:n] 187 } 188 wind.Winevent(t.W, "%c%d %d %d %d %s\n", c, aq0, aq1, f, n, string(r)) 189 } else { 190 wind.Winevent(t.W, "%c%d %d %d 0 \n", c, aq0, aq1, f) 191 } 192 if q0 != aq0 || q1 != aq1 { 193 n = q1 - q0 194 t.File.Read(q0, r[:n]) 195 if n <= wind.EVENTSIZE { 196 wind.Winevent(t.W, "%c%d %d 0 %d %s\n", c, q0, q1, n, string(r[:n])) 197 } else { 198 wind.Winevent(t.W, "%c%d %d 0 0 \n", c, q0, q1) 199 } 200 } 201 if a != nil { 202 wind.Winevent(t.W, "%c0 0 0 %d %s\n", c, utf8.RuneCountInString(*a), *a) 203 if aa != nil { 204 wind.Winevent(t.W, "%c0 0 0 %d %s\n", c, utf8.RuneCountInString(*aa), *aa) 205 } else { 206 wind.Winevent(t.W, "%c0 0 0 0 \n", c) 207 } 208 } 209 return 210 } 211 if e != nil { 212 if e.mark && wind.Seltext != nil { 213 if wind.Seltext.What == wind.Body { 214 file.Seq++ 215 wind.Seltext.W.Body.File.Mark() 216 } 217 } 218 s := runes.SkipBlank(r[:q1-q0]) 219 s = runes.SkipNonBlank(s) 220 s = runes.SkipBlank(s) 221 e.fn(t, wind.Seltext, argt, e.flag1, e.flag2, s) 222 return 223 } 224 225 b := string(r) 226 dir := wind.Dirname(t, nil) 227 if len(dir) == 1 && dir[0] == '.' { // sigh 228 dir = nil 229 } 230 aa = getbytearg(argt, true, true, &a) 231 if t.W != nil { 232 util.Incref(&t.W.Ref) 233 } 234 Run(t.W, b, dir, true, aa, a, false) 235 } 236 237 func getbytearg(argt *wind.Text, doaddr, dofile bool, bp **string) *string { 238 *bp = nil 239 var r []rune 240 a := ui.Getarg(argt, doaddr, dofile, &r) 241 if r == nil { 242 return nil 243 } 244 b := string(r) 245 *bp = &b 246 return a 247 } 248 249 var doabort_n int 250 251 func doabort(_, _, _ *wind.Text, _, _ bool, _ []rune) { 252 if doabort_n == 0 { 253 doabort_n++ 254 alog.Printf("executing Abort again will call abort()\n") 255 return 256 } 257 panic("abort") 258 } 259 260 func newcol(et, _, _ *wind.Text, _, _ bool, _ []rune) { 261 262 c := wind.RowAdd(et.Row, nil, -1) 263 ui.Clearmouse() 264 if c != nil { 265 w := ui.ColaddAndMouse(c, nil, nil, -1) 266 wind.Winsettag(w) 267 Xfidlog(w, "new") 268 } 269 } 270 271 func delcol(et, _, _ *wind.Text, _, _ bool, _ []rune) { 272 273 c := et.Col 274 if c == nil || !wind.Colclean(c) { 275 return 276 } 277 for i := 0; i < len(c.W); i++ { 278 w := c.W[i] 279 if w.External { 280 alog.Printf("can't delete column; %s is running an external command\n", string(w.Body.File.Name())) 281 return 282 } 283 } 284 wind.Rowclose(et.Col.Row, et.Col, true) 285 ui.Clearmouse() 286 } 287 288 func del(et, _, _ *wind.Text, isDelete, _ bool, _ []rune) { 289 if et.Col == nil || et.W == nil { 290 return 291 } 292 if isDelete || len(et.W.Body.File.Text) > 1 || wind.Winclean(et.W, false) { 293 ui.ColcloseAndMouse(et.Col, et.W, true) 294 } 295 } 296 297 func xsort(et, _, _ *wind.Text, _, _ bool, _ []rune) { 298 299 if et.Col != nil { 300 ui.Clearmouse() 301 wind.Colsort(et.Col) 302 } 303 } 304 305 func getname(t *wind.Text, argt *wind.Text, arg []rune, isput bool) string { 306 var r []rune 307 ui.Getarg(argt, false, true, &r) 308 promote := false 309 if r == nil { 310 promote = true 311 } else if isput { 312 // if are doing a Put, want to synthesize name even for non-existent file 313 // best guess is that file name doesn't contain a slash 314 promote = true 315 for i := 0; i < len(r); i++ { 316 if r[i] == '/' { 317 promote = false 318 break 319 } 320 } 321 if promote { 322 t = argt 323 arg = r 324 } 325 } 326 if promote { 327 if len(arg) == 0 { 328 return string(t.File.Name()) 329 } 330 var dir []rune 331 // prefix with directory name if necessary 332 dir = nil 333 if len(arg) > 0 && arg[0] != '/' { 334 dir = wind.Dirname(t, nil) 335 if len(dir) == 1 && dir[0] == '.' { // sigh 336 dir = nil 337 } 338 } 339 if dir != nil { 340 r = make([]rune, len(dir)+1+len(arg)) 341 r = append(r, dir...) 342 if len(r) > 0 && r[len(r)-1] != '/' && len(arg) > 0 && arg[0] != '/' { 343 r = append(r, '/') 344 } 345 r = append(r, arg...) 346 } else { 347 r = arg 348 } 349 } 350 return string(r) 351 } 352 353 func zeroxx(et, t, _ *wind.Text, _, _ bool, _ []rune) { 354 355 locked := false 356 if t != nil && t.W != nil && t.W != et.W { 357 locked = true 358 c := 'M' 359 if et.W != nil { 360 c = et.W.Owner 361 } 362 wind.Winlock(t.W, c) 363 } 364 if t == nil { 365 t = et 366 } 367 if t == nil || t.W == nil { 368 return 369 } 370 t = &t.W.Body 371 if t.W.IsDir { 372 alog.Printf("%s is a directory; Zerox illegal\n", string(t.File.Name())) 373 } else { 374 nw := ui.ColaddAndMouse(t.W.Col, nil, t.W, -1) 375 // ugly: fix locks so w->unlock works 376 wind.Winlock1(nw, t.W.Owner) 377 Xfidlog(nw, "zerox") 378 } 379 if locked { 380 wind.Winunlock(t.W) 381 } 382 } 383 384 type TextAddr struct { 385 lorigin int 386 rorigin int 387 lq0 int 388 rq0 int 389 lq1 int 390 rq1 int 391 } 392 393 func Get(et, t, argt *wind.Text, flag1, _ bool, arg []rune) { 394 if flag1 { 395 if et == nil || et.W == nil { 396 return 397 } 398 } 399 if !et.W.IsDir && (et.W.Body.Len() > 0 && !wind.Winclean(et.W, true)) { 400 return 401 } 402 w := et.W 403 t = &w.Body 404 name := getname(t, argt, arg, false) 405 if name == "" { 406 alog.Printf("no file name\n") 407 return 408 } 409 if len(t.File.Text) > 1 { 410 if info, err := os.Stat(name); err == nil && info.IsDir() { 411 alog.Printf("%s is a directory; can't read with multiple windows on it\n", name) 412 return 413 } 414 } 415 addr_ := make([]TextAddr, len(t.File.Text)) 416 for i := 0; i < len(t.File.Text); i++ { 417 a := &addr_[i] 418 u := t.File.Text[i] 419 a.lorigin = edit.Nlcount(u, 0, u.Org, &a.rorigin) 420 a.lq0 = edit.Nlcount(u, 0, u.Q0, &a.rq0) 421 a.lq1 = edit.Nlcount(u, u.Q0, u.Q1, &a.rq1) 422 } 423 r := []rune(name) 424 for i := 0; i < len(t.File.Text); i++ { 425 u := t.File.Text[i] 426 // second and subsequent calls with zero an already empty buffer, but OK 427 wind.Textreset(u) 428 wind.Windirfree(u.W) 429 } 430 samename := runes.Equal(r, t.File.Name()) 431 fileload.Textload(t, 0, name, samename) 432 var dirty bool 433 if samename { 434 t.File.SetMod(false) 435 dirty = false 436 } else { 437 t.File.SetMod(true) 438 dirty = true 439 } 440 for i := 0; i < len(t.File.Text); i++ { 441 t.File.Text[i].W.Dirty = dirty 442 } 443 wind.Winsettag(w) 444 t.File.Unread = false 445 for i := 0; i < len(t.File.Text); i++ { 446 u := t.File.Text[i] 447 wind.Textsetselect(&u.W.Tag, u.W.Tag.Len(), u.W.Tag.Len()) 448 if samename { 449 a := &addr_[i] 450 // Printf("%d %d %d %d %d %d\n", a->lorigin, a->rorigin, a->lq0, a->rq0, a->lq1, a->rq1); 451 q0 := addr.Advance(u, 0, a.lq0, a.rq0) 452 q1 := addr.Advance(u, q0, a.lq1, a.rq1) 453 wind.Textsetselect(u, q0, q1) 454 q0 = addr.Advance(u, 0, a.lorigin, a.rorigin) 455 wind.Textsetorigin(u, q0, false) 456 } 457 wind.Textscrdraw(u) 458 } 459 Xfidlog(w, "get") 460 } 461 462 func checksha1(name string, f *wind.File, info os.FileInfo) { 463 fd, err := os.Open(name) 464 if err != nil { 465 return 466 } 467 buf := make([]byte, bufs.Len) 468 h := sha1.New() 469 for { 470 n, err := fd.Read(buf) 471 h.Write(buf[:n]) 472 if err != nil { 473 break 474 } 475 } 476 fd.Close() 477 var out [20]uint8 478 h.Sum(out[:0]) 479 if out == f.SHA1 { 480 f.Info = info 481 } 482 } 483 484 func sameInfo(fi1, fi2 os.FileInfo) bool { 485 return fi1 != nil && fi2 != nil && os.SameFile(fi1, fi2) && fi1.ModTime().Equal(fi2.ModTime()) && fi1.Size() == fi2.Size() 486 } 487 488 func Putfile(f *wind.File, q0 int, q1 int, namer []rune) { 489 w := f.Curtext.W 490 name := string(namer) 491 info, err := os.Stat(name) 492 if err == nil && runes.Equal(namer, f.Name()) { 493 if !sameInfo(info, f.Info) { 494 checksha1(name, f, info) 495 } 496 if !sameInfo(info, f.Info) { 497 if f.Unread { 498 alog.Printf("%s not written; file already exists\n", name) 499 } else { 500 alog.Printf("%s modified since last read\n\twas %v; now %v\n", name, f.Info.ModTime().Format(timefmt), info.ModTime().Format(timefmt)) 501 } 502 f.Info = info 503 return 504 } 505 } 506 fd, err := os.Create(name) 507 if err != nil { 508 alog.Printf("can't create file %s: %v\n", name, err) 509 return 510 } 511 defer fd.Close() // for Rescue case 512 513 // Use bio in order to force the writes to be large and 514 // block-aligned (bio's default is 8K). This is not strictly 515 // necessary; it works around some buggy underlying 516 // file systems that mishandle unaligned writes. 517 // https://codereview.appspot.com/89550043/ 518 b := bufio.NewWriter(fd) 519 r := bufs.AllocRunes() 520 s := bufs.AllocRunes() 521 info, err = fd.Stat() 522 h := sha1.New() 523 isAppend := err == nil && info.Size() > 0 && info.Mode()&os.ModeAppend != 0 524 if isAppend { 525 alog.Printf("%s not written; file is append only\n", name) 526 goto Rescue2 527 } 528 { 529 var n int 530 for q := q0; q < q1; q += n { 531 n = q1 - q 532 if n > bufs.Len/utf8.UTFMax { 533 n = bufs.Len / utf8.UTFMax 534 } 535 f.Read(q, r[:n]) 536 buf := []byte(string(r[:n])) // TODO(rsc) 537 h.Write(buf) 538 if _, err := b.Write(buf); err != nil { // TODO(rsc): avoid alloc 539 alog.Printf("can't write file %s: %v\n", name, err) 540 goto Rescue2 541 } 542 } 543 } 544 if err := b.Flush(); err != nil { 545 alog.Printf("can't write file %s: %v\n", name, err) 546 goto Rescue2 547 } 548 if err := fd.Close(); err != nil { 549 alog.Printf("can't write file %s: %v\n", name, err) 550 goto Rescue2 // flush or close failed 551 } 552 if runes.Equal(namer, f.Name()) { 553 if q0 != 0 || q1 != f.Len() { 554 f.SetMod(true) 555 w.Dirty = true 556 f.Unread = true 557 } else { 558 // In case the file is on NFS, reopen the fd 559 // before dirfstat to cause the attribute cache 560 // to be updated (otherwise the mtime in the 561 // dirfstat below will be stale and not match 562 // what NFS sees). The file is already written, 563 // so this should be a no-op when not on NFS. 564 // Opening for OWRITE (but no truncation) 565 // in case we don't have read permission. 566 // (The create above worked, so we probably 567 // still have write permission.) 568 if fd, err := os.OpenFile(name, os.O_WRONLY, 0); err == nil { 569 if info1, err := fd.Stat(); err == nil { 570 info = info1 571 } 572 fd.Close() 573 } 574 f.Info = info 575 h.Sum(f.SHA1[:0]) 576 f.SetMod(false) 577 w.Dirty = false 578 f.Unread = false 579 } 580 for i := 0; i < len(f.Text); i++ { 581 f.Text[i].W.Putseq = f.Seq() 582 f.Text[i].W.Dirty = w.Dirty 583 } 584 } 585 bufs.FreeRunes(s) 586 wind.Winsettag(w) 587 return 588 589 Rescue2: 590 bufs.FreeRunes(s) 591 bufs.FreeRunes(r) 592 // fall through 593 } 594 595 func trimspaces(et *wind.Text) { 596 t := &et.W.Body 597 f := t.File 598 marked := 0 599 600 if t.W != nil && et.W != t.W { 601 // can this happen when t == &et->w->body? 602 c := 'M' 603 if et.W != nil { 604 c = et.W.Owner 605 } 606 wind.Winlock(t.W, c) 607 } 608 609 r := bufs.AllocRunes() 610 q0 := f.Len() 611 delstart := q0 // end of current space run, or 0 if no active run; = q0 to delete spaces before EOF 612 for q0 > 0 { 613 n := bufs.RuneLen 614 if n > q0 { 615 n = q0 616 } 617 q0 -= n 618 f.Read(q0, r[:n]) 619 for i := n; ; i-- { 620 if i == 0 || (r[i-1] != ' ' && r[i-1] != '\t') { 621 // Found non-space or start of buffer. Delete active space run. 622 if q0+i < delstart { 623 if marked == 0 { 624 marked = 1 625 file.Seq++ 626 f.Mark() 627 } 628 wind.Textdelete(t, q0+i, delstart, true) 629 } 630 if i == 0 { 631 // keep run active into tail of next buffer 632 if delstart > 0 { 633 delstart = q0 634 } 635 break 636 } 637 delstart = 0 638 if r[i-1] == '\n' { 639 delstart = q0 + i - 1 // delete spaces before this newline 640 } 641 } 642 } 643 } 644 bufs.FreeRunes(r) 645 646 if t.W != nil && et.W != t.W { 647 wind.Winunlock(t.W) 648 } 649 } 650 651 func Put(et, _, argt *wind.Text, _, _ bool, arg []rune) { 652 653 if et == nil || et.W == nil || et.W.IsDir { 654 return 655 } 656 w := et.W 657 f := w.Body.File 658 name := getname(&w.Body, argt, arg, true) 659 if name == "" { 660 alog.Printf("no file name\n") 661 return 662 } 663 if w.Autoindent { 664 trimspaces(et) 665 } 666 namer := []rune(name) 667 Putfile(f, 0, f.Len(), namer) 668 Xfidlog(w, "put") 669 } 670 671 func dump_(_, _, argt *wind.Text, isdump, _ bool, arg []rune) { 672 var name *string 673 if len(arg) != 0 { 674 s := string(arg) 675 name = &s 676 } else { 677 getbytearg(argt, false, true, &name) 678 } 679 if isdump { 680 dump.Dump(&wind.TheRow, name) 681 } else { 682 dump.Load(&wind.TheRow, name, false) 683 } 684 } 685 686 func look(et, t, argt *wind.Text, _, _ bool, arg []rune) { 687 if et != nil && et.W != nil { 688 t = &et.W.Body 689 if len(arg) > 0 { 690 ui.Search(t, arg) 691 return 692 } 693 var r []rune 694 ui.Getarg(argt, false, false, &r) 695 if r == nil { 696 r = make([]rune, t.Q1-t.Q0) 697 t.File.Read(t.Q0, r) 698 } 699 ui.Search(t, r) 700 } 701 } 702 703 func sendx(et, t, _ *wind.Text, _, _ bool, _ []rune) { 704 if et.W == nil { 705 return 706 } 707 t = &et.W.Body 708 if t.Q0 != t.Q1 { 709 ui.XCut(t, t, nil, true, false, nil) 710 } 711 wind.Textsetselect(t, t.Len(), t.Len()) 712 ui.XPaste(t, t, nil, true, true, nil) 713 if t.RuneAt(t.Len()-1) != '\n' { 714 wind.Textinsert(t, t.Len(), []rune("\n"), true) 715 wind.Textsetselect(t, t.Len(), t.Len()) 716 } 717 t.IQ1 = t.Q1 718 wind.Textshow(t, t.Q1, t.Q1, true) 719 } 720 721 func edit_(et, _, argt *wind.Text, _, _ bool, arg []rune) { 722 if et == nil { 723 return 724 } 725 var r []rune 726 ui.Getarg(argt, false, true, &r) 727 file.Seq++ 728 if r != nil { 729 edit.Editcmd(et, r) 730 } else { 731 edit.Editcmd(et, arg) 732 } 733 } 734 735 func xexit(_, _, _ *wind.Text, _, _ bool, _ []rune) { 736 if wind.Rowclean(&wind.TheRow) { 737 Cexit <- 0 738 runtime.Goexit() // TODO(rsc) 739 } 740 } 741 742 func putall(et, _, _ *wind.Text, _, _ bool, _ []rune) { 743 for _, c := range wind.TheRow.Col { 744 for _, w := range c.W { 745 if w.IsScratch || w.IsDir || len(w.Body.File.Name()) == 0 { 746 continue 747 } 748 if w.External { 749 continue 750 } 751 a := string(w.Body.File.Name()) 752 _, e := os.Stat(a) 753 if w.Body.File.Mod() || len(w.Body.Cache) != 0 { 754 if e != nil { 755 alog.Printf("no auto-Put of %s: %v\n", a, e) 756 } else { 757 wind.Wincommit(w, &w.Body) 758 Put(&w.Body, nil, nil, XXX, XXX, nil) 759 } 760 } 761 } 762 } 763 } 764 765 func id(et, _, _ *wind.Text, _, _ bool, _ []rune) { 766 if et != nil && et.W != nil { 767 alog.Printf("/mnt/acme/%d/\n", et.W.ID) 768 } 769 } 770 771 func local(et, _, argt *wind.Text, _, _ bool, arg []rune) { 772 var a *string 773 aa := getbytearg(argt, true, true, &a) 774 dir := wind.Dirname(et, nil) 775 if len(dir) == 1 && dir[0] == '.' { // sigh 776 dir = nil 777 } 778 Run(nil, string(arg), dir, false, aa, a, false) 779 } 780 781 func xkill(_, _, argt *wind.Text, _, _ bool, arg []rune) { 782 var r []rune 783 ui.Getarg(argt, false, false, &r) 784 if r != nil { 785 xkill(nil, nil, nil, false, false, r) 786 } 787 // loop condition: *arg is not a blank 788 for { 789 a := runes.SkipNonBlank(arg) 790 if len(a) == len(arg) { 791 break 792 } 793 Ckill <- runes.Clone(arg[:len(arg)-len(a)]) 794 arg = runes.SkipBlank(a) 795 } 796 } 797 798 func incl(et, _, argt *wind.Text, _, _ bool, arg []rune) { 799 if et == nil || et.W == nil { 800 return 801 } 802 w := et.W 803 n := 0 804 var r []rune 805 ui.Getarg(argt, false, true, &r) 806 if r != nil { 807 n++ 808 wind.Winaddincl(w, r) 809 } 810 // loop condition: len(arg) == 0 || arg[0] is not a blank 811 for { 812 a := runes.SkipNonBlank(arg) 813 if len(a) == len(arg) { 814 break 815 } 816 r = runes.Clone(arg[:len(arg)-len(a)]) 817 wind.Winaddincl(w, r) 818 arg = runes.SkipBlank(a) 819 } 820 if n == 0 && len(w.Incl) > 0 { 821 for n = len(w.Incl); ; { 822 n-- 823 if n < 0 { 824 break 825 } 826 alog.Printf("%s ", string(w.Incl[n])) 827 } 828 alog.Printf("\n") 829 } 830 } 831 832 const ( 833 IGlobal = -2 834 IError = -1 835 Ioff = 0 836 Ion = 1 837 ) 838 839 func indentval(s []rune) int { 840 if len(s) < 2 { 841 return IError 842 } 843 if runes.Equal(s, []rune("ON")) { 844 wind.GlobalAutoindent = true 845 alog.Printf("Indent ON\n") 846 return IGlobal 847 } 848 if runes.Equal(s, []rune("OFF")) { 849 wind.GlobalAutoindent = false 850 alog.Printf("Indent OFF\n") 851 return IGlobal 852 } 853 if runes.Equal(s, []rune("on")) { 854 return Ion 855 } 856 return Ioff 857 } 858 859 func fixindent(w *wind.Window, arg interface{}) { 860 w.Autoindent = wind.GlobalAutoindent 861 } 862 863 func indent(et, _, argt *wind.Text, _, _ bool, arg []rune) { 864 var w *wind.Window 865 if et != nil && et.W != nil { 866 w = et.W 867 } 868 autoindent := IError 869 var r []rune 870 ui.Getarg(argt, false, true, &r) 871 if len(r) > 0 { 872 autoindent = indentval(r) 873 } else { 874 a := runes.SkipNonBlank(arg) 875 if len(a) != len(arg) { 876 autoindent = indentval(arg[:len(arg)-len(a)]) 877 } 878 } 879 if autoindent == IGlobal { 880 wind.All(fixindent, nil) 881 } else if w != nil && autoindent >= 0 { 882 w.Autoindent = autoindent == Ion 883 } 884 } 885 886 func tab(et, _, argt *wind.Text, _, _ bool, arg []rune) { 887 if et == nil || et.W == nil { 888 return 889 } 890 w := et.W 891 var r []rune 892 ui.Getarg(argt, false, true, &r) 893 tab := 0 894 if len(r) > 0 { 895 p := string(r) 896 if '0' <= p[0] && p[0] <= '9' { 897 tab, _ = strconv.Atoi(p) 898 } 899 } else { 900 a := runes.SkipNonBlank(arg) 901 if len(a) != len(arg) { 902 p := string(arg[:len(arg)-len(a)]) 903 if '0' <= p[0] && p[0] <= '9' { 904 tab, _ = strconv.Atoi(p) 905 } 906 } 907 } 908 if tab > 0 { 909 if w.Body.Tabstop != tab { 910 w.Body.Tabstop = tab 911 ui.WinresizeAndMouse(w, w.R, false, true) 912 } 913 } else { 914 alog.Printf("%s: Tab %d\n", string(w.Body.File.Name()), w.Body.Tabstop) 915 } 916 } 917 918 func runproc(win *wind.Window, s string, rdir []rune, newns bool, argaddr, xarg *string, c *Command, cpid chan *os.Process, iseditcmd bool) { 919 t := strings.TrimLeft(s, " \n\t") 920 name := t 921 if i := strings.IndexAny(name, " \n\t"); i >= 0 { 922 name = name[:i] 923 } 924 if i := strings.LastIndex(name, "/"); i >= 0 { 925 name = name[i+1:] 926 } 927 name += " " // add blank here for ease in waittask 928 c.Name = []rune(name) 929 pipechar := '\x00' 930 if len(t) > 0 && (t[0] == '<' || t[0] == '|' || t[0] == '>') { 931 pipechar = rune(t[0]) 932 t = t[1:] 933 } 934 c.IsEditCmd = iseditcmd 935 c.text = s 936 var sfd [3]*os.File 937 if newns { 938 var incl [][]rune 939 var winid int 940 // end of args 941 var filename string 942 if win != nil { 943 filename = string(win.Body.File.Name()) 944 if len(win.Incl) > 0 { 945 incl = make([][]rune, len(win.Incl)) 946 for i := range win.Incl { 947 incl[i] = runes.Clone(win.Incl[i]) 948 } 949 } 950 winid = win.ID 951 } else if wind.Activewin != nil { 952 winid = wind.Activewin.ID 953 } 954 955 os.Setenv("winid", fmt.Sprint(winid)) // TODO(rsc) 956 957 if filename != "" { 958 os.Setenv("%", filename) // TODO(rsc) 959 os.Setenv("samfile", filename) // TODO(rsc) 960 } 961 962 var err error 963 c.Mntdir = Fsysmount(rdir, incl) 964 965 fs, err := client.MountServiceAname("acme", fmt.Sprint(c.Mntdir.ID)) 966 if err != nil { 967 fmt.Fprintf(os.Stderr, "child: can't mount acme: %v\n", err) 968 Fsysdelid(c.Mntdir) 969 c.Mntdir = nil 970 return // TODO(rsc): goto Fail? 971 } 972 if winid > 0 && (pipechar == '|' || pipechar == '>') { 973 sfd[0], _ = fsopenfd(fs, fmt.Sprintf("%d/rdsel", winid), plan9.OREAD) 974 } else { 975 sfd[0], _ = os.Open(os.DevNull) 976 } 977 if (winid > 0 || iseditcmd) && (pipechar == '|' || pipechar == '<') { 978 var buf string 979 if iseditcmd { 980 if winid > 0 { 981 buf = fmt.Sprintf("%d/editout", winid) 982 } else { 983 buf = "editout" 984 } 985 } else { 986 buf = fmt.Sprintf("%d/wrsel", winid) 987 } 988 sfd[1], _ = fsopenfd(fs, buf, plan9.OWRITE) 989 sfd[2], _ = fsopenfd(fs, "cons", plan9.OWRITE) 990 } else { 991 sfd[1], _ = fsopenfd(fs, "cons", plan9.OWRITE) 992 sfd[2] = sfd[1] 993 } 994 // fsunmount(fs) // TODO(rsc): implement 995 } else { 996 // TODO(rsc): This is "Local foo". 997 // Interpret the command since a subshell will not be Local. 998 // Can look for 'Local cd' and 'Local x=y'. 999 alog.Printf("Local not implemented") 1000 goto Fail 1001 } 1002 if win != nil { 1003 wind.Winclose(win) 1004 } 1005 defer sfd[0].Close() 1006 defer sfd[1].Close() 1007 defer sfd[2].Close() 1008 1009 if argaddr != nil { 1010 os.Setenv("acmeaddr", *argaddr) // TODO(rsc) 1011 } 1012 if Acmeshell != "" { 1013 goto Hard 1014 } 1015 { 1016 inarg := false 1017 for _, r := range t { 1018 if r == ' ' || r == '\t' { 1019 continue 1020 } 1021 if r < ' ' { 1022 goto Hard 1023 } 1024 if strings.ContainsRune("#;&|^$=`'{}()<>[]*?^~`/", r) { 1025 goto Hard 1026 } 1027 inarg = true 1028 } 1029 if !inarg { 1030 goto Fail 1031 } 1032 1033 av := strings.Fields(t) 1034 if xarg != nil { 1035 av = append(av, *xarg) 1036 } 1037 c.av = av 1038 1039 var dir string 1040 if rdir != nil { 1041 dir = string(rdir) 1042 } 1043 cmd := exec.Command(av[0], av[1:]...) 1044 cmd.Stdin = sfd[0] 1045 cmd.Stdout = sfd[1] 1046 cmd.Stderr = sfd[2] 1047 cmd.Dir = dir 1048 err := cmd.Start() 1049 if err == nil { 1050 if cpid != nil { 1051 cpid <- cmd.Process // TODO(rsc): send cmd.Process 1052 } 1053 go func() { 1054 Cwait <- Waitmsg{cmd.Process, cmd.Wait()} 1055 }() 1056 return 1057 } 1058 goto Fail 1059 } 1060 1061 Hard: 1062 { 1063 if xarg != nil { 1064 t += " '" + *xarg + "'" // BUG: what if quote in *xarg? TODO(rsc) 1065 c.text = t 1066 } 1067 var dir string 1068 if rdir != nil { 1069 dir = string(rdir) 1070 } 1071 shell := Acmeshell 1072 if shell == "" { 1073 shell = "rc" 1074 } 1075 //static void *parg[2]; 1076 cmd := exec.Command(shell, "-c", t) 1077 cmd.Dir = dir 1078 cmd.Stdin = sfd[0] 1079 cmd.Stdout = sfd[1] 1080 cmd.Stderr = sfd[2] 1081 err := cmd.Start() 1082 if err == nil { 1083 if cpid != nil { 1084 cpid <- cmd.Process // TODO(rsc): send cmd.Process 1085 } 1086 go func() { 1087 Cwait <- Waitmsg{cmd.Process, cmd.Wait()} 1088 }() 1089 return 1090 } 1091 alog.Printf("exec %s: %v\n", shell, err) 1092 // threadexec hasn't happened, so send a zero 1093 } 1094 1095 Fail: 1096 if cpid != nil { // TODO(rsc): is it always non-nil? 1097 cpid <- nil 1098 } 1099 } 1100 1101 func runwaittask(c *Command, cproc chan *os.Process) { 1102 c.Proc = <-cproc 1103 c.av = nil 1104 if c.Proc != nil { // successful exec 1105 Ccommand <- c 1106 } else if c.IsEditCmd { 1107 // Avoid deadlock when we can't execute the command. 1108 edit.Cedit <- 0 1109 } 1110 } 1111 1112 func Run(win *wind.Window, s string, rdir []rune, newns bool, argaddr, xarg *string, iseditcmd bool) { 1113 if s == "" { 1114 return 1115 } 1116 c := new(Command) 1117 cproc := make(chan *os.Process, 0) 1118 go runproc(win, s, rdir, newns, argaddr, xarg, c, cproc, iseditcmd) 1119 // mustn't block here because must be ready to answer mount() call in run() 1120 go runwaittask(c, cproc) 1121 } 1122 1123 func fsopenfd(fs *client.Fsys, name string, mode uint8) (*os.File, error) { 1124 fd, err := fs.Open(name, mode) 1125 if err != nil { 1126 return nil, err 1127 } 1128 r, w, err := os.Pipe() 1129 if err != nil { 1130 fd.Close() 1131 return nil, err 1132 } 1133 if mode == plan9.OREAD { 1134 go func() { 1135 io.Copy(w, fd) 1136 w.Close() 1137 fd.Close() 1138 }() 1139 return r, nil 1140 } 1141 go func() { 1142 io.Copy(fd, r) 1143 r.Close() 1144 fd.Close() 1145 }() 1146 return w, nil 1147 } 1148 1149 type Command struct { 1150 Proc *os.Process 1151 Name []rune 1152 text string 1153 av []string 1154 IsEditCmd bool 1155 Mntdir *base.Mntdir 1156 Next *Command 1157 } 1158 1159 const XXX = false 1160 1161 const timefmt = "2006/01/02 15:04:05" 1162 1163 var Acmeshell string 1164 1165 var ( 1166 Ccommand = make(chan *Command) 1167 Ckill = make(chan []rune) 1168 Cexit = make(chan int) 1169 )