v0.7.0 cli client with readline added, directory structure updated

This commit is contained in:
2025-11-13 08:55:06 -05:00
parent 52868af4ea
commit 6bdc061508
52 changed files with 2260 additions and 157 deletions

106
cmd/chess-server/pid.go Normal file
View File

@ -0,0 +1,106 @@
// FILE: lixenwraith/chess/cmd/chess-server/pid.go
package main
import (
"errors"
"fmt"
"os"
"strconv"
"strings"
"syscall"
)
// managePIDFile creates and manages a PID file with optional locking
// Returns a cleanup function that must be called on exit
func managePIDFile(path string, lock bool) (func(), error) {
// Open/create PID file with exclusive create first attempt
file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
if !os.IsExist(err) {
return nil, fmt.Errorf("cannot create PID file: %w", err)
}
// File exists - check if stale
if lock {
if err := checkStalePID(path); err != nil {
return nil, err
}
}
// Reopen for writing (truncate existing content)
file, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return nil, fmt.Errorf("cannot open PID file: %w", err)
}
}
// Acquire exclusive lock if requested
if lock {
if err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
file.Close()
if errors.Is(err, syscall.EWOULDBLOCK) {
return nil, fmt.Errorf("cannot acquire lock: another instance is running")
}
return nil, fmt.Errorf("lock failed: %w", err)
}
}
// Write current PID
pid := os.Getpid()
if _, err = fmt.Fprintf(file, "%d\n", pid); err != nil {
file.Close()
os.Remove(path)
return nil, fmt.Errorf("cannot write PID: %w", err)
}
// Sync to ensure PID is written
if err = file.Sync(); err != nil {
file.Close()
os.Remove(path)
return nil, fmt.Errorf("cannot sync PID file: %w", err)
}
// Return cleanup function
cleanup := func() {
if lock {
// Release lock explicitly, file close works too
syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
}
file.Close()
os.Remove(path)
}
return cleanup, nil
}
// checkStalePID reads an existing PID file and checks if the process is running
func checkStalePID(path string) error {
// Try to read existing PID
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("cannot read existing PID file: %w", err)
}
pidStr := string(data)
pid, err := strconv.Atoi(strings.TrimSpace(pidStr))
if err != nil {
// Corrupted PID file
return fmt.Errorf("corrupted PID file (contains: %q)", pidStr)
}
// Check if process exists using kill(0), never errors on Unix
proc, _ := os.FindProcess(pid)
// Send signal 0 to check if process exists
if err = proc.Signal(syscall.Signal(0)); err != nil {
// Process doesn't exist or we don't have permission
if errors.Is(err, os.ErrProcessDone) || errors.Is(err, syscall.ESRCH) {
return fmt.Errorf("stale PID file found for defunct process %d", pid)
}
// Process exists but we can't signal it (different user?)
return fmt.Errorf("process %d exists but cannot verify ownership: %v", pid, err)
}
// Process is running
return fmt.Errorf("stale PID file: process %d is running but not holding lock", pid)
}