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
138 lines
3.2 KiB
Go
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
|
|
}
|