项目文件夹

文件
wehub-resource-sync 26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:34:16 +08:00

133 行
3.2 KiB
Go

//go:build !windows
package daemon
import (
"errors"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
"time"
)
func setSysProcAttr(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
}
// listDaemonProcesses enumerates running processes via ps and returns the ones
// that look like `no-mistakes daemon run --root <root>`. It powers the
// pgrep-style collision detection in reconcileCollidingDaemons. `ps` is used
// (rather than /proc) so the same approach works on Linux and macOS.
func listDaemonProcesses() ([]daemonProcessInfo, error) {
cmd := exec.Command(psExecutable(), "-ww", "-eo", "pid=,command=")
env := upsertEnv(os.Environ(), "LC_ALL", "C")
cmd.Env = upsertEnv(env, "LANG", "C")
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("enumerate processes: %w", err)
}
return parseDaemonProcessOutput(string(out), splitUnixProcessLine), nil
}
// splitUnixProcessLine parses one line of `ps -eo pid=,command=` output into a
// pid and the trailing command line.
func splitUnixProcessLine(line string) (int, string, bool) {
trimmed := strings.TrimLeft(line, " \t")
if trimmed == "" {
return 0, "", false
}
sep := strings.IndexAny(trimmed, " \t")
if sep < 0 {
return 0, "", false
}
pid, err := strconv.Atoi(trimmed[:sep])
if err != nil || pid <= 0 {
return 0, "", false
}
return pid, strings.TrimLeft(trimmed[sep:], " \t"), true
}
func processRunning(pid int) (bool, error) {
err := syscall.Kill(pid, 0)
if err == nil {
state, err := processState(pid)
if err != nil {
return false, err
}
if strings.HasPrefix(state, "Z") {
return false, nil
}
return true, nil
}
if errors.Is(err, syscall.ESRCH) {
return false, nil
}
if errors.Is(err, syscall.EPERM) {
return true, nil
}
return false, err
}
func processState(pid int) (string, error) {
cmd := processStateCommand(pid)
out, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}
func processStateCommand(pid int) *exec.Cmd {
cmd := exec.Command(psExecutable(), "-p", fmt.Sprintf("%d", pid), "-o", "stat=")
env := upsertEnv(os.Environ(), "LC_ALL", "C")
cmd.Env = upsertEnv(env, "LANG", "C")
return cmd
}
func processStartTime(pid int) (time.Time, error) {
if pid <= 0 {
return time.Time{}, fmt.Errorf("invalid pid %d", pid)
}
cmd := processStartTimeCommand(pid)
out, err := cmd.Output()
if err != nil {
return time.Time{}, err
}
startedAt := strings.TrimSpace(string(out))
if startedAt == "" {
return time.Time{}, fmt.Errorf("missing process start time")
}
parsed, err := parseProcessStartTime(startedAt, time.Local)
if err != nil {
return time.Time{}, err
}
return parsed, nil
}
func processStartTimeCommand(pid int) *exec.Cmd {
cmd := exec.Command(psExecutable(), "-p", fmt.Sprintf("%d", pid), "-o", "lstart=")
env := upsertEnv(os.Environ(), "LC_ALL", "C")
cmd.Env = upsertEnv(env, "LANG", "C")
return cmd
}
func psExecutable() string {
if path, err := exec.LookPath("ps"); err == nil {
return path
}
if _, err := os.Stat("/bin/ps"); err == nil {
return "/bin/ps"
}
return "ps"
}
func parseProcessStartTime(value string, loc *time.Location) (time.Time, error) {
if loc == nil {
loc = time.Local
}
return time.ParseInLocation("Mon Jan 2 15:04:05 2006", value, loc)
}