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