From 306a29980a272891387d53ede50a2f83df7dcfda Mon Sep 17 00:00:00 2001
From: Michael Eischer <michael.eischer@fau.de>
Date: Sun, 23 Apr 2023 14:56:36 +0200
Subject: [PATCH] Print stacktrace in SIGINT handler if
 RESTIC_DEBUG_STACKTRACE_SIGINT set

The builtin mechanism to capture a stacktrace in Go is to send a SIGQUIT
to the running process. However, this mechanism is not avaiable on
Windows. Thus, tweak the SIGINT handler to dump a stacktrace if the
environment variable `RESTIC_DEBUG_STACKTRACE_SIGINT` is set.
---
 CONTRIBUTING.md              | 10 ++++++++++
 cmd/restic/cleanup.go        |  6 ++++++
 internal/debug/stacktrace.go | 15 +++++++++++++++
 3 files changed, 31 insertions(+)
 create mode 100644 internal/debug/stacktrace.go

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4b4be0757..36a7c0695 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -58,6 +58,16 @@ Please be aware that the debug log file will contain potentially sensitive
 things like file and directory names, so please either redact it before
 uploading it somewhere or post only the parts that are really relevant.
 
+If restic gets stuck, please also include a stacktrace in the description.
+On non-Windows systems, you can send a SIGQUIT signal to restic or press
+`Ctrl-\` to achieve the same result. This causes restic to print a stacktrace
+and then exit immediatelly. This will not damage your repository, however,
+it might be necessary to manually clean up stale lock files using
+`restic unlock`.
+
+On Windows, please set the environment variable `RESTIC_DEBUG_STACKTRACE_SIGINT`
+to `true` and press `Ctrl-C` to create a stacktrace.
+
 
 Development Environment
 =======================
diff --git a/cmd/restic/cleanup.go b/cmd/restic/cleanup.go
index 967957106..75933fe96 100644
--- a/cmd/restic/cleanup.go
+++ b/cmd/restic/cleanup.go
@@ -62,6 +62,12 @@ func CleanupHandler(c <-chan os.Signal) {
 		debug.Log("signal %v received, cleaning up", s)
 		Warnf("%ssignal %v received, cleaning up\n", clearLine(0), s)
 
+		if val, _ := os.LookupEnv("RESTIC_DEBUG_STACKTRACE_SIGINT"); val != "" {
+			_, _ = os.Stderr.WriteString("\n--- STACKTRACE START ---\n\n")
+			_, _ = os.Stderr.WriteString(debug.DumpStacktrace())
+			_, _ = os.Stderr.WriteString("\n--- STACKTRACE END ---\n")
+		}
+
 		code := 0
 
 		if s == syscall.SIGINT {
diff --git a/internal/debug/stacktrace.go b/internal/debug/stacktrace.go
new file mode 100644
index 000000000..a8db83160
--- /dev/null
+++ b/internal/debug/stacktrace.go
@@ -0,0 +1,15 @@
+package debug
+
+import "runtime"
+
+func DumpStacktrace() string {
+	buf := make([]byte, 128*1024)
+
+	for {
+		l := runtime.Stack(buf, true)
+		if l < len(buf) {
+			return string(buf[:l])
+		}
+		buf = make([]byte, len(buf)*2)
+	}
+}