248 lines
5.7 KiB
Go
248 lines
5.7 KiB
Go
//go:generate ../../../tools/readme_config_includer/generator
|
|
//go:build windows
|
|
|
|
package win_services
|
|
|
|
import (
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"golang.org/x/sys/windows"
|
|
"golang.org/x/sys/windows/svc"
|
|
"golang.org/x/sys/windows/svc/mgr"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
"github.com/influxdata/telegraf/filter"
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
|
)
|
|
|
|
//go:embed sample.conf
|
|
var sampleConfig string
|
|
|
|
type WinServices struct {
|
|
ServiceNames []string `toml:"service_names"`
|
|
ServiceNamesExcluded []string `toml:"excluded_service_names"`
|
|
|
|
Log telegraf.Logger `toml:"-"`
|
|
|
|
mgrProvider managerProvider
|
|
servicesFilter filter.Filter
|
|
}
|
|
|
|
// winService provides interface for svc.Service
|
|
type winService interface {
|
|
Close() error
|
|
Config() (mgr.Config, error)
|
|
Query() (svc.Status, error)
|
|
}
|
|
|
|
// managerProvider sets interface for acquiring manager instance, like mgr.Mgr
|
|
type managerProvider interface {
|
|
connect() (winServiceManager, error)
|
|
}
|
|
|
|
// winServiceManager provides interface for mgr.Mgr
|
|
type winServiceManager interface {
|
|
disconnect() error
|
|
openService(name string) (winService, error)
|
|
listServices() ([]string, error)
|
|
}
|
|
|
|
type serviceInfo struct {
|
|
ServiceName string
|
|
DisplayName string
|
|
State int
|
|
StartUpMode int
|
|
}
|
|
|
|
func (*WinServices) SampleConfig() string {
|
|
return sampleConfig
|
|
}
|
|
|
|
func (m *WinServices) Init() error {
|
|
// For case insensitive comparison (see issue #8796) we need to transform the services
|
|
// to lowercase
|
|
servicesInclude := make([]string, 0, len(m.ServiceNames))
|
|
for _, s := range m.ServiceNames {
|
|
servicesInclude = append(servicesInclude, strings.ToLower(s))
|
|
}
|
|
servicesExclude := make([]string, 0, len(m.ServiceNamesExcluded))
|
|
for _, s := range m.ServiceNamesExcluded {
|
|
servicesExclude = append(servicesExclude, strings.ToLower(s))
|
|
}
|
|
|
|
f, err := filter.NewIncludeExcludeFilter(servicesInclude, servicesExclude)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m.servicesFilter = f
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *WinServices) Gather(acc telegraf.Accumulator) error {
|
|
scmgr, err := m.mgrProvider.connect()
|
|
if err != nil {
|
|
return fmt.Errorf("could not open service manager: %w", err)
|
|
}
|
|
defer scmgr.disconnect()
|
|
|
|
serviceNames, err := m.listServices(scmgr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, srvName := range serviceNames {
|
|
service, err := collectServiceInfo(scmgr, srvName)
|
|
if err != nil {
|
|
if isPermission(err) {
|
|
m.Log.Debug(err.Error())
|
|
} else {
|
|
m.Log.Error(err.Error())
|
|
}
|
|
continue
|
|
}
|
|
|
|
tags := map[string]string{
|
|
"service_name": service.ServiceName,
|
|
}
|
|
// display name could be empty, but still valid service
|
|
if len(service.DisplayName) > 0 {
|
|
tags["display_name"] = service.DisplayName
|
|
}
|
|
|
|
fields := map[string]interface{}{
|
|
"state": service.State,
|
|
"startup_mode": service.StartUpMode,
|
|
}
|
|
acc.AddFields("win_services", fields, tags)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// listServices returns a list of services to gather.
|
|
func (m *WinServices) listServices(scmgr winServiceManager) ([]string, error) {
|
|
names, err := scmgr.listServices()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not list services: %w", err)
|
|
}
|
|
|
|
var services []string
|
|
for _, name := range names {
|
|
// Compare case-insensitive. Use lowercase as we already converted the filter to use it.
|
|
n := strings.ToLower(name)
|
|
if m.servicesFilter.Match(n) {
|
|
services = append(services, name)
|
|
}
|
|
}
|
|
|
|
return services, nil
|
|
}
|
|
|
|
func isPermission(err error) bool {
|
|
var serviceErr *serviceError
|
|
if errors.As(err, &serviceErr) {
|
|
return errors.Is(serviceErr, fs.ErrPermission)
|
|
}
|
|
return false
|
|
}
|
|
|
|
// collectServiceInfo gathers info about a service.
|
|
func collectServiceInfo(scmgr winServiceManager, serviceName string) (*serviceInfo, error) {
|
|
srv, err := scmgr.openService(serviceName)
|
|
if err != nil {
|
|
return nil, &serviceError{
|
|
message: "could not open service",
|
|
service: serviceName,
|
|
err: err,
|
|
}
|
|
}
|
|
defer srv.Close()
|
|
|
|
srvStatus, err := srv.Query()
|
|
if err != nil {
|
|
return nil, &serviceError{
|
|
message: "could not query service",
|
|
service: serviceName,
|
|
err: err,
|
|
}
|
|
}
|
|
|
|
srvCfg, err := srv.Config()
|
|
if err != nil {
|
|
return nil, &serviceError{
|
|
message: "could not get config of service",
|
|
service: serviceName,
|
|
err: err,
|
|
}
|
|
}
|
|
|
|
serviceInfo := &serviceInfo{
|
|
ServiceName: serviceName,
|
|
DisplayName: srvCfg.DisplayName,
|
|
StartUpMode: int(srvCfg.StartType),
|
|
State: int(srvStatus.State),
|
|
}
|
|
return serviceInfo, nil
|
|
}
|
|
|
|
type serviceError struct {
|
|
message string
|
|
service string
|
|
err error
|
|
}
|
|
|
|
func (e *serviceError) Error() string {
|
|
return fmt.Sprintf("%s: %q: %v", e.message, e.service, e.err)
|
|
}
|
|
|
|
// winSvcMgr is wrapper for mgr.Mgr implementing winServiceManager interface
|
|
type winSvcMgr struct {
|
|
realMgr *mgr.Mgr
|
|
}
|
|
|
|
func (m *winSvcMgr) disconnect() error {
|
|
return m.realMgr.Disconnect()
|
|
}
|
|
|
|
func (m *winSvcMgr) openService(name string) (winService, error) {
|
|
serviceName, err := syscall.UTF16PtrFromString(name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot convert service name %q: %w", name, err)
|
|
}
|
|
h, err := windows.OpenService(m.realMgr.Handle, serviceName, windows.GENERIC_READ)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &mgr.Service{Name: name, Handle: h}, nil
|
|
}
|
|
|
|
func (m *winSvcMgr) listServices() ([]string, error) {
|
|
return m.realMgr.ListServices()
|
|
}
|
|
|
|
// mgProvider is an implementation of WinServiceManagerProvider interface returning winSvcMgr
|
|
type mgProvider struct {
|
|
}
|
|
|
|
func (*mgProvider) connect() (winServiceManager, error) {
|
|
h, err := windows.OpenSCManager(nil, nil, windows.GENERIC_READ)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
scmgr := &mgr.Mgr{Handle: h}
|
|
return &winSvcMgr{scmgr}, nil
|
|
}
|
|
|
|
func init() {
|
|
inputs.Add("win_services", func() telegraf.Input {
|
|
return &WinServices{
|
|
mgrProvider: &mgProvider{},
|
|
}
|
|
})
|
|
}
|