fix(process): kill 兜底弃 taskkill 命令行, 改 Windows API 进程树强杀
- Toolhelp32 快照枚举父子关系→收集后代(防环)→OpenProcess+TerminateProcess 按 PID 定向结束 - 产品红线: 系统操作禁拼 cmd 命令字符串(2026-10-09 用户令, 与 zomaintain AGENTS §一同源) - wire 不变: 仅错误文案 taskkill 段改'进程树终止'
This commit is contained in:
@@ -0,0 +1,10 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
// 非 Windows 无进程树兜底(killProcess 仅 Windows 分支调用)。
|
||||||
|
package process
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func killProcessTree(root uint32) error {
|
||||||
|
return errors.New("kill tree 仅支持 Windows")
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// 进程树强杀(编程方式, Windows API)——2026-10-09 起替代原 taskkill /F /T 命令行兜底
|
||||||
|
// (产品红线: 系统操作禁拼 cmd 命令字符串)。
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// killProcessTree 按 PID 定向结束进程及其全部后代:
|
||||||
|
// Toolhelp32 快照枚举全进程父子关系 → 收集后代(防父子环) → 逐个 OpenProcess+TerminateProcess。
|
||||||
|
func killProcessTree(root uint32) error {
|
||||||
|
parentOf, err := snapshotParentMap()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var pids []uint32
|
||||||
|
visited := map[uint32]bool{}
|
||||||
|
var visit func(pid uint32)
|
||||||
|
visit = func(pid uint32) {
|
||||||
|
if visited[pid] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visited[pid] = true
|
||||||
|
pids = append(pids, pid)
|
||||||
|
for child, parent := range parentOf {
|
||||||
|
if parent == pid {
|
||||||
|
visit(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visit(root)
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
for _, pid := range pids {
|
||||||
|
h, err := windows.OpenProcess(windows.PROCESS_TERMINATE, false, pid)
|
||||||
|
if err != nil {
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = fmt.Errorf("pid=%d OpenProcess: %w", pid, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := windows.TerminateProcess(h, 1); err != nil {
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = fmt.Errorf("pid=%d TerminateProcess: %w", pid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = windows.CloseHandle(h)
|
||||||
|
}
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// snapshotParentMap 全进程快照: pid → 父 pid。
|
||||||
|
func snapshotParentMap() (map[uint32]uint32, error) {
|
||||||
|
snap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("CreateToolhelp32Snapshot: %w", err)
|
||||||
|
}
|
||||||
|
defer windows.CloseHandle(snap)
|
||||||
|
|
||||||
|
parentOf := map[uint32]uint32{}
|
||||||
|
var pe windows.ProcessEntry32
|
||||||
|
pe.Size = uint32(unsafe.Sizeof(pe))
|
||||||
|
if err := windows.Process32First(snap, &pe); err != nil {
|
||||||
|
return nil, fmt.Errorf("Process32First: %w", err)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
parentOf[pe.ProcessID] = pe.ParentProcessID
|
||||||
|
if err := windows.Process32Next(snap, &pe); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parentOf, nil
|
||||||
|
}
|
||||||
@@ -7,10 +7,8 @@ package process
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/process"
|
"github.com/shirou/gopsutil/v3/process"
|
||||||
|
|
||||||
@@ -85,7 +83,8 @@ func listProcesses(w http.ResponseWriter, _ *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// killProcess 结束进程: {pid: 必填, name: 可选校验(防 pid 复用误杀)}。
|
// killProcess 结束进程: {pid: 必填, name: 可选校验(防 pid 复用误杀)}。
|
||||||
// Windows 上 gopsutil 权限不足时回退 taskkill /F /T(连子进程树强杀)。
|
// Windows 上 gopsutil 权限不足时回退进程树强杀(Windows API: Toolhelp32 快照+TerminateProcess,
|
||||||
|
// 2026-10-09 起替代原 taskkill /F /T 命令行兜底——产品红线禁命令行查杀)。
|
||||||
func killProcess(w http.ResponseWriter, r *http.Request) {
|
func killProcess(w http.ResponseWriter, r *http.Request) {
|
||||||
var req struct {
|
var req struct {
|
||||||
Pid int32 `json:"pid"`
|
Pid int32 `json:"pid"`
|
||||||
@@ -111,8 +110,8 @@ func killProcess(w http.ResponseWriter, r *http.Request) {
|
|||||||
fail(w, "结束进程失败: "+err.Error())
|
fail(w, "结束进程失败: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if out, e := exec.Command("taskkill", "/F", "/T", "/PID", strconv.Itoa(int(req.Pid))).CombinedOutput(); e != nil {
|
if e := killProcessTree(uint32(req.Pid)); e != nil {
|
||||||
fail(w, "结束进程失败: "+err.Error()+"; taskkill: "+string(out))
|
fail(w, "结束进程失败: "+err.Error()+"; 进程树终止: "+e.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user