cmd/buildlet: recreate accidentally deleted dirs lazily

The buildlet's /removeall handler previously restored any deletes of
important directories (the $WORKDIR, $GOCACHE, and $TMP) immediately
after the deletes, before the /removeall handler returned to the
client.

Unfortunately, some clients actually do want to delete $GOCACHE and
$TMP: in CL 159257 (submitted too early), cmd/release wants to delete
those before tarring/zipping up the rest of $WORKDIR to ship a
release.

This CL changes the remove handler to just remove stuff without fixing
mistakes. Instead, missing directories are created lazily later if
needed.

Also, minor debug logging change to cmd/release that I hadn't yet
uploaded to CL 159257 before it was submitted.

Fixes golang/go#29906

Change-Id: Ifb5d3c16e2d83e7ac9e7f8e353d1b7b866f7d338
Reviewed-on: https://go-review.googlesource.com/c/159320
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/buildlet/buildlet.go b/cmd/buildlet/buildlet.go
index f11437b..f57737a 100644
--- a/cmd/buildlet/buildlet.go
+++ b/cmd/buildlet/buildlet.go
@@ -524,6 +524,9 @@
 		http.Error(w, "requires GET method", http.StatusBadRequest)
 		return
 	}
+	if !mkdirAllWorkdir(w) {
+		return
+	}
 	dir := r.FormValue("dir")
 	if !validRelativeDir(dir) {
 		http.Error(w, "bogus dir", http.StatusBadRequest)
@@ -584,6 +587,9 @@
 }
 
 func handleWriteTGZ(w http.ResponseWriter, r *http.Request) {
+	if !mkdirAllWorkdir(w) {
+		return
+	}
 	urlParam, _ := url.ParseQuery(r.URL.RawQuery)
 	baseDir := *workDir
 	if dir := urlParam.Get("dir"); dir != "" {
@@ -841,6 +847,19 @@
 		http.Error(w, "HTTP/1.1 or higher required", http.StatusBadRequest)
 		return
 	}
+	// Create *workDir and (if needed) tmp and gocache.
+	if !mkdirAllWorkdir(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
+		}
+	}
 
 	w.Header().Set("Trailer", hdrProcessState) // declare it so we can set it
 
@@ -1229,16 +1248,18 @@
 			return
 		}
 	}
-	// If we nuked the work directory (or tmp or gocache), recreate them.
-	for _, dir := range []string{*workDir, processTmpDirEnv, processGoCacheEnv} {
-		if dir == "" {
-			continue
-		}
-		if err := os.MkdirAll(dir, 0755); err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
+}
+
+// mkdirAllWorkdir reports whether *workDir either exists or was created.
+// If it returns false, it also writes an HTTP 500 error to w.
+// This is used by callers to verify *workDir exists, even if it might've been
+// deleted previously.
+func mkdirAllWorkdir(w http.ResponseWriter) bool {
+	if err := os.MkdirAll(*workDir, 0755); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return false
 	}
+	return true
 }
 
 func handleWorkDir(w http.ResponseWriter, r *http.Request) {
@@ -1280,6 +1301,9 @@
 		http.Error(w, "bogus dir", http.StatusBadRequest)
 		return
 	}
+	if !mkdirAllWorkdir(w) {
+		return
+	}
 	base := filepath.Join(*workDir, filepath.FromSlash(dir))
 	anyOutput := false
 	err := filepath.Walk(base, func(path string, fi os.FileInfo, err error) error {
diff --git a/cmd/release/release.go b/cmd/release/release.go
index 9d69adc..8a30f2e 100644
--- a/cmd/release/release.go
+++ b/cmd/release/release.go
@@ -532,7 +532,7 @@
 	}
 
 	// And verify there's no other top-level stuff besides the "go" directory:
-	if err := checkTopLevelDirs(client); err != nil {
+	if err := b.checkTopLevelDirs(client); err != nil {
 		return fmt.Errorf("verifying no unwanted top-level directories: %v", err)
 	}
 
@@ -544,15 +544,15 @@
 
 // checkTopLevelDirs checks that all files under client's "."
 // ($WORKDIR) are are under "go/".
-func checkTopLevelDirs(client *buildlet.Client) error {
+func (b *Build) checkTopLevelDirs(client *buildlet.Client) error {
 	var badFileErr error // non-nil once an unexpected file/dir is found
 	if err := client.ListDir(".", buildlet.ListDirOpts{Recursive: true}, func(ent buildlet.DirEntry) {
-		if badFileErr != nil {
-			return
-		}
 		name := ent.Name()
 		if !(strings.HasPrefix(name, "go/") || strings.HasPrefix(name, `go\`)) {
-			badFileErr = fmt.Errorf("unexpected filename %q found after cleaning", name)
+			b.logf("unexpected file: %q", name)
+			if badFileErr == nil {
+				badFileErr = fmt.Errorf("unexpected filename %q found after cleaning", name)
+			}
 		}
 	}); err != nil {
 		return err