service.go 9.7 KB

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