Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/buggregator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func main() {
// The module is optional: without a dir it stays off, the rest runs.
traceDir := filepath.Join(cfg.Storage.Path, "xdebug-traces")
if cfg.Storage.Mode != "filesystem" {
tmp, err := os.MkdirTemp("", "buggregator-xdebug-traces-")
tmp, err := xdebugtrace.TempDir("")
if err != nil {
slog.Error("xdebug-trace: cannot create temp dir; module disabled", "err", err)
traceDir = ""
Expand Down
15 changes: 15 additions & 0 deletions modules/xdebugtrace/process_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build !windows

package xdebugtrace

import (
"errors"
"syscall"
)

// processAlive reports whether a process with pid exists (EPERM: it does,
// owned by another user).
func processAlive(pid int) bool {
err := syscall.Kill(pid, 0)
return err == nil || errors.Is(err, syscall.EPERM)
}
21 changes: 21 additions & 0 deletions modules/xdebugtrace/process_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build windows

package xdebugtrace

import (
"errors"
"os"
"syscall"
)

// processAlive reports whether a process with pid exists: FindProcess
// opens a handle on Windows and fails for a pid that is gone. Access
// denied (an elevated process seen from a plain one) means it exists.
func processAlive(pid int) bool {
p, err := os.FindProcess(pid)
if err != nil {
return errors.Is(err, syscall.ERROR_ACCESS_DENIED)
}
p.Release()
return true
}
45 changes: 45 additions & 0 deletions modules/xdebugtrace/tempdir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package xdebugtrace

import (
"log/slog"
"os"
"path/filepath"
"strconv"
"strings"
)

const tempPrefix = "buggregator-xdebug-traces-"

// TempDir makes the trace dir for memory storage mode: <base>/
// buggregator-xdebug-traces-<pid>. Dirs of processes that are gone (a
// crash, kill -9 skips the cleanup at exit) are removed first, so stale
// traces never pile up. base "" = os.TempDir().
func TempDir(base string) (string, error) {
if base == "" {
base = os.TempDir()
}
self := os.Getpid()
if entries, err := os.ReadDir(base); err == nil {
for _, e := range entries {
name := e.Name()
if !e.IsDir() || !strings.HasPrefix(name, tempPrefix) {
continue
}
pid, err := strconv.Atoi(strings.TrimPrefix(name, tempPrefix))
if err != nil || pid <= 0 || (pid != self && processAlive(pid)) {
continue
}
// Our own pid here is a leftover of an earlier process. Two
// containers sharing one /tmp volume are both pid 1: give each
// its own TMPDIR.
if err := os.RemoveAll(filepath.Join(base, name)); err == nil {
slog.Info("xdebug-trace: removed stale temp dir", "dir", name)
}
}
}
dir := filepath.Join(base, tempPrefix+strconv.Itoa(self))
if err := os.Mkdir(dir, 0o700); err != nil {
return "", err
}
return dir, nil
}
60 changes: 60 additions & 0 deletions modules/xdebugtrace/tempdir_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package xdebugtrace

import (
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"testing"
)

func TestTempDir_RemovesDeadProcessDirs(t *testing.T) {
base := t.TempDir()

// A pid that is surely gone: a finished child.
cmd := exec.Command(os.Args[0], "-test.run=^$")
if err := cmd.Run(); err != nil {
t.Fatal(err)
}
dead := cmd.Process.Pid
alive := os.Getppid() // the go test runner

mk := func(name string) {
if err := os.MkdirAll(filepath.Join(base, name, "sub"), 0o755); err != nil {
t.Fatal(err)
}
}
mk(tempPrefix + strconv.Itoa(dead))
mk(tempPrefix + strconv.Itoa(alive))
mk(tempPrefix + strconv.Itoa(os.Getpid())) // own leftover
mk(tempPrefix + "abc")
mk("other-dir")

dir, err := TempDir(base)
if err != nil {
t.Fatal(err)
}
if dir != filepath.Join(base, tempPrefix+strconv.Itoa(os.Getpid())) {
t.Errorf("dir = %s", dir)
}
if _, err := os.Stat(filepath.Join(dir, "sub")); !os.IsNotExist(err) {
t.Error("own leftover dir not emptied")
}
entries, _ := os.ReadDir(base)
var got []string
for _, e := range entries {
got = append(got, e.Name())
}
sort.Strings(got)
want := []string{tempPrefix + "abc", tempPrefix + strconv.Itoa(alive), tempPrefix + strconv.Itoa(os.Getpid()), "other-dir"}
sort.Strings(want)
if len(got) != len(want) {
t.Fatalf("left %v, want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("left %v, want %v", got, want)
}
}
}
Loading