OSDN Git Service

new repo
[bytom/vapor.git] / vendor / github.com / btcsuite / btcd / service_windows.go
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 we're running interactively (or that can't
279         // be determined due to an error).
280         isInteractive, err := svc.IsAnInteractiveSession()
281         if err != nil {
282                 return false, err
283         }
284         if isInteractive {
285                 return false, nil
286         }
287
288         elog, err = eventlog.Open(svcName)
289         if err != nil {
290                 return false, err
291         }
292         defer elog.Close()
293
294         err = svc.Run(svcName, &btcdService{})
295         if err != nil {
296                 elog.Error(1, fmt.Sprintf("Service start failed: %v", err))
297                 return true, err
298         }
299
300         return true, nil
301 }
302
303 // Set windows specific functions to real functions.
304 func init() {
305         runServiceCommand = performServiceCommand
306         winServiceMain = serviceMain
307 }