You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

317 lines
8.2 KiB

  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. // +build windows
  5. // Package svc provides everything required to build Windows service.
  6. //
  7. package svc
  8. import (
  9. "errors"
  10. "runtime"
  11. "syscall"
  12. "unsafe"
  13. "golang.org/x/sys/windows"
  14. )
  15. // State describes service execution state (Stopped, Running and so on).
  16. type State uint32
  17. const (
  18. Stopped = State(windows.SERVICE_STOPPED)
  19. StartPending = State(windows.SERVICE_START_PENDING)
  20. StopPending = State(windows.SERVICE_STOP_PENDING)
  21. Running = State(windows.SERVICE_RUNNING)
  22. ContinuePending = State(windows.SERVICE_CONTINUE_PENDING)
  23. PausePending = State(windows.SERVICE_PAUSE_PENDING)
  24. Paused = State(windows.SERVICE_PAUSED)
  25. )
  26. // Cmd represents service state change request. It is sent to a service
  27. // by the service manager, and should be actioned upon by the service.
  28. type Cmd uint32
  29. const (
  30. Stop = Cmd(windows.SERVICE_CONTROL_STOP)
  31. Pause = Cmd(windows.SERVICE_CONTROL_PAUSE)
  32. Continue = Cmd(windows.SERVICE_CONTROL_CONTINUE)
  33. Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
  34. Shutdown = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
  35. )
  36. // Accepted is used to describe commands accepted by the service.
  37. // Note that Interrogate is always accepted.
  38. type Accepted uint32
  39. const (
  40. AcceptStop = Accepted(windows.SERVICE_ACCEPT_STOP)
  41. AcceptShutdown = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
  42. AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
  43. )
  44. // Status combines State and Accepted commands to fully describe running service.
  45. type Status struct {
  46. State State
  47. Accepts Accepted
  48. CheckPoint uint32 // used to report progress during a lengthy operation
  49. WaitHint uint32 // estimated time required for a pending operation, in milliseconds
  50. }
  51. // ChangeRequest is sent to the service Handler to request service status change.
  52. type ChangeRequest struct {
  53. Cmd Cmd
  54. CurrentStatus Status
  55. }
  56. // Handler is the interface that must be implemented to build Windows service.
  57. type Handler interface {
  58. // Execute will be called by the package code at the start of
  59. // the service, and the service will exit once Execute completes.
  60. // Inside Execute you must read service change requests from r and
  61. // act accordingly. You must keep service control manager up to date
  62. // about state of your service by writing into s as required.
  63. // args contains service name followed by argument strings passed
  64. // to the service.
  65. // You can provide service exit code in exitCode return parameter,
  66. // with 0 being "no error". You can also indicate if exit code,
  67. // if any, is service specific or not by using svcSpecificEC
  68. // parameter.
  69. Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
  70. }
  71. var (
  72. // These are used by asm code.
  73. goWaitsH uintptr
  74. cWaitsH uintptr
  75. ssHandle uintptr
  76. sName *uint16
  77. sArgc uintptr
  78. sArgv **uint16
  79. ctlHandlerProc uintptr
  80. cSetEvent uintptr
  81. cWaitForSingleObject uintptr
  82. cRegisterServiceCtrlHandlerW uintptr
  83. )
  84. func init() {
  85. k := syscall.MustLoadDLL("kernel32.dll")
  86. cSetEvent = k.MustFindProc("SetEvent").Addr()
  87. cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr()
  88. a := syscall.MustLoadDLL("advapi32.dll")
  89. cRegisterServiceCtrlHandlerW = a.MustFindProc("RegisterServiceCtrlHandlerW").Addr()
  90. }
  91. type ctlEvent struct {
  92. cmd Cmd
  93. errno uint32
  94. }
  95. // service provides access to windows service api.
  96. type service struct {
  97. name string
  98. h windows.Handle
  99. cWaits *event
  100. goWaits *event
  101. c chan ctlEvent
  102. handler Handler
  103. }
  104. func newService(name string, handler Handler) (*service, error) {
  105. var s service
  106. var err error
  107. s.name = name
  108. s.c = make(chan ctlEvent)
  109. s.handler = handler
  110. s.cWaits, err = newEvent()
  111. if err != nil {
  112. return nil, err
  113. }
  114. s.goWaits, err = newEvent()
  115. if err != nil {
  116. s.cWaits.Close()
  117. return nil, err
  118. }
  119. return &s, nil
  120. }
  121. func (s *service) close() error {
  122. s.cWaits.Close()
  123. s.goWaits.Close()
  124. return nil
  125. }
  126. type exitCode struct {
  127. isSvcSpecific bool
  128. errno uint32
  129. }
  130. func (s *service) updateStatus(status *Status, ec *exitCode) error {
  131. if s.h == 0 {
  132. return errors.New("updateStatus with no service status handle")
  133. }
  134. var t windows.SERVICE_STATUS
  135. t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
  136. t.CurrentState = uint32(status.State)
  137. if status.Accepts&AcceptStop != 0 {
  138. t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
  139. }
  140. if status.Accepts&AcceptShutdown != 0 {
  141. t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
  142. }
  143. if status.Accepts&AcceptPauseAndContinue != 0 {
  144. t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
  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. const (
  161. sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota
  162. sysErrNewThreadInCallback
  163. )
  164. func (s *service) run() {
  165. s.goWaits.Wait()
  166. s.h = windows.Handle(ssHandle)
  167. argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc]
  168. args := make([]string, len(argv))
  169. for i, a := range argv {
  170. args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:])
  171. }
  172. cmdsToHandler := make(chan ChangeRequest)
  173. changesFromHandler := make(chan Status)
  174. exitFromHandler := make(chan exitCode)
  175. go func() {
  176. ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler)
  177. exitFromHandler <- exitCode{ss, errno}
  178. }()
  179. status := Status{State: Stopped}
  180. ec := exitCode{isSvcSpecific: true, errno: 0}
  181. var outch chan ChangeRequest
  182. inch := s.c
  183. var cmd Cmd
  184. loop:
  185. for {
  186. select {
  187. case r := <-inch:
  188. if r.errno != 0 {
  189. ec.errno = r.errno
  190. break loop
  191. }
  192. inch = nil
  193. outch = cmdsToHandler
  194. cmd = r.cmd
  195. case outch <- ChangeRequest{cmd, status}:
  196. inch = s.c
  197. outch = nil
  198. case c := <-changesFromHandler:
  199. err := s.updateStatus(&c, &ec)
  200. if err != nil {
  201. // best suitable error number
  202. ec.errno = sysErrSetServiceStatusFailed
  203. if err2, ok := err.(syscall.Errno); ok {
  204. ec.errno = uint32(err2)
  205. }
  206. break loop
  207. }
  208. status = c
  209. case ec = <-exitFromHandler:
  210. break loop
  211. }
  212. }
  213. s.updateStatus(&Status{State: Stopped}, &ec)
  214. s.cWaits.Set()
  215. }
  216. func newCallback(fn interface{}) (cb uintptr, err error) {
  217. defer func() {
  218. r := recover()
  219. if r == nil {
  220. return
  221. }
  222. cb = 0
  223. switch v := r.(type) {
  224. case string:
  225. err = errors.New(v)
  226. case error:
  227. err = v
  228. default:
  229. err = errors.New("unexpected panic in syscall.NewCallback")
  230. }
  231. }()
  232. return syscall.NewCallback(fn), nil
  233. }
  234. // BUG(brainman): There is no mechanism to run multiple services
  235. // inside one single executable. Perhaps, it can be overcome by
  236. // using RegisterServiceCtrlHandlerEx Windows api.
  237. // Run executes service name by calling appropriate handler function.
  238. func Run(name string, handler Handler) error {
  239. runtime.LockOSThread()
  240. tid := windows.GetCurrentThreadId()
  241. s, err := newService(name, handler)
  242. if err != nil {
  243. return err
  244. }
  245. ctlHandler := func(ctl uint32) uintptr {
  246. e := ctlEvent{cmd: Cmd(ctl)}
  247. // We assume that this callback function is running on
  248. // the same thread as Run. Nowhere in MS documentation
  249. // I could find statement to guarantee that. So putting
  250. // check here to verify, otherwise things will go bad
  251. // quickly, if ignored.
  252. i := windows.GetCurrentThreadId()
  253. if i != tid {
  254. e.errno = sysErrNewThreadInCallback
  255. }
  256. s.c <- e
  257. return 0
  258. }
  259. var svcmain uintptr
  260. getServiceMain(&svcmain)
  261. t := []windows.SERVICE_TABLE_ENTRY{
  262. {syscall.StringToUTF16Ptr(s.name), svcmain},
  263. {nil, 0},
  264. }
  265. goWaitsH = uintptr(s.goWaits.h)
  266. cWaitsH = uintptr(s.cWaits.h)
  267. sName = t[0].ServiceName
  268. ctlHandlerProc, err = newCallback(ctlHandler)
  269. if err != nil {
  270. return err
  271. }
  272. go s.run()
  273. err = windows.StartServiceCtrlDispatcher(&t[0])
  274. if err != nil {
  275. return err
  276. }
  277. return nil
  278. }