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
36 changes: 36 additions & 0 deletions cmd/relayfile-cli/background_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ package main
import (
"bytes"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"reflect"
"slices"
"strconv"
"strings"
"syscall"
Expand Down Expand Up @@ -841,3 +843,37 @@ func TestA14LogsCommandTailsBackgroundLog(t *testing.T) {
}
}
}

// TestBackgroundListenChildArgsOnlyUseRegisteredFlags pins the contract that
// broke `relayfile listen --background` entirely: the parent appended
// `--pid-file`, which `runListen` never registered, so the child died at flag
// parsing while the parent reported "Listen started in background".
//
// `runListen` parses with flag.ContinueOnError and a discarded output sink, so
// an unknown flag produces no diagnostic anywhere the user looks. Asserting on
// the parse outcome — rather than on a hardcoded argv — keeps any future flag
// added to the child honest.
func TestBackgroundListenChildArgsOnlyUseRegisteredFlags(t *testing.T) {
t.Setenv("HOME", t.TempDir())
clearRelayfileEnv(t)

args := backgroundListenChildArgs([]string{"--provider", "linear", "--background"})

if slices.Contains(args, "--background") {
t.Fatalf("child argv still carries --background: %v", args)
}
if !slices.Contains(args, "--daemonized") {
t.Fatalf("child argv is missing --daemonized: %v", args)
}

// runListen fails on credentials in a bare HOME, which is fine: the point
// is that it gets past flag parsing at all. Before the fix it returned
// "flag provided but not defined: -pid-file" and never reached the work.
err := runListen(args, io.Discard)
if err == nil {
return
}
if strings.Contains(err.Error(), "flag provided but not defined") {
t.Fatalf("child argv %v carries a flag runListen does not register: %v", args, err)
}
}
43 changes: 25 additions & 18 deletions cmd/relayfile-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8652,9 +8652,7 @@ func runListen(args []string, stdout io.Writer) error {
}

if *background && !*daemonized {
logFile := listenLogFile()
pidFile := listenPIDFile()
return spawnBackgroundListenProcess(args, pidFile, logFile)
return spawnBackgroundListenProcess(args, listenLogFile())
}
if *daemonized {
if err := rotateLogFile(listenLogFile()); err != nil {
Expand Down Expand Up @@ -8881,32 +8879,41 @@ func runListenSession(rootCtx context.Context, cfg listenSessionConfig) (bool, e
}
}

func listenPIDFile() string {
return filepath.Join(configDir(), "listen.pid")
}

func listenLogFile() string {
return filepath.Join(configDir(), "listen.log")
}

func spawnBackgroundListenProcess(originalArgs []string, pidFile, logFile string) error {
if err := rotateLogFile(logFile); err != nil {
return err
}
executable, err := os.Executable()
if err != nil {
return err
}
filtered := make([]string, 0, len(originalArgs))
// backgroundListenChildArgs builds the argv `listen --background` re-invokes
// itself with: the caller's own flags minus `--background`, plus the internal
// `--daemonized` marker.
//
// Every flag here must be one `runListen` registers. Its flagset is
// ContinueOnError with output discarded, so an unrecognised flag makes the
// child exit immediately with nothing on a terminal, while the parent has
// already printed "Listen started in background". `--pid-file` was passed for
// exactly that reason and broke every background listen; nothing read the
// listen pid file, so it was dropped rather than registered.
func backgroundListenChildArgs(originalArgs []string) []string {
filtered := make([]string, 0, len(originalArgs)+1)
for _, arg := range originalArgs {
if arg == "--background" || arg == "-background" ||
strings.HasPrefix(arg, "--background=") || strings.HasPrefix(arg, "-background=") {
continue
}
filtered = append(filtered, arg)
}
childArgs := append([]string{"listen"}, filtered...)
childArgs = append(childArgs, "--daemonized", "--pid-file", pidFile)
return append(filtered, "--daemonized")
}

func spawnBackgroundListenProcess(originalArgs []string, logFile string) error {
if err := rotateLogFile(logFile); err != nil {
return err
}
executable, err := os.Executable()
if err != nil {
return err
}
childArgs := append([]string{"listen"}, backgroundListenChildArgs(originalArgs)...)
logHandle, err := os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
if err != nil {
return err
Expand Down
Loading