1package bot
2
3import (
4 "errors"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strconv"
9 "strings"
10 "time"
11)
12
13// WritePIDFile persists pid to path. Overwrites any existing file.
14func WritePIDFile(path string, pid int) error {
15 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
16 return err
17 }
18 return os.WriteFile(path, []byte(strconv.Itoa(pid)+"\n"), 0o644)
19}
20
21// ReadPIDFile returns the pid stored at path, or 0 if the file doesn't exist.
22func ReadPIDFile(path string) (int, error) {
23 b, err := os.ReadFile(path)
24 if errors.Is(err, os.ErrNotExist) {
25 return 0, nil
26 }
27 if err != nil {
28 return 0, err
29 }
30 pid, err := strconv.Atoi(strings.TrimSpace(string(b)))
31 if err != nil {
32 return 0, fmt.Errorf("parse pid: %w", err)
33 }
34 return pid, nil
35}
36
37// RemovePIDFile deletes the pid file if it exists.
38func RemovePIDFile(path string) error {
39 err := os.Remove(path)
40 if errors.Is(err, os.ErrNotExist) {
41 return nil
42 }
43 return err
44}
45
46// IsRunningAt reports whether a live process is recorded at pidPath.
47// Semantics identical to the old telegram.IsRunning.
48func IsRunningAt(pidPath string) (int, bool, error) {
49 pid, err := ReadPIDFile(pidPath)
50 if err != nil {
51 return 0, false, err
52 }
53 if pid <= 0 {
54 return 0, false, nil
55 }
56 alive, err := processAlive(pid)
57 if err != nil {
58 return pid, false, nil
59 }
60 return pid, alive, nil
61}
62
63// StopProcess asks pid to exit, waits up to graceful, then force-kills.
64func StopProcess(pid int, graceful time.Duration) error {
65 return stopProcess(pid, graceful)
66}