From 26add8e657611bc639cc4f7a2418fb2011a0bf86 Mon Sep 17 00:00:00 2001 From: pytimer Date: Mon, 21 May 2018 20:14:17 +0800 Subject: [PATCH] [windows]service: add windows service feature --- winservices/manager.go | 32 ++++++++++ winservices/winservices.go | 126 +++++++++++++++++++++++++++++++++++++ winservices/zsyscall_windows_ex.go | 57 +++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 winservices/manager.go create mode 100644 winservices/winservices.go create mode 100644 winservices/zsyscall_windows_ex.go diff --git a/winservices/manager.go b/winservices/manager.go new file mode 100644 index 0000000..1f1f937 --- /dev/null +++ b/winservices/manager.go @@ -0,0 +1,32 @@ +// +build windows + +package winservices + +import ( + "golang.org/x/sys/windows/svc/mgr" +) + +type scmanager struct { + mgr *mgr.Mgr +} + +func openSCManager() (*scmanager, error) { + m, err := mgr.Connect() + if err != nil { + return nil, err + } + return &scmanager{m}, nil +} + +func (sc *scmanager) close() error { + return sc.mgr.Disconnect() +} + +func getService(serviceName string) (*mgr.Service, error) { + m, err := openSCManager() + if err != nil { + return nil, err + } + defer m.close() + return m.mgr.OpenService(serviceName) +} diff --git a/winservices/winservices.go b/winservices/winservices.go new file mode 100644 index 0000000..47e6783 --- /dev/null +++ b/winservices/winservices.go @@ -0,0 +1,126 @@ +// +build windows + +package winservices + +import ( + "context" + "unsafe" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/mgr" +) + +// Service represent a windows service. +type Service struct { + Name string + Config mgr.Config + Status ServiceStatus + srv *mgr.Service +} + +// ServiceStatus combines State and Accepted commands to fully describe running service. +type ServiceStatus struct { + State svc.State + Accepts svc.Accepted + Pid uint32 + Win32ExitCode uint32 +} + +// NewService create and return a windows Service +func NewService(name string) (*Service, error) { + // call windows service function need to OpenService handler, + // so first call func OpenService to get the specified service handler. + service, err := getService(name) + if err != nil { + return nil, err + } + return &Service{ + Name: name, + srv: service, + }, nil +} + +// GetServiceDetail get a windows service by name +func (s *Service) GetServiceDetail() error { + return s.GetServiceDetailWithContext(context.Background()) +} + +// GetServiceDetailWithContext get a windows service by name +func (s *Service) GetServiceDetailWithContext(ctx context.Context) error { + config, err := s.QueryServiceConfigWithContext(ctx) + if err != nil { + return err + } + s.Config = config + + status, err := s.QueryStatusWithContext(ctx) + if err != nil { + return err + } + s.Status = status + + return nil +} + +// QueryServiceConfig return the specified service config +func (s *Service) QueryServiceConfig() (mgr.Config, error) { + return s.QueryServiceConfigWithContext(context.Background()) +} + +// QueryServiceConfigWithContext call QueryServiceConfig() and QueryServiceConfig2() +// implement windows https://msdn.microsoft.com/en-us/library/windows/desktop/ms684932(v=vs.85).aspx +func (s *Service) QueryServiceConfigWithContext(ctx context.Context) (mgr.Config, error) { + return s.srv.Config() +} + +// QueryStatus return the specified name service currentState and ControlsAccepted +func (s *Service) QueryStatus() (ServiceStatus, error) { + return s.QueryStatusWithContext(context.Background()) +} + +// QueryStatusWithContext return the specified name service currentState and ControlsAccepted +func (s *Service) QueryStatusWithContext(ctx context.Context) (ServiceStatus, error) { + var p *windows.SERVICE_STATUS_PROCESS + var bytesNeeded uint32 + var buf []byte + + if err := QueryServiceStatusEx(s.srv.Handle, SC_STATUS_PROCESS_INFO, nil, 0, &bytesNeeded); err != windows.ERROR_INSUFFICIENT_BUFFER { + return ServiceStatus{}, err + } + + buf = make([]byte, bytesNeeded) + p = (*windows.SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buf[0])) + if err := QueryServiceStatusEx(s.srv.Handle, SC_STATUS_PROCESS_INFO, &buf[0], uint32(len(buf)), &bytesNeeded); err != nil { + return ServiceStatus{}, err + } + + return ServiceStatus{ + State: svc.State(p.CurrentState), + Accepts: svc.Accepted(p.ControlsAccepted), + Pid: p.ProcessId, + Win32ExitCode: p.Win32ExitCode, + }, nil +} + +// ListServices return all windows service +// reference to golang.org/x/sys/windows/svc/mgr#ListServices() +func ListServices() ([]Service, error) { + m, err := openSCManager() + if err != nil { + return nil, err + } + defer m.close() + + names, err := m.mgr.ListServices() + if err != nil { + return nil, err + } + + services := make([]Service, 0) + for _, name := range names { + services = append(services, Service{Name: name}) + } + + return services, nil +} diff --git a/winservices/zsyscall_windows_ex.go b/winservices/zsyscall_windows_ex.go new file mode 100644 index 0000000..8db9c51 --- /dev/null +++ b/winservices/zsyscall_windows_ex.go @@ -0,0 +1,57 @@ +// +build windows + +package winservices + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + procQueryServiceStatusEx = modadvapi32.NewProc("QueryServiceStatusEx") +) + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +// SC_STATUS_PROCESS_INFO reference to https://msdn.microsoft.com/en-us/library/windows/desktop/ms684941(v=vs.85).aspx, +// Use SC_STATUS_PROCESS_INFO to retrieve the service status information. +const SC_STATUS_PROCESS_INFO int = 0 + +// QueryServiceStatusEx golang/sys/windows standard library can not implement QueryServiceStatusEx. +func QueryServiceStatusEx(service windows.Handle, infoLevel int, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) { + r1, _, e1 := syscall.Syscall6(procQueryServiceStatusEx.Addr(), 5, uintptr(service), uintptr(infoLevel), uintptr(unsafe.Pointer(buff)), uintptr(buffSize), uintptr(unsafe.Pointer(bytesNeeded)), 0) + if r1 == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +}