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