Repository: syncthing/syncthing (language: go)

Issue:
panic with "WARNING: Error starting the main Syncthing process: exec: "syncthing.exe": cannot run executable found relative to current directory" when starting v1.21.0-rc.1 in command line in Windows
This happens if you try to start the current RC from the command line in Windows.

```
R:\syncthing-windows-amd64-v1.21.0-rc.1>syncthing.exe
[monitor] 2022/08/13 15:32:57 INFO: Log output saved to file "C:\Users\User\AppData\Local\Syncthing\syncthing.log"
[monitor] 2022/08/13 15:32:57 WARNING: Error starting the main Syncthing process: exec: "syncthing.exe": cannot run executable found relative to current directory
panic: Error starting the main Syncthing process

goroutine 1 [running]:
main.monitorMain({{{0x0}, {0x0, 0x0}, {0x0, 0x0}, 0x0, 0x0}, 0x0, 0x0, {0x0, ...}, ...})
        github.com/syncthing/syncthing/cmd/syncthing/monitor.go:124 +0x110f
main.serveOptions.Run({{{0x0}, {0x0, 0x0}, {0x0, 0x0}, 0x0, 0x0}, 0x0, 0x0, {0x0, ...}, ...})
        github.com/syncthing/syncthing/cmd/syncthing/main.go:404 +0x798
reflect.Value.call({0x10df7c0?, 0x1a8c660?, 0xf13515?}, {0x10ed8d2, 0x4}, {0xc0001dbb80, 0x0, 0x645948?})
        reflect/value.go:584 +0x8c5
reflect.Value.Call({0x10df7c0?, 0x1a8c660?, 0xfde580?}, {0xc0001dbb80?, 0x10?, 0x1a8caa0?})
        reflect/value.go:368 +0xbc
github.com/alecthomas/kong.callMethod({0x10ed2c7, 0x3}, {0x10df7c0?, 0x1a8c660?, 0x3?}, {0x10df7c0?, 0x1a8c660?, 0x0?}, 0x0?)
        github.com/alecthomas/kong@v0.6.1/callbacks.go:95 +0x549
github.com/alecthomas/kong.(*Context).RunNode(0xc000160d80, 0xc0001262d0, {0x0, 0x0, 0x0})
        github.com/alecthomas/kong@v0.6.1/context.go:734 +0x5f7
github.com/alecthomas/kong.(*Context).Run(0xf6efe0?, {0x0?, 0x0?, 0x0?})
        github.com/alecthomas/kong@v0.6.1/context.go:759 +0x14e
main.main()
        github.com/syncthing/syncthing/cmd/syncthing/main.go:255 +0x3a5

goroutine 6 [chan receive]:
github.com/syncthing/notify.(*recursiveTree).dispatch(0xc00007c7d0)
        github.com/syncthing/notify@v0.0.0-20210616190510-c6b7342338d2/tree_recursive.go:125 +0x48
created by github.com/syncthing/notify.newRecursiveTree
        github.com/syncthing/notify@v0.0.0-20210616190510-c6b7342338d2/tree_recursive.go:119 +0x165

goroutine 7 [sleep]:
time.Sleep(0x1dcd6500)
        runtime/time.go:195 +0x13c
github.com/syncthing/syncthing/lib/dialer.init.0.func2()
        github.com/syncthing/syncthing/lib/dialer/internal.go:43 +0x1e
created by github.com/syncthing/syncthing/lib/dialer.init.0
        github.com/syncthing/syncthing/lib/dialer/internal.go:42 +0xbe

goroutine 8 [select]:
main.(*autoclosedFile).closerLoop(0xc000247c00)
        github.com/syncthing/syncthing/cmd/syncthing/monitor.go:528 +0x71
created by main.newAutoclosedFile
        github.com/syncthing/syncthing/cmd/syncthing/monitor.go:468 +0x1e5

goroutine 9 [syscall]:
os/signal.signal_recv()
        runtime/sigqueue.go:152 +0x2f
os/signal.loop()
        os/signal/signal_unix.go:23 +0x19
created by os/signal.Notify.func1.1
        os/signal/signal.go:151 +0x2a
```

