svcdiag_windows.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package winutil
  4. import (
  5. "encoding/hex"
  6. "encoding/json"
  7. "fmt"
  8. "strings"
  9. "unsafe"
  10. "golang.org/x/sys/windows"
  11. "golang.org/x/sys/windows/svc"
  12. "golang.org/x/sys/windows/svc/mgr"
  13. "tailscale.com/types/logger"
  14. "tailscale.com/util/set"
  15. )
  16. // LogSvcState obtains the state of the Windows service named rootSvcName and
  17. // all of its dependencies, and then emits that state to logf.
  18. func LogSvcState(logf logger.Logf, rootSvcName string) {
  19. logEntries := []svcStateLogEntry{}
  20. walkFn := func(svc *mgr.Service, config mgr.Config) {
  21. status, err := svc.Query()
  22. if err != nil {
  23. logf("Failed retrieving Status for service %q: %v", svc.Name, err)
  24. }
  25. logEntries = append(logEntries, makeLogEntry(svc, status, config))
  26. }
  27. err := walkServices(rootSvcName, walkFn)
  28. if err != nil {
  29. logf("LogSvcState error: %v", err)
  30. return
  31. }
  32. json, err := json.MarshalIndent(logEntries, "", " ")
  33. if err != nil {
  34. logf("Error marshaling service log entries: %v", err)
  35. return
  36. }
  37. var builder strings.Builder
  38. builder.WriteString("State of service ")
  39. fmt.Fprintf(&builder, "%q", rootSvcName)
  40. builder.WriteString(" and its dependencies:")
  41. builder.WriteString("\n")
  42. builder.Write(json)
  43. builder.WriteString("\n")
  44. logf(builder.String())
  45. }
  46. // walkSvcFunc is type of the callback function invoked by WalkServices.
  47. type walkSvcFunc func(*mgr.Service, mgr.Config)
  48. // walkServices opens the service named rootSvcName and walks its dependency
  49. // graph, invoking callback for each service (including the root itself).
  50. func walkServices(rootSvcName string, callback walkSvcFunc) error {
  51. scm, err := ConnectToLocalSCMForRead()
  52. if err != nil {
  53. return fmt.Errorf("connecting to Service Control Manager: %w", err)
  54. }
  55. defer scm.Disconnect()
  56. rootSvc, err := OpenServiceForRead(scm, rootSvcName)
  57. if err != nil {
  58. return fmt.Errorf("opening service %q: %w", rootSvcName, err)
  59. }
  60. deps := []*mgr.Service{rootSvc}
  61. defer func() {
  62. // Any service still in deps when we return is open and must be closed.
  63. for _, dep := range deps {
  64. dep.Close()
  65. }
  66. }()
  67. seen := set.Set[string]{}
  68. for err == nil && len(deps) > 0 {
  69. err = func() error {
  70. curSvc := deps[len(deps)-1]
  71. defer curSvc.Close()
  72. deps = deps[:len(deps)-1]
  73. seen.Add(curSvc.Name)
  74. curCfg, err := curSvc.Config()
  75. if err != nil {
  76. return fmt.Errorf("retrieving Config for service %q: %w", curSvc.Name, err)
  77. }
  78. callback(curSvc, curCfg)
  79. for _, depName := range curCfg.Dependencies {
  80. if seen.Contains(depName) {
  81. continue
  82. }
  83. depSvc, err := OpenServiceForRead(scm, depName)
  84. if err != nil {
  85. return fmt.Errorf("opening service %q: %w", depName, err)
  86. }
  87. deps = append(deps, depSvc)
  88. }
  89. return nil
  90. }()
  91. }
  92. return err
  93. }
  94. type svcStateLogEntry struct {
  95. ServiceName string `json:"serviceName"`
  96. ServiceType string `json:"serviceType"`
  97. State string `json:"state"`
  98. StartupType string `json:"startupType"`
  99. Triggers *_SERVICE_TRIGGER_INFO `json:"triggers,omitempty"`
  100. TriggersError error `json:"triggersError,omitempty"`
  101. }
  102. type _SERVICE_TRIGGER_SPECIFIC_DATA_ITEM struct {
  103. dataType uint32
  104. cbData uint32
  105. data *byte
  106. }
  107. type serviceTriggerSpecificDataItemJSONMarshal struct {
  108. DataType uint32 `json:"dataType"`
  109. Data string `json:"data,omitempty"`
  110. }
  111. func (tsdi *_SERVICE_TRIGGER_SPECIFIC_DATA_ITEM) MarshalJSON() ([]byte, error) {
  112. m := serviceTriggerSpecificDataItemJSONMarshal{DataType: tsdi.dataType}
  113. const maxDataLen = 128
  114. data := unsafe.Slice(tsdi.data, tsdi.cbData)
  115. if len(data) > maxDataLen {
  116. // Only output the first maxDataLen bytes.
  117. m.Data = fmt.Sprintf("%s... (truncated %d bytes)", hex.EncodeToString(data[:maxDataLen]), len(data)-maxDataLen)
  118. } else {
  119. m.Data = hex.EncodeToString(data)
  120. }
  121. return json.Marshal(m)
  122. }
  123. type _SERVICE_TRIGGER struct {
  124. triggerType uint32
  125. action uint32
  126. triggerSubtype *windows.GUID
  127. cDataItems uint32
  128. pDataItems *_SERVICE_TRIGGER_SPECIFIC_DATA_ITEM
  129. }
  130. type serviceTriggerJSONMarshal struct {
  131. TriggerType uint32 `json:"triggerType"`
  132. Action uint32 `json:"action"`
  133. TriggerSubtype string `json:"triggerSubtype,omitempty"`
  134. DataItems []_SERVICE_TRIGGER_SPECIFIC_DATA_ITEM `json:"dataItems"`
  135. }
  136. func (ti *_SERVICE_TRIGGER) MarshalJSON() ([]byte, error) {
  137. m := serviceTriggerJSONMarshal{
  138. TriggerType: ti.triggerType,
  139. Action: ti.action,
  140. DataItems: unsafe.Slice(ti.pDataItems, ti.cDataItems),
  141. }
  142. if ti.triggerSubtype != nil {
  143. m.TriggerSubtype = ti.triggerSubtype.String()
  144. }
  145. return json.Marshal(m)
  146. }
  147. type _SERVICE_TRIGGER_INFO struct {
  148. cTriggers uint32
  149. pTriggers *_SERVICE_TRIGGER
  150. _ *byte // pReserved
  151. }
  152. func (sti *_SERVICE_TRIGGER_INFO) MarshalJSON() ([]byte, error) {
  153. triggers := unsafe.Slice(sti.pTriggers, sti.cTriggers)
  154. return json.Marshal(triggers)
  155. }
  156. // getSvcTriggerInfo obtains information about any system events that may be
  157. // used to start svc. Only relevant for demand-start (aka manual) services.
  158. func getSvcTriggerInfo(svc *mgr.Service) (*_SERVICE_TRIGGER_INFO, error) {
  159. var desiredLen uint32
  160. err := queryServiceConfig2(svc.Handle, windows.SERVICE_CONFIG_TRIGGER_INFO,
  161. nil, 0, &desiredLen)
  162. if err != windows.ERROR_INSUFFICIENT_BUFFER {
  163. return nil, err
  164. }
  165. buf := make([]byte, desiredLen)
  166. err = queryServiceConfig2(svc.Handle, windows.SERVICE_CONFIG_TRIGGER_INFO,
  167. &buf[0], desiredLen, &desiredLen)
  168. if err != nil {
  169. return nil, err
  170. }
  171. return (*_SERVICE_TRIGGER_INFO)(unsafe.Pointer(&buf[0])), nil
  172. }
  173. // makeLogEntry consolidates relevant service information into a svcStateLogEntry.
  174. // We record the values of various service configuration constants as strings
  175. // so the the log entries are easy to interpret at a glance by humans.
  176. func makeLogEntry(svc *mgr.Service, status svc.Status, cfg mgr.Config) (entry svcStateLogEntry) {
  177. entry.ServiceName = svc.Name
  178. switch status.State {
  179. case windows.SERVICE_STOPPED:
  180. entry.State = "STOPPED"
  181. case windows.SERVICE_START_PENDING:
  182. entry.State = "START_PENDING"
  183. case windows.SERVICE_STOP_PENDING:
  184. entry.State = "STOP_PENDING"
  185. case windows.SERVICE_RUNNING:
  186. entry.State = "RUNNING"
  187. case windows.SERVICE_CONTINUE_PENDING:
  188. entry.State = "CONTINUE_PENDING"
  189. case windows.SERVICE_PAUSE_PENDING:
  190. entry.State = "PAUSE_PENDING"
  191. case windows.SERVICE_PAUSED:
  192. entry.State = "PAUSED"
  193. case windows.SERVICE_NO_CHANGE:
  194. entry.State = "NO_CHANGE"
  195. default:
  196. entry.State = fmt.Sprintf("Unknown constant %d", status.State)
  197. }
  198. switch cfg.ServiceType {
  199. case windows.SERVICE_FILE_SYSTEM_DRIVER:
  200. entry.ServiceType = "FILE_SYSTEM_DRIVER"
  201. case windows.SERVICE_KERNEL_DRIVER:
  202. entry.ServiceType = "KERNEL_DRIVER"
  203. case windows.SERVICE_WIN32_OWN_PROCESS, windows.SERVICE_WIN32_SHARE_PROCESS:
  204. entry.ServiceType = "WIN32"
  205. default:
  206. entry.ServiceType = fmt.Sprintf("Unknown constant %d", cfg.ServiceType)
  207. }
  208. switch cfg.StartType {
  209. case windows.SERVICE_BOOT_START:
  210. entry.StartupType = "BOOT_START"
  211. case windows.SERVICE_SYSTEM_START:
  212. entry.StartupType = "SYSTEM_START"
  213. case windows.SERVICE_AUTO_START:
  214. if cfg.DelayedAutoStart {
  215. entry.StartupType = "DELAYED_AUTO_START"
  216. } else {
  217. entry.StartupType = "AUTO_START"
  218. }
  219. case windows.SERVICE_DEMAND_START:
  220. entry.StartupType = "DEMAND_START"
  221. triggerInfo, err := getSvcTriggerInfo(svc)
  222. if err == nil {
  223. entry.Triggers = triggerInfo
  224. } else {
  225. entry.TriggersError = err
  226. }
  227. case windows.SERVICE_DISABLED:
  228. entry.StartupType = "DISABLED"
  229. default:
  230. entry.StartupType = fmt.Sprintf("Unknown constant %d", cfg.StartType)
  231. }
  232. return entry
  233. }
  234. // ConnectToLocalSCMForRead connects to the Windows Service Control Manager with
  235. // read-only access. x/sys/windows/svc/mgr/Connect requests read+write access,
  236. // which requires Administrative access rights.
  237. func ConnectToLocalSCMForRead() (*mgr.Mgr, error) {
  238. h, err := windows.OpenSCManager(nil, nil, windows.GENERIC_READ)
  239. if err != nil {
  240. return nil, err
  241. }
  242. return &mgr.Mgr{Handle: h}, nil
  243. }
  244. // OpenServiceForRead opens a service with read-only access.
  245. // x/sys/windows/svc/mgr/(*Mgr).OpenService requests read+write access,
  246. // which requires Administrative access rights.
  247. func OpenServiceForRead(scm *mgr.Mgr, svcName string) (*mgr.Service, error) {
  248. svcNamePtr, err := windows.UTF16PtrFromString(svcName)
  249. if err != nil {
  250. return nil, err
  251. }
  252. h, err := windows.OpenService(scm.Handle, svcNamePtr, windows.GENERIC_READ)
  253. if err != nil {
  254. return nil, err
  255. }
  256. return &mgr.Service{Name: svcName, Handle: h}, nil
  257. }