package util
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
)
func WritePIDFile(pidFile string) error {
if pidFile == "" {
return nil
}
if err := os.MkdirAll(filepath.Dir(pidFile), 0o755); err != nil {
return fmt.Errorf("failed to create pid file directory: %w", err)
}
if existingPID, err := readPIDFile(pidFile); err == nil {
running, runErr := isProcessRunning(existingPID)
if runErr != nil {
return fmt.Errorf("unexpected error from checking existing pid %d: %w", existingPID, runErr)
}
if running {
return fmt.Errorf("pid file %s already exists and process %d is still running", pidFile, existingPID)
}
if err := os.Remove(pidFile); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove stale pid file: %w", err)
}
} else if !errors.Is(err, os.ErrNotExist) {
return err
}
pid := strconv.Itoa(os.Getpid()) + "\n"
if err := os.WriteFile(pidFile, []byte(pid), 0o644); err != nil {
return fmt.Errorf("failed to write pid file: %w", err)
}
return nil
}
func RemovePIDFile(pidFile string) {
if pidFile == "" {
return
}
_ = os.Remove(pidFile)
}
func readPIDFile(pidFile string) (int, error) {
data, err := os.ReadFile(pidFile)
if err != nil {
if os.IsNotExist(err) {
return 0, os.ErrNotExist
}
return 0, fmt.Errorf("failed to read pid file: %w", err)
}
pid, err := strconv.Atoi(strings.TrimSpace(string(data)))
if err != nil {
return 0, fmt.Errorf("failed to parse pid file %s: %w", pidFile, err)
}
return pid, nil
}
func isProcessRunning(pid int) (bool, error) {
err := syscall.Kill(pid, 0)
if err == nil || errors.Is(err, syscall.EPERM) {
return true, nil
}
if errors.Is(err, syscall.ESRCH) {
return false, nil
}
return false, err
}