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  )