The workaround is to use `.\syncthing.exe`, but this wasn't the case before when compiled with Go 1.18. I personally think it is a big problem, because this requirement is very likely to break custom scripts, especially since using `.\` isn't common in batch scripts at all (as it's optional and thus usually omitted).


Discussion / hints:
Isn't this a new restriction introduced in go 1.19?

https://tip.golang.org/doc/go1.19#os-exec-path
Probably, because I've just recompiled with Go 1.18, and the issue is gone.

Is this restriction absolutely necessary though? Would it be a problem to disable it? It breaks the established workflow, as no command line Windows software behaves like that (or at least I've never encountered anything similar until now). The CMD itself doesn't even add `.\` to paths when using TAB autocomplete (in contrary to PowerShell, which does).

**Edit**:

I also want to add that this crashes Syncthing on upgrade from an older version.

1. Run v1.20.4 from the command line, e.g. `syncthing.exe -home=test`
2. Enable upgrade to release candidates, restart, and press the upgrade button in the GUI.
3. The upgraded executable fails to start with the same panic as described above.
That's because of our monitor stuff using `exec` with `arg[0]` which is `syncthing`, and thus being affected by that behaviour change. I'll take care of it.


Fix patch:
diff --git a/cmd/syncthing/monitor.go b/cmd/syncthing/monitor.go
index d15a449efc0..fd76966a68b 100644
--- a/cmd/syncthing/monitor.go
+++ b/cmd/syncthing/monitor.go
@@ -83,6 +83,15 @@ func monitorMain(options serveOptions) {
 	}
 
 	args := os.Args
+	binary := args[0]
+	if build.IsWindows {
+		var err error
+		binary, err = expandExecutableInCurrentDirectory(binary)
+		if err != nil {
+			l.Warnln("Error starting the main Syncthing process:", err)
+			panic("Error starting the main Syncthing process")
+		}
+	}
 	var restarts [restartCounts]time.Time
 
 	stopSign := make(chan os.Signal, 1)
@@ -104,7 +113,7 @@ func monitorMain(options serveOptions) {
 		copy(restarts[0:], restarts[1:])
 		restarts[len(restarts)-1] = time.Now()
 
-		cmd := exec.Command(args[0], args[1:]...)
+		cmd := exec.Command(binary, args[1:]...)
 		cmd.Env = childEnv
 
 		stderr, err := cmd.StderrPipe()
@@ -180,7 +189,7 @@ func monitorMain(options serveOptions) {
 				// Restart the monitor process to release the .old
 				// binary as part of the upgrade process.
 				l.Infoln("Restarting monitor...")
-				if err = restartMonitor(args); err != nil {
+				if err = restartMonitor(binary, args); err != nil {
 					l.Warnln("Restart:", err)
 				}
 				os.Exit(exitCode)
@@ -203,6 +212,21 @@ func monitorMain(options serveOptions) {
 	}
 }
 
+func expandExecutableInCurrentDirectory(args0 string) (string, error) {
+	// Works around a restriction added in go1.19 that executables in the
+	// current directory are not resolved when specifying just an executable
+	// name (like e.g. "syncthing")
+	if !strings.ContainsRune(args0, os.PathSeparator) {
+		// Check if it's in PATH
+		_, err := exec.LookPath(args0)
+		if err != nil {
+			// Try to get the path to the current executable
+			return os.Executable()
+		}
+	}
+	return args0, nil
+}
+
 func copyStderr(stderr io.Reader, dst io.Writer) {
 	br := bufio.NewReader(stderr)
 
@@ -309,7 +333,7 @@ func copyStdout(stdout io.Reader, dst io.Writer) {
 	}
 }
 
-func restartMonitor(args []string) error {
+func restartMonitor(binary string, args []string) error {
 	// Set the STRESTART environment variable to indicate to the next
 	// process that this is a restart and not initial start. This prevents
 	// opening the browser on startup.
@@ -319,19 +343,20 @@ func restartMonitor(args []string) error {
 		// syscall.Exec is the cleanest way to restart on Unixes as it
 		// replaces the current process with the new one, keeping the pid and
 		// controlling terminal and so on
-		return restartMonitorUnix(args)
+		return restartMonitorUnix(binary, args)
 	}
 
 	// but it isn't supported on Windows, so there we start a normal
 	// exec.Command and return.
-	return restartMonitorWindows(args)
+	return restartMonitorWindows(binary, args)
 }
 
-func restartMonitorUnix(args []string) error {
-	if !strings.ContainsRune(args[0], os.PathSeparator) {
+func restartMonitorUnix(binary string, args []string) error {
+	if !strings.ContainsRune(binary, os.PathSeparator) {
 		// The path to the binary doesn't contain a slash, so it should be
 		// found in $PATH.
-		binary, err := exec.LookPath(args[0])
+		var err error
+		binary, err = exec.LookPath(binary)
 		if err != nil {
 			return err
 		}
@@ -341,8 +366,8 @@ func restartMonitorUnix(args []string) error {
 	return syscall.Exec(args[0], args, os.Environ())
 }
 
-func restartMonitorWindows(args []string) error {
-	cmd := exec.Command(args[0], args[1:]...)
+func restartMonitorWindows(binary string, args []string) error {
+	cmd := exec.Command(binary, args[1:]...)
 	// Retain the standard streams
 	cmd.Stderr = os.Stderr
 	cmd.Stdout = os.Stdout


Test patch: