cmd/buildlet: handle shutdown on Windows reverse buildlets

This change makes room for a race between the main function exiting, and
the delay built into our halting process when shutting down a reverse
dialer.

Ideally, there would be no race, but plumbing the shutdown signal
through is a separate significant effort.

For golang/go#42604

Change-Id: I0762cb71eac50bcda7f0cd571e04396e98bff699
Reviewed-on: https://go-review.googlesource.com/c/build/+/331669
Trust: Alexander Rakoczy <alex@golang.org>
Run-TryBot: Alexander Rakoczy <alex@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/buildlet/buildlet.go b/cmd/buildlet/buildlet.go
index 824a453..aa991f2 100644
--- a/cmd/buildlet/buildlet.go
+++ b/cmd/buildlet/buildlet.go
@@ -102,7 +102,6 @@
 
 // Functionality set non-nil by some platforms:
 var (
-	osHalt                   func()
 	configureSerialLogOutput func()
 	setOSRlimit              func() error
 )
@@ -251,6 +250,11 @@
 			log.Fatalf("Error dialing coordinator: %v", err)
 		}
 		log.Printf("buildlet reverse mode exiting.")
+		if *haltEntireOS {
+			// The coordinator disconnects before doHalt has time to
+			// execute. handleHalt has a 1s delay.
+			time.Sleep(5 * time.Second)
+		}
 		os.Exit(0)
 	}
 }
@@ -1194,29 +1198,23 @@
 	// requests from doing anything from this point on in the
 	// remaining second.
 	log.Printf("Halting in 1 second.")
-	time.AfterFunc(1*time.Second, doHalt)
-}
-
-func doHalt() {
-	if *rebootOnHalt {
-		if err := exec.Command("reboot").Run(); err != nil {
-			log.Printf("Error running reboot: %v", err)
+	time.AfterFunc(1*time.Second, func() {
+		if *rebootOnHalt {
+			doReboot()
 		}
-		os.Exit(0)
-	}
-	if !*haltEntireOS {
+		if *haltEntireOS {
+			doHalt()
+		}
 		log.Printf("Ending buildlet process due to halt.")
 		os.Exit(0)
 		return
-	}
+	})
+}
+
+func doHalt() {
 	log.Printf("Halting machine.")
-	time.AfterFunc(5*time.Second, func() { os.Exit(0) })
-	if osHalt != nil {
-		// TODO: Windows: http://msdn.microsoft.com/en-us/library/windows/desktop/aa376868%28v=vs.85%29.aspx
-		osHalt()
-		os.Exit(0)
-	}
 	// Backup mechanism, if exec hangs for any reason:
+	time.AfterFunc(5*time.Second, func() { os.Exit(0) })
 	var err error
 	switch runtime.GOOS {
 	case "openbsd":
@@ -1238,9 +1236,9 @@
 			err = errors.New("not respecting -halt flag on macOS in unknown environment")
 		}
 	case "windows":
-		err = errors.New("not respsecting -halt flag on windows in unknown environment")
+		err = errors.New("not respecting -halt flag on Windows in unknown environment")
 		if runtime.GOARCH == "arm64" {
-			err = exec.Command("shutdown /s").Run()
+			err = exec.Command("shutdown", "/s").Run()
 		}
 	default:
 		err = errors.New("no system-specific halt command run; will just end buildlet process")
@@ -1250,6 +1248,20 @@
 	os.Exit(0)
 }
 
+func doReboot() {
+	log.Printf("Rebooting machine.")
+	var err error
+	switch runtime.GOOS {
+	case "windows":
+		err = exec.Command("shutdown", "/r").Run()
+	default:
+		err = exec.Command("reboot").Run()
+	}
+	log.Printf("Reboot: %v", err)
+	log.Printf("Ending buildlet process post-halt")
+	os.Exit(0)
+}
+
 func handleRemoveAll(w http.ResponseWriter, r *http.Request) {
 	if r.Method != "POST" {
 		http.Error(w, "requires POST method", http.StatusBadRequest)
diff --git a/cmd/buildlet/stage0/stage0.go b/cmd/buildlet/stage0/stage0.go
index 8bb26d9..00d6f33 100644
--- a/cmd/buildlet/stage0/stage0.go
+++ b/cmd/buildlet/stage0/stage0.go
@@ -200,6 +200,15 @@
 	case "solaris/amd64", "illumos/amd64":
 		hostType := buildEnv
 		cmd.Args = append(cmd.Args, reverseHostTypeArgs(hostType)...)
+	case "windows/arm64":
+		switch buildEnv {
+		case "host-windows-arm64-mini":
+			cmd.Args = append(cmd.Args,
+				"--halt=true",
+				"--reverse-type="+buildEnv,
+				"--coordinator=farmer.golang.org:443",
+			)
+		}
 	}
 	// Release the serial port (if we opened it) so the buildlet
 	// process can open & write to it. At least on Windows, only