wincmd/svc.go
starainrt 7e6cc73106
完善 Windows 运维封装与 NTFS 索引解析
- 新增自启动幂等配置、统一错误语义、进程等待和进程树终止能力
- 增强服务生命周期管理,支持等待状态、重启、幂等创建和配置更新
- 新增 NTFS 卷索引、文件 ID 解析、文件遍历、USN 变更监听和 bookmark 持久化
- 修复 NTFS boot sector、fragment、MFT、USN 解析边界和路径重建问题
- 补充权限、进程、服务、NTFS 解析和工作流回归测试
- 增加 Windows 测试脚本和管理员 NTFS smoke 验证脚本
- 升级 Go 兼容版本到 1.18,并更新 stario、win32api 及相关间接依赖
2026-06-09 15:59:31 +08:00

380 lines
8.7 KiB
Go

package wincmd
import (
"fmt"
"syscall"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
"strings"
"time"
)
type SvcStatus svc.State
const (
Stopped = SvcStatus(svc.Stopped)
StartPending = SvcStatus(svc.StartPending)
StopPending = SvcStatus(svc.StopPending)
Running = SvcStatus(svc.Running)
ContinuePending = SvcStatus(svc.ContinuePending)
PausePending = SvcStatus(svc.PausePending)
Paused = SvcStatus(svc.Paused)
StartManual = windows.SERVICE_DEMAND_START // the service must be started manually
StartAutomatic = windows.SERVICE_AUTO_START // the service will start by itself whenever the computer reboots
StartDisabled = windows.SERVICE_DISABLED // the service cannot be started
// The severity of the error, and action taken,
// if this service fails to start.
ErrorCritical = windows.SERVICE_ERROR_CRITICAL
ErrorIgnore = windows.SERVICE_ERROR_IGNORE
ErrorNormal = windows.SERVICE_ERROR_NORMAL
ErrorSevere = windows.SERVICE_ERROR_SEVERE
)
type WinSvcExecute struct {
Run func()
Stop func()
Interrupt func()
Pause func()
Continue func()
OtherMethod func(svc.Cmd)
Name string
Accepted []svc.Accepted
}
type WinSvcInput struct {
Name string
DisplayName string
ExecPath string
DelayedAutoStart bool
Description string
StartType uint32
Args []string
RecoveryActions []mgr.RecoveryAction
RecoveryResetSec uint32
RecoveryCommand string
RecoveryCommandSet bool
RecoveryOnFail *bool
}
type WinSvc struct {
*mgr.Service
}
func connectServiceManager() (*mgr.Mgr, error) {
elevated, err := IsElevated()
if err != nil {
return nil, wrapPermissionError("query elevation", err)
}
if !elevated {
return nil, wrapPermissionError("admin required for service operations", nil)
}
winmgr, err := mgr.Connect()
if err != nil {
return nil, err
}
return winmgr, nil
}
func serviceExistsWithManager(winmgr *mgr.Mgr, name string) (bool, error) {
if winmgr == nil {
return false, wrapInputError("nil service manager")
}
service, err := winmgr.OpenService(name)
if err != nil {
if isServiceNotExists(err) {
return false, nil
}
return false, err
}
service.Close()
return true, nil
}
func IsServiceExists(name string) (bool, error) {
name = strings.TrimSpace(name)
if name == "" {
return false, wrapInputError("empty service name")
}
winmgr, err := connectServiceManager()
if err != nil {
return false, err
}
defer winmgr.Disconnect()
return serviceExistsWithManager(winmgr, name)
}
func CreateService(mysvc WinSvcInput) (*WinSvc, error) {
if strings.TrimSpace(mysvc.Name) == "" {
return nil, wrapInputError("empty service name")
}
if strings.TrimSpace(mysvc.ExecPath) == "" {
return nil, wrapInputError("empty executable path")
}
winmgr, err := connectServiceManager()
if err != nil {
return nil, err
}
defer winmgr.Disconnect()
if exists, err := serviceExistsWithManager(winmgr, mysvc.Name); err != nil {
return nil, err
} else if exists {
return nil, wrapInputError("service already exists")
}
mycfg := mgr.Config{
DisplayName: mysvc.DisplayName,
StartType: mysvc.StartType,
DelayedAutoStart: mysvc.DelayedAutoStart,
Description: mysvc.Description,
}
gsvc, err := winmgr.CreateService(mysvc.Name, mysvc.ExecPath, mycfg, mysvc.Args...)
if err != nil {
return nil, err
}
created := false
defer func() {
if !created {
_ = gsvc.Close()
}
}()
err = eventlog.InstallAsEventCreate(mysvc.Name, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
_ = gsvc.Delete()
return nil, fmt.Errorf("winsvc.InstallService: InstallAsEventCreate failed, err = %v", err)
}
if _, err := applyServiceRecoverySettings(gsvc, mysvc); err != nil {
_ = eventlog.Remove(mysvc.Name)
_ = gsvc.Delete()
return nil, fmt.Errorf("winsvc.InstallService: apply recovery config failed, err = %v", err)
}
var result WinSvc
result.Service = gsvc
created = true
return &result, nil
}
func OpenService(name string) (*WinSvc, error) {
name = strings.TrimSpace(name)
if name == "" {
return nil, wrapInputError("empty service name")
}
winmgr, err := connectServiceManager()
if err != nil {
return nil, err
}
defer winmgr.Disconnect()
gsvc, err := winmgr.OpenService(name)
if err != nil {
if isServiceNotExists(err) {
return nil, wrapNotFoundError("service " + name)
}
return nil, err
}
var result WinSvc
result.Service = gsvc
return &result, nil
}
func DeleteService(name string) error {
name = strings.TrimSpace(name)
if name == "" {
return wrapInputError("empty service name")
}
winmgr, err := connectServiceManager()
if err != nil {
return err
}
defer winmgr.Disconnect()
service, err := winmgr.OpenService(name)
if err != nil {
if isServiceNotExists(err) {
return wrapNotFoundError("service " + name)
}
return err
}
if err := service.Delete(); err != nil {
service.Close()
return err
}
service.Close()
err = eventlog.Remove(name)
if err != nil {
return err
}
return waitUntil(defaultServiceWaitTimeout, servicePollInterval, "wait service deletion", func() (bool, error) {
ok, err := serviceExistsWithManager(winmgr, name)
if err != nil {
return false, err
}
return !ok, nil
})
}
func StopService(name string) error {
mysvc, err := OpenService(name)
if err != nil {
return err
}
defer mysvc.Close()
status, err := mysvc.Service.Query()
if err != nil {
return err
}
if status.State == svc.Stopped {
return nil
}
_, err = mysvc.Service.Control(svc.Stop)
if err != nil {
if errno, ok := err.(syscall.Errno); !ok || errno != windows.ERROR_SERVICE_NOT_ACTIVE {
return err
}
}
return waitServiceStatus(mysvc.Service, svc.Stopped, defaultServiceWaitTimeout)
}
func StartService(name string) error {
mysvc, err := OpenService(name)
if err != nil {
return err
}
defer mysvc.Close()
status, err := mysvc.Service.Query()
if err != nil {
return err
}
if status.State == svc.Running {
return nil
}
if err := mysvc.Service.Start(); err != nil {
return err
}
return waitServiceStatus(mysvc.Service, svc.Running, defaultServiceWaitTimeout)
}
func ServiceStatus(name string) (SvcStatus, error) {
mysvc, err := OpenService(name)
if err != nil {
return Stopped, err
}
defer mysvc.Close()
status, err := mysvc.Service.Query()
return SvcStatus(status.State), err
}
func InService() (bool, error) {
return svc.IsWindowsService()
}
func (w *WinSvc) Stop() error {
return StopService(w.Name)
}
func (w *WinSvc) Delete() error {
if err := w.Close(); err != nil {
return err
}
return DeleteService(w.Name)
}
func (w *WinSvc) StartService() error {
status, err := w.Query()
if err != nil {
return err
}
if status.State == svc.Running {
return nil
}
if err := w.Service.Start(); err != nil {
return err
}
return waitServiceStatus(w.Service, svc.Running, defaultServiceWaitTimeout)
}
func InServiceBool() bool {
ok, _ := svc.IsWindowsService()
return ok
}
func (w *WinSvcExecute) Execute(args []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
var sva svc.Accepted
alreadyStoped := make(chan int)
for _, v := range w.Accepted {
sva = sva | v
}
s <- svc.Status{State: svc.StartPending}
go func() {
s <- svc.Status{State: svc.Running, Accepts: sva}
w.Run()
alreadyStoped <- 1
}()
for {
select {
case <-alreadyStoped:
return
case c := <-r:
switch c.Cmd {
case svc.Interrogate:
s <- c.CurrentStatus
w.Interrupt()
s <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
s <- svc.Status{State: svc.StopPending}
w.Stop()
s <- svc.Status{State: svc.Stopped}
return
case svc.Pause:
s <- svc.Status{State: svc.PausePending, Accepts: sva}
if w.Pause != nil {
w.Pause()
}
s <- svc.Status{State: svc.Paused, Accepts: sva}
case svc.Continue:
s <- svc.Status{State: svc.ContinuePending, Accepts: sva}
if w.Continue != nil {
w.Continue()
}
s <- svc.Status{State: svc.Running, Accepts: sva}
default:
if w.OtherMethod != nil {
w.OtherMethod(c.Cmd)
}
s <- svc.Status{State: svc.Running, Accepts: sva}
}
}
}
}
func NewWinSvcExecute(name string, run, stop func()) *WinSvcExecute {
var res WinSvcExecute
res.Name = name
res.Run = run
res.Stop = stop
res.Interrupt = func() {
time.Sleep(time.Millisecond)
}
res.Accepted = []svc.Accepted{svc.AcceptStop, svc.AcceptShutdown, svc.AcceptPauseAndContinue}
return &res
}
func (w *WinSvcExecute) StartService() error {
return svc.Run(w.Name, w)
}
func (w *WinSvcExecute) InService() (bool, error) {
return svc.IsWindowsService()
}
func (w *WinSvcExecute) InServiceBool() bool {
ok, _ := svc.IsWindowsService()
return ok
}