cmd/buildlet: set up & clean TMPDIR and GOCACHE for child processes

Also, remove use of envutil to de-dup environment variables. Go has
done that automatically since Go 1.9.

Fixes golang/go#27182
Fixes golang/go#28041

Change-Id: I8e81e7996b5cee305465814aeb1b14df80b799dd
Reviewed-on: https://go-review.googlesource.com/c/144637
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/buildlet/buildlet.go b/cmd/buildlet/buildlet.go
index 6b33546..45b36ad 100644
--- a/cmd/buildlet/buildlet.go
+++ b/cmd/buildlet/buildlet.go
@@ -43,7 +43,6 @@
 
 	"cloud.google.com/go/compute/metadata"
 	"golang.org/x/build/buildlet"
-	"golang.org/x/build/envutil"
 	"golang.org/x/build/internal/httpdl"
 	"golang.org/x/build/pargzip"
 )
@@ -73,7 +72,8 @@
 //   15: ssh support
 //   16: make macstadium builders always haltEntireOS
 //   17: make macstadium halts use sudo
-const buildletVersion = 17
+//   18: set TMPDIR and GOCACHE
+const buildletVersion = 18
 
 func defaultListenAddr() string {
 	if runtime.GOOS == "darwin" {
@@ -100,6 +100,13 @@
 	setOSRlimit              func() error
 )
 
+// If non-empty, the $TMPDIR and $GOCACHE environment variables to use
+// for child processes.
+var (
+	processTmpDirEnv  string
+	processGoCacheEnv string
+)
+
 func main() {
 	switch os.Getenv("GO_BUILDER_ENV") {
 	case "macstadium_vm":
@@ -133,8 +140,8 @@
 		*rebootOnHalt = true
 	}
 
-	// Optimize emphemeral filesystems. Prefer speed over safety, since these machines
-	// will be gone soon.
+	// Optimize emphemeral filesystems. Prefer speed over safety,
+	// since these VMs only last for the duration of one build.
 	switch runtime.GOOS {
 	case "openbsd", "freebsd", "netbsd":
 		makeBSDFilesystemFast()
@@ -197,6 +204,15 @@
 	if _, err := os.Lstat(*workDir); err != nil {
 		log.Fatalf("invalid --workdir %q: %v", *workDir, err)
 	}
+
+	// Set up and clean $TMPDIR and $GOCACHE directories.
+	if runtime.GOOS != "windows" && runtime.GOOS != "plan9" {
+		processTmpDirEnv = filepath.Join(*workDir, "tmp")
+		processGoCacheEnv = filepath.Join(*workDir, "gocache")
+		removeAllAndMkdir(processTmpDirEnv)
+		removeAllAndMkdir(processGoCacheEnv)
+	}
+
 	initGorootBootstrap()
 
 	http.HandleFunc("/", handleRoot)
@@ -872,29 +888,17 @@
 
 	env := append(baseEnv(goarch), r.PostForm["env"]...)
 
-	// Set TMPDIR in the child process and clean up after it.
-	// Do this at least for Solaris (golang.org/issue/22798)
-	// because Solaris reuses its disk per run (for now). The other builders
-	// generally run in their own containers/VMs and thus don't leak.
-	// Ideally Solaris would do the same and we wouldn't need this.
-	if builder := getEnv(env, "GO_BUILDER_NAME"); builder == "solaris-amd64-smartosbuildlet" {
-		childTmp, err := ioutil.TempDir("", "buildlet-exec")
-		if err != nil {
-			// Not critical. Not worth dying over. (at least for now)
-			log.Printf("failed to create a temp directory: %v", err)
-		} else {
-			env = append(env, "TMPDIR="+childTmp)
-			defer os.RemoveAll(childTmp)
-		}
+	if v := processTmpDirEnv; v != "" {
+		env = append(env, "TMPDIR="+v)
 	}
-
-	env = envutil.Dedup(runtime.GOOS == "windows", env)
+	if v := processGoCacheEnv; v != "" {
+		env = append(env, "GOCACHE="+v)
+	}
 
 	// Prefer buildlet process's inherited GOROOT_BOOTSTRAP if
 	// there was one and the one we're about to use doesn't exist.
 	if v := getEnv(env, "GOROOT_BOOTSTRAP"); v != "" && inheritedGorootBootstrap != "" && pathNotExist(v) {
-		env = envutil.Dedup(runtime.GOOS == "windows", append(env,
-			"GOROOT_BOOTSTRAP="+inheritedGorootBootstrap))
+		env = append(env, "GOROOT_BOOTSTRAP="+inheritedGorootBootstrap)
 	}
 	env = setPathEnv(env, r.PostForm["path"], *workDir)
 
@@ -1133,7 +1137,6 @@
 			log.Printf("Error running reboot: %v", err)
 		}
 		os.Exit(0)
-
 	}
 	if !*haltEntireOS {
 		log.Printf("Ending buildlet process due to halt.")
@@ -1751,3 +1754,14 @@
 		os.Setenv("HOME", "/root")
 	}
 }
+
+// removeAllAndMkdir calls os.RemoveAll and then os.Mkdir on the given
+// dir, failing the process if either step fails.
+func removeAllAndMkdir(dir string) {
+	if err := os.RemoveAll(dir); err != nil {
+		log.Fatal(err)
+	}
+	if err := os.Mkdir(dir, 0755); err != nil {
+		log.Fatal(err)
+	}
+}