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  }