コード例 #1
0
ファイル: parts.go プロジェクト: Christopheraburns/clive
func testText() *wax.Part {
	t := `- write a ws pg that knows how to display multiple parts with
	  drag and drop and resizes. Play embedding just a chunk of html
	  and later play with embedding something from a different url

	- write a canvas ctl
	- port the old canvas text frame using the canvas ctl
	- write an terminal variant that simply honors In,Out,Err chans
	  on a text frame.
	- use local storage to save the layout editing state for errors
	  and to recover those if desired
	- make sure we can share an inner part with controls multiple times
	 (ie not just repeating controls, but parts with controls, although
	 for this we should probably use iframes)

	- use tls for conns to the registry and peers
	- put in place some kind of auth
	- make the registry a hierarchy, so that we can have
	  registry islands and they sync to each other
	  make it use broadcast to discover other machines nearby
	  and propagate island information

`
	if err := tTxt.Ins([]rune(t), 0); err != nil {
		dbg.Fatal("txt ins: %s", err)
	}
	p, err := wax.New("$txt$")
	if err != nil {
		dbg.Fatal("new part: %s", err)
	}
	p.SetEnv(map[string]interface{}{"txt": tTxt})
	return p

}
コード例 #2
0
ファイル: zx.go プロジェクト: Christopheraburns/clive
func usage(err error) {
	opts.Usage(os.Stderr)
	fmt.Fprintf(os.Stderr, "\tspec = name | name!file | name!file!flags \n")
	fmt.Fprintf(os.Stderr, "\tspec flags = ro | rw | ncro | ncrw \n")
	if err == nil {
		dbg.Fatal("usage")
	}
	dbg.Fatal(err)
}
コード例 #3
0
ファイル: bltin.go プロジェクト: Christopheraburns/clive
func bexit(c cmd.Ctx) error {
	args := c.Args
	xprintf("exiting...\n")
	if len(args) <= 1 || args[1] == "" {
		dbg.Fatal("")
	} else {
		dbg.Fatal(args[1])
	}
	return nil
}
コード例 #4
0
ファイル: dump.go プロジェクト: Christopheraburns/clive
func main() {
	defer dbg.Exits("")
	os.Args[0] = "zxdump"
	dfltdump := zx.Path(dbg.Home, "dump")
	opts.NewFlag("s", "don't dump right now, wait until next at 5am", &Skip)
	opts.NewFlag("1", "dump once and exit", &Once)
	opts.NewFlag("v", "verbose", &Verbose)
	opts.NewFlag("D", "debug", &Debug)
	opts.NewFlag("x", "expr: files excluded (.*, tmp.* if none given); tmp always excluded.", &Xcludes)
	Dump = dfltdump
	opts.NewFlag("d", "dir: where to keep the dump, or empty if none", &Dump)
	args, err := opts.Parse(os.Args)
	if err != nil {
		dbg.Warn("%s", err)
		opts.Usage()
		dbg.Exits(err)
	}
	if len(Xcludes) == 0 {
		Xcludes = []string{".*", "tmp.*", "*.tmp"}
	}
	Xcludes = append(Xcludes, "tmp")
	if len(args) == 0 {
		dbg.Warn("arguments missing")
		opts.Usage()
		dbg.Exits("usage")
	}
	if Skip && Once {
		dbg.Fatal("can't skip the current dump and dump once now")
	}
	nt := 0
	ec := make(chan bool)
	for i := 0; i < len(args); i++ {
		al := strings.SplitN(args[i], "!", 2)
		if len(al) == 1 {
			al = append(al, al[0])
			al[0] = path.Base(al[0])
		}
		t, err := lfs.New(al[0], al[1], lfs.RO)
		if err != nil {
			dbg.Warn("%s: %s", al[0], err)
			continue
		}
		t.ReadAttrs(true)
		nt++
		go dump(Dump, t, ec)
	}
	if nt == 0 {
		dbg.Fatal("no trees to dump")
	}
	for nt > 0 {
		<-ec
		nt--
	}
}
コード例 #5
0
ファイル: zxfs.go プロジェクト: Christopheraburns/clive
func main() {
	defer dbg.Exits("")
	os.Args[0] = "zxfs"
	quiet := false

	opts.NewFlag("q", "don't print errors to stderr", &quiet)
	opts.NewFlag("D", "debug and zxfs calls", &zxfs.Debug)
	opts.NewFlag("r", "read only", &rflag)
	opts.NewFlag("s", "statistics", &sflag)
	opts.NewFlag("n", "don't use caching (otherwise write-through cache)", &nocaching)
	opts.NewFlag("d", "use delayed writes cache", &delayed)
	opts.NewFlag("Z", "debug zx requests", &zdebug)
	opts.NewFlag("V", "verbose debug and fuse requests", &fs.Debug)
	opts.NewFlag("x", "addr: re-export locally the cached tree to this address, if any", &xaddr)
	opts.NewFlag("m", "use mfs caching", &mfscfs)
	opts.NewFlag("l", "dir: use lfs caching at dir", &lfsdir)
	args, err := opts.Parse(os.Args)
	if err != nil {
		opts.Usage(os.Stderr)
		dbg.Fatal(err)
	}
	zxfs.Debug = zxfs.Debug || fs.Debug
	zxfs.Verb = !quiet || zxfs.Debug
	if fs.Debug {
		fuse.Debug = func(m interface{}) {
			fmt.Fprintf(os.Stderr, "fuse: %v\n", m)
		}
	}
	switch len(args) {
	case 2:
		addr = args[0]
		mntdir = args[1]
	case 1:
		addr = args[0]
	default:
		opts.Usage(os.Stderr)
		dbg.Fatal("usage")
	}
	dprintf("debug on\n")
	xfs, fn, err := mkfs(addr)
	if err != nil {
		dbg.Fatal("%s", err)
	}
	defer fn()
	if nocaching || !delayed {
		err = ncmount(xfs)
	} else {
		dbg.Fatal("delayed write mount is gone")
	}
	if err != nil {
		dbg.Fatal("%s", err)
	}
	dbg.Warn("unmounted: exiting")
}
コード例 #6
0
ファイル: mdfs_test.go プロジェクト: Christopheraburns/clive
func ExampleNew() {
	// create a tree using the local directory /tmp/cache for the cache
	dfs, err := lfs.New("cache", "/tmp/cache", lfs.RW)
	if err != nil {
		dbg.Fatal("lfs: %s", err)
	}
	fs, err := New("example mdfs", dfs)
	if err != nil {
		dbg.Fatal("mfds: %s", err)
	}
	dbg.Warn("fs %s ready", fs)
	// Now use it...
}
コード例 #7
0
ファイル: proto.go プロジェクト: Christopheraburns/clive
func (m *Msg) Pack() []byte {
	buf := make([]byte, 0, 100)
	var n [8]byte

	if m.Op<Tmin || m.Op>=Tend {
		dbg.Fatal("unknown msg type %d", m.Op)
	}
	buf = append(buf, byte(m.Op))
	buf = nchan.PutString(buf, m.Rid)
	if m.Op==Tget || m.Op==Tput {
		binary.LittleEndian.PutUint64(n[0:], uint64(m.Off))
		buf = append(buf, n[:8]...)
	}
	if m.Op == Tget {
		binary.LittleEndian.PutUint64(n[0:], uint64(m.Count))
		buf = append(buf, n[:8]...)
	}
	if m.Op==Tput || m.Op==Tmkdir || m.Op==Twstat {
		buf = append(buf, m.D.Pack()...)
	}
	if m.Op == Tmove {
		buf = nchan.PutString(buf, m.To)
	}
	if m.Op==Tfind || m.Op==Tget || m.Op==Tput || m.Op==Tfindget {
		buf = nchan.PutString(buf, m.Pred)
	}
	if m.Op==Tfind || m.Op==Tfindget {
		buf = nchan.PutString(buf, m.Spref)
		buf = nchan.PutString(buf, m.Dpref)
		binary.LittleEndian.PutUint64(n[0:], uint64(m.Depth))
		buf = append(buf, n[:8]...)
	}
	return buf
}
コード例 #8
0
ファイル: trfs_test.go プロジェクト: Christopheraburns/clive
func testfn(t *testing.T, fn func(t fstest.Fataler, fss ...zx.Tree)) {
	fstest.Repeats = 1
	fs, err := mfs.New("example mfs")
	if err != nil {
		dbg.Fatal("lfs: %s", err)
	}
	xfs, _ := fs.AuthFor(&auth.Info{Uid: dbg.Usr, SpeaksFor: dbg.Usr, Ok: true})
	tr := New(xfs)
	tc := make(chan string)
	dc := make(chan bool)
	go func() {
		for x := range tc {
			printf("%s\n", x)
		}
		dc <- true
	}()
	fstest.MkZXTree(t, tr)
	fs.Dbg = testing.Verbose()
	if fs.Dbg {
		defer fs.Dump(os.Stdout)
	}
	tr.C = tc
	if fn != nil {
		fn(t, tr)
	}
	tr.Close(nil)
	close(tc)
	<-dc
}
コード例 #9
0
ファイル: parts.go プロジェクト: Christopheraburns/clive
func testFor() *wax.Part {
	forpg := `
		Trees:
		<ul>
		$for t in repl.Trees do$
			<li>
			Name: $t.Name$;
			Path: $t.Path$;
			Kind: $t.Sync$
			<ul>
			$for p in t.Peers do$
				<li>
				$p$
			$end$
			</ul>
		$end$
		</ul>
	`
	p, err := wax.New(forpg)
	if err != nil {
		dbg.Fatal("new part: %s", err)
	}
	tenv := map[string]interface{}{"repl": tRepl}
	p.SetEnv(tenv)
	return p
}
コード例 #10
0
ファイル: exec.go プロジェクト: Christopheraburns/clive
// both for Ninblk and Nhereblk
func (nd *Nd) xInBlk() ([]string, error) {
	xprintf("x %s\n", nd)
	if len(nd.Child) == 0 {
		return []string{}, nil
	}
	if len(nd.Child)>1 || nd.Child[0].Kind!=Npipe {
		dbg.Fatal("inblk child bug; it assumes a single pipe in block")
		return []string{}, dbg.ErrBug
	}
	c := nd.Child[0]
	fd, w, _ := os.Pipe()
	c.mkExec(nd.Stdin, w, nd.Stderr, nd.args...)
	c.extraFds(nd)
	c.closefds = append(c.closefds, w)
	c.Args = nil // don't & it
	c.xPipe()
	var b bytes.Buffer
	io.Copy(&b, fd)
	fd.Close()
	xprintf("x %s done\n", nd)
	if nd.Kind == Nhereblk {
		return []string{b.String()}, nil
	}
	words := strings.Fields(b.String())
	return words, nil
}
コード例 #11
0
ファイル: app.go プロジェクト: Christopheraburns/clive
func ctx() *Ctx {
	c := AppCtx()
	if c == nil {
		dbg.Fatal("no context for %d", runtime.AppId())
	}
	return c
}
コード例 #12
0
ファイル: parts.go プロジェクト: Christopheraburns/clive
func testAdt() *wax.Part {
	p, err := wax.New(`$repl$`)
	if err != nil {
		dbg.Fatal("new part: %s", err)
	}
	p.SetEnv(map[string]interface{}{"repl": tRepl})
	return p
}
コード例 #13
0
ファイル: parts.go プロジェクト: Christopheraburns/clive
func testCanvas() *wax.Part {
	p, err := wax.New("$tc$")
	if err != nil {
		dbg.Fatal("new part: %s", err)
	}
	p.SetEnv(map[string]interface{}{"tc": tCanvas})
	return p
}
コード例 #14
0
ファイル: opt.go プロジェクト: Christopheraburns/clive
// Define a new flag with the given name and usage.
// valuep must be a pointer to the argument type and will be set to
// the command line flag value if the flag is found.
// Known types are bool, int, Counter, Octal, Hexa, int64, uint64, string,
// float64, time.Duration, time.Time, and []string.
// []string is a string option that may be repeated.
// The time formats understood are
//	"01/02"
//	"01/02/06"
//	"01/02/2006"
//	"2006/0102"
//	"15:04:05"
//	"15:04"
//	"3pm"
//	"3:04pm"
//	"01/02 15:04"
//	"01/02/06 15:04"
//	"01/02/2006 15:04"
//	"2006/0102 15:04 "
//
// If the name is "+..." or "-..." and vp is *int, then it is understood as
// a request to accept +number or -number as an argument.
//
// The help string should just describe the flag for flags with no
// argument, and should be something like "dir: do this with dir"
// if the flag accepts a "dir" argument. This convention is used to generate
// a good usage diagnostic.
func (f *Flags) NewFlag(name, help string, vp interface{}) {
	if vp == nil {
		dbg.Fatal("flag %s: nil value", name)
	}
	if len(name) == 0 {
		dbg.Fatal("empty flag name")
	}
	if f.defs[name] != nil {
		dbg.Fatal("flag %s redefined", name)
	}
	aname := ""
	if i := strings.Index(help, ":"); i > 0 {
		aname = help[:i]
	}
	switch vp.(type) {
	case *bool:

	case *int:
		if aname == "" {
			aname = "num"
		}
		if name[0] == '+' {
			if f.plus != nil {
				dbg.Fatal("flag +number redefined")
			}
			f.plus = &def{name: name, help: help, valp: vp, argname: aname}
			return
		}
		if name[0] == '-' {
			if f.minus != nil {
				dbg.Fatal("flag -number redefined")
			}
			f.minus = &def{name: name, help: help, valp: vp, argname: aname}
			return
		}
	case *Counter:
	case *Octal, *Hexa, *int64, *uint64, *float64:
		if aname == "" {
			aname = "num"
		}
	case *string, *[]string:
		if aname == "" {
			aname = "str"
		}
	case *time.Duration:
		if aname == "" {
			aname = "ival"
		}
	case *time.Time:
		if aname == "" {
			aname = "time"
		}
	default:
		dbg.Fatal("flag %s: unknown flag type", name)
	}
	if name[0]=='+' || name[0]=='-' {
		dbg.Fatal("name '±...' is only for *int")
	}
	f.defs[name] = &def{name: name, help: help, valp: vp, argname: aname}
}
コード例 #15
0
ファイル: fs_test.go プロジェクト: Christopheraburns/clive
func ExampleNewDir() {
	fi, err := os.Stat("/a/file")
	if err != nil {
		dbg.Fatal(err)
	}
	d := NewDir(fi, 0)
	printf("%s\n", d)
	// could print `path"/a/file", name:"file" type:"d" mode:"0755" size:"3" mtime:"5000000000"`
}
コード例 #16
0
ファイル: mfs_test.go プロジェクト: Christopheraburns/clive
func ExampleNew() {
	// create a tree
	fs, err := New("example mfs")
	if err != nil {
		dbg.Fatal("lfs: %s", err)
	}
	dbg.Warn("fs %s ready", fs)
	// Now use it...
}
コード例 #17
0
ファイル: lfs_test.go プロジェクト: Christopheraburns/clive
func ExampleLfs_Get() {
	// let's use a tree rooted at / in RO mode
	fs, err := New("example lfs", "/", RO)
	if err != nil {
		dbg.Fatal("lfs: %s", err)
	}
	// Now do a "cat" of /etc/hosts
	dc := fs.Get("/etc/hosts", 0, zx.All, "")

	// if had an early error, it's reported by the close of dc
	// and we won't receive any data.
	for data := range dc {
		os.Stdout.Write(data)
	}
	if cerror(dc) != nil {
		dbg.Fatal("get: %s", cerror(dc))
	}
}
コード例 #18
0
ファイル: lfs_test.go プロジェクト: Christopheraburns/clive
func ExampleNew() {
	// create a tree rooted at /bin in RO mode
	fs, err := New("example lfs", "/bin", RO)
	if err != nil {
		dbg.Fatal("lfs: %s", err)
	}
	dbg.Warn("fs %s ready", fs)
	// Now use it...
}
コード例 #19
0
ファイル: lfs_test.go プロジェクト: Christopheraburns/clive
func ExampleLfs_Stat() {
	var fs *Lfs // = New("tag", path, RO|RW)

	dirc := fs.Stat("/ls")
	dir := <-dirc
	if dir == nil {
		dbg.Fatal("stat: %s", cerror(dirc))
	}
	dbg.Warn("stat was %s", dir)
}
コード例 #20
0
ファイル: part_test.go プロジェクト: Christopheraburns/clive
func TestPart(t *testing.T) {
	r := repl{
		Debug: true,
		Trees: []*tree{
			{
				Name:  "t1<p ?''",
				Path:  "p1",
				Peers: []string{"t2", "t4"},
			},
			{
				Name:  "t2",
				Path:  "p2",
				Peers: []string{"t2"},
			},
			{
				Name: "t3",
			},
		},
	}

	vars1 := map[string]interface{}{
		"t": r.Trees[1],
	}
	p1, err := New(tpg)
	if err != nil {
		dbg.Fatal("new part: %s", err)
	}
	p1.SetEnv(vars1)
	var buf1 bytes.Buffer
	if err := p1.apply(&buf1); err != nil {
		t.Fatalf("apply part: %s", err)
	}
	printf("p1: {%s}\n", buf1.String())
	if buf1.String() != p1out {
		t.Fatalf("wrong p1 output")
	}
	u := ui{"another ui", p1}
	vars := map[string]interface{}{
		"debug": &r.Debug,
		"port":  3333,
		"repl":  r,
		"p1":    p1,
		"u":     u,
	}
	var buf2 bytes.Buffer
	if err := applyNew(&buf2, pg, vars); err != nil {
		t.Fatalf("new part: %s", err)
	}
	printf("p2: {%s}\n", buf2.String())
	if buf2.String() != p2out {
		fmt.Printf("wrong? p2 output: was [%s]", buf2.String())
	}

}
コード例 #21
0
ファイル: lex.go プロジェクト: Christopheraburns/clive
func (l *lex) unget() {
	if l.eofmet {
		if !Interactive {
			l.eofmet = false
		}
		return
	}
	if err := l.in[0].UnreadRune(); err != nil {
		dbg.Fatal("lex: bug: unreadrune: %s", err)
	}
	l.val = l.val[0 : len(l.val)-1]
}
コード例 #22
0
ファイル: repl.go プロジェクト: Christopheraburns/clive
func cfgPath(nm string) string {
	if _, err := os.Stat(nm); err == nil {
		return nm
	}
	if strings.ContainsRune(nm, '/') {
		return nm
	}
	if nm == "." || nm == ".." {
		dbg.Fatal("Can't use . or .. as the cfg name")
	}
	return fmt.Sprintf("%s/lib/%s", dbg.Home, nm)
}
コード例 #23
0
ファイル: flags.go プロジェクト: Christopheraburns/clive
func (t *Flags) add(name string, vp interface{}, ro bool) {
	if t.usr == nil {
		t.usr = make(map[string]interface{})
		t.ro = make(map[string]bool)
	}
	if vp == nil {
		dbg.Fatal("flag %s: nil value", name)
	}
	switch t := vp.(type) {
	case *bool:
	case *int:
	case *string:
	case func(...string)error:
	default:
		dbg.Fatal("unknown flag type %T", t)
	}
	if t.usr == nil {
		t.usr = make(map[string]interface{})
	}
	t.usr[name] = vp
	t.ro[name] = ro
}
コード例 #24
0
ファイル: exec.go プロジェクト: Christopheraburns/clive
func (nd *Nd) xCmds() error {
	xprintf("x %s\n", nd)
	if err := nd.redirs(); err != nil {
		xprintf("x %s done\n", nd)
		nd.closeAll()
		nd.waitc <- err
		return err
	}
	go func() {
		var err error
		for _, c := range nd.Child {
			if nd.interrupted() {
				err = errors.New("interrupted")
				break
			}
			switch c.Kind {
			case Nnone, Nnop:
				continue
			case Npipe:
				c.mkExec(nd.Stdin, nd.Stdout, nd.Stderr, nd.args...)
				c.extraFds(nd)
				c.xPipe()
				err = nil
				if nd.async() {
					bg.Add(c)
					go func() {
						err := c.wait()
						bg.Del(c, err)
					}()
				} else {
					err = c.wait()
				}
				setsts(err)
			case Nset:
				c.mkExec(nd.Stdin, nd.Stdout, nd.Stderr, nd.args...)
				c.extraFds(nd)
				c.xSet()
				err = c.wait()
				setsts(err)
			default:
				dbg.Fatal("xcmds: child bug")
			}

		}
		xprintf("x %s done\n", nd)
		nd.closeAll()
		nd.waitc <- err
	}()
	return nil

}
コード例 #25
0
ファイル: main.go プロジェクト: Christopheraburns/clive
func main() {
	os.Args[0] = "wax"
	flag.Usage = usage
	flag.BoolVar(&test, "t", false, "create some test parts")
	flag.BoolVar(&wax.Verbose, "v", false, "verbose")
	flag.StringVar(&port, "p", "9191", "port")
	flag.Parse()
	wax.Verbose = true
	ctl.Debug = true
	wax.ServeLogin("/", "/index")
	index.ServeAt("/index", testParts())
	if err := wax.Serve(":" + port); err != nil {
		dbg.Fatal("serve: %s", err)
	}
}
コード例 #26
0
ファイル: rfs_test.go プロジェクト: Christopheraburns/clive
func ExampleRfs() {
	xfs, err := Import("tcp!whatever!rfs")
	if err != nil {
		dbg.Fatal("import: %s", err)
	}
	fs := xfs.(*Rfs)
	// perhaps enable IO stats
	fs.IOstats = &zx.IOstats{}
	defer func() {
		fs.IOstats.Averages()
		fmt.Printf("iostats:\n%s\n", fs.IOstats)
	}()

	// use it, eg. to do a stat
	dc := fs.Stat("/a/file")
	d := <-dc
	if d == nil {
		dbg.Fatal("stat: %s", cerror(dc))
	}
	dbg.Warn("got dir %s", dc)

	// stop rfs when done.
	fs.Close(nil)
}
コード例 #27
0
ファイル: exec.go プロジェクト: Christopheraburns/clive
func (nd *Nd) mkExec(i io.Reader, o, e io.Writer, args ...string) {
	intrlk.Lock()
	nd.NdExec = &NdExec{
		NdIO:  NdIO{i, o, e},
		waitc: make(chan error, 1),
		intrc: intrc,
	}
	intrlk.Unlock()
	if (nd.Kind==Npipe || nd.Kind==Npipeblk) && len(nd.Args)>0 && len(nd.Args[0])>0 {
		fd, err := os.Open(os.DevNull)
		if err != nil {
			dbg.Fatal("can't open /dev/null: %s", err)
		}
		nd.Stdin = fd
		nd.closefds = append(nd.closefds, fd)
	}
	nd.args = args
}
コード例 #28
0
ファイル: parts.go プロジェクト: Christopheraburns/clive
func testTb() *wax.Part {
	tbpg := `
		<p> Single entry<br>
		$entry$
		<p>
		A tool bar:<br>
		$tb$
	`
	p, err := wax.New(tbpg)
	if err != nil {
		dbg.Fatal("new part: %s", err)
	}
	tenv := map[string]interface{}{
		"entry": tEntry,
		"tb":    tTb,
	}
	p.SetEnv(tenv)
	return p
}
コード例 #29
0
ファイル: exec.go プロジェクト: Christopheraburns/clive
func (nd *Nd) xIf() error {
	xprintf("x %s\n", nd)
	if len(nd.Child)<2 || len(nd.Child)%2!=0 {
		dbg.Fatal("if bug")
	}
	if err := nd.redirs(); err != nil {
		xprintf("x %s done\n", nd)
		nd.closeAll()
		nd.waitc <- err
		return err
	}
	go func() {
		var err error
		for i := 0; i < len(nd.Child)-1; i += 2 {
			if nd.interrupted() {
				err = errors.New("interrupted")
				break
			}
			cond, body := nd.Child[i], nd.Child[i+1]
			if cond != nil {
				cond.mkExec(nd.Stdin, nd.Stdout, nd.Stderr, nd.args...)
				cond.extraFds(nd)
				err = cond.xPipe()
				if err == nil {
					err = cond.wait()
				}
				if err != nil {
					continue
				}
			}
			body.mkExec(nd.Stdin, nd.Stdout, nd.Stderr, nd.args...)
			body.extraFds(nd)
			err = body.xCmds()
			if err == nil {
				err = body.wait()
			}
			break
		}
		nd.closeAll()
		nd.waitc <- err
	}()
	return nil
}
コード例 #30
0
ファイル: exec.go プロジェクト: Christopheraburns/clive
func (nd *Nd) xFor() error {
	xprintf("x %s\n", nd)
	if len(nd.Child) != 2 {
		dbg.Fatal("for bug")
	}
	if err := nd.redirs(); err != nil {
		xprintf("x %s done\n", nd)
		nd.closeAll()
		nd.waitc <- err
		return err
	}
	go func() {
		iter, body := nd.Child[0], nd.Child[1]
		nd.Child = iter.Child
		args := nd.names()
		nd.Child = []*Nd{iter, body}
		if len(args) < 2 {
			xprintf("x %s done\n", nd)
			nd.closeAll()
			nd.waitc <- nil
			return
		}
		vname := args[0]
		var err error
		for args = args[1:]; len(args) > 0; args = args[1:] {
			if nd.interrupted() {
				err = errors.New("interrupted")
				break
			}
			body.mkExec(nd.Stdin, nd.Stdout, nd.Stderr, nd.args...)
			body.extraFds(nd)
			os.Setenv(vname, args[0])
			err = body.xCmds()
			if err == nil {
				err = body.wait()
			}
		}
		nd.closeAll()
		xprintf("x %s done\n", nd)
		nd.waitc <- err
	}()
	return nil
}