service.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. // Copyright 2012 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. //go:build windows
  5. // +build windows
  6. // Package svc provides everything required to build Windows service.
  7. package svc
  8. import (
  9. "errors"
  10. "sync"
  11. "unsafe"
  12. "golang.org/x/sys/windows"
  13. )
  14. // State describes service execution state (Stopped, Running and so on).
  15. type State uint32
  16. const (
  17. Stopped = State(windows.SERVICE_STOPPED)
  18. StartPending = State(windows.SERVICE_START_PENDING)
  19. StopPending = State(windows.SERVICE_STOP_PENDING)
  20. Running = State(windows.SERVICE_RUNNING)
  21. ContinuePending = State(windows.SERVICE_CONTINUE_PENDING)
  22. PausePending = State(windows.SERVICE_PAUSE_PENDING)
  23. Paused = State(windows.SERVICE_PAUSED)
  24. )
  25. // Cmd represents service state change request. It is sent to a service
  26. // by the service manager, and should be actioned upon by the service.
  27. type Cmd uint32
  28. const (
  29. Stop = Cmd(windows.SERVICE_CONTROL_STOP)
  30. Pause = Cmd(windows.SERVICE_CONTROL_PAUSE)
  31. Continue = Cmd(windows.SERVICE_CONTROL_CONTINUE)
  32. Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
  33. Shutdown = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
  34. ParamChange = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE)
  35. NetBindAdd = Cmd(windows.SERVICE_CONTROL_NETBINDADD)
  36. NetBindRemove = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE)
  37. NetBindEnable = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE)
  38. NetBindDisable = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE)
  39. DeviceEvent = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT)
  40. HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE)
  41. PowerEvent = Cmd(windows.SERVICE_CONTROL_POWEREVENT)
  42. SessionChange = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE)
  43. PreShutdown = Cmd(windows.SERVICE_CONTROL_PRESHUTDOWN)
  44. )
  45. // Accepted is used to describe commands accepted by the service.
  46. // Note that Interrogate is always accepted.
  47. type Accepted uint32
  48. const (
  49. AcceptStop = Accepted(windows.SERVICE_ACCEPT_STOP)
  50. AcceptShutdown = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
  51. AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
  52. AcceptParamChange = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)
  53. AcceptNetBindChange = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE)
  54. AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE)
  55. AcceptPowerEvent = Accepted(windows.SERVICE_ACCEPT_POWEREVENT)
  56. AcceptSessionChange = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE)
  57. AcceptPreShutdown = Accepted(windows.SERVICE_ACCEPT_PRESHUTDOWN)
  58. )
  59. // ActivityStatus allows for services to be selected based on active and inactive categories of service state.
  60. type ActivityStatus uint32
  61. const (
  62. Active = ActivityStatus(windows.SERVICE_ACTIVE)
  63. Inactive = ActivityStatus(windows.SERVICE_INACTIVE)
  64. AnyActivity = ActivityStatus(windows.SERVICE_STATE_ALL)
  65. )
  66. // Status combines State and Accepted commands to fully describe running service.
  67. type Status struct {
  68. State State
  69. Accepts Accepted
  70. CheckPoint uint32 // used to report progress during a lengthy operation
  71. WaitHint uint32 // estimated time required for a pending operation, in milliseconds
  72. ProcessId uint32 // if the service is running, the process identifier of it, and otherwise zero
  73. Win32ExitCode uint32 // set if the service has exited with a win32 exit code
  74. ServiceSpecificExitCode uint32 // set if the service has exited with a service-specific exit code
  75. }
  76. // StartReason is the reason that the service was started.
  77. type StartReason uint32
  78. const (
  79. StartReasonDemand = StartReason(windows.SERVICE_START_REASON_DEMAND)
  80. StartReasonAuto = StartReason(windows.SERVICE_START_REASON_AUTO)
  81. StartReasonTrigger = StartReason(windows.SERVICE_START_REASON_TRIGGER)
  82. StartReasonRestartOnFailure = StartReason(windows.SERVICE_START_REASON_RESTART_ON_FAILURE)
  83. StartReasonDelayedAuto = StartReason(windows.SERVICE_START_REASON_DELAYEDAUTO)
  84. )
  85. // ChangeRequest is sent to the service Handler to request service status change.
  86. type ChangeRequest struct {
  87. Cmd Cmd
  88. EventType uint32
  89. EventData uintptr
  90. CurrentStatus Status
  91. Context uintptr
  92. }
  93. // Handler is the interface that must be implemented to build Windows service.
  94. type Handler interface {
  95. // Execute will be called by the package code at the start of
  96. // the service, and the service will exit once Execute completes.
  97. // Inside Execute you must read service change requests from r and
  98. // act accordingly. You must keep service control manager up to date
  99. // about state of your service by writing into s as required.
  100. // args contains service name followed by argument strings passed
  101. // to the service.
  102. // You can provide service exit code in exitCode return parameter,
  103. // with 0 being "no error". You can also indicate if exit code,
  104. // if any, is service specific or not by using svcSpecificEC
  105. // parameter.
  106. Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
  107. }
  108. type ctlEvent struct {
  109. cmd Cmd
  110. eventType uint32
  111. eventData uintptr
  112. context uintptr
  113. errno uint32
  114. }
  115. // service provides access to windows service api.
  116. type service struct {
  117. name string
  118. h windows.Handle
  119. c chan ctlEvent
  120. handler Handler
  121. }
  122. type exitCode struct {
  123. isSvcSpecific bool
  124. errno uint32
  125. }
  126. func (s *service) updateStatus(status *Status, ec *exitCode) error {
  127. if s.h == 0 {
  128. return errors.New("updateStatus with no service status handle")
  129. }
  130. var t windows.SERVICE_STATUS
  131. t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
  132. t.CurrentState = uint32(status.State)
  133. if status.Accepts&AcceptStop != 0 {
  134. t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
  135. }
  136. if status.Accepts&AcceptShutdown != 0 {
  137. t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
  138. }
  139. if status.Accepts&AcceptPauseAndContinue != 0 {
  140. t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
  141. }
  142. if status.Accepts&AcceptParamChange != 0 {
  143. t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
  144. }
  145. if status.Accepts&AcceptNetBindChange != 0 {
  146. t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
  147. }
  148. if status.Accepts&AcceptHardwareProfileChange != 0 {
  149. t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
  150. }
  151. if status.Accepts&AcceptPowerEvent != 0 {
  152. t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
  153. }
  154. if status.Accepts&AcceptSessionChange != 0 {
  155. t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
  156. }
  157. if status.Accepts&AcceptPreShutdown != 0 {
  158. t.ControlsAccepted |= windows.SERVICE_ACCEPT_PRESHUTDOWN
  159. }
  160. if ec.errno == 0 {
  161. t.Win32ExitCode = windows.NO_ERROR
  162. t.ServiceSpecificExitCode = windows.NO_ERROR
  163. } else if ec.isSvcSpecific {
  164. t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
  165. t.ServiceSpecificExitCode = ec.errno
  166. } else {
  167. t.Win32ExitCode = ec.errno
  168. t.ServiceSpecificExitCode = windows.NO_ERROR
  169. }
  170. t.CheckPoint = status.CheckPoint
  171. t.WaitHint = status.WaitHint
  172. return windows.SetServiceStatus(s.h, &t)
  173. }
  174. var (
  175. initCallbacks sync.Once
  176. ctlHandlerCallback uintptr
  177. serviceMainCallback uintptr
  178. )
  179. func ctlHandler(ctl, evtype, evdata, context uintptr) uintptr {
  180. s := (*service)(unsafe.Pointer(context))
  181. e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: 123456} // Set context to 123456 to test issue #25660.
  182. s.c <- e
  183. return 0
  184. }
  185. var theService service // This is, unfortunately, a global, which means only one service per process.
  186. // serviceMain is the entry point called by the service manager, registered earlier by
  187. // the call to StartServiceCtrlDispatcher.
  188. func serviceMain(argc uint32, argv **uint16) uintptr {
  189. handle, err := windows.RegisterServiceCtrlHandlerEx(windows.StringToUTF16Ptr(theService.name), ctlHandlerCallback, uintptr(unsafe.Pointer(&theService)))
  190. if sysErr, ok := err.(windows.Errno); ok {
  191. return uintptr(sysErr)
  192. } else if err != nil {
  193. return uintptr(windows.ERROR_UNKNOWN_EXCEPTION)
  194. }
  195. theService.h = handle
  196. defer func() {
  197. theService.h = 0
  198. }()
  199. args16 := unsafe.Slice(argv, int(argc))
  200. args := make([]string, len(args16))
  201. for i, a := range args16 {
  202. args[i] = windows.UTF16PtrToString(a)
  203. }
  204. cmdsToHandler := make(chan ChangeRequest)
  205. changesFromHandler := make(chan Status)
  206. exitFromHandler := make(chan exitCode)
  207. go func() {
  208. ss, errno := theService.handler.Execute(args, cmdsToHandler, changesFromHandler)
  209. exitFromHandler <- exitCode{ss, errno}
  210. }()
  211. ec := exitCode{isSvcSpecific: true, errno: 0}
  212. outcr := ChangeRequest{
  213. CurrentStatus: Status{State: Stopped},
  214. }
  215. var outch chan ChangeRequest
  216. inch := theService.c
  217. loop:
  218. for {
  219. select {
  220. case r := <-inch:
  221. if r.errno != 0 {
  222. ec.errno = r.errno
  223. break loop
  224. }
  225. inch = nil
  226. outch = cmdsToHandler
  227. outcr.Cmd = r.cmd
  228. outcr.EventType = r.eventType
  229. outcr.EventData = r.eventData
  230. outcr.Context = r.context
  231. case outch <- outcr:
  232. inch = theService.c
  233. outch = nil
  234. case c := <-changesFromHandler:
  235. err := theService.updateStatus(&c, &ec)
  236. if err != nil {
  237. ec.errno = uint32(windows.ERROR_EXCEPTION_IN_SERVICE)
  238. if err2, ok := err.(windows.Errno); ok {
  239. ec.errno = uint32(err2)
  240. }
  241. break loop
  242. }
  243. outcr.CurrentStatus = c
  244. case ec = <-exitFromHandler:
  245. break loop
  246. }
  247. }
  248. theService.updateStatus(&Status{State: Stopped}, &ec)
  249. return windows.NO_ERROR
  250. }
  251. // Run executes service name by calling appropriate handler function.
  252. func Run(name string, handler Handler) error {
  253. initCallbacks.Do(func() {
  254. ctlHandlerCallback = windows.NewCallback(ctlHandler)
  255. serviceMainCallback = windows.NewCallback(serviceMain)
  256. })
  257. theService.name = name
  258. theService.handler = handler
  259. theService.c = make(chan ctlEvent)
  260. t := []windows.SERVICE_TABLE_ENTRY{
  261. {ServiceName: windows.StringToUTF16Ptr(theService.name), ServiceProc: serviceMainCallback},
  262. {ServiceName: nil, ServiceProc: 0},
  263. }
  264. return windows.StartServiceCtrlDispatcher(&t[0])
  265. }
  266. // StatusHandle returns service status handle. It is safe to call this function
  267. // from inside the Handler.Execute because then it is guaranteed to be set.
  268. func StatusHandle() windows.Handle {
  269. return theService.h
  270. }
  271. // DynamicStartReason returns the reason why the service was started. It is safe
  272. // to call this function from inside the Handler.Execute because then it is
  273. // guaranteed to be set.
  274. func DynamicStartReason() (StartReason, error) {
  275. var allocReason *uint32
  276. err := windows.QueryServiceDynamicInformation(theService.h, windows.SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON, unsafe.Pointer(&allocReason))
  277. if err != nil {
  278. return 0, err
  279. }
  280. reason := StartReason(*allocReason)
  281. windows.LocalFree(windows.Handle(unsafe.Pointer(allocReason)))
  282. return reason, nil
  283. }