Files
wehub-resource-sync 36b3af2e3d
PR Check / Code Quality: Format (push) Failing after 1s
PR Check / Code Quality: Lint (darwin) (push) Failing after 0s
PR Check / Code Quality: Lint (freebsd) (push) Failing after 1s
PR Check / Code Quality: Lint (windows) (push) Failing after 1s
PR Check / Code Quality: Lint (linux) (push) Failing after 1s
PR Check / Security: Vulnerability Scan (push) Failing after 0s
Update Documentation / update-docs (push) Failing after 2s
PR Check / Code Quality: Vendor (push) Failing after 1s
PR Check / Code Quality: Coverage (push) Failing after 0s
PR Check / Tests: Unit (macos-latest) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04) (push) Has been cancelled
PR Check / Tests: Unit (ubuntu-24.04-arm) (push) Has been cancelled
PR Check / Tests: Unit (windows-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:06 +08:00

138 lines
3.2 KiB
Go

//go:build linux
package target
import (
"fmt"
"os"
"os/exec"
"sort"
"strconv"
"strings"
procpkg "github.com/pranshuparmar/witr/internal/proc"
)
func ResolveName(name string, exact bool) ([]int, error) {
var procPIDs []int
entries, _ := os.ReadDir("/proc")
lowerName := strings.ToLower(name)
selfPid := os.Getpid()
// Build ignored PID set lazily — only resolve ancestry if we actually
// need to filter matches (avoids walking the chain on every invocation)
var ignoredPids map[int]bool
isIgnored := func(pid int) bool {
if pid == selfPid {
return true
}
if ignoredPids == nil {
ignoredPids = make(map[int]bool)
ignoredPids[selfPid] = true
if ancestry, err := procpkg.ResolveAncestry(selfPid); err == nil {
for _, p := range ancestry {
ignoredPids[p.PID] = true
}
}
}
return ignoredPids[pid]
}
for _, e := range entries {
pid, err := strconv.Atoi(e.Name())
if err != nil {
continue
}
if lowerName == strconv.Itoa(pid) {
continue
}
if isIgnored(pid) {
continue
}
comm, err := os.ReadFile("/proc/" + e.Name() + "/comm")
if err == nil {
commLower := strings.ToLower(strings.TrimSpace(string(comm)))
var match bool
if exact {
match = commLower == lowerName
} else {
match = strings.Contains(commLower, lowerName)
}
if match {
procPIDs = append(procPIDs, pid)
continue
}
}
cmdline, err := os.ReadFile("/proc/" + e.Name() + "/cmdline")
if err == nil {
cmd := strings.ReplaceAll(string(cmdline), "\x00", " ")
cmdLower := strings.ToLower(cmd)
var match bool
if exact {
match = matchesExactToken(cmdLower, lowerName)
} else {
match = strings.Contains(cmdLower, lowerName)
}
if match {
procPIDs = append(procPIDs, pid)
}
}
}
// Only consult systemd when the /proc scan found nothing. A running
// service's main process is virtually always caught by the comm/cmdline
// match above, so this skips a systemctl fork on the common path and only
// pays it when resolving a service whose process name differs (or a service
// that isn't currently running).
var servicePID int
if len(procPIDs) == 0 {
servicePID, _ = resolveSystemdServiceMainPID(name)
}
seen := map[int]bool{}
var procUnique []int
for _, pid := range procPIDs {
if pid == servicePID || seen[pid] {
continue
}
seen[pid] = true
procUnique = append(procUnique, pid)
}
sort.Ints(procUnique)
var pids []int
if servicePID > 0 {
pids = append(pids, servicePID)
}
pids = append(pids, procUnique...)
if len(pids) == 0 {
return nil, fmt.Errorf("no running process or service named %q", name)
}
return pids, nil
}
// resolveSystemdServiceMainPID tries to resolve a systemd service and returns its MainPID if running.
func resolveSystemdServiceMainPID(name string) (int, error) {
// Accept both foo and foo.service
svcName := name
if !strings.HasSuffix(svcName, ".service") {
svcName += ".service"
}
out, err := exec.Command("systemctl", "show", "-p", "MainPID", "--value", "--", svcName).Output()
if err != nil {
return 0, err
}
pidStr := strings.TrimSpace(string(out))
pid, err := strconv.Atoi(pidStr)
if err != nil || pid == 0 {
return 0, fmt.Errorf("service %q not running", svcName)
}
return pid, nil
}