| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package winutil
- import (
- "encoding/hex"
- "encoding/json"
- "fmt"
- "strings"
- "unsafe"
- "golang.org/x/sys/windows"
- "golang.org/x/sys/windows/svc"
- "golang.org/x/sys/windows/svc/mgr"
- "tailscale.com/types/logger"
- "tailscale.com/util/set"
- )
- // LogSvcState obtains the state of the Windows service named rootSvcName and
- // all of its dependencies, and then emits that state to logf.
- func LogSvcState(logf logger.Logf, rootSvcName string) {
- logEntries := []svcStateLogEntry{}
- walkFn := func(svc *mgr.Service, config mgr.Config) {
- status, err := svc.Query()
- if err != nil {
- logf("Failed retrieving Status for service %q: %v", svc.Name, err)
- }
- logEntries = append(logEntries, makeLogEntry(svc, status, config))
- }
- err := walkServices(rootSvcName, walkFn)
- if err != nil {
- logf("LogSvcState error: %v", err)
- return
- }
- json, err := json.MarshalIndent(logEntries, "", " ")
- if err != nil {
- logf("Error marshaling service log entries: %v", err)
- return
- }
- var builder strings.Builder
- builder.WriteString("State of service ")
- fmt.Fprintf(&builder, "%q", rootSvcName)
- builder.WriteString(" and its dependencies:")
- builder.WriteString("\n")
- builder.Write(json)
- builder.WriteString("\n")
- logf(builder.String())
- }
- // walkSvcFunc is type of the callback function invoked by WalkServices.
- type walkSvcFunc func(*mgr.Service, mgr.Config)
- // walkServices opens the service named rootSvcName and walks its dependency
- // graph, invoking callback for each service (including the root itself).
- func walkServices(rootSvcName string, callback walkSvcFunc) error {
- scm, err := ConnectToLocalSCMForRead()
- if err != nil {
- return fmt.Errorf("connecting to Service Control Manager: %w", err)
- }
- defer scm.Disconnect()
- rootSvc, err := OpenServiceForRead(scm, rootSvcName)
- if err != nil {
- return fmt.Errorf("opening service %q: %w", rootSvcName, err)
- }
- deps := []*mgr.Service{rootSvc}
- defer func() {
- // Any service still in deps when we return is open and must be closed.
- for _, dep := range deps {
- dep.Close()
- }
- }()
- seen := set.Set[string]{}
- for err == nil && len(deps) > 0 {
- err = func() error {
- curSvc := deps[len(deps)-1]
- defer curSvc.Close()
- deps = deps[:len(deps)-1]
- seen.Add(curSvc.Name)
- curCfg, err := curSvc.Config()
- if err != nil {
- return fmt.Errorf("retrieving Config for service %q: %w", curSvc.Name, err)
- }
- callback(curSvc, curCfg)
- for _, depName := range curCfg.Dependencies {
- if seen.Contains(depName) {
- continue
- }
- depSvc, err := OpenServiceForRead(scm, depName)
- if err != nil {
- return fmt.Errorf("opening service %q: %w", depName, err)
- }
- deps = append(deps, depSvc)
- }
- return nil
- }()
- }
- return err
- }
- type svcStateLogEntry struct {
- ServiceName string `json:"serviceName"`
- ServiceType string `json:"serviceType"`
- State string `json:"state"`
- StartupType string `json:"startupType"`
- Triggers *_SERVICE_TRIGGER_INFO `json:"triggers,omitempty"`
- TriggersError error `json:"triggersError,omitempty"`
- }
- type _SERVICE_TRIGGER_SPECIFIC_DATA_ITEM struct {
- dataType uint32
- cbData uint32
- data *byte
- }
- type serviceTriggerSpecificDataItemJSONMarshal struct {
- DataType uint32 `json:"dataType"`
- Data string `json:"data,omitempty"`
- }
- func (tsdi *_SERVICE_TRIGGER_SPECIFIC_DATA_ITEM) MarshalJSON() ([]byte, error) {
- m := serviceTriggerSpecificDataItemJSONMarshal{DataType: tsdi.dataType}
- const maxDataLen = 128
- data := unsafe.Slice(tsdi.data, tsdi.cbData)
- if len(data) > maxDataLen {
- // Only output the first maxDataLen bytes.
- m.Data = fmt.Sprintf("%s... (truncated %d bytes)", hex.EncodeToString(data[:maxDataLen]), len(data)-maxDataLen)
- } else {
- m.Data = hex.EncodeToString(data)
- }
- return json.Marshal(m)
- }
- type _SERVICE_TRIGGER struct {
- triggerType uint32
- action uint32
- triggerSubtype *windows.GUID
- cDataItems uint32
- pDataItems *_SERVICE_TRIGGER_SPECIFIC_DATA_ITEM
- }
- type serviceTriggerJSONMarshal struct {
- TriggerType uint32 `json:"triggerType"`
- Action uint32 `json:"action"`
- TriggerSubtype string `json:"triggerSubtype,omitempty"`
- DataItems []_SERVICE_TRIGGER_SPECIFIC_DATA_ITEM `json:"dataItems"`
- }
- func (ti *_SERVICE_TRIGGER) MarshalJSON() ([]byte, error) {
- m := serviceTriggerJSONMarshal{
- TriggerType: ti.triggerType,
- Action: ti.action,
- DataItems: unsafe.Slice(ti.pDataItems, ti.cDataItems),
- }
- if ti.triggerSubtype != nil {
- m.TriggerSubtype = ti.triggerSubtype.String()
- }
- return json.Marshal(m)
- }
- type _SERVICE_TRIGGER_INFO struct {
- cTriggers uint32
- pTriggers *_SERVICE_TRIGGER
- _ *byte // pReserved
- }
- func (sti *_SERVICE_TRIGGER_INFO) MarshalJSON() ([]byte, error) {
- triggers := unsafe.Slice(sti.pTriggers, sti.cTriggers)
- return json.Marshal(triggers)
- }
- // getSvcTriggerInfo obtains information about any system events that may be
- // used to start svc. Only relevant for demand-start (aka manual) services.
- func getSvcTriggerInfo(svc *mgr.Service) (*_SERVICE_TRIGGER_INFO, error) {
- var desiredLen uint32
- err := queryServiceConfig2(svc.Handle, windows.SERVICE_CONFIG_TRIGGER_INFO,
- nil, 0, &desiredLen)
- if err != windows.ERROR_INSUFFICIENT_BUFFER {
- return nil, err
- }
- buf := make([]byte, desiredLen)
- err = queryServiceConfig2(svc.Handle, windows.SERVICE_CONFIG_TRIGGER_INFO,
- &buf[0], desiredLen, &desiredLen)
- if err != nil {
- return nil, err
- }
- return (*_SERVICE_TRIGGER_INFO)(unsafe.Pointer(&buf[0])), nil
- }
- // makeLogEntry consolidates relevant service information into a svcStateLogEntry.
- // We record the values of various service configuration constants as strings
- // so the the log entries are easy to interpret at a glance by humans.
- func makeLogEntry(svc *mgr.Service, status svc.Status, cfg mgr.Config) (entry svcStateLogEntry) {
- entry.ServiceName = svc.Name
- switch status.State {
- case windows.SERVICE_STOPPED:
- entry.State = "STOPPED"
- case windows.SERVICE_START_PENDING:
- entry.State = "START_PENDING"
- case windows.SERVICE_STOP_PENDING:
- entry.State = "STOP_PENDING"
- case windows.SERVICE_RUNNING:
- entry.State = "RUNNING"
- case windows.SERVICE_CONTINUE_PENDING:
- entry.State = "CONTINUE_PENDING"
- case windows.SERVICE_PAUSE_PENDING:
- entry.State = "PAUSE_PENDING"
- case windows.SERVICE_PAUSED:
- entry.State = "PAUSED"
- case windows.SERVICE_NO_CHANGE:
- entry.State = "NO_CHANGE"
- default:
- entry.State = fmt.Sprintf("Unknown constant %d", status.State)
- }
- switch cfg.ServiceType {
- case windows.SERVICE_FILE_SYSTEM_DRIVER:
- entry.ServiceType = "FILE_SYSTEM_DRIVER"
- case windows.SERVICE_KERNEL_DRIVER:
- entry.ServiceType = "KERNEL_DRIVER"
- case windows.SERVICE_WIN32_OWN_PROCESS, windows.SERVICE_WIN32_SHARE_PROCESS:
- entry.ServiceType = "WIN32"
- default:
- entry.ServiceType = fmt.Sprintf("Unknown constant %d", cfg.ServiceType)
- }
- switch cfg.StartType {
- case windows.SERVICE_BOOT_START:
- entry.StartupType = "BOOT_START"
- case windows.SERVICE_SYSTEM_START:
- entry.StartupType = "SYSTEM_START"
- case windows.SERVICE_AUTO_START:
- if cfg.DelayedAutoStart {
- entry.StartupType = "DELAYED_AUTO_START"
- } else {
- entry.StartupType = "AUTO_START"
- }
- case windows.SERVICE_DEMAND_START:
- entry.StartupType = "DEMAND_START"
- triggerInfo, err := getSvcTriggerInfo(svc)
- if err == nil {
- entry.Triggers = triggerInfo
- } else {
- entry.TriggersError = err
- }
- case windows.SERVICE_DISABLED:
- entry.StartupType = "DISABLED"
- default:
- entry.StartupType = fmt.Sprintf("Unknown constant %d", cfg.StartType)
- }
- return entry
- }
- // ConnectToLocalSCMForRead connects to the Windows Service Control Manager with
- // read-only access. x/sys/windows/svc/mgr/Connect requests read+write access,
- // which requires Administrative access rights.
- func ConnectToLocalSCMForRead() (*mgr.Mgr, error) {
- h, err := windows.OpenSCManager(nil, nil, windows.GENERIC_READ)
- if err != nil {
- return nil, err
- }
- return &mgr.Mgr{Handle: h}, nil
- }
- // OpenServiceForRead opens a service with read-only access.
- // x/sys/windows/svc/mgr/(*Mgr).OpenService requests read+write access,
- // which requires Administrative access rights.
- func OpenServiceForRead(scm *mgr.Mgr, svcName string) (*mgr.Service, error) {
- svcNamePtr, err := windows.UTF16PtrFromString(svcName)
- if err != nil {
- return nil, err
- }
- h, err := windows.OpenService(scm.Handle, svcNamePtr, windows.GENERIC_READ)
- if err != nil {
- return nil, err
- }
- return &mgr.Service{Name: svcName, Handle: h}, nil
- }
|