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