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