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  }