github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/cmds/exp/cpu/cpu.go (about)

     1  // Copyright 2018-2019 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"flag"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"log"
    15  	"net"
    16  	"os"
    17  	"os/exec"
    18  	"path/filepath"
    19  	"strings"
    20  	"syscall"
    21  	"unsafe"
    22  
    23  	// We use this ssh because it implements port redirection.
    24  	// It can not, however, unpack password-protected keys yet.
    25  	"github.com/gliderlabs/ssh"
    26  	"github.com/kr/pty" // TODO: get rid of krpty
    27  	"github.com/u-root/u-root/pkg/mount"
    28  	"github.com/u-root/u-root/pkg/termios"
    29  
    30  	// We use this ssh because it can unpack password-protected private keys.
    31  	ossh "golang.org/x/crypto/ssh"
    32  	"golang.org/x/sys/unix"
    33  )
    34  
    35  var (
    36  	// For the ssh server part
    37  	hostKeyFile = flag.String("hk", "" /*"/etc/ssh/ssh_host_rsa_key"*/, "file for host key")
    38  	pubKeyFile  = flag.String("pk", "key.pub", "file for public key")
    39  	port        = flag.String("sp", "2222", "ssh default port")
    40  
    41  	debug     = flag.Bool("d", false, "enable debug prints")
    42  	runAsInit = flag.Bool("init", false, "run as init (Debug only; normal test is if we are pid 1")
    43  	v         = func(string, ...interface{}) {}
    44  	remote    = flag.Bool("remote", false, "indicates we are the remote side of the cpu session")
    45  	network   = flag.String("network", "tcp", "network to use")
    46  	keyFile   = flag.String("key", filepath.Join(os.Getenv("HOME"), ".ssh/cpu_rsa"), "key file")
    47  	srv9p     = flag.String("srv", "unpfs", "what server to run")
    48  	bin       = flag.String("bin", "cpu", "path of cpu binary")
    49  	port9p    = flag.String("port9p", "", "port9p # on remote machine for 9p mount")
    50  	dbg9p     = flag.Bool("dbg9p", false, "show 9p io")
    51  	root      = flag.String("root", "/", "9p root")
    52  	bindover  = flag.String("bindover", "/lib:/lib64:/lib32:/usr:/bin:/etc", ": separated list of directories in /tmp/cpu to bind over /")
    53  )
    54  
    55  func verbose(f string, a ...interface{}) {
    56  	v(f+"\r\n", a...)
    57  }
    58  
    59  func dial(n, a string, config *ossh.ClientConfig) (*ossh.Client, error) {
    60  	client, err := ossh.Dial(n, a, config)
    61  	if err != nil {
    62  		return nil, fmt.Errorf("Failed to dial: %v", err)
    63  	}
    64  	return client, nil
    65  }
    66  
    67  func config(kf string) (*ossh.ClientConfig, error) {
    68  	cb := ossh.InsecureIgnoreHostKey()
    69  	//var hostKey ssh.PublicKey
    70  	// A public key may be used to authenticate against the remote
    71  	// server by using an unencrypted PEM-encoded private key file.
    72  	//
    73  	// If you have an encrypted private key, the crypto/x509 package
    74  	// can be used to decrypt it.
    75  	key, err := ioutil.ReadFile(kf)
    76  	if err != nil {
    77  		return nil, fmt.Errorf("unable to read private key %v: %v", kf, err)
    78  	}
    79  
    80  	// Create the Signer for this private key.
    81  	signer, err := ossh.ParsePrivateKey(key)
    82  	if err != nil {
    83  		return nil, fmt.Errorf("ParsePrivateKey %v: %v", kf, err)
    84  	}
    85  	if *hostKeyFile != "" {
    86  		hk, err := ioutil.ReadFile(*hostKeyFile)
    87  		if err != nil {
    88  			return nil, fmt.Errorf("unable to read host key %v: %v", *hostKeyFile, err)
    89  		}
    90  		pk, err := ossh.ParsePublicKey(hk)
    91  		if err != nil {
    92  			return nil, fmt.Errorf("host key %v: %v", string(hk), err)
    93  		}
    94  		cb = ossh.FixedHostKey(pk)
    95  	}
    96  	config := &ossh.ClientConfig{
    97  		User: os.Getenv("USER"),
    98  		Auth: []ossh.AuthMethod{
    99  			// Use the PublicKeys method for remote authentication.
   100  			ossh.PublicKeys(signer),
   101  		},
   102  		HostKeyCallback: cb,
   103  	}
   104  	return config, nil
   105  }
   106  
   107  func cmd(client *ossh.Client, s string) ([]byte, error) {
   108  	session, err := client.NewSession()
   109  	if err != nil {
   110  		return nil, fmt.Errorf("Failed to create session: %v", err)
   111  	}
   112  	defer session.Close()
   113  
   114  	var b bytes.Buffer
   115  	session.Stdout = &b
   116  	if err := session.Run(s); err != nil {
   117  		return nil, fmt.Errorf("Failed to run %v: %v", s, err.Error())
   118  	}
   119  	return b.Bytes(), nil
   120  }
   121  
   122  func dropPrivs() error {
   123  	uid := unix.Getuid()
   124  	v("dropPrives: uid is %v", uid)
   125  	if uid == 0 {
   126  		v("dropPrivs: not dropping privs")
   127  		return nil
   128  	}
   129  	gid := unix.Getgid()
   130  	v("dropPrivs: gid is %v", gid)
   131  	if err := unix.Setreuid(-1, uid); err != nil {
   132  		return err
   133  	}
   134  	return unix.Setregid(-1, gid)
   135  }
   136  
   137  // start up a namespace. We must
   138  // mkdir /tmp/cpu on the remote machine
   139  // issue the mount command
   140  // test via an ls of /tmp/cpu
   141  // TODO: unshare first
   142  // We enter here as uid 0 and once the mount is done, back down.
   143  func runRemote(cmd, port9p string) error {
   144  	// for some reason echo is not set.
   145  	t, err := termios.New()
   146  	if err != nil {
   147  		log.Printf("can't get a termios; oh well; %v", err)
   148  	} else {
   149  		term, err := t.Get()
   150  		if err != nil {
   151  			log.Printf("can't get a termios; oh well; %v", err)
   152  		} else {
   153  			term.Lflag |= unix.ECHO | unix.ECHONL
   154  			if err := t.Set(term); err != nil {
   155  				log.Printf("can't set a termios; oh well; %v", err)
   156  			}
   157  		}
   158  	}
   159  
   160  	// It's true we are making this directory while still root.
   161  	// This ought to be safe as it is a private namespace mount.
   162  	for _, n := range []string{"/tmp/cpu", "/tmp/local", "/tmp/merge", "/tmp/root"} {
   163  		if err := os.Mkdir(n, 0666); err != nil && !os.IsExist(err) {
   164  			log.Println(err)
   165  		}
   166  	}
   167  
   168  	user := os.Getenv("USER")
   169  	if user == "" {
   170  		user = "nouser"
   171  	}
   172  	flags := uintptr(unix.MS_NODEV | unix.MS_NOSUID)
   173  	opts := fmt.Sprintf("version=9p2000.L,trans=tcp,port=%v,uname=%v", port9p, user)
   174  	if err := unix.Mount("127.0.0.1", "/tmp/cpu", "9p", flags, opts); err != nil {
   175  		return fmt.Errorf("9p mount %v", err)
   176  	}
   177  
   178  	// Further, bind / onto /tmp/local so a non-hacked-on version may be visible.
   179  	if err := unix.Mount("/", "/tmp/local", "", syscall.MS_BIND, ""); err != nil {
   180  		log.Printf("Warning: binding / over /tmp/cpu did not work: %v, continuing anyway", err)
   181  	}
   182  
   183  	var overlaid bool
   184  	if mount.FindFileSystem("overlay") == nil {
   185  		if err := unix.Mount("overlay", "/tmp/root", "overlay", unix.MS_MGC_VAL, "lowerdir=/tmp/cpu,upperdir=/tmp/local,workdir=/tmp/merge"); err == nil {
   186  			//overlaid = true
   187  		} else {
   188  			log.Printf("Overlayfs mount failed: %v. Proceeding with selective mounts from /tmp/cpu into /", err)
   189  		}
   190  	}
   191  	if !overlaid {
   192  		// We could not get an overlayfs mount.
   193  		// There are lots of cases where binaries REQUIRE that ld.so be in the right place.
   194  		// In some cases if you set LD_LIBRARY_PATH it is ignored.
   195  		// This is disappointing to say the least. We just bind a few things into /
   196  		// bind *may* hide local resources but for now it's the least worst option.
   197  		dirs := strings.Split(*bindover, ":")
   198  		for _, n := range dirs {
   199  			t := filepath.Join("/tmp/cpu", n)
   200  			if err := unix.Mount(t, n, "", syscall.MS_BIND, ""); err != nil {
   201  				log.Printf("Warning: mounting %v on %v failed: %v", t, n, err)
   202  			} else {
   203  				log.Printf("Mounted %v on %v", t, n)
   204  			}
   205  
   206  		}
   207  	}
   208  	// We don't want to run as the wrong uid.
   209  	if err := dropPrivs(); err != nil {
   210  		return err
   211  	}
   212  	// The unmount happens for free since we unshared.
   213  	v("runRemote: command is %q", cmd)
   214  	c := exec.Command("/bin/sh", "-c", cmd)
   215  	c.Stdin, c.Stdout, c.Stderr = os.Stdin, os.Stdout, os.Stderr
   216  	return c.Run()
   217  }
   218  
   219  // srv on 5641.
   220  // TODO: make it more private, and also, have server only take
   221  // one connection or use stdin/stdout
   222  func srv(ctx context.Context) (net.Conn, *exec.Cmd, error) {
   223  	c := exec.CommandContext(ctx, "unpfs", "tcp!localhost!5641", *root)
   224  	o, err := c.StdoutPipe()
   225  	if err != nil {
   226  		return nil, nil, err
   227  	}
   228  	c.Stderr = c.Stdout
   229  	if err := c.Start(); err != nil {
   230  		return nil, nil, err
   231  	}
   232  	// Wait for the ready message.
   233  	var b = make([]byte, 8192)
   234  	n, err := o.Read(b)
   235  	if err != nil {
   236  		return nil, nil, err
   237  	}
   238  	v("Server says: %q", string(b[:n]))
   239  
   240  	srvSock, err := net.Dial("tcp", "localhost:5641")
   241  	if err != nil {
   242  		return nil, nil, err
   243  	}
   244  	return srvSock, c, nil
   245  }
   246  
   247  // We only do one accept for now.
   248  func forward(l net.Listener, s net.Conn) error {
   249  	//if err := l.SetDeadline(time.Now().Add(10 * time.Second)); err != nil {
   250  	//return fmt.Errorf("Can't set 9p client listen deadline: %v", err)
   251  	//}
   252  	c, err := l.Accept()
   253  	v("forward: c %v err %v", c, err)
   254  	if err != nil {
   255  		v("forward: accept: %v", err)
   256  		return err
   257  	}
   258  	go io.Copy(s, c)
   259  	go io.Copy(c, s)
   260  	return nil
   261  }
   262  
   263  // To make sure defer gets run and you tty is sane on exit
   264  func runClient(host, a string) error {
   265  	c, err := config(*keyFile)
   266  	if err != nil {
   267  		return err
   268  	}
   269  	cl, err := dial(*network, net.JoinHostPort(host, *port), c)
   270  	if err != nil {
   271  		return err
   272  	}
   273  	ctx, cancel := context.WithCancel(context.Background())
   274  	srvSock, p, err := srv(ctx)
   275  	if err != nil {
   276  		cancel()
   277  		return err
   278  	}
   279  	defer func() {
   280  		cancel()
   281  		p.Wait()
   282  	}()
   283  	// Arrange port forwarding from remote ssh to our server.
   284  
   285  	// Request the remote side to open port 5640 on all interfaces.
   286  	// Note: cl.Listen returns a TCP listener with network is "tcp"
   287  	// or variants. This lets us use a listen deadline.
   288  	l, err := cl.Listen("tcp", "127.0.0.1:0")
   289  	if err != nil {
   290  		return fmt.Errorf("First cl.Listen %v", err)
   291  	}
   292  	ap := strings.Split(l.Addr().String(), ":")
   293  	if len(ap) == 0 {
   294  		return fmt.Errorf("Can't find a port number in %v", l.Addr().String())
   295  	}
   296  	port := ap[len(ap)-1]
   297  	v("listener %T %v addr %v port %v", l, l, l.Addr().String(), port)
   298  
   299  	go forward(l, srvSock)
   300  	v("Connected to %v", cl)
   301  
   302  	// now run stuff.
   303  	if err := shell(cl, a, port); err != nil {
   304  		return err
   305  	}
   306  	return nil
   307  }
   308  
   309  // env sets environment variables. While we might think we ought to set
   310  // HOME and PATH, it's possibly not a great idea. We leave them here as markers
   311  // to remind ourselves not to try it later.
   312  // We don't just grab all environment variables because complex bash functions
   313  // will have no meaning to elvish. If there are simpler environment variables
   314  // you want to set, add them here. Note however that even basic ones like TERM
   315  // don't work either.
   316  func env(s *ossh.Session) {
   317  	e := []string{"HOME", "PATH", "LD_LIBRARY_PATH"}
   318  	// HOME and PATH are not allowed to be set by many sshds. Annoying.
   319  	for _, v := range e {
   320  		if err := s.Setenv(v, os.Getenv(v)); err != nil {
   321  			log.Printf("Warning: s.Setenv(%q, %q): %v", v, os.Getenv(v), err)
   322  		}
   323  	}
   324  }
   325  
   326  func shell(client *ossh.Client, a, port9p string) error {
   327  	t, err := termios.New()
   328  	if err != nil {
   329  		return err
   330  	}
   331  	r, err := t.Raw()
   332  	if err != nil {
   333  		return err
   334  	}
   335  	defer t.Set(r)
   336  	if *bin == "" {
   337  		if *bin, err = exec.LookPath("cpu"); err != nil {
   338  			return err
   339  		}
   340  	}
   341  	a = fmt.Sprintf("%v -remote -port9p %v -bin %v %v", *bin, port9p, *bin, a)
   342  	v("command is %q", a)
   343  	session, err := client.NewSession()
   344  	if err != nil {
   345  		return err
   346  	}
   347  	defer session.Close()
   348  	env(session)
   349  	// Set up terminal modes
   350  	modes := ossh.TerminalModes{
   351  		ossh.ECHO:          0,     // disable echoing
   352  		ossh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
   353  		ossh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
   354  	}
   355  	// Request pseudo terminal
   356  	if err := session.RequestPty("ansi", 40, 80, modes); err != nil {
   357  		log.Fatal("request for pseudo terminal failed: ", err)
   358  	}
   359  	i, err := session.StdinPipe()
   360  	if err != nil {
   361  		return err
   362  	}
   363  	o, err := session.StdoutPipe()
   364  	if err != nil {
   365  		return err
   366  	}
   367  	e, err := session.StderrPipe()
   368  	if err != nil {
   369  		return err
   370  	}
   371  
   372  	// sshd doesn't want to set us set the HOME and PATH via the normal
   373  	// request route. So we do this nasty hack to ensure we can find
   374  	// the cpu binary. We append our paths to the one the shell has.
   375  	// This should suffice for u-root systems with paths including
   376  	// /bbin and /ubin as well as more conventional systems.
   377  	// The only possible flaw in this approach is elvish, which
   378  	// has a very odd PATH syntax. For elvish, the PATH= is ignored,
   379  	// so does no harm. Our use case for elvish is u-root, and
   380  	// we will have the right path anyway, so it will still work.
   381  	// It is working well in testing.
   382  	//	cmd := fmt.Sprintf("PATH=$PATH:%s %s", os.Getenv("PATH"), a)
   383  	cmd := a
   384  	v("Start remote with command %q", cmd)
   385  	if err := session.Start(cmd); err != nil {
   386  		return fmt.Errorf("Failed to run %v: %v", a, err.Error())
   387  	}
   388  	go io.Copy(i, os.Stdin)
   389  	go io.Copy(os.Stdout, o)
   390  	go io.Copy(os.Stderr, e)
   391  	return session.Wait()
   392  }
   393  
   394  // We do flag parsing in init so we can
   395  // Unshare if needed while we are still
   396  // single threaded.
   397  func init() {
   398  	flag.Parse()
   399  	if *debug {
   400  		v = log.Printf
   401  	}
   402  	if os.Getpid() == 1 {
   403  		*runAsInit, *debug = true, true
   404  		v = log.Printf
   405  	}
   406  	if *remote {
   407  		// The unshare system call in Linux doesn't unshare mount points
   408  		// mounted with --shared. Systemd mounts / with --shared. For a
   409  		// long discussion of the pros and cons of this see debian bug 739593.
   410  		// The Go model of unsharing is more like Plan 9, where you ask
   411  		// to unshare and the namespaces are unconditionally unshared.
   412  		// To make this model work we must further mark / as MS_PRIVATE.
   413  		// This is what the standard unshare command does.
   414  		var (
   415  			none  = [...]byte{'n', 'o', 'n', 'e', 0}
   416  			slash = [...]byte{'/', 0}
   417  			flags = uintptr(unix.MS_PRIVATE | unix.MS_REC) // Thanks for nothing Linux.
   418  		)
   419  		if err := syscall.Unshare(syscall.CLONE_NEWNS); err != nil {
   420  			log.Printf("bad Unshare: %v", err)
   421  		}
   422  		_, _, err1 := syscall.RawSyscall6(unix.SYS_MOUNT, uintptr(unsafe.Pointer(&none[0])), uintptr(unsafe.Pointer(&slash[0])), 0, flags, 0, 0)
   423  		if err1 != 0 {
   424  			log.Printf("Warning: unshare failed (%v). There will be no private 9p mount", err1)
   425  		}
   426  		flags = 0
   427  		if err := unix.Mount("cpu", "/tmp", "tmpfs", flags, ""); err != nil {
   428  			log.Printf("Warning: tmpfs mount on /tmp (%v) failed. There will be no 9p mount", err)
   429  		}
   430  	}
   431  }
   432  
   433  func setWinsize(f *os.File, w, h int) {
   434  	syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), uintptr(syscall.TIOCSWINSZ),
   435  		uintptr(unsafe.Pointer(&struct{ h, w, x, y uint16 }{uint16(h), uint16(w), 0, 0})))
   436  }
   437  
   438  // adjust adjusts environment variables containing paths from
   439  // / to /tmp/cpu. On Plan 9 this is done with a union mount.
   440  // PATH variables are the union mounts of Unix so we use them
   441  // instead.
   442  func adjust(env []string) []string {
   443  	var res []string
   444  	for _, e := range env {
   445  		n := strings.SplitN(e, "=", 2)
   446  		if len(n) < 2 {
   447  			res = append(res, e)
   448  			continue
   449  		}
   450  		v := strings.Split(n[1], ":")
   451  		for i := range v {
   452  			if filepath.IsAbs(v[i]) {
   453  				v[i] = filepath.Join("/tmp/cpu", v[i])
   454  			}
   455  		}
   456  		res = append(res, n[0]+"="+strings.Join(v, ":"))
   457  	}
   458  	return res
   459  }
   460  
   461  func handler(s ssh.Session) {
   462  	a := s.Command()
   463  	verbose("the handler is here, cmd is %v", a)
   464  	cmd := exec.Command(a[0], a[1:]...)
   465  	log.Printf("cmd.Env ius %v", cmd.Env)
   466  	adj := adjust(s.Environ())
   467  	log.Printf("s.Environt is %v, adjusted is %v", s.Environ(), adj)
   468  	cmd.Env = append(cmd.Env, adj...)
   469  	ptyReq, winCh, isPty := s.Pty()
   470  	verbose("the command is %v", *cmd)
   471  	if isPty {
   472  		cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
   473  		f, err := pty.Start(cmd)
   474  		verbose("command started with pty")
   475  		if err != nil {
   476  			log.Print(err)
   477  			return
   478  		}
   479  		go func() {
   480  			for win := range winCh {
   481  				setWinsize(f, win.Width, win.Height)
   482  			}
   483  		}()
   484  		go func() {
   485  			io.Copy(f, s) // stdin
   486  		}()
   487  		io.Copy(s, f) // stdout
   488  	} else {
   489  		cmd.Stdin, cmd.Stdout, cmd.Stderr = s, s, s
   490  		verbose("running command without pty")
   491  		if err := cmd.Run(); err != nil {
   492  			log.Print(err)
   493  			return
   494  		}
   495  	}
   496  	verbose("handler exits")
   497  }
   498  
   499  func doInit() error {
   500  	if err := cpuSetup(); err != nil {
   501  		log.Printf("CPU setup error with cpu running as init: %v", err)
   502  	}
   503  	cmds := [][]string{{"/bin/defaultsh"}, {"/bbin/dhclient", "-v"}}
   504  	verbose("Try to run %v", cmds)
   505  
   506  	for _, v := range cmds {
   507  		verbose("Let's try to run %v", v)
   508  		if _, err := os.Stat(v[0]); os.IsNotExist(err) {
   509  			verbose("it's not there")
   510  			continue
   511  		}
   512  
   513  		// I *love* special cases. Evaluate just the top-most symlink.
   514  		//
   515  		// In source mode, this would be a symlink like
   516  		// /buildbin/defaultsh -> /buildbin/elvish ->
   517  		// /buildbin/installcommand.
   518  		//
   519  		// To actually get the command to build, argv[0] has to end
   520  		// with /elvish, so we resolve one level of symlink.
   521  		if filepath.Base(v[0]) == "defaultsh" {
   522  			s, err := os.Readlink(v[0])
   523  			if err == nil {
   524  				v[0] = s
   525  			}
   526  			verbose("readlink of %v returns %v", v[0], s)
   527  			// and, well, it might be a relative link.
   528  			// We must go deeper.
   529  			d, b := filepath.Split(v[0])
   530  			d = filepath.Base(d)
   531  			v[0] = filepath.Join("/", os.Getenv("UROOT_ROOT"), d, b)
   532  			verbose("is now %v", v[0])
   533  		}
   534  
   535  		cmd := exec.Command(v[0], v[1:]...)
   536  		cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
   537  		cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
   538  		verbose("Run %v", cmd)
   539  		if err := cmd.Start(); err != nil {
   540  			log.Printf("Error starting %v: %v", v, err)
   541  			continue
   542  		}
   543  	}
   544  	publicKeyOption := func(ctx ssh.Context, key ssh.PublicKey) bool {
   545  		// Glob the users's home directory for all the
   546  		// possible keys?
   547  		data, err := ioutil.ReadFile(*pubKeyFile)
   548  		if err != nil {
   549  			fmt.Print(err)
   550  			return false
   551  		}
   552  		allowed, _, _, _, _ := ssh.ParseAuthorizedKey(data)
   553  		return ssh.KeysEqual(key, allowed)
   554  	}
   555  
   556  	// Now we run as an ssh server, and each time we get a connection,
   557  	// we run that command after setting things up for it.
   558  	server := ssh.Server{
   559  		LocalPortForwardingCallback: ssh.LocalPortForwardingCallback(func(ctx ssh.Context, dhost string, dport uint32) bool {
   560  			log.Println("Accepted forward", dhost, dport)
   561  			return true
   562  		}),
   563  		Addr:             ":" + *port,
   564  		PublicKeyHandler: publicKeyOption,
   565  		ReversePortForwardingCallback: ssh.ReversePortForwardingCallback(func(ctx ssh.Context, host string, port uint32) bool {
   566  			log.Println("attempt to bind", host, port, "granted")
   567  			return true
   568  		}),
   569  		Handler: handler,
   570  	}
   571  
   572  	// start the process reaper
   573  	procs := make(chan uint)
   574  	go cpuDone(procs)
   575  
   576  	server.SetOption(ssh.HostKeyFile(*hostKeyFile))
   577  	log.Println("starting ssh server on port " + *port)
   578  	if err := server.ListenAndServe(); err != nil {
   579  		log.Print(err)
   580  	}
   581  	verbose("server.ListenAndServer returned")
   582  
   583  	numprocs := <-procs
   584  	verbose("Reaped %d procs", numprocs)
   585  	return nil
   586  }
   587  
   588  // TODO: we've been tryinmg to figure out the right way to do usage for years.
   589  // If this is a good way, it belongs in the uroot package.
   590  func usage() {
   591  	var b bytes.Buffer
   592  	flag.CommandLine.SetOutput(&b)
   593  	flag.PrintDefaults()
   594  	log.Fatalf("Usage: cpu [options] host [shell command]:\n%v", b.String())
   595  }
   596  
   597  func main() {
   598  	verbose("Args %v pid %d *runasinit %v *remote %v", os.Args, os.Getpid(), *runAsInit, *remote)
   599  	args := flag.Args()
   600  	switch {
   601  	case *runAsInit:
   602  		verbose("Running as Init")
   603  		if err := doInit(); err != nil {
   604  			log.Fatal(err)
   605  		}
   606  	case *remote:
   607  		verbose("Running as remote")
   608  		if err := runRemote(strings.Join(flag.Args(), " "), *port9p); err != nil {
   609  			log.Fatal(err)
   610  		}
   611  	default:
   612  		if len(args) == 0 {
   613  			usage()
   614  		}
   615  		host := args[0]
   616  		a := strings.Join(args[1:], " ")
   617  		verbose("Running as client")
   618  		if a == "" {
   619  			a = os.Getenv("SHELL")
   620  		}
   621  		if err := runClient(host, a); err != nil {
   622  			log.Fatal(err)
   623  		}
   624  	}
   625  }