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
139 lines
3.6 KiB
Go
139 lines
3.6 KiB
Go
//go:build windows
|
|
|
|
package proc
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
scManagerEnumerateService uint32 = 0x0004
|
|
scEnumProcessInfo uint32 = 0
|
|
serviceWin32 uint32 = 0x00000030
|
|
serviceStateAll uint32 = 0x00000003
|
|
)
|
|
|
|
var (
|
|
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
|
procOpenSCManagerW = modadvapi32.NewProc("OpenSCManagerW")
|
|
procCloseServiceHandle = modadvapi32.NewProc("CloseServiceHandle")
|
|
procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW")
|
|
)
|
|
|
|
type serviceStatusProcess struct {
|
|
ServiceType uint32
|
|
CurrentState uint32
|
|
ControlsAccepted uint32
|
|
Win32ExitCode uint32
|
|
ServiceSpecificExitCode uint32
|
|
CheckPoint uint32
|
|
WaitHint uint32
|
|
ProcessId uint32
|
|
ServiceFlags uint32
|
|
}
|
|
|
|
type enumServiceStatusProcessW struct {
|
|
ServiceName *uint16
|
|
DisplayName *uint16
|
|
ServiceStatusProcess serviceStatusProcess
|
|
}
|
|
|
|
var (
|
|
serviceMapCache map[int]string
|
|
serviceMapCacheTime time.Time
|
|
serviceMapCacheMu sync.Mutex
|
|
serviceMapCacheTTL = 2 * time.Second
|
|
)
|
|
|
|
// serviceMapForPIDs returns a PID → service-name map for every running
|
|
// Windows service. Cached so an ancestry walk pays one SCM scan, not N.
|
|
func serviceMapForPIDs() (map[int]string, error) {
|
|
serviceMapCacheMu.Lock()
|
|
defer serviceMapCacheMu.Unlock()
|
|
|
|
if serviceMapCache != nil && time.Since(serviceMapCacheTime) < serviceMapCacheTTL {
|
|
return serviceMapCache, nil
|
|
}
|
|
|
|
scm, _, callErr := procOpenSCManagerW.Call(0, 0, uintptr(scManagerEnumerateService))
|
|
if scm == 0 {
|
|
return nil, fmt.Errorf("OpenSCManager: %w", callErr)
|
|
}
|
|
defer procCloseServiceHandle.Call(scm)
|
|
|
|
// First call with a zero buffer probes for the required size.
|
|
var bytesNeeded, count, resume uint32
|
|
procEnumServicesStatusExW.Call(
|
|
scm,
|
|
uintptr(scEnumProcessInfo),
|
|
uintptr(serviceWin32),
|
|
uintptr(serviceStateAll),
|
|
0, 0,
|
|
uintptr(unsafe.Pointer(&bytesNeeded)),
|
|
uintptr(unsafe.Pointer(&count)),
|
|
uintptr(unsafe.Pointer(&resume)),
|
|
0,
|
|
)
|
|
if bytesNeeded == 0 {
|
|
serviceMapCache = map[int]string{}
|
|
serviceMapCacheTime = time.Now()
|
|
return serviceMapCache, nil
|
|
}
|
|
|
|
buf := make([]byte, bytesNeeded)
|
|
count = 0
|
|
resume = 0
|
|
ret, _, callErr := procEnumServicesStatusExW.Call(
|
|
scm,
|
|
uintptr(scEnumProcessInfo),
|
|
uintptr(serviceWin32),
|
|
uintptr(serviceStateAll),
|
|
uintptr(unsafe.Pointer(&buf[0])),
|
|
uintptr(bytesNeeded),
|
|
uintptr(unsafe.Pointer(&bytesNeeded)),
|
|
uintptr(unsafe.Pointer(&count)),
|
|
uintptr(unsafe.Pointer(&resume)),
|
|
0,
|
|
)
|
|
if ret == 0 {
|
|
return nil, fmt.Errorf("EnumServicesStatusEx: %w", callErr)
|
|
}
|
|
|
|
out := make(map[int]string, count)
|
|
entrySize := unsafe.Sizeof(enumServiceStatusProcessW{})
|
|
base := unsafe.Pointer(&buf[0])
|
|
for i := uintptr(0); i < uintptr(count); i++ {
|
|
entry := (*enumServiceStatusProcessW)(unsafe.Pointer(uintptr(base) + i*entrySize))
|
|
pid := int(entry.ServiceStatusProcess.ProcessId)
|
|
if pid == 0 {
|
|
// Service registered but not currently running.
|
|
continue
|
|
}
|
|
name := utf16PtrToString(entry.ServiceName)
|
|
if name == "" {
|
|
continue
|
|
}
|
|
// First writer wins so share-process hosts (svchost.exe) keep a
|
|
// stable name across calls.
|
|
if _, exists := out[pid]; !exists {
|
|
out[pid] = name
|
|
}
|
|
}
|
|
|
|
serviceMapCache = out
|
|
serviceMapCacheTime = time.Now()
|
|
return out, nil
|
|
}
|
|
|
|
// utf16PtrToString converts a null-terminated UTF-16 pointer to a Go string.
|
|
func utf16PtrToString(p *uint16) string {
|
|
if p == nil {
|
|
return ""
|
|
}
|
|
return syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(p))[:])
|
|
}
|