bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/scollector/service_windows.go (about)

     1  package main
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"os"
     7  	"time"
     8  
     9  	"bosun.org/_version"
    10  	"bosun.org/slog"
    11  	"golang.org/x/sys/windows/svc"
    12  	"golang.org/x/sys/windows/svc/debug"
    13  	"golang.org/x/sys/windows/svc/eventlog"
    14  	"golang.org/x/sys/windows/svc/mgr"
    15  )
    16  
    17  var win_service_command = flag.String("winsvc", "", "For Windows Service, can be install, remove, start, stop")
    18  
    19  var serviceRunning = false
    20  
    21  func init() {
    22  	mains = append(mains, win_service_main)
    23  }
    24  
    25  func win_service_main() {
    26  	const svcName = "scollector"
    27  	var err error
    28  	switch *win_service_command {
    29  	case "install":
    30  		err = installService(svcName, "Stack Exchange's Metric Collection Agent")
    31  	case "remove":
    32  		err = removeService(svcName)
    33  	case "start":
    34  		err = startService(svcName)
    35  	case "stop":
    36  		err = controlService(svcName, svc.Stop, svc.Stopped)
    37  	case "":
    38  		isIntSess, err := svc.IsAnInteractiveSession()
    39  		if err != nil {
    40  			slog.Fatalf("failed to determine if we are running in an interactive session: %v", err)
    41  		}
    42  		if !isIntSess {
    43  			go runService(svcName, false)
    44  			for {
    45  				//Need to wait for service go routine to finish initializing. Otherwise the collector goroutines could
    46  				//use all the CPU and cause Windows Service API to bail with a service unresponsive on startup error.
    47  				//If service doesn't start within 30 seconds then the Windows Service API will kill the process.
    48  				time.Sleep(time.Millisecond * 200)
    49  				if serviceRunning {
    50  					break
    51  				}
    52  			}
    53  		}
    54  		return
    55  	default:
    56  		slog.Fatalf("unknown winsvc command: %v", *win_service_command)
    57  	}
    58  	if err != nil {
    59  		slog.Fatalf("failed to %s %s: %v", *win_service_command, svcName, err)
    60  	}
    61  	os.Exit(0)
    62  }
    63  
    64  func installService(name, desc string) error {
    65  	exepath, err := exePath()
    66  	if err != nil {
    67  		return err
    68  	}
    69  	m, err := mgr.Connect()
    70  	if err != nil {
    71  		return err
    72  	}
    73  	defer m.Disconnect()
    74  	s, err := m.OpenService(name)
    75  	if err == nil {
    76  		s.Close()
    77  		return fmt.Errorf("service %s already exists", name)
    78  	}
    79  	s, err = m.CreateService(name, exepath, mgr.Config{DisplayName: name,
    80  		StartType:   mgr.StartAutomatic,
    81  		Description: desc})
    82  	if err != nil {
    83  		return err
    84  	}
    85  	defer s.Close()
    86  	err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info)
    87  	if err != nil {
    88  		s.Delete()
    89  		return fmt.Errorf("SetupEventLogSource() failed: %s", err)
    90  	}
    91  	return nil
    92  }
    93  
    94  func removeService(name string) error {
    95  	m, err := mgr.Connect()
    96  	if err != nil {
    97  		return err
    98  	}
    99  	defer m.Disconnect()
   100  	s, err := m.OpenService(name)
   101  	if err != nil {
   102  		return fmt.Errorf("service %s is not installed", name)
   103  	}
   104  	defer s.Close()
   105  	err = s.Delete()
   106  	if err != nil {
   107  		return err
   108  	}
   109  	err = eventlog.Remove(name)
   110  	if err != nil {
   111  		return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
   112  	}
   113  	return nil
   114  }
   115  
   116  func startService(name string) error {
   117  	m, err := mgr.Connect()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	defer m.Disconnect()
   122  	s, err := m.OpenService(name)
   123  	if err != nil {
   124  		return fmt.Errorf("could not access service: %v", err)
   125  	}
   126  	defer s.Close()
   127  	err = s.Start()
   128  	if err != nil {
   129  		return fmt.Errorf("could not start service: %v", err)
   130  	}
   131  	return nil
   132  }
   133  
   134  func controlService(name string, c svc.Cmd, to svc.State) error {
   135  	m, err := mgr.Connect()
   136  	if err != nil {
   137  		return err
   138  	}
   139  	defer m.Disconnect()
   140  	s, err := m.OpenService(name)
   141  	if err != nil {
   142  		return fmt.Errorf("could not access service: %v", err)
   143  	}
   144  	defer s.Close()
   145  	status, err := s.Control(c)
   146  	if err != nil {
   147  		return fmt.Errorf("could not send control=%d: %v", c, err)
   148  	}
   149  	timeout := time.Now().Add(35 * time.Second)
   150  	for status.State != to {
   151  		if timeout.Before(time.Now()) {
   152  			return fmt.Errorf("timeout waiting for service to go to state=%d", to)
   153  		}
   154  		time.Sleep(300 * time.Millisecond)
   155  		status, err = s.Query()
   156  		if err != nil {
   157  			return fmt.Errorf("could not retrieve service status: %v", err)
   158  		}
   159  	}
   160  	return nil
   161  }
   162  
   163  type s struct{}
   164  
   165  func (m *s) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
   166  	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
   167  	changes <- svc.Status{State: svc.StartPending}
   168  	changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
   169  	serviceRunning = true
   170  loop:
   171  	for c := range r {
   172  		switch c.Cmd {
   173  		case svc.Interrogate:
   174  			changes <- c.CurrentStatus
   175  		case svc.Stop, svc.Shutdown:
   176  			break loop
   177  		default:
   178  			slog.Errorf("unexpected control request #%d", c)
   179  		}
   180  	}
   181  	changes <- svc.Status{State: svc.StopPending}
   182  	return
   183  }
   184  
   185  func runService(name string, isDebug bool) {
   186  	if isDebug {
   187  		slog.SetEventLog(debug.New(name), 1)
   188  	} else {
   189  		elog, err := eventlog.Open(name)
   190  		if err != nil {
   191  			return
   192  		}
   193  		slog.SetEventLog(elog, 1)
   194  		defer elog.Close()
   195  	}
   196  	slog.Infof("starting service %s%s", name, version.GetVersionInfo(""))
   197  	run := svc.Run
   198  	if isDebug {
   199  		run = debug.Run
   200  	}
   201  	err := run(name, &s{})
   202  	if err != nil {
   203  		slog.Errorf("%s service failed: %v", name, err)
   204  		return
   205  	}
   206  	slog.Infof("%s service stopped", name)
   207  	os.Exit(0)
   208  }