github.com/jlmucb/cloudproxy@v0.0.0-20170830161738-b5aa0b619bc4/go/apps/tao_launch/tao_launch.go (about)

     1  // Copyright (c) 2014, Google, Inc.  All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  // Job control, signals, and stdio for running hosted programs:
    18  //
    19  // If stdin is a terminal, we proxy input over a pipe instead of handing it
    20  // directly to the hosted program. We also proxy a few signals, like SIGINT,
    21  // SIGKILL, SIGHUP, SIGTSTP, and SIGCONT. This mostly lets you use normal job
    22  // control with Control-C, Control-V, &, fg, and bg.
    23  //
    24  // Known bugs and limitations:
    25  //  - SIGTTOU and `stty stop` is not supported at all
    26  //  - Attempting to run in the background as the head of a pipeline is broken;
    27  //    the hosted program will run, but all other processes in the pipeline will
    28  //    probably stop due to a misleading SIGTTIN.
    29  //  - Running in the background (not as part of a pipeline) works, but the shell
    30  //    (and ps) will report that we are stopped waiting for input.
    31  //  - There are various race conditions if signals arrive back-to-back.
    32  //
    33  // Otherwise, simple job control, signals, and redirection mostly kinda works.
    34  // But the implementation is all a bit fragile and broken. Here is why.
    35  //
    36  // Handing stdin to a hosted program causes problems with job control. Normally,
    37  // a process gets a SIGTTIN (which, by default, causes it to stop) when it
    38  // writes to a stream if all of these are true:
    39  //   (1) the stream is a tty,
    40  //   (2) the tty is the controlling terminal of the process,
    41  //   (3) the process is in the background (or maybe non-foreground?) group for
    42  //       the session associated with that tty
    43  // When we hand stdin to a hosted program, (2) and maybe (3) are false, so it
    44  // never gets the signal. The terminal is associated with our session, and there
    45  // isn't a reliable way for the hosted program to join our session. We could
    46  // possibly create a new pty, but that seems complex. So we aren't going to ever
    47  // meet conditions (2) and (3). There is one problematic scenario: if stdin is
    48  // our controlling tty, and when we are put into the background, then the hosted
    49  // program will steal input from the console. Unfortunately, this seems like a
    50  // common scenario.
    51  //
    52  // If we proxy stdin, the hosted program will read from our pipe, while we
    53  // continously read from the tty and write to the pipe. When we are put into the
    54  // background, we will get SIGTTIN almost immediately, because we are
    55  // continuously reading. We have a few options for handling the SIGTTIN:
    56  //   (a) We can stop ourselves with a SIGSTOP. The shell will report that we are
    57  //       stopped waiting for input. That won't stop the hosted program
    58  //       immediately, but it will probably block on stdin eventually. If we
    59  //       proxy stdout and stderr, those would stop too, unfortunately, breaking
    60  //       background mode. So we shouldn't proxy stdout and stderr in this case.
    61  //       There is a downside to this, explained below. And, the shell's notice
    62  //       that we are stopped waiting for input will be misleading, since the
    63  //       output will keep going. Moreover, the SIGTTIN will cause other
    64  //       processes in our process group (e.g. other parts of a pipeline) to
    65  //       stop.
    66  //   (b) We can try to stop only the input proxying when we get a SIGTTIN. The
    67  //       The downside is that the shell will not realize that the hosted program is
    68  //       blocked waiting for input, since we don't actually stop. This seems
    69  //       like a minor problem.
    70  // Sadly, (b) is apparently impossible in go: go installs the SIGTTIN handler as
    71  // SA_RESTART, there is no way to suspend the proxying goroutine nor a way to
    72  // cause its read of stdin to return EINTR or EIO, and there is no way within go
    73  // to ignore SIGTTIN at the OS level.
    74  //
    75  // Handing stdout or stderr to a hosted program may cause slight problems.
    76  // Normally, a process gets a SIGTTOU (which, by default, causes it to stop)
    77  // when it writes to a stream if the above (1), (2), and (3) are true, and:
    78  //   (4) and 'stty tostop' is in effect for that tty.
    79  // Again, we aren't going to meet conditions (2) and (3). Fortunately, (4) is
    80  // probably false in most cases anyway. Or, at least, I have never heard of it
    81  // before and have never set that option. So having broken SIGTTOU delivery is
    82  // probably okay.
    83  //
    84  // If we proxy stdout and stderr instead, when the hosted program writes to our
    85  // pipe, we will read it, then write to the tty. If a SIGTTOU is needed, it will
    86  // be delivered to us. We can catch it, stop the hosted program with a SIGTTOU,
    87  // and stop ourselves with a SIGSTOP. The hosted program can ignore the SIGTTOU
    88  // and keep writing, but it will get buffered in the pipe and not appear on
    89  // screen.
    90  //
    91  // In summary, our best options are:
    92  //
    93  //   Option 1: Don't proxy stdout or stderr. If stdin is our controlling
    94  //   terminal, then proxy it. When put into the background, the shell will
    95  //   immediately report that we are stopped waiting for input, but confusingly
    96  //   the output may keep going. SIGTTOU is not supported at all. The code is at
    97  //   least somewhat clean.
    98  //
    99  //   Option 2: Proxy anything that is our controlling terminal. When put into
   100  //   the background, then shell will never report that we are stopped waiting
   101  //   for input. When we get SIGTTIN, catch the signal and cause the proxying on
   102  //   stdin to stop, but allow the proxying on stdout and stderr to continue.
   103  //   Unfortunately, this is tricky in go. There is a goroutine that continuously
   104  //   does read() on stdin. When we catch SIGTTIN, the read() on stdin seems to
   105  //   get restarted (via ERESTART?) immediately, leading to another SIGTTIN, and
   106  //   so on, in a tight loop. If we could set SIGTTIN to SIG_IGN, then I believe
   107  //   read() would fail with EIO instead of restarting, allowing the goroutine to
   108  //   detect the error and stop trying to read() the tty. Sadly, it isn't obvious
   109  //   how to call sigaction SIG_IGN in go, and it isn't clear if that would
   110  //   interfere with go's own signal handling efforts.
   111  
   112  import (
   113  	"flag"
   114  	"fmt"
   115  	"io"
   116  	"io/ioutil"
   117  	"net"
   118  	"os"
   119  	"os/signal"
   120  	"path"
   121  	"regexp"
   122  	"strings"
   123  	"syscall"
   124  	"text/tabwriter"
   125  	"time"
   126  	"unsafe"
   127  
   128  	"github.com/jlmucb/cloudproxy/go/tao"
   129  	"github.com/jlmucb/cloudproxy/go/tao/auth"
   130  	"github.com/jlmucb/cloudproxy/go/util"
   131  	"github.com/jlmucb/cloudproxy/go/util/options"
   132  )
   133  
   134  var opts = []options.Option{
   135  	// Flags for all commands
   136  	{"tao_domain", "", "<dir>", "Tao domain configuration directory", "all,all+run"},
   137  	{"host", "", "<dir>", "Host configuration, relative to domain directory or absolute", "all,all+run"},
   138  }
   139  
   140  var run_opts = []options.Option{
   141  	// Flags for run
   142  	{"pidfile", "", "<file>", "Write hosted program pid to this file", "run,all+run"},
   143  	{"namefile", "", "<file>", "Write hosted program subprin name to this file", "run,all+run"},
   144  	{"disown", false, "", "Don't wait for hosted program to exit", "run,all+run"},
   145  	{"daemon", false, "", "Don't pipe stdio or wait for hosted program to exit", "run,all+run"},
   146  	{"verbose", false, "", "Be more verbose", "run,all+run"},
   147  }
   148  
   149  func init() {
   150  	options.Add(opts...)
   151  	switch path.Base(os.Args[0]) {
   152  	case "tao_stop", "tao_kill", "tao_list":
   153  	case "tao_run":
   154  		options.Add(run_opts...)
   155  	default:
   156  		options.Add(run_opts...)
   157  	}
   158  }
   159  
   160  var noise = ioutil.Discard
   161  
   162  func help() {
   163  	w := new(tabwriter.Writer)
   164  	w.Init(os.Stderr, 4, 0, 2, ' ', 0)
   165  	av0 := path.Base(os.Args[0])
   166  
   167  	switch av0 {
   168  	case "tao_stop", "tao_kill":
   169  		fmt.Fprintf(w, "Usage: %s [options] <subprin> [subprin...]\n", av0)
   170  		categories := []options.Category{
   171  			{"all", "Options"},
   172  			{"logging", "Options to control log output"},
   173  		}
   174  		options.ShowRelevant(w, categories...)
   175  	case "tao_list":
   176  		fmt.Fprintf(w, "Usage: %s [options]\n", av0)
   177  		categories := []options.Category{
   178  			{"all", "Options"},
   179  			{"logging", "Options to control log output"},
   180  		}
   181  		options.ShowRelevant(w, categories...)
   182  	case "tao_run":
   183  		fmt.Fprintf(w, "Usage: %s [options] <prog> [args...]\n", av0)
   184  		categories := []options.Category{
   185  			{"all+run", "Options"},
   186  			{"logging", "Options to control log output"},
   187  		}
   188  		options.ShowRelevant(w, categories...)
   189  	default:
   190  		fmt.Fprintf(w, "Tao Hosted Program Utility\n")
   191  		fmt.Fprintf(w, "Usage:\n")
   192  		fmt.Fprintf(w, "  %s run [options] [process:]<prog> [args...]\t Run a new hosted process\n", av0)
   193  		fmt.Fprintf(w, "  %s run [options] docker:<img> [dockerargs...] [-- [imgargs...]]\t Run a new hosted docker image\n", av0)
   194  		fmt.Fprintf(w, "  %s run [options] kvm_coreos:<img> [dockerargs...] [-- [imgargs...]]\t Run a new hosted QEMU/kvm CoreOS image\n", av0)
   195  		fmt.Fprintf(w, "  %s run [options] kvm_custom:<img> [-- <kernel image path> <initram image path> <SSH port>]\t Run a new hosted QEMU/kvm custom instance\n", av0)
   196  		fmt.Fprintf(w, "  %s list [options]\t List hosted programs\n", av0)
   197  		fmt.Fprintf(w, "  %s stop [options] subprin [subprin...]\t Stop hosted programs\n", av0)
   198  		fmt.Fprintf(w, "  %s stop [options] subprin [subprin...]\t Kill hosted programs\n", av0)
   199  		categories := []options.Category{
   200  			{"all", "Basic options for all commands"},
   201  			{"run", "Options for 'run' command"},
   202  			{"logging", "Options to control log output"},
   203  		}
   204  		options.ShowRelevant(w, categories...)
   205  	}
   206  	w.Flush()
   207  }
   208  
   209  func main() {
   210  	flag.Usage = help
   211  
   212  	cmd := "help"
   213  	switch av0 := path.Base(os.Args[0]); av0 {
   214  	case "tao_run", "tao_list", "tao_stop", "tao_kill":
   215  		cmd = av0[4:]
   216  		flag.Parse()
   217  	default:
   218  		// Get options before the command verb
   219  		flag.Parse()
   220  		// Get command verb
   221  		if flag.NArg() > 0 {
   222  			cmd = flag.Arg(0)
   223  		}
   224  		// Get options after the command verb
   225  		if flag.NArg() > 1 {
   226  			flag.CommandLine.Parse(flag.Args()[1:])
   227  		}
   228  	}
   229  
   230  	if b, ok := options.Bool["verbose"]; ok && *b {
   231  		noise = os.Stderr
   232  	}
   233  
   234  	sockPath := path.Join(hostPath(), "admin_socket")
   235  	conn, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: sockPath, Net: "unix"})
   236  	options.FailIf(err, "Can't connect to host admin socket")
   237  	defer conn.Close()
   238  
   239  	client := tao.NewLinuxHostAdminClient(conn)
   240  	switch cmd {
   241  	case "help":
   242  		help()
   243  	case "run":
   244  		runHosted(&client, flag.Args())
   245  	case "stop":
   246  		for _, s := range flag.Args() {
   247  			var subprin auth.SubPrin
   248  			_, err := fmt.Sscanf(s, "%v", &subprin)
   249  			options.FailIf(err, "Not a subprin: %s", s)
   250  			err = client.StopHostedProgram(subprin)
   251  			options.FailIf(err, "Could not stop %s", s)
   252  		}
   253  	case "kill":
   254  		for _, s := range flag.Args() {
   255  			var subprin auth.SubPrin
   256  			options.FailIf(err, "Not a subprin: %s", s)
   257  			err = client.KillHostedProgram(subprin)
   258  			options.FailIf(err, "Could not kill %s", s)
   259  		}
   260  	case "list":
   261  		names, pids, err := client.ListHostedPrograms()
   262  		options.FailIf(err, "Can't list hosted programs")
   263  		for i, p := range pids {
   264  			fmt.Printf("pid=%d subprin=%v\n", p, names[i])
   265  		}
   266  		fmt.Printf("%d hosted programs\n", len(pids))
   267  	default:
   268  		options.Usage("Unrecognized command: %s", cmd)
   269  	}
   270  
   271  	return
   272  }
   273  
   274  const moment = 100 * time.Millisecond
   275  
   276  func split(a []string, delim string) (before []string, after []string) {
   277  	for i, s := range a {
   278  		if s == delim {
   279  			before = append(before, a[0:i]...)
   280  			after = append(after, a[i+1:]...)
   281  			return
   282  		}
   283  	}
   284  	before = append(before, a...)
   285  	return
   286  }
   287  
   288  func runHosted(client *tao.LinuxHostAdminClient, args []string) {
   289  	var err error
   290  
   291  	if len(args) == 0 {
   292  		options.Usage("Missing program path and arguments")
   293  	}
   294  
   295  	spec := new(tao.HostedProgramSpec)
   296  
   297  	ctype := "process"
   298  	spec.Path = args[0]
   299  	for _, prefix := range []string{"process", "docker", "kvm_coreos", "kvm_custom"} {
   300  		if strings.HasPrefix(spec.Path, prefix+":") {
   301  			ctype = prefix
   302  			spec.Path = strings.TrimPrefix(spec.Path, prefix+":")
   303  		}
   304  	}
   305  
   306  	switch ctype {
   307  	case "process":
   308  		dirs := util.LiberalSearchPath()
   309  		binary := util.FindExecutable(args[0], dirs)
   310  		if binary == "" {
   311  			options.Fail(nil, "Can't find `%s` on path '%s'", args[0], strings.Join(dirs, ":"))
   312  		}
   313  		spec.ContainerArgs = []string{spec.Path}
   314  		spec.Args = args[1:]
   315  		spec.Path = binary
   316  	case "docker", "kvm_coreos":
   317  		// args contains [ "docker:argv0", docker_args..., "--", prog_args... ]
   318  		spec.ContainerArgs, spec.Args = split(args, "--")
   319  		// Replace docker arg 0 with valid image name constructed from // base(argv[0]).
   320  		r, _ := regexp.Compile("[^a-zA-Z0-9_.]+")
   321  		spec.ContainerArgs[0] = r.ReplaceAllLiteralString(path.Base(spec.Path), "_")
   322  	case "kvm_custom":
   323  		_, spec.Args = split(args, "--")
   324  	}
   325  
   326  	pidfile := *options.String["pidfile"]
   327  	var pidOut *os.File
   328  	if pidfile == "-" {
   329  		pidOut = os.Stdout
   330  	} else if pidfile != "" {
   331  		pidOut, err = os.Create(pidfile)
   332  		options.FailIf(err, "Can't open pid file")
   333  	}
   334  
   335  	namefile := *options.String["namefile"]
   336  	var nameOut *os.File
   337  	if namefile == "-" {
   338  		nameOut = os.Stdout
   339  	} else if namefile != "" {
   340  		nameOut, err = os.Create(namefile)
   341  		options.FailIf(err, "Can't open name file")
   342  	}
   343  
   344  	daemon := *options.Bool["daemon"]
   345  	disown := *options.Bool["disown"]
   346  
   347  	var pr, pw *os.File
   348  	proxying := false
   349  	tty := isCtty(int(os.Stdin.Fd()))
   350  	if daemon {
   351  		// stdio is nil
   352  	} else if disown {
   353  		// We are assuming that if stdin is a terminal, it is our controlling
   354  		// terminal. I don't know any way to verify it, but it seems likely.
   355  		if tty {
   356  			// stdin is nil, else they would steal input from tty
   357  		} else {
   358  			spec.Stdin = os.Stdin
   359  		}
   360  		spec.Stdout = os.Stdout
   361  		spec.Stderr = os.Stderr
   362  	} else {
   363  		// interactive
   364  		proxying = tty
   365  		if proxying {
   366  			pr, pw, err = os.Pipe()
   367  			options.FailIf(err, "Can't pipe")
   368  			spec.Stdin = pr
   369  		} else {
   370  			spec.Stdin = os.Stdin
   371  		}
   372  		spec.Stdout = os.Stdout
   373  		spec.Stderr = os.Stderr
   374  		fmt.Fprintf(noise, "[proxying stdin]\n")
   375  	}
   376  
   377  	spec.Dir, err = os.Getwd()
   378  	options.FailIf(err, "Can't get working directory")
   379  
   380  	// Start catching signals early, buffering a few, so we don't miss any. We
   381  	// don't proxy SIGTTIN. However, we do catch it and stop ourselves, rather
   382  	// than letting the OS stop us. This is necessary so that we can send
   383  	// SIGCONT to the child at the right times.
   384  	// Here is the easy case
   385  	//   we start in background
   386  	//   we fork (output starts going)
   387  	//   we are background, so leave SIGTTIN handling at the default
   388  	//   we read and get SIGTTIN, so are stopped
   389  	//   child is not stopped, it keeps outputting, as desired
   390  	//   upon fg, we get SIGCONT, start dropping SIGTTIN and looping for input and signals
   391  	// Here is the tricky case:
   392  	//   we start in foreground
   393  	//   we fork (output starts going)
   394  	//   we are foreground, so catch and drop SIGTTIN (we use SIGTSTP instead)
   395  	//   we get SIGTSTP via ctrl-z
   396  	//   we send child SIGTSTP, so it stops
   397  	//      [we are still dropping SIGTTIN]
   398  	//   we send ourselves SIGSTOP, so we stop
   399  	//   we get SIGCONT via either bg or fg
   400  	//      [if bg, now furiously catching and dropping SIGTTIN]
   401  	//      [if fg, dropping too, but there should not be any SIGTTIN]
   402  	//   send child the SIGCONT
   403  	//   if we are foreground, so go back to top of loop
   404  	//   if we are background, reset SIGTTIN which causes us to stop
   405  	//
   406  	// The basic invariant we are trying to maintain is that when we are
   407  	// foreground we catch and drop SIGTTIN, allowing us to properly handle
   408  	// SIGTSTP events. There shouldn't be any SIGTTIN anyway, except for the
   409  	// brief moments when we are transitioning to stopped.
   410  	// And when the child is supposed to be running in the background, we should
   411  	// leave the default SIGTTIN behavior, so that the OS will stop our read
   412  	// loop.
   413  
   414  	signals := make(chan os.Signal, 10) // some buffering
   415  	signal.Notify(signals,
   416  		syscall.SIGINT,  // Ctrl-C
   417  		syscall.SIGTERM, // SIGINT wannabe (e.g. via kill)
   418  		syscall.SIGQUIT, // Ctrl-\
   419  		syscall.SIGTSTP, // Ctrl-Z
   420  		syscall.SIGHUP,  // tty hangup (e.g. via disown)
   421  		syscall.SIGABRT, // abort (e.g. via kill)
   422  		syscall.SIGUSR1, // user-defined (e.g. via kill)
   423  		syscall.SIGUSR2, // user-defined (e.g. via kill)
   424  	)
   425  
   426  	// Start the hosted program
   427  	subprin, pid, err := client.StartHostedProgram(spec)
   428  	options.FailIf(err, "Can't start hosted program")
   429  	fmt.Fprintf(noise, "[started hosted program with pid %d]\n", pid)
   430  	fmt.Fprintf(noise, "[subprin is %v]\n", subprin)
   431  
   432  	if pidOut != nil {
   433  		fmt.Fprintln(pidOut, pid)
   434  		pidOut.Close()
   435  	}
   436  
   437  	if nameOut != nil {
   438  		fmt.Fprintln(nameOut, subprin)
   439  		nameOut.Close()
   440  	}
   441  
   442  	if disown || daemon {
   443  		return
   444  	}
   445  
   446  	// Listen for exit status from host
   447  	status := make(chan int, 1)
   448  	go func() {
   449  		s, _ := client.WaitHostedProgram(pid, subprin)
   450  		// For short programs, we often lose the race, so
   451  		// we get a "no such hosted program" error. Ignore it.
   452  		status <- s
   453  	}()
   454  
   455  	wasForeground := false
   456  	if proxying && isForeground() {
   457  		fmt.Fprintf(noise, "[in foreground]\n")
   458  		dropSIGTTIN()
   459  		wasForeground = true
   460  	}
   461  
   462  	// Proxy stdin, if needed
   463  	if proxying {
   464  		pr.Close()
   465  		go func() {
   466  			_, err := io.Copy(pw, os.Stdin)
   467  			options.WarnIf(err, "Can't copy from stdin to pipe")
   468  			pw.Close()
   469  		}()
   470  	}
   471  
   472  	// If we are proxying and (were) background, we should probably
   473  	// have done a read() by now and gotten SIGTTIN and stopped. Let's
   474  	// pause a moment to be sure the read() happens.
   475  	time.Sleep(moment)
   476  
   477  	// By this point, if we had been foreground, we might still be. Or, we might
   478  	// have been foreground but just gotten SIGTSTP and are now madly dropping
   479  	// SIGTTIN until we get into the loop below to handle the SIGTSTP.
   480  	//
   481  	// Alternatively, if we had been background, we would have been stopped by
   482  	// the default SIGTTIN, so the only way we would be here is if we later got
   483  	// pulled foreground via fg. We want to be dropping SIGTTIN in case we get a
   484  	// SIGTSTP.
   485  	if proxying && !wasForeground {
   486  		dropSIGTTIN()
   487  	}
   488  
   489  	next := cont
   490  	for next != done {
   491  		select {
   492  		case s := <-status:
   493  			fmt.Fprintf(noise, "[hosted program exited, %s]\n", exitCode(s))
   494  			next = done
   495  		case sig := <-signals:
   496  			next = handle(sig, pid)
   497  		}
   498  		if next == resumed && proxying && !isForeground() {
   499  			// Need to toggle SIGTTIN handling and block (that's the only way to
   500  			// stop spinning on SIGTTIN), but only after handling all pending
   501  			// signals (e.g. SIGCONT then SIGHUP, or SIGCONT then SIGTERM).
   502  			for next == resumed {
   503  				select {
   504  				case s := <-status:
   505  					fmt.Fprintf(noise, "[hosted program exited, %s]\n", exitCode(s))
   506  					next = done
   507  				case sig := <-signals:
   508  					next = handle(sig, pid)
   509  					if next == cont {
   510  						next = resumed
   511  					}
   512  				default:
   513  					next = cont
   514  					defaultSIGTTIN()
   515  					time.Sleep(moment)
   516  					dropSIGTTIN()
   517  				}
   518  			}
   519  		}
   520  	}
   521  	signal.Stop(signals)
   522  }
   523  
   524  type todo int
   525  
   526  const (
   527  	done    todo = iota // we are done, time to exit
   528  	resumed             // we just woke up
   529  	cont                // neither of the above
   530  )
   531  
   532  func handle(sig os.Signal, pid int) todo {
   533  	switch sig {
   534  	case syscall.SIGTSTP:
   535  		send(pid, syscall.SIGTSTP)
   536  		fmt.Fprintf(noise, "[stopping]\n")
   537  		syscall.Kill(syscall.Getpid(), syscall.SIGSTOP)
   538  		time.Sleep(moment)
   539  		fmt.Fprintf(noise, "[resuming]\n")
   540  		send(pid, syscall.SIGCONT)
   541  		return resumed
   542  	case syscall.SIGHUP: // tty hangup (e.g. via disown)
   543  		noise = ioutil.Discard
   544  		os.Stdin.Close()
   545  		os.Stdout.Close()
   546  		os.Stderr.Close()
   547  		send(pid, sig.(syscall.Signal))
   548  		// Our tty is gone, so there is little left to do. We could hang
   549  		// around proxying signals (e.g. those sent via kill). But those
   550  		// could be just as easily sent directly to the hosted program,
   551  		// so let's not bother.
   552  		return done
   553  	default:
   554  		send(pid, sig.(syscall.Signal))
   555  	}
   556  	return cont
   557  }
   558  
   559  var discard = make(chan os.Signal, 1) // minimal buffering
   560  
   561  func defaultSIGTTIN() {
   562  	fmt.Fprintf(noise, "[default SIGTTIN handling]\n")
   563  	signal.Stop(discard)
   564  }
   565  
   566  func dropSIGTTIN() {
   567  	fmt.Fprintf(noise, "[dropping SIGTTIN]\n")
   568  	signal.Notify(discard, syscall.SIGTTIN)
   569  }
   570  
   571  func isForeground() bool {
   572  	tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
   573  	if err != nil {
   574  
   575  	}
   576  	fpgrp := 0
   577  	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCGPGRP, uintptr(unsafe.Pointer(&fpgrp)))
   578  	if errno != 0 {
   579  	}
   580  	return syscall.Getpgrp() == fpgrp
   581  }
   582  
   583  // Note: There is a slight race here. If pids are reused very quickly, we might
   584  // end up sending a signal to the wrong child just after the hosted program
   585  // exits. That seems unlikely. To fix it, we would have to coordinate with
   586  // linux_host, e.g. have linux_host send the signal on our behalf.
   587  func send(pid int, sig syscall.Signal) {
   588  	fmt.Fprintf(noise, "[sending %s to hosted program]\n", sigName(sig))
   589  	syscall.Kill(pid, sig)
   590  }
   591  
   592  func sigName(sig syscall.Signal) string {
   593  	var name string
   594  	switch sig {
   595  	case syscall.SIGINT:
   596  		name = "SIGINT"
   597  	case syscall.SIGTERM:
   598  		name = "SIGTERM"
   599  	case syscall.SIGQUIT:
   600  		name = "SIGQUIT"
   601  	case syscall.SIGTSTP:
   602  		name = "SIGTSTP"
   603  	case syscall.SIGHUP:
   604  		name = "SIGHUP"
   605  	case syscall.SIGABRT:
   606  		name = "SIGABRT"
   607  	case syscall.SIGUSR1:
   608  		name = "SIGUSR1"
   609  	case syscall.SIGUSR2:
   610  		name = "SIGUSR2"
   611  	case syscall.SIGCONT:
   612  		name = "SIGCONT"
   613  	case syscall.SIGSTOP:
   614  		name = "SIGSTOP"
   615  	}
   616  	return fmt.Sprintf("%s (%d)", name, int(sig))
   617  }
   618  
   619  func exitCode(s int) string {
   620  	ws := syscall.WaitStatus(s)
   621  	if ws.Exited() {
   622  		return fmt.Sprintf("status %d", ws.ExitStatus())
   623  	} else if ws.Signaled() && ws.CoreDump() {
   624  		return fmt.Sprintf("signal %v, with core dump", ws.Signal())
   625  	} else if ws.Signaled() {
   626  		return fmt.Sprintf("signal %v", ws.Signal())
   627  	} else if ws.Stopped() {
   628  		return fmt.Sprintf("stopped by %v", ws.StopSignal())
   629  	} else if ws.Continued() {
   630  		return fmt.Sprintf("continued")
   631  	} else {
   632  		return fmt.Sprintf("exit status unknown")
   633  	}
   634  }
   635  
   636  func domainPath() string {
   637  	if path := *options.String["tao_domain"]; path != "" {
   638  		return path
   639  	}
   640  	if path := os.Getenv("TAO_DOMAIN"); path != "" {
   641  		return path
   642  	}
   643  	options.Usage("Must supply -tao_domain or set $TAO_DOMAIN")
   644  	return ""
   645  }
   646  
   647  func hostPath() string {
   648  	hostPath := *options.String["host"]
   649  	if hostPath == "" {
   650  		hostPath = "linux_tao_host"
   651  	}
   652  	if !path.IsAbs(hostPath) {
   653  		hostPath = path.Join(domainPath(), hostPath)
   654  	}
   655  	return hostPath
   656  }