gitee.com/h79/goutils@v1.22.10/common/svc/winsvc.go (about)

     1  //go:build windows
     2  
     3  package svc
     4  
     5  import (
     6  	"flag"
     7  	"fmt"
     8  	"gitee.com/h79/goutils/common/logger"
     9  	"gitee.com/h79/goutils/common/system"
    10  	"golang.org/x/sys/windows"
    11  	"golang.org/x/sys/windows/svc"
    12  	"golang.org/x/sys/windows/svc/debug"
    13  	"golang.org/x/sys/windows/svc/mgr"
    14  	"os"
    15  	"path/filepath"
    16  	"time"
    17  )
    18  
    19  const (
    20  	kServiceName    = "serviceName"
    21  	kServiceDesc    = "serviceDesc"
    22  	kServiceInstall = "serviceInstall"
    23  	kServiceRemove  = "serviceRemove"
    24  	kServiceStart   = "serviceStart"
    25  	kServiceReStart = "serviceReStart"
    26  	kServiceStop    = "serviceStop"
    27  )
    28  
    29  var (
    30  	appPath     string
    31  	serviceName string
    32  	serviceDesc string
    33  
    34  	flagServiceInstall = flag.Bool(kServiceInstall, false, "Install service")
    35  	flagServiceRemove  = flag.Bool(kServiceRemove, false, "Remove service")
    36  	flagServiceStart   = flag.Bool(kServiceStart, false, "Start service")
    37  	flagServiceReStart = flag.Bool(kServiceReStart, false, "ReStart service")
    38  	flagServiceStop    = flag.Bool(kServiceStop, false, "Stop service")
    39  )
    40  
    41  func init() {
    42  
    43  	flag.StringVar(&serviceName, kServiceName, "", "Set service name")
    44  	flag.StringVar(&serviceDesc, kServiceDesc, "", "Set service description")
    45  
    46  	// change to current dir
    47  	var err error
    48  	if appPath, err = getExePath(); err != nil {
    49  		logger.Fatal("err= %v", err)
    50  	}
    51  	if err = os.Chdir(filepath.Dir(appPath)); err != nil {
    52  		logger.Fatal("err= %v", err)
    53  	}
    54  }
    55  
    56  func DoService(conf Config, startService func(isWindowsService bool), stopService func()) {
    57  	if len(serviceName) <= 0 {
    58  		serviceName = conf.ServiceName
    59  	}
    60  	if len(serviceDesc) <= 0 {
    61  		serviceDesc = conf.ServiceDesc
    62  	}
    63  	// install service
    64  	if *flagServiceInstall {
    65  		if len(serviceName) <= 0 || len(serviceDesc) <= 0 {
    66  			logger.Error("failed to install service: serviceName OR serviceDesc is empty")
    67  			os.Exit(-2)
    68  		}
    69  		args := os.Args[1:]
    70  		args = system.StripArgs(args, "-"+kServiceName)
    71  		args = system.StripArgs(args, "-"+kServiceDesc)
    72  		args = system.StripArgs(args, "-"+kServiceInstall)
    73  		if err := Install(appPath, serviceName, serviceDesc, args...); err != nil {
    74  			logger.Error("failed to install service: %v, err: %v", serviceName, err)
    75  		}
    76  		logger.Info("done")
    77  		os.Exit(0)
    78  	}
    79  
    80  	// uninstall service
    81  	if *flagServiceRemove {
    82  		if len(serviceName) <= 0 {
    83  			logger.Error("failed to uninstall service: serviceName is empty")
    84  			os.Exit(-2)
    85  		}
    86  		if err := Remove(serviceName); err != nil {
    87  			logger.Error("failed to uninstall service: %v, err: %v", serviceName, err)
    88  		} else {
    89  			logger.Info("done")
    90  		}
    91  		os.Exit(0)
    92  	}
    93  
    94  	// start service
    95  	if *flagServiceStart {
    96  		if len(serviceName) <= 0 {
    97  			logger.Error("failed to start service: serviceName is empty")
    98  			os.Exit(-2)
    99  		}
   100  		if err := Start(serviceName); err != nil {
   101  			logger.Error("failed to start service: %v, err: %v", serviceName, err)
   102  		} else {
   103  			logger.Info("done")
   104  		}
   105  		os.Exit(0)
   106  	}
   107  
   108  	// restart service
   109  	if *flagServiceReStart {
   110  		if len(serviceName) <= 0 {
   111  			logger.Error("failed to restart service: serviceName is empty")
   112  			os.Exit(-2)
   113  		}
   114  		if err := ReStart(serviceName); err != nil {
   115  			logger.Error("failed to restart service: %v, err: %v", serviceName, err)
   116  		} else {
   117  			logger.Info("done")
   118  		}
   119  		os.Exit(0)
   120  	}
   121  
   122  	// stop service
   123  	if *flagServiceStop {
   124  		if len(serviceName) <= 0 {
   125  			logger.Error("failed to stop service: serviceName is empty")
   126  			os.Exit(-2)
   127  		}
   128  		if err := Stop(serviceName); err != nil {
   129  			logger.Error("failed to stop service: %v, err: %v", serviceName, err)
   130  		} else {
   131  			logger.Info("done")
   132  		}
   133  		os.Exit(0)
   134  	}
   135  
   136  	if !IsWindowsService() {
   137  		startService(false)
   138  		return
   139  	}
   140  
   141  	if len(serviceName) <= 0 {
   142  		logger.Error("failed to Run service: serviceName is empty")
   143  		os.Exit(-2)
   144  	}
   145  	// run as service
   146  	if err := RunAs(serviceName, startService, stopService, false); err != nil {
   147  		logger.Error("Run failure, err= %v", err)
   148  	}
   149  }
   150  
   151  func getExePath() (string, error) {
   152  	prog := os.Args[0]
   153  	p, err := filepath.Abs(prog)
   154  	if err != nil {
   155  		return "", err
   156  	}
   157  	fi, err := os.Stat(p)
   158  	if err == nil {
   159  		if !fi.Mode().IsDir() {
   160  			return p, nil
   161  		}
   162  		err = fmt.Errorf("GetAppPath: %s is directory", p)
   163  	}
   164  	if filepath.Ext(p) == "" {
   165  		p += ".exe"
   166  		fi, err = os.Stat(p)
   167  		if err == nil {
   168  			if !fi.Mode().IsDir() {
   169  				return p, nil
   170  			}
   171  			err = fmt.Errorf("GetAppPath: %s is directory", p)
   172  		}
   173  	}
   174  	return "", err
   175  }
   176  
   177  func IsWindowsService() bool {
   178  	ok, err := svc.IsWindowsService()
   179  	if err != nil {
   180  		fmt.Println("IsWindowsService:  err = ", err)
   181  	}
   182  	return ok
   183  }
   184  
   185  func Install(appPath, name, desc string, params ...string) error {
   186  	m, err := mgr.Connect()
   187  	if err != nil {
   188  		return err
   189  	}
   190  	defer m.Disconnect()
   191  	s, err := m.OpenService(name)
   192  	if err == nil {
   193  		s.Close()
   194  		return fmt.Errorf("InstallService: service %s already exists", name)
   195  	}
   196  	s, err = m.CreateService(name, appPath,
   197  		mgr.Config{
   198  			DisplayName: desc,
   199  			Description: desc,
   200  			StartType:   windows.SERVICE_AUTO_START,
   201  		},
   202  		params...,
   203  	)
   204  	if err != nil {
   205  		return err
   206  	}
   207  	defer s.Close()
   208  	//_ = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info)
   209  	return nil
   210  }
   211  
   212  func Remove(name string) error {
   213  	m, err := mgr.Connect()
   214  	if err != nil {
   215  		return err
   216  	}
   217  	defer m.Disconnect()
   218  	s, err := m.OpenService(name)
   219  	if err != nil {
   220  		return fmt.Errorf("RemoveService: service %s is not installed", name)
   221  	}
   222  	defer s.Close()
   223  	err = s.Delete()
   224  	if err != nil {
   225  		return err
   226  	}
   227  	//_ = eventlog.Remove(name)
   228  	return nil
   229  }
   230  
   231  func Start(name string) error {
   232  	m, err := mgr.Connect()
   233  	if err != nil {
   234  		return err
   235  	}
   236  	defer m.Disconnect()
   237  	s, err := m.OpenService(name)
   238  	if err != nil {
   239  		return fmt.Errorf("StartService: could not access service: %v", err)
   240  	}
   241  	defer s.Close()
   242  	err = s.Start("p1", "p2", "p3")
   243  	if err != nil {
   244  		return fmt.Errorf("StartService: could not start service: %v", err)
   245  	}
   246  	return nil
   247  }
   248  
   249  func ReStart(name string) error {
   250  	if err := Control(name, svc.Stop, svc.Stopped); err != nil {
   251  		return err
   252  	}
   253  	return Start(name)
   254  }
   255  
   256  func Stop(name string) error {
   257  	if err := Control(name, svc.Stop, svc.Stopped); err != nil {
   258  		return err
   259  	}
   260  	return nil
   261  }
   262  
   263  func Query(name string) (status string, err error) {
   264  	m, err := mgr.Connect()
   265  	if err != nil {
   266  		return
   267  	}
   268  	defer m.Disconnect()
   269  	s, err := m.OpenService(name)
   270  	if err != nil {
   271  		err = fmt.Errorf("Query: could not access service: %v", err)
   272  		return
   273  	}
   274  	defer s.Close()
   275  
   276  	statusCode, err := s.Query()
   277  	if err != nil {
   278  		return
   279  	}
   280  	switch statusCode.State {
   281  	case svc.Stopped:
   282  		return "Stopped", nil
   283  	case svc.StartPending:
   284  		return "StartPending", nil
   285  	case svc.StopPending:
   286  		return "StopPending", nil
   287  	case svc.Running:
   288  		return "Running", nil
   289  	case svc.ContinuePending:
   290  		return "ContinuePending", nil
   291  	case svc.PausePending:
   292  		return "PausePending", nil
   293  	case svc.Paused:
   294  		return "Paused", nil
   295  	}
   296  	panic("unreached")
   297  }
   298  
   299  func RunAs(name string, start func(isWindowsService bool), stop func(), isDebug bool) (err error) {
   300  
   301  	run := svc.Run
   302  	if isDebug {
   303  		run = debug.Run
   304  	}
   305  
   306  	fmt.Println("RunAs: starting service: ", name)
   307  	if err = run(name, &winService{Start: start, Stop: stop}); err != nil {
   308  		fmt.Println(name, "service failed: ", err)
   309  		return
   310  	}
   311  	fmt.Println("RunAs:", name, "service stopped")
   312  	return
   313  }
   314  
   315  func Control(name string, c svc.Cmd, to svc.State) error {
   316  	m, err := mgr.Connect()
   317  	if err != nil {
   318  		return err
   319  	}
   320  	defer m.Disconnect()
   321  	s, err := m.OpenService(name)
   322  	if err != nil {
   323  		return fmt.Errorf("Control: could not access service: %v", err)
   324  	}
   325  	defer s.Close()
   326  	status, err := s.Control(c)
   327  	if err != nil {
   328  		return fmt.Errorf("Control: could not send control=%d: %v", c, err)
   329  	}
   330  	timeout := time.Now().Add(10 * time.Second)
   331  	for status.State != to {
   332  		if timeout.Before(time.Now()) {
   333  			return fmt.Errorf("Control: timeout waiting for service to go to state=%d", to)
   334  		}
   335  		time.Sleep(300 * time.Millisecond)
   336  		status, err = s.Query()
   337  		if err != nil {
   338  			return fmt.Errorf("Control: could not retrieve service status: %v", err)
   339  		}
   340  	}
   341  	return nil
   342  }
   343  
   344  type winService struct {
   345  	Start func(isWindowsService bool)
   346  	Stop  func()
   347  }
   348  
   349  func (p *winService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
   350  	logger.Info("Execute: begin")
   351  	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
   352  	changes <- svc.Status{State: svc.StartPending}
   353  	changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
   354  
   355  	var exit = make(chan struct{})
   356  	system.ChildRunning(func() {
   357  		p.Start(true)
   358  		close(exit)
   359  	})
   360  	var stop = false
   361  loop:
   362  	for {
   363  		select {
   364  		case <-exit:
   365  			stop = true
   366  			break
   367  		case c := <-r:
   368  			switch c.Cmd {
   369  			case svc.Interrogate:
   370  				changes <- c.CurrentStatus
   371  				// testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
   372  				time.Sleep(100 * time.Millisecond)
   373  				changes <- c.CurrentStatus
   374  			case svc.Stop, svc.Shutdown:
   375  				break loop
   376  			case svc.Pause:
   377  				changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
   378  			case svc.Continue:
   379  				changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
   380  			default:
   381  				logger.Error("Execute:: unexpected control request #%d", c)
   382  			}
   383  		}
   384  		if stop {
   385  			break
   386  		}
   387  	}
   388  	changes <- svc.Status{State: svc.StopPending}
   389  	p.Stop()
   390  
   391  	logger.Info("Execute: end")
   392  	return
   393  }