windows/svc: make IsWindowsService handle parent exit
IsWindowsService fails when parent process has exited.
This change makes IsWindowsService return false instead.
Added a test to verify the change.
Change-Id: Ibb2aa9478e8afd9e35011bbdc0985bdf8f0af9cc
Reviewed-on: https://go-review.googlesource.com/c/sys/+/294729
TryBot-Result: Go Bot <gobot@golang.org>
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
Trust: Jason A. Donenfeld <Jason@zx2c4.com>
Trust: Alex Brainman <alex.brainman@gmail.com>
diff --git a/windows/svc/security.go b/windows/svc/security.go
index da6df1d..8cc6778 100644
--- a/windows/svc/security.go
+++ b/windows/svc/security.go
@@ -96,6 +96,13 @@
var psid uint32
err := windows.ProcessIdToSessionId(uint32(pbi[5]), &psid)
if err != nil {
+ if err == windows.ERROR_INVALID_PARAMETER {
+ // This error happens when Windows cannot find process parent.
+ // Perhaps process parent exited.
+ // Assume we are not running in a service, because service
+ // parent process (services.exe) cannot exit.
+ return false, nil
+ }
return false, err
}
if psid != 0 {
diff --git a/windows/svc/svc_test.go b/windows/svc/svc_test.go
index 5bf123d..a6c8bbc 100644
--- a/windows/svc/svc_test.go
+++ b/windows/svc/svc_test.go
@@ -171,3 +171,74 @@
t.Error("IsWindowsService retuns true when not running in a service.")
}
}
+
+func TestIsWindowsServiceWhenParentExits(t *testing.T) {
+ if os.Getenv("GO_WANT_HELPER_PROCESS") == "parent" {
+ // in parent process
+
+ // Start the child and exit quickly.
+ child := exec.Command(os.Args[0], "-test.run=TestIsWindowsServiceWhenParentExits")
+ child.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=child")
+ err := child.Start()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, fmt.Sprintf("child start failed: %v", err))
+ os.Exit(1)
+ }
+ os.Exit(0)
+ }
+
+ if os.Getenv("GO_WANT_HELPER_PROCESS") == "child" {
+ // in child process
+ dumpPath := os.Getenv("GO_WANT_HELPER_PROCESS_FILE")
+ if dumpPath == "" {
+ // We cannot report this error. But main test will notice
+ // that we did not create dump file.
+ os.Exit(1)
+ }
+ var msg string
+ isSvc, err := svc.IsWindowsService()
+ if err != nil {
+ msg = err.Error()
+ }
+ if isSvc {
+ msg = "IsWindowsService retuns true when not running in a service."
+ }
+ err = os.WriteFile(dumpPath, []byte(msg), 0644)
+ if err != nil {
+ // We cannot report this error. But main test will notice
+ // that we did not create dump file.
+ os.Exit(2)
+ }
+ os.Exit(0)
+ }
+
+ // Run in a loop until it fails.
+ for i := 0; i < 10; i++ {
+ childDumpPath := filepath.Join(t.TempDir(), "issvc.txt")
+
+ parent := exec.Command(os.Args[0], "-test.run=TestIsWindowsServiceWhenParentExits")
+ parent.Env = append(os.Environ(),
+ "GO_WANT_HELPER_PROCESS=parent",
+ "GO_WANT_HELPER_PROCESS_FILE="+childDumpPath)
+ parentOutput, err := parent.CombinedOutput()
+ if err != nil {
+ t.Errorf("parent failed: %v: %v", err, string(parentOutput))
+ }
+ for i := 0; ; i++ {
+ if _, err := os.Stat(childDumpPath); err == nil {
+ break
+ }
+ time.Sleep(100 * time.Millisecond)
+ if i > 10 {
+ t.Fatal("timed out waiting for child ouput file to be created.")
+ }
+ }
+ childOutput, err := ioutil.ReadFile(childDumpPath)
+ if err != nil {
+ t.Fatalf("reading child ouput failed: %v", err)
+ }
+ if got, want := string(childOutput), ""; got != want {
+ t.Fatalf("child output: want %q, got %q", want, got)
+ }
+ }
+}