reviewed pid management
This commit is contained in:
+182
-83
@@ -3,123 +3,222 @@ package bus
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SockName = "control.sock"
|
const (
|
||||||
const PidName = "hyprvoice.pid"
|
SockName = "control.sock"
|
||||||
const ProtoVer = "0.1"
|
PidName = "hyprvoice.pid"
|
||||||
|
ProtoVer = "0.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pidManager struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPidManager() (*pidManager, error) {
|
||||||
|
pidPath, err := getPidPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get PID path: %w", err)
|
||||||
|
}
|
||||||
|
return &pidManager{path: pidPath}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pidManager) checkExisting() error {
|
||||||
|
log.Printf("Checking for existing daemon at: %s", pm.path)
|
||||||
|
|
||||||
|
pidData, err := os.ReadFile(pm.path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
log.Printf("No PID file found, daemon not running")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading PID file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Found PID file with content: %s", string(pidData))
|
||||||
|
|
||||||
|
pid, err := strconv.Atoi(string(pidData))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Invalid PID in file, removing stale PID file: %v", err)
|
||||||
|
pm.removeStaleFile()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if pm.isProcessAlive(pid) {
|
||||||
|
log.Printf("Process %d is alive, daemon already running", pid)
|
||||||
|
return fmt.Errorf("daemon already running with PID %d", pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Process %d not alive, removing stale PID file", pid)
|
||||||
|
pm.removeStaleFile()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pidManager) create() error {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(pm.path), 0o700); err != nil {
|
||||||
|
return fmt.Errorf("failed to create PID directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pid := os.Getpid()
|
||||||
|
log.Printf("Creating PID file at %s with PID %d", pm.path, pid)
|
||||||
|
|
||||||
|
err := os.WriteFile(pm.path, []byte(strconv.Itoa(pid)), 0o600)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write PID file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pidManager) remove() error {
|
||||||
|
log.Printf("Removing PID file: %s", pm.path)
|
||||||
|
if err := os.Remove(pm.path); err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("failed to remove PID file: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pidManager) isProcessAlive(pid int) bool {
|
||||||
|
log.Printf("Checking if process %d is alive", pid)
|
||||||
|
|
||||||
|
proc, err := os.FindProcess(pid)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Process %d not found: %v", pid, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
err = proc.Signal(syscall.Signal(0))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Process %d not alive (signal failed: %v)", pid, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pm *pidManager) removeStaleFile() {
|
||||||
|
if err := os.Remove(pm.path); err != nil && !os.IsNotExist(err) {
|
||||||
|
log.Printf("Warning: failed to remove stale PID file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type socketManager struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSocketManager() (*socketManager, error) {
|
||||||
|
sockPath, err := getSockPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get socket path: %w", err)
|
||||||
|
}
|
||||||
|
return &socketManager{path: sockPath}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *socketManager) listen() (net.Listener, error) {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(sm.path), 0o700); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create socket directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Remove(sm.path)
|
||||||
|
|
||||||
|
listener, err := net.Listen("unix", sm.path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to listen on socket %s: %w", sm.path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return listener, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *socketManager) dial() (net.Conn, error) {
|
||||||
|
conn, err := net.Dial("unix", sm.path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to dial socket %s: %w", sm.path, err)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSockPath() (string, error) {
|
||||||
|
dir, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(dir, "hyprvoice", SockName), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPidPath() (string, error) {
|
||||||
|
dir, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(dir, "hyprvoice", PidName), nil
|
||||||
|
}
|
||||||
|
|
||||||
// ~/.cache/hyprvoice/control.sock
|
|
||||||
func SockPath() (string, error) {
|
func SockPath() (string, error) {
|
||||||
dir, err := os.UserCacheDir()
|
return getSockPath()
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
hd := filepath.Join(dir, "hyprvoice")
|
|
||||||
return filepath.Join(hd, SockName), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ~/.cache/hyprvoice/hyprvoice.pid
|
|
||||||
func PidPath() (string, error) {
|
|
||||||
dir, err := os.UserCacheDir()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
hd := filepath.Join(dir, "hyprvoice")
|
|
||||||
return filepath.Join(hd, PidName), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Listen() (net.Listener, error) {
|
func Listen() (net.Listener, error) {
|
||||||
sp, err := SockPath()
|
sm, err := newSocketManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(filepath.Dir(sp), 0o700); err != nil {
|
return sm.listen()
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_ = os.Remove(sp) // stale socket from last run
|
|
||||||
return net.Listen("unix", sp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Dial() (net.Conn, error) {
|
func Dial() (net.Conn, error) {
|
||||||
sp, err := SockPath()
|
sm, err := newSocketManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return net.Dial("unix", sp)
|
return sm.dial()
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckExistingDaemon() error {
|
||||||
|
pm, err := newPidManager()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pm.checkExisting()
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreatePidFile() error {
|
||||||
|
pm, err := newPidManager()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pm.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemovePidFile() error {
|
||||||
|
pm, err := newPidManager()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pm.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendCommand(cmd byte) (string, error) {
|
func SendCommand(cmd byte) (string, error) {
|
||||||
c, err := Dial()
|
c, err := Dial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("failed to connect to daemon: %w", err)
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
_, err = c.Write([]byte{cmd, '\n'})
|
_, err = c.Write([]byte{cmd, '\n'})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("failed to send command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := bufio.NewReader(c).ReadString('\n')
|
resp, err := bufio.NewReader(c).ReadString('\n')
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckExistingDaemon() error {
|
|
||||||
pidPath, err := PidPath()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", fmt.Errorf("failed to read response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pidData, err := os.ReadFile(pidPath)
|
return resp, nil
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil // no existing daemon
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pid, err := strconv.Atoi(string(pidData))
|
|
||||||
if err != nil {
|
|
||||||
return nil // invalid pid file, assume stale
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if process exists
|
|
||||||
proc, err := os.FindProcess(pid)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to signal the process to check if it's alive
|
|
||||||
if err := proc.Signal(os.Signal(nil)); err != nil {
|
|
||||||
return nil // process not alive, stale pid file
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("daemon already running with PID %d", pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreatePidFile() error {
|
|
||||||
pidPath, err := PidPath()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Dir(pidPath), 0o700); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pid := os.Getpid()
|
|
||||||
return os.WriteFile(pidPath, []byte(strconv.Itoa(pid)), 0o600)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemovePidFile() error {
|
|
||||||
pidPath, err := PidPath()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Remove(pidPath)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user