package daemon import ( "crypto/sha256" "encoding/hex" "fmt" "os" "path/filepath" "strconv" "strings" "syscall" ) const ( sockName = "daemon.sock" pidName = "daemon.pid" logName = "daemon.log" ) // Paths holds filesystem locations for one DataDir's daemon instance. type Paths struct { RuntimeDir string Socket string PIDFile string LogFile string } // PathsForDataDir returns runtime paths unique to dataDir. func PathsForDataDir(dataDir string) (Paths, error) { dataDir = filepath.Clean(dataDir) sum := sha256.Sum256([]byte(dataDir)) id := hex.EncodeToString(sum[:8]) dir, err := mkdirRuntime(id, dataDir) if err != nil { return Paths{}, err } return Paths{ RuntimeDir: dir, Socket: filepath.Join(dir, sockName), PIDFile: filepath.Join(dir, pidName), LogFile: filepath.Join(dir, logName), }, nil } func mkdirRuntime(id, dataDir string) (string, error) { candidates := []string{} if r := strings.TrimSpace(os.Getenv("XDG_RUNTIME_DIR")); r != "" { candidates = append(candidates, filepath.Join(r, "task-master-"+id)) } candidates = append(candidates, filepath.Join(os.TempDir(), fmt.Sprintf("task-master-%d-%s", os.Getuid(), id)), filepath.Join(dataDir, ".runtime"), ) var last error for _, dir := range candidates { if err := os.MkdirAll(dir, 0o700); err != nil { last = err continue } return dir, nil } if last == nil { last = fmt.Errorf("no runtime dir candidate") } return "", fmt.Errorf("create runtime dir: %w", last) } func writePID(path string, pid int) error { return os.WriteFile(path, []byte(strconv.Itoa(pid)+"\n"), 0o600) } func readPID(path string) (int, error) { data, err := os.ReadFile(path) if err != nil { return 0, err } s := strings.TrimSpace(string(data)) if s == "" { return 0, fmt.Errorf("empty pid file") } return strconv.Atoi(s) } func processAlive(pid int) bool { if pid <= 0 { return false } err := syscall.Kill(pid, 0) return err == nil }