Beispiel #1
0
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)
}
Beispiel #2
0
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)
}