service_windows.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. // Copyright (c) 2013-2016 The btcsuite developers
  2. // Use of this source code is governed by an ISC
  3. // license that can be found in the LICENSE file.
  4. package main
  5. import (
  6. "fmt"
  7. "os"
  8. "path/filepath"
  9. "time"
  10. "github.com/pkt-cash/pktd/btcutil/er"
  11. "github.com/pkt-cash/pktd/pktconfig/version"
  12. "github.com/btcsuite/winsvc/eventlog"
  13. "github.com/btcsuite/winsvc/mgr"
  14. "github.com/btcsuite/winsvc/svc"
  15. )
  16. const (
  17. // svcName is the name of pktd service.
  18. svcName = "pktdsvc"
  19. // svcDisplayName is the service name that will be shown in the windows
  20. // services list. Not the svcName is the "real" name which is used
  21. // to control the service. This is only for display purposes.
  22. svcDisplayName = "PKTd Service"
  23. // svcDesc is the description of the service.
  24. svcDesc = "Synchronizes with the PKT blockchain" +
  25. "and provides access and services to applications."
  26. )
  27. // elog is used to send messages to the Windows event log.
  28. var elog *eventlog.Log
  29. // logServiceStartOfDay logs information about pktd when the main server has
  30. // been started to the Windows event log.
  31. func logServiceStartOfDay(srvr *server) {
  32. var message string
  33. message += fmt.Sprintf("Version %s\n", version.Version())
  34. message += fmt.Sprintf("Configuration directory: %s\n", defaultHomeDir)
  35. message += fmt.Sprintf("Configuration file: %s\n", cfg.ConfigFile)
  36. message += fmt.Sprintf("Data directory: %s\n", cfg.DataDir)
  37. elog.Info(1, message)
  38. }
  39. // pktdService houses the main service handler which handles all service
  40. // updates and launching pktdMain.
  41. type pktdService struct{}
  42. // Execute is the main entry point the winsvc package calls when receiving
  43. // information from the Windows service control manager. It launches the
  44. // long-running pktdMain (which is the real meat of pktd), handles service
  45. // change requests, and notifies the service control manager of changes.
  46. func (s *pktdService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
  47. // Service start is pending.
  48. const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
  49. changes <- svc.Status{State: svc.StartPending}
  50. // Start pktdMain in a separate goroutine so the service can start
  51. // quickly. Shutdown (along with a potential error) is reported via
  52. // doneChan. serverChan is notified with the main server instance once
  53. // it is started so it can be gracefully stopped.
  54. doneChan := make(chan er.R)
  55. serverChan := make(chan *server)
  56. go func() {
  57. err := pktdMain(serverChan)
  58. doneChan <- err
  59. }()
  60. // Service is now started.
  61. changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
  62. var mainServer *server
  63. loop:
  64. for {
  65. select {
  66. case c := <-r:
  67. switch c.Cmd {
  68. case svc.Interrogate:
  69. changes <- c.CurrentStatus
  70. case svc.Stop, svc.Shutdown:
  71. // Service stop is pending. Don't accept any
  72. // more commands while pending.
  73. changes <- svc.Status{State: svc.StopPending}
  74. // Signal the main function to exit.
  75. shutdownRequestChannel <- struct{}{}
  76. default:
  77. elog.Error(1, fmt.Sprintf("Unexpected control "+
  78. "request #%d.", c))
  79. }
  80. case srvr := <-serverChan:
  81. mainServer = srvr
  82. logServiceStartOfDay(mainServer)
  83. case err := <-doneChan:
  84. if err != nil {
  85. elog.Error(1, err.String())
  86. }
  87. break loop
  88. }
  89. }
  90. // Service is now stopped.
  91. changes <- svc.Status{State: svc.Stopped}
  92. return false, 0
  93. }
  94. // installService attempts to install the pktd service. Typically this should
  95. // be done by the msi installer, but it is provided here since it can be useful
  96. // for development.
  97. func installService() er.R {
  98. // Get the path of the current executable. This is needed because
  99. // os.Args[0] can vary depending on how the application was launched.
  100. // For example, under cmd.exe it will only be the name of the app
  101. // without the path or extension, but under mingw it will be the full
  102. // path including the extension.
  103. exePath, errr := filepath.Abs(os.Args[0])
  104. if errr != nil {
  105. return er.E(errr)
  106. }
  107. if filepath.Ext(exePath) == "" {
  108. exePath += ".exe"
  109. }
  110. // Connect to the windows service manager.
  111. serviceManager, errr := mgr.Connect()
  112. if errr != nil {
  113. return er.E(errr)
  114. }
  115. defer serviceManager.Disconnect()
  116. // Ensure the service doesn't already exist.
  117. service, err := serviceManager.OpenService(svcName)
  118. if err == nil {
  119. service.Close()
  120. return er.Errorf("service %s already exists", svcName)
  121. }
  122. // Install the service.
  123. service, errr = serviceManager.CreateService(svcName, exePath, mgr.Config{
  124. DisplayName: svcDisplayName,
  125. Description: svcDesc,
  126. })
  127. if errr != nil {
  128. return er.E(errr)
  129. }
  130. defer service.Close()
  131. // Support events to the event log using the standard "standard" Windows
  132. // EventCreate.exe message file. This allows easy logging of custom
  133. // messges instead of needing to create our own message catalog.
  134. eventlog.Remove(svcName)
  135. eventsSupported := uint32(eventlog.Error | eventlog.Warning | eventlog.Info)
  136. if errr := eventlog.InstallAsEventCreate(svcName, eventsSupported); errr != nil {
  137. return er.E(errr)
  138. }
  139. return nil
  140. }
  141. // removeService attempts to uninstall the pktd service. Typically this should
  142. // be done by the msi uninstaller, but it is provided here since it can be
  143. // useful for development. Not the eventlog entry is intentionally not removed
  144. // since it would invalidate any existing event log messages.
  145. func removeService() er.R {
  146. // Connect to the windows service manager.
  147. serviceManager, errr := mgr.Connect()
  148. if errr != nil {
  149. return er.E(errr)
  150. }
  151. defer serviceManager.Disconnect()
  152. // Ensure the service exists.
  153. service, err := serviceManager.OpenService(svcName)
  154. if err != nil {
  155. return er.Errorf("service %s is not installed", svcName)
  156. }
  157. defer service.Close()
  158. // Remove the service.
  159. if errr := service.Delete(); errr != nil {
  160. return er.E(errr)
  161. }
  162. return nil
  163. }
  164. // startService attempts to start the pktd service.
  165. func startService() er.R {
  166. // Connect to the windows service manager.
  167. serviceManager, errr := mgr.Connect()
  168. if errr != nil {
  169. return er.E(errr)
  170. }
  171. defer serviceManager.Disconnect()
  172. service, err := serviceManager.OpenService(svcName)
  173. if err != nil {
  174. return er.Errorf("could not access service: %v", err)
  175. }
  176. defer service.Close()
  177. err = service.Start(os.Args)
  178. if err != nil {
  179. return er.Errorf("could not start service: %v", err)
  180. }
  181. return nil
  182. }
  183. // controlService allows commands which change the status of the service. It
  184. // also waits for up to 10 seconds for the service to change to the passed
  185. // state.
  186. func controlService(c svc.Cmd, to svc.State) er.R {
  187. // Connect to the windows service manager.
  188. serviceManager, errr := mgr.Connect()
  189. if errr != nil {
  190. return er.E(errr)
  191. }
  192. defer serviceManager.Disconnect()
  193. service, err := serviceManager.OpenService(svcName)
  194. if err != nil {
  195. return er.Errorf("could not access service: %v", err)
  196. }
  197. defer service.Close()
  198. status, err := service.Control(c)
  199. if err != nil {
  200. return er.Errorf("could not send control=%d: %v", c, err)
  201. }
  202. // Send the control message.
  203. timeout := time.Now().Add(10 * time.Second)
  204. for status.State != to {
  205. if timeout.Before(time.Now()) {
  206. return er.Errorf("timeout waiting for service to go "+
  207. "to state=%d", to)
  208. }
  209. time.Sleep(300 * time.Millisecond)
  210. status, err = service.Query()
  211. if err != nil {
  212. return er.Errorf("could not retrieve service "+
  213. "status: %v", err)
  214. }
  215. }
  216. return nil
  217. }
  218. // performServiceCommand attempts to run one of the supported service commands
  219. // provided on the command line via the service command flag. An appropriate
  220. // error is returned if an invalid command is specified.
  221. func performServiceCommand(command string) er.R {
  222. var err er.R
  223. switch command {
  224. case "install":
  225. err = installService()
  226. case "remove":
  227. err = removeService()
  228. case "start":
  229. err = startService()
  230. case "stop":
  231. err = controlService(svc.Stop, svc.Stopped)
  232. default:
  233. err = er.Errorf("invalid service command [%s]", command)
  234. }
  235. return err
  236. }
  237. // serviceMain checks whether we're being invoked as a service, and if so uses
  238. // the service control manager to start the long-running server. A flag is
  239. // returned to the caller so the application can determine whether to exit (when
  240. // running as a service) or launch in normal interactive mode.
  241. func serviceMain() (bool, er.R) {
  242. // Don't run as a service if we're running interactively (or that can't
  243. // be determined due to an error).
  244. isInteractive, errr := svc.IsAnInteractiveSession()
  245. if errr != nil {
  246. return false, er.E(errr)
  247. }
  248. if isInteractive {
  249. return false, nil
  250. }
  251. elog, errr = eventlog.Open(svcName)
  252. if errr != nil {
  253. return false, er.E(errr)
  254. }
  255. defer elog.Close()
  256. errr = svc.Run(svcName, &pktdService{})
  257. if errr != nil {
  258. elog.Error(1, fmt.Sprintf("Service start failed: %v", errr))
  259. return true, er.E(errr)
  260. }
  261. return true, nil
  262. }
  263. // Set windows specific functions to real functions.
  264. func init() {
  265. runServiceCommand = performServiceCommand
  266. winServiceMain = serviceMain
  267. }