func subcmd(cmd, prog string, args []string) { dirs := util.LiberalSearchPath() binary := util.FindExecutable(prog, dirs) if binary == "" { options.Fail(nil, "Can't find `%s` on path '%s'", prog, strings.Join(dirs, ":")) } args = append([]string{"tao_" + cmd}, args...) err := syscall.Exec(binary, args, os.Environ()) options.Fail(err, "Can't exec `%s`", cmd) }
func hash(p string) ([]byte, error) { dirs := util.LiberalSearchPath() binary := util.FindExecutable(p, dirs) if binary == "" { return nil, fmt.Errorf("Can't find `%s` on path '%s'", p, strings.Join(dirs, ":")) } file, err := os.Open(binary) if err != nil { return nil, err } hasher := sha256.New() _, err = io.Copy(hasher, file) options.FailIf(err, "Can't hash file") return hasher.Sum(nil), nil }
func runHosted(client *tao.LinuxHostAdminClient, args []string) { var err error if len(args) == 0 { options.Usage("Missing program path and arguments") } spec := new(tao.HostedProgramSpec) ctype := "process" spec.Path = args[0] for _, prefix := range []string{"process", "docker", "kvm_coreos"} { if strings.HasPrefix(spec.Path, prefix+":") { ctype = prefix spec.Path = strings.TrimPrefix(spec.Path, prefix+":") } } switch ctype { case "process": dirs := util.LiberalSearchPath() binary := util.FindExecutable(args[0], dirs) if binary == "" { options.Fail(nil, "Can't find `%s` on path '%s'", args[0], strings.Join(dirs, ":")) } spec.ContainerArgs = []string{spec.Path} spec.Args = args[1:] spec.Path = binary case "docker", "kvm_coreos": // args contains [ "docker:argv0", docker_args..., "--", prog_args... ] spec.ContainerArgs, spec.Args = split(args, "--") // Replace docker arg 0 with valid image name constructed from // base(argv[0]). r, _ := regexp.Compile("[^a-zA-Z0-9_.]+") spec.ContainerArgs[0] = r.ReplaceAllLiteralString(path.Base(spec.Path), "_") } pidfile := *options.String["pidfile"] var pidOut *os.File if pidfile == "-" { pidOut = os.Stdout } else if pidfile != "" { pidOut, err = os.Create(pidfile) options.FailIf(err, "Can't open pid file") } namefile := *options.String["namefile"] var nameOut *os.File if namefile == "-" { nameOut = os.Stdout } else if namefile != "" { nameOut, err = os.Create(namefile) options.FailIf(err, "Can't open name file") } daemon := *options.Bool["daemon"] disown := *options.Bool["disown"] var pr, pw *os.File proxying := false tty := isCtty(int(os.Stdin.Fd())) if daemon { // stdio is nil } else if disown { // We are assuming that if stdin is a terminal, it is our controlling // terminal. I don't know any way to verify it, but it seems likely. if tty { // stdin is nil, else they would steal input from tty } else { spec.Stdin = os.Stdin } spec.Stdout = os.Stdout spec.Stderr = os.Stderr } else { // interactive proxying = tty if proxying { pr, pw, err = os.Pipe() options.FailIf(err, "Can't pipe") spec.Stdin = pr } else { spec.Stdin = os.Stdin } spec.Stdout = os.Stdout spec.Stderr = os.Stderr fmt.Fprintf(noise, "[proxying stdin]\n") } spec.Dir, err = os.Getwd() options.FailIf(err, "Can't get working directory") // Start catching signals early, buffering a few, so we don't miss any. We // don't proxy SIGTTIN. However, we do catch it and stop ourselves, rather // than letting the OS stop us. This is necessary so that we can send // SIGCONT to the child at the right times. // Here is the easy case // we start in background // we fork (output starts going) // we are background, so leave SIGTTIN handling at the default // we read and get SIGTTIN, so are stopped // child is not stopped, it keeps outputting, as desired // upon fg, we get SIGCONT, start dropping SIGTTIN and looping for input and signals // Here is the tricky case: // we start in foreground // we fork (output starts going) // we are foreground, so catch and drop SIGTTIN (we use SIGTSTP instead) // we get SIGTSTP via ctrl-z // we send child SIGTSTP, so it stops // [we are still dropping SIGTTIN] // we send ourselves SIGSTOP, so we stop // we get SIGCONT via either bg or fg // [if bg, now furiously catching and dropping SIGTTIN] // [if fg, dropping too, but there should not be any SIGTTIN] // send child the SIGCONT // if we are foreground, so go back to top of loop // if we are background, reset SIGTTIN which causes us to stop // // The basic invariant we are trying to maintain is that when we are // foreground we catch and drop SIGTTIN, allowing us to properly handle // SIGTSTP events. There shouldn't be any SIGTTIN anyway, except for the // brief moments when we are transitioning to stopped. // And when the child is supposed to be running in the background, we should // leave the default SIGTTIN behavior, so that the OS will stop our read // loop. signals := make(chan os.Signal, 10) // some buffering signal.Notify(signals, syscall.SIGINT, // Ctrl-C syscall.SIGTERM, // SIGINT wannabe (e.g. via kill) syscall.SIGQUIT, // Ctrl-\ syscall.SIGTSTP, // Ctrl-Z syscall.SIGHUP, // tty hangup (e.g. via disown) syscall.SIGABRT, // abort (e.g. via kill) syscall.SIGUSR1, // user-defined (e.g. via kill) syscall.SIGUSR2, // user-defined (e.g. via kill) ) // Start the hosted program subprin, pid, err := client.StartHostedProgram(spec) options.FailIf(err, "Can't start hosted program") fmt.Fprintf(noise, "[started hosted program with pid %d]\n", pid) fmt.Fprintf(noise, "[subprin is %v]\n", subprin) if pidOut != nil { fmt.Fprintln(pidOut, pid) pidOut.Close() } if nameOut != nil { fmt.Fprintln(nameOut, subprin) nameOut.Close() } if disown || daemon { return } // Listen for exit status from host status := make(chan int, 1) go func() { s, _ := client.WaitHostedProgram(pid, subprin) // For short programs, we often lose the race, so // we get a "no such hosted program" error. Ignore it. status <- s }() wasForeground := false if proxying && isForeground() { fmt.Fprintf(noise, "[in foreground]\n") dropSIGTTIN() wasForeground = true } // Proxy stdin, if needed if proxying { pr.Close() go func() { _, err := io.Copy(pw, os.Stdin) options.WarnIf(err, "Can't copy from stdin to pipe") pw.Close() }() } // If we are proxying and (were) background, we should probably // have done a read() by now and gotten SIGTTIN and stopped. Let's // pause a moment to be sure the read() happens. time.Sleep(moment) // By this point, if we had been foreground, we might still be. Or, we might // have been foreground but just gotten SIGTSTP and are now madly dropping // SIGTTIN until we get into the loop below to handle the SIGTSTP. // // Alternatively, if we had been background, we would have been stopped by // the default SIGTTIN, so the only way we would be here is if we later got // pulled foreground via fg. We want to be dropping SIGTTIN in case we get a // SIGTSTP. if proxying && !wasForeground { dropSIGTTIN() } next := cont for next != done { select { case s := <-status: fmt.Fprintf(noise, "[hosted program exited, %s]\n", exitCode(s)) next = done case sig := <-signals: next = handle(sig, pid) } if next == resumed && proxying && !isForeground() { // Need to toggle SIGTTIN handling and block (that's the only way to // stop spinning on SIGTTIN), but only after handling all pending // signals (e.g. SIGCONT then SIGHUP, or SIGCONT then SIGTERM). for next == resumed { select { case s := <-status: fmt.Fprintf(noise, "[hosted program exited, %s]\n", exitCode(s)) next = done case sig := <-signals: next = handle(sig, pid) if next == cont { next = resumed } default: next = cont defaultSIGTTIN() time.Sleep(moment) dropSIGTTIN() } } } } signal.Stop(signals) }