package daemon import ( "fmt" "os" "path/filepath" "strconv" "strings" "syscall" ) // getPIDDir returns the absolute path to the PID directory func getPIDDir() (string, error) { homeDir, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("failed to get user home directory: %w", err) } return filepath.Join(homeDir, ".kvs", "pids"), nil } // getLogDir returns the absolute path to the log directory func getLogDir() (string, error) { homeDir, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("failed to get user home directory: %w", err) } return filepath.Join(homeDir, ".kvs", "logs"), nil } // GetPIDFilePath returns the PID file path for a given config file func GetPIDFilePath(configPath string) string { pidDir, err := getPIDDir() if err != nil { // Fallback to local directory pidDir = ".kvs/pids" } // Extract basename without extension basename := filepath.Base(configPath) name := strings.TrimSuffix(basename, filepath.Ext(basename)) return filepath.Join(pidDir, name+".pid") } // EnsurePIDDir creates the PID directory if it doesn't exist func EnsurePIDDir() error { pidDir, err := getPIDDir() if err != nil { return err } return os.MkdirAll(pidDir, 0755) } // WritePID writes the current process PID to a file func WritePID(configPath string) error { if err := EnsurePIDDir(); err != nil { return fmt.Errorf("failed to create PID directory: %w", err) } pidFile := GetPIDFilePath(configPath) pid := os.Getpid() return os.WriteFile(pidFile, []byte(fmt.Sprintf("%d\n", pid)), 0644) } // ReadPID reads the PID from a file and checks if the process is running func ReadPID(configPath string) (int, bool, error) { pidFile := GetPIDFilePath(configPath) data, err := os.ReadFile(pidFile) if err != nil { if os.IsNotExist(err) { return 0, false, nil } return 0, false, fmt.Errorf("failed to read PID file: %w", err) } pidStr := strings.TrimSpace(string(data)) pid, err := strconv.Atoi(pidStr) if err != nil { return 0, false, fmt.Errorf("invalid PID in file: %w", err) } // Check if process is actually running process, err := os.FindProcess(pid) if err != nil { return pid, false, nil } // Send signal 0 to check if process exists err = process.Signal(syscall.Signal(0)) if err != nil { return pid, false, nil } return pid, true, nil } // RemovePID removes the PID file func RemovePID(configPath string) error { pidFile := GetPIDFilePath(configPath) err := os.Remove(pidFile) if err != nil && !os.IsNotExist(err) { return fmt.Errorf("failed to remove PID file: %w", err) } return nil } // ListRunningInstances returns a list of running KVS instances func ListRunningInstances() ([]InstanceInfo, error) { var instances []InstanceInfo pidDir, err := getPIDDir() if err != nil { return nil, err } // Check if PID directory exists if _, err := os.Stat(pidDir); os.IsNotExist(err) { return instances, nil } entries, err := os.ReadDir(pidDir) if err != nil { return nil, fmt.Errorf("failed to read PID directory: %w", err) } for _, entry := range entries { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".pid") { continue } name := strings.TrimSuffix(entry.Name(), ".pid") configPath := name + ".yaml" // Assume .yaml extension pid, running, err := ReadPID(configPath) if err != nil { continue } instances = append(instances, InstanceInfo{ Name: name, PID: pid, Running: running, }) } return instances, nil } // InstanceInfo holds information about a KVS instance type InstanceInfo struct { Name string PID int Running bool } // StopProcess stops a process by PID func StopProcess(pid int) error { process, err := os.FindProcess(pid) if err != nil { return fmt.Errorf("failed to find process: %w", err) } // Try graceful shutdown first (SIGTERM) if err := process.Signal(syscall.SIGTERM); err != nil { return fmt.Errorf("failed to send SIGTERM: %w", err) } return nil }