1
0
Fork 0
telegraf/plugins/inputs/win_services/win_services.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

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{},
}
})
}