From 26add8e657611bc639cc4f7a2418fb2011a0bf86 Mon Sep 17 00:00:00 2001 From: pytimer Date: Mon, 21 May 2018 20:14:17 +0800 Subject: [PATCH 1/2] [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 +} From ca3c7ff69e94fa7be0197a25a624eb939e8fe54d Mon Sep 17 00:00:00 2001 From: pytimer Date: Mon, 28 May 2018 13:41:40 +0800 Subject: [PATCH 2/2] [windows]services remove zsyscall_windows_ex.go file use golang/sys/windows QueryServiceStatusEx --- winservices/winservices.go | 4 +-- winservices/zsyscall_windows_ex.go | 57 -------------------------------------- 2 files changed, 2 insertions(+), 59 deletions(-) delete mode 100644 winservices/zsyscall_windows_ex.go diff --git a/winservices/winservices.go b/winservices/winservices.go index 47e6783..4112b2c 100644 --- a/winservices/winservices.go +++ b/winservices/winservices.go @@ -85,13 +85,13 @@ func (s *Service) QueryStatusWithContext(ctx context.Context) (ServiceStatus, er var bytesNeeded uint32 var buf []byte - if err := QueryServiceStatusEx(s.srv.Handle, SC_STATUS_PROCESS_INFO, nil, 0, &bytesNeeded); err != windows.ERROR_INSUFFICIENT_BUFFER { + if err := windows.QueryServiceStatusEx(s.srv.Handle, windows.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 { + if err := windows.QueryServiceStatusEx(s.srv.Handle, windows.SC_STATUS_PROCESS_INFO, &buf[0], uint32(len(buf)), &bytesNeeded); err != nil { return ServiceStatus{}, err } diff --git a/winservices/zsyscall_windows_ex.go b/winservices/zsyscall_windows_ex.go deleted file mode 100644 index 8db9c51..0000000 --- a/winservices/zsyscall_windows_ex.go +++ /dev/null @@ -1,57 +0,0 @@ -// +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 -}