in cmd/buildlet/buildlet.go [847:993]
func handleExec(w http.ResponseWriter, r *http.Request) {
cn := w.(http.CloseNotifier)
clientGone := cn.CloseNotify()
handlerDone := make(chan bool)
defer close(handlerDone)
if r.Method != "POST" {
http.Error(w, "requires POST method", http.StatusBadRequest)
return
}
if r.ProtoMajor*10+r.ProtoMinor < 11 {
// We need trailers, only available in HTTP/1.1 or HTTP/2.
http.Error(w, "HTTP/1.1 or higher required", http.StatusBadRequest)
return
}
// Create *workDir and (if needed) tmp and gocache.
if !mkdirAllWorkdirOr500(w) {
return
}
for _, dir := range []string{processTmpDirEnv, processGoCacheEnv} {
if dir == "" {
continue
}
if err := os.MkdirAll(dir, 0755); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
if err := checkAndroidEmulator(); err != nil {
http.Error(w, "android emulator not running: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Trailer", hdrProcessState) // declare it so we can set it
cmdPath := r.FormValue("cmd") // required
absCmd := cmdPath
dir := r.FormValue("dir") // optional
sysMode := r.FormValue("mode") == "sys"
debug, _ := strconv.ParseBool(r.FormValue("debug"))
if sysMode {
if cmdPath == "" {
http.Error(w, "requires 'cmd' parameter", http.StatusBadRequest)
return
}
if dir == "" {
dir = *workDir
} else {
dir = filepath.FromSlash(dir)
if !filepath.IsAbs(dir) {
dir = filepath.Join(*workDir, dir)
}
}
} else {
if !validRelPath(cmdPath) {
http.Error(w, "requires 'cmd' parameter", http.StatusBadRequest)
return
}
absCmd = filepath.Join(*workDir, filepath.FromSlash(cmdPath))
if dir == "" {
dir = filepath.Dir(absCmd)
} else {
if !validRelPath(dir) {
http.Error(w, "bogus 'dir' parameter", http.StatusBadRequest)
return
}
dir = filepath.Join(*workDir, filepath.FromSlash(dir))
}
}
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
postEnv := r.PostForm["env"]
goarch := "amd64" // unless we find otherwise
if v := envutil.Get(runtime.GOOS, postEnv, "GOARCH"); v != "" {
goarch = v
}
if v, _ := strconv.ParseBool(envutil.Get(runtime.GOOS, postEnv, "GO_DISABLE_OUTBOUND_NETWORK")); v {
disableOutboundNetwork()
}
env := append(baseEnv(goarch), postEnv...)
if v := processTmpDirEnv; v != "" {
env = append(env, "TMPDIR="+v)
}
if v := processGoCacheEnv; v != "" {
env = append(env, "GOCACHE="+v)
}
if path := r.PostForm["path"]; len(path) > 0 {
if kv, ok := pathEnv(runtime.GOOS, env, path, *workDir); ok {
env = append(env, kv)
}
}
env = envutil.Dedup(runtime.GOOS, env)
var cmd *exec.Cmd
if needsBashWrapper(absCmd) {
cmd = exec.Command("bash", absCmd)
} else {
cmd = exec.Command(absCmd)
}
cmd.Args = append(cmd.Args, r.PostForm["cmdArg"]...)
cmd.Env = env
envutil.SetDir(cmd, dir)
cmdOutput := flushWriter{w}
cmd.Stdout = cmdOutput
cmd.Stderr = cmdOutput
log.Printf("[%p] Running %s with args %q and env %q in dir %s",
cmd, cmd.Path, cmd.Args, cmd.Env, cmd.Dir)
if debug {
fmt.Fprintf(cmdOutput, ":: Running %s with args %q and env %q in dir %s\n\n",
cmd.Path, cmd.Args, cmd.Env, cmd.Dir)
}
t0 := time.Now()
err := cmd.Start()
if err == nil {
go func() {
select {
case <-clientGone:
err := killProcessTree(cmd.Process)
if err != nil {
log.Printf("Kill failed: %v", err)
}
case <-handlerDone:
return
}
}()
err = cmd.Wait()
}
state := "ok"
if err != nil {
if ps := cmd.ProcessState; ps != nil {
state = ps.String()
} else {
state = err.Error()
}
}
w.Header().Set(hdrProcessState, state)
log.Printf("[%p] Run = %s, after %v", cmd, state, time.Since(t0))
}