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.
13 "github.com/btcsuite/winsvc/eventlog"
14 "github.com/btcsuite/winsvc/mgr"
15 "github.com/btcsuite/winsvc/svc"
19 // svcName is the name of btcd service.
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"
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."
32 // elog is used to send messages to the Windows event log.
33 var elog *eventlog.Log
35 // logServiceStartOfDay logs information about btcd when the main server has
36 // been started to the Windows event log.
37 func logServiceStartOfDay(srvr *server) {
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)
47 // btcdService houses the main service handler which handles all service
48 // updates and launching btcdMain.
49 type btcdService struct{}
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}
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)
67 err := btcdMain(serverChan)
71 // Service is now started.
72 changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
74 var mainServer *server
81 changes <- c.CurrentStatus
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}
88 // Signal the main function to exit.
89 shutdownRequestChannel <- struct{}{}
92 elog.Error(1, fmt.Sprintf("Unexpected control "+
96 case srvr := <-serverChan:
98 logServiceStartOfDay(mainServer)
100 case err := <-doneChan:
102 elog.Error(1, err.Error())
108 // Service is now stopped.
109 changes <- svc.Status{State: svc.Stopped}
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
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])
126 if filepath.Ext(exePath) == "" {
130 // Connect to the windows service manager.
131 serviceManager, err := mgr.Connect()
135 defer serviceManager.Disconnect()
137 // Ensure the service doesn't already exist.
138 service, err := serviceManager.OpenService(svcName)
141 return fmt.Errorf("service %s already exists", svcName)
144 // Install the service.
145 service, err = serviceManager.CreateService(svcName, exePath, mgr.Config{
146 DisplayName: svcDisplayName,
147 Description: svcDesc,
152 defer service.Close()
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)
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()
172 defer serviceManager.Disconnect()
174 // Ensure the service exists.
175 service, err := serviceManager.OpenService(svcName)
177 return fmt.Errorf("service %s is not installed", svcName)
179 defer service.Close()
181 // Remove the service.
182 return service.Delete()
185 // startService attempts to start the btcd service.
186 func startService() error {
187 // Connect to the windows service manager.
188 serviceManager, err := mgr.Connect()
192 defer serviceManager.Disconnect()
194 service, err := serviceManager.OpenService(svcName)
196 return fmt.Errorf("could not access service: %v", err)
198 defer service.Close()
200 err = service.Start(os.Args)
202 return fmt.Errorf("could not start service: %v", err)
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
211 func controlService(c svc.Cmd, to svc.State) error {
212 // Connect to the windows service manager.
213 serviceManager, err := mgr.Connect()
217 defer serviceManager.Disconnect()
219 service, err := serviceManager.OpenService(svcName)
221 return fmt.Errorf("could not access service: %v", err)
223 defer service.Close()
225 status, err := service.Control(c)
227 return fmt.Errorf("could not send control=%d: %v", c, err)
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 "+
237 time.Sleep(300 * time.Millisecond)
238 status, err = service.Query()
240 return fmt.Errorf("could not retrieve service "+
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 {
255 err = installService()
258 err = removeService()
264 err = controlService(svc.Stop, svc.Stopped)
267 err = fmt.Errorf("invalid service command [%s]", command)
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 we're running interactively (or that can't
279 // be determined due to an error).
280 isInteractive, err := svc.IsAnInteractiveSession()
288 elog, err = eventlog.Open(svcName)
294 err = svc.Run(svcName, &btcdService{})
296 elog.Error(1, fmt.Sprintf("Service start failed: %v", err))
303 // Set windows specific functions to real functions.
305 runServiceCommand = performServiceCommand
306 winServiceMain = serviceMain