296 lines
8.0 KiB
Go
296 lines
8.0 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
const (
|
|
CreateToolhelp32SnapshotProcess = 0x00000002
|
|
ProcessSetInformation = 0x00000200
|
|
ProcessQueryLimitedInformation = 0x00001000
|
|
|
|
ErrorNoMoreFiles = 18
|
|
ErrorAlreadyExists = 183
|
|
)
|
|
|
|
type processEntry32 struct {
|
|
Size uint32
|
|
CntUsage uint32
|
|
PID uint32
|
|
DefaultHeapID uintptr
|
|
ModuleID uint32
|
|
CntThreads uint32
|
|
ParentPID uint32
|
|
PrioClass int32
|
|
Flags uint32
|
|
ExeFile [260]uint16
|
|
}
|
|
|
|
type processMemoryCounters struct {
|
|
CBM uint32
|
|
PageFaultCount uint32
|
|
PeakWorkingSetSize uintptr
|
|
WorkingSetSize uintptr
|
|
QuotaPeakPagedPoolUsage uintptr
|
|
QuotaPagedPoolUsage uintptr
|
|
QuotaPeakNonPagedPoolUsage uintptr
|
|
QuotaNonPagedPoolUsage uintptr
|
|
PagefileUsage uintptr
|
|
PeakPagefileUsage uintptr
|
|
PrivateUsage uintptr
|
|
}
|
|
|
|
type processKey struct {
|
|
PID uint32
|
|
Created uint64
|
|
}
|
|
|
|
type procInfo struct {
|
|
PID uint32
|
|
Name string
|
|
RSS uint64
|
|
Key processKey
|
|
Prio uint32
|
|
Handle syscall.Handle
|
|
Waitable bool
|
|
ParentPID uint32
|
|
}
|
|
|
|
var (
|
|
k32 = syscall.NewLazyDLL("kernel32.dll")
|
|
|
|
procCreateSnap = k32.NewProc("CreateToolhelp32Snapshot")
|
|
procProcess32First = k32.NewProc("Process32FirstW")
|
|
procProcess32Next = k32.NewProc("Process32NextW")
|
|
procGetMemInfo = k32.NewProc("K32GetProcessMemoryInfo")
|
|
procSetPriority = k32.NewProc("SetPriorityClass")
|
|
procGetPriority = k32.NewProc("GetPriorityClass")
|
|
procOpenProcess = k32.NewProc("OpenProcess")
|
|
procCloseHandle = k32.NewProc("CloseHandle")
|
|
procGetProcessTimes = k32.NewProc("GetProcessTimes")
|
|
procQueryImageName = k32.NewProc("QueryFullProcessImageNameW")
|
|
procCreateMutex = k32.NewProc("CreateMutexW")
|
|
)
|
|
|
|
func closeH(h syscall.Handle) {
|
|
_, _, _ = procCloseHandle.Call(uintptr(h))
|
|
}
|
|
|
|
func openProc(pid uint32, acc uint32) (syscall.Handle, error) {
|
|
r, _, e := procOpenProcess.Call(uintptr(acc), 0, uintptr(pid))
|
|
if r == 0 {
|
|
return 0, fmt.Errorf("OpenProcess(%d) failed: %w", pid, e)
|
|
}
|
|
return syscall.Handle(r), nil
|
|
}
|
|
|
|
func setPrio(h syscall.Handle, pid uint32, cls uint32) error {
|
|
r, _, e := procSetPriority.Call(uintptr(h), uintptr(cls))
|
|
if r == 0 {
|
|
return fmt.Errorf("SetPriorityClass(%d) failed: %w", pid, e)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func processCreated(h syscall.Handle) (uint64, error) {
|
|
var created, exited, kernel, user syscall.Filetime
|
|
r, _, e := procGetProcessTimes.Call(
|
|
uintptr(h),
|
|
uintptr(unsafe.Pointer(&created)),
|
|
uintptr(unsafe.Pointer(&exited)),
|
|
uintptr(unsafe.Pointer(&kernel)),
|
|
uintptr(unsafe.Pointer(&user)),
|
|
)
|
|
if r == 0 {
|
|
return 0, fmt.Errorf("GetProcessTimes failed: %w", e)
|
|
}
|
|
return uint64(created.HighDateTime)<<32 | uint64(created.LowDateTime), nil
|
|
}
|
|
|
|
func processName(h syscall.Handle, buf []uint16) (string, error) {
|
|
size := uint32(len(buf))
|
|
r, _, e := procQueryImageName.Call(uintptr(h), 0, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)))
|
|
if r == 0 {
|
|
return "", fmt.Errorf("QueryFullProcessImageName failed: %w", e)
|
|
}
|
|
return filepath.Base(syscall.UTF16ToString(buf[:size])), nil
|
|
}
|
|
|
|
func processAlive(h syscall.Handle) (bool, error) {
|
|
r, _, err := procWaitForSingleObject.Call(uintptr(h), 0)
|
|
return decodeWaitResult(r, err)
|
|
}
|
|
func decodeWaitResult(r uintptr, err error) (bool, error) {
|
|
switch r {
|
|
case 0:
|
|
return false, nil
|
|
case 0x102:
|
|
return true, nil
|
|
default:
|
|
return false, fmt.Errorf("WaitForSingleObject returned %#x: %v", r, err)
|
|
}
|
|
}
|
|
|
|
func allProcs() ([]procInfo, error) {
|
|
snap, _, e := procCreateSnap.Call(CreateToolhelp32SnapshotProcess, 0)
|
|
if snap == ^uintptr(0) {
|
|
return nil, fmt.Errorf("CreateToolhelp32Snapshot failed: %w", e)
|
|
}
|
|
defer closeH(syscall.Handle(snap))
|
|
|
|
pe := processEntry32{Size: uint32(unsafe.Sizeof(processEntry32{}))}
|
|
r, _, e := procProcess32First.Call(snap, uintptr(unsafe.Pointer(&pe)))
|
|
if r == 0 {
|
|
if e == syscall.Errno(ErrorNoMoreFiles) {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("Process32First failed: %w", e)
|
|
}
|
|
|
|
var out []procInfo
|
|
for {
|
|
name := syscall.UTF16ToString(pe.ExeFile[:])
|
|
out = append(out, procInfo{PID: pe.PID, ParentPID: pe.ParentPID, Name: name})
|
|
pe.Size = uint32(unsafe.Sizeof(processEntry32{}))
|
|
r, _, e = procProcess32Next.Call(snap, uintptr(unsafe.Pointer(&pe)))
|
|
if r == 0 {
|
|
if e == syscall.Errno(ErrorNoMoreFiles) {
|
|
break
|
|
}
|
|
return nil, fmt.Errorf("Process32Next failed: %w", e)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func singleInstance() (syscall.Handle, bool, error) {
|
|
name, err := syscall.UTF16PtrFromString(`Local\autoPriority`)
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
r, _, e := procCreateMutex.Call(0, 0, uintptr(unsafe.Pointer(name)))
|
|
if r == 0 {
|
|
return 0, false, fmt.Errorf("CreateMutex failed: %w", e)
|
|
}
|
|
return syscall.Handle(r), e == syscall.Errno(ErrorAlreadyExists), nil
|
|
}
|
|
|
|
const processSynchronize = 0x00100000
|
|
|
|
var procWaitForSingleObject = k32.NewProc("WaitForSingleObject")
|
|
|
|
type windowsBackend struct {
|
|
list func() ([]procInfo, error)
|
|
self uint32
|
|
nameBuffer [32768]uint16
|
|
unreadable map[uint32]procInfo
|
|
}
|
|
|
|
func newWindowsBackend() *windowsBackend {
|
|
return &windowsBackend{list: allProcs, self: uint32(os.Getpid()), unreadable: make(map[uint32]procInfo)}
|
|
}
|
|
func (w *windowsBackend) inspect(p procInfo) (procInfo, error) {
|
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation|processSynchronize)
|
|
p.Waitable = err == nil
|
|
if err != nil {
|
|
h, err = openProc(p.PID, ProcessQueryLimitedInformation)
|
|
}
|
|
if err != nil {
|
|
return procInfo{}, err
|
|
}
|
|
good := false
|
|
defer func() {
|
|
if !good {
|
|
closeH(h)
|
|
}
|
|
}()
|
|
created, err := processCreated(h)
|
|
if err != nil {
|
|
return procInfo{}, err
|
|
}
|
|
if name, e := processName(h, w.nameBuffer[:]); e == nil {
|
|
p.Name = name
|
|
}
|
|
p.Handle = h
|
|
p.Key = processKey{PID: p.PID, Created: created}
|
|
var m processMemoryCounters
|
|
m.CBM = uint32(unsafe.Sizeof(m))
|
|
r, _, e := procGetMemInfo.Call(uintptr(h), uintptr(unsafe.Pointer(&m)), uintptr(unsafe.Sizeof(m)))
|
|
if r == 0 {
|
|
return procInfo{}, fmt.Errorf("K32GetProcessMemoryInfo: %w", e)
|
|
}
|
|
p.RSS = uint64(m.WorkingSetSize)
|
|
p.Prio, err = w.Priority(&p)
|
|
if err != nil {
|
|
return procInfo{}, err
|
|
}
|
|
good = true
|
|
return p, nil
|
|
}
|
|
func (w *windowsBackend) Snapshot() (processSnapshot, error) {
|
|
entries, err := w.list()
|
|
if err != nil {
|
|
return processSnapshot{}, err
|
|
}
|
|
snap := processSnapshot{PIDs: make(map[uint32]bool, len(entries)), Processes: make([]procInfo, 0, len(entries))}
|
|
for _, entry := range entries {
|
|
snap.PIDs[entry.PID] = true
|
|
if entry.PID == 0 || entry.PID == w.self {
|
|
continue
|
|
}
|
|
if blocked, ok := w.unreadable[entry.PID]; ok && blocked.Name == entry.Name && blocked.ParentPID == entry.ParentPID {
|
|
continue
|
|
}
|
|
info, err := w.inspect(entry)
|
|
if err != nil {
|
|
snap.Errors = append(snap.Errors, processError{Name: entry.Name, PID: entry.PID, Description: fmt.Sprintf("чтение: %v; внесён в блоклист", err)})
|
|
w.unreadable[entry.PID] = entry
|
|
continue
|
|
}
|
|
delete(w.unreadable, entry.PID)
|
|
snap.Processes = append(snap.Processes, info)
|
|
}
|
|
for pid := range w.unreadable {
|
|
if !snap.PIDs[pid] {
|
|
delete(w.unreadable, pid)
|
|
}
|
|
}
|
|
return snap, nil
|
|
}
|
|
func (w *windowsBackend) Alive(p *procInfo) (bool, error) { return processAlive(p.Handle) }
|
|
func (w *windowsBackend) Priority(p *procInfo) (uint32, error) {
|
|
r, _, err := procGetPriority.Call(uintptr(p.Handle))
|
|
if r == 0 {
|
|
return 0, fmt.Errorf("GetPriorityClass: %w", err)
|
|
}
|
|
return uint32(r), nil
|
|
}
|
|
func (w *windowsBackend) SetPriority(p *procInfo, target uint32) error {
|
|
// Request write access only for an actual change, never while enumerating.
|
|
h, err := openProc(p.PID, ProcessQueryLimitedInformation|ProcessSetInformation)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer closeH(h)
|
|
created, err := processCreated(h)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if created != p.Key.Created {
|
|
return fmt.Errorf("process instance changed; refusing to write")
|
|
}
|
|
return setPrio(h, p.PID, target)
|
|
}
|
|
func (w *windowsBackend) Close(p *procInfo) {
|
|
if p.Handle != 0 {
|
|
closeH(p.Handle)
|
|
p.Handle = 0
|
|
}
|
|
}
|