github.com/sohaha/zlsgo@v1.7.13-0.20240501141223-10dd1a906f76/zutil/daemon/daemon_windows.go (about)

     1  package daemon
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/sohaha/zlsgo/zshell"
    12  	"golang.org/x/sys/windows/registry"
    13  	"golang.org/x/sys/windows/svc"
    14  	"golang.org/x/sys/windows/svc/eventlog"
    15  	"golang.org/x/sys/windows/svc/mgr"
    16  )
    17  
    18  type (
    19  	windowsSystem  struct{}
    20  	windowsService struct {
    21  		i            Iface
    22  		stopStartErr error
    23  		*Config
    24  		errSync sync.Mutex
    25  	}
    26  )
    27  
    28  const version = "windows-service"
    29  
    30  var interactive = false
    31  
    32  func init() {
    33  	var err error
    34  	chooseSystem(windowsSystem{})
    35  	interactive, err = svc.IsAnInteractiveSession()
    36  	if err != nil {
    37  		panic(err)
    38  	}
    39  }
    40  
    41  func (windowsSystem) String() string {
    42  	return version
    43  }
    44  
    45  func (windowsSystem) Detect() bool {
    46  	return true
    47  }
    48  
    49  func (windowsSystem) Interactive() bool {
    50  	return interactive
    51  }
    52  
    53  func (windowsSystem) New(i Iface, c *Config) (ServiceIface, error) {
    54  	if c.Context == nil {
    55  		c.Context = context.Background()
    56  	}
    57  
    58  	ws := &windowsService{
    59  		i:      i,
    60  		Config: c,
    61  	}
    62  
    63  	return ws, nil
    64  }
    65  
    66  func (w *windowsService) String() string {
    67  	if len(w.DisplayName) > 0 {
    68  		return w.DisplayName
    69  	}
    70  	return w.Name
    71  }
    72  
    73  func (w *windowsService) setError(err error) {
    74  	w.errSync.Lock()
    75  	defer w.errSync.Unlock()
    76  	w.stopStartErr = err
    77  }
    78  
    79  func (w *windowsService) getError() error {
    80  	w.errSync.Lock()
    81  	defer w.errSync.Unlock()
    82  	return w.stopStartErr
    83  }
    84  
    85  func (w *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
    86  	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
    87  	changes <- svc.Status{State: svc.StartPending}
    88  
    89  	if err := w.i.Start(w); err != nil {
    90  		w.setError(err)
    91  		return true, 1
    92  	}
    93  
    94  	changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
    95  loop:
    96  	for {
    97  		c := <-r
    98  		switch c.Cmd {
    99  		case svc.Interrogate:
   100  			changes <- c.CurrentStatus
   101  		case svc.Stop, svc.Shutdown:
   102  			changes <- svc.Status{State: svc.StopPending}
   103  			if err := w.i.Stop(w); err != nil {
   104  				w.setError(err)
   105  				return true, 2
   106  			}
   107  			break loop
   108  		default:
   109  			continue loop
   110  		}
   111  	}
   112  
   113  	return false, 0
   114  }
   115  
   116  func (w *windowsService) Install() error {
   117  	m, err := connect()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	defer m.Disconnect()
   122  	exepath := w.execPath()
   123  	s, err := m.OpenService(w.Name)
   124  	if err == nil {
   125  		s.Close()
   126  		return fmt.Errorf("service %s already exists", w.Name)
   127  	}
   128  	password := ""
   129  	if p, ok := w.Options["Password"]; ok {
   130  		password, _ = p.(string)
   131  	}
   132  	s, err = m.CreateService(w.Name, exepath, mgr.Config{
   133  		DisplayName:      w.DisplayName,
   134  		Description:      w.Description,
   135  		StartType:        mgr.StartAutomatic,
   136  		ServiceStartName: w.UserName,
   137  		Password:         password,
   138  	}, w.Arguments...)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	defer s.Close()
   143  	err = eventlog.InstallAsEventCreate(w.Name, eventlog.Error|eventlog.Warning|eventlog.Info)
   144  	if err != nil {
   145  		_ = s.Delete()
   146  		return fmt.Errorf("installAsEventCreate() failed: %s", err)
   147  	}
   148  
   149  	if isServiceRestart(w.Config) {
   150  		_ = s.SetRecoveryActions([]mgr.RecoveryAction{
   151  			{
   152  				Type:  mgr.ServiceRestart,
   153  				Delay: 0,
   154  			},
   155  		}, 0)
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  func (w *windowsService) Uninstall() error {
   162  	m, err := connect()
   163  	if err != nil {
   164  		return err
   165  	}
   166  	defer m.Disconnect()
   167  
   168  	s, err := m.OpenService(w.Name)
   169  	if err != nil {
   170  		return fmt.Errorf("service %s is not installed", w.Name)
   171  	}
   172  	defer s.Close()
   173  
   174  	_ = w.Stop()
   175  
   176  	err = s.Delete()
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	err = eventlog.Remove(w.Name)
   182  	if err != nil {
   183  		return fmt.Errorf("removeEventLogSource() failed: %s", err)
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func (w *windowsService) Run() error {
   190  	w.setError(nil)
   191  	if !interactive {
   192  		runErr := svc.Run(w.Name, w)
   193  		startStopErr := w.getError()
   194  		if startStopErr != nil {
   195  			return startStopErr
   196  		}
   197  		if runErr != nil {
   198  			return runErr
   199  		}
   200  		return nil
   201  	}
   202  	err := w.i.Start(w)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	select {
   208  	case <-SingleKillSignal():
   209  	case <-w.Config.Context.Done():
   210  	}
   211  
   212  	return w.i.Stop(w)
   213  }
   214  
   215  func (w *windowsService) Start() error {
   216  	m, err := connect()
   217  	if err != nil {
   218  		return err
   219  	}
   220  	defer m.Disconnect()
   221  	s, err := m.OpenService(w.Name)
   222  	if err != nil {
   223  		return err
   224  	}
   225  	defer s.Close()
   226  
   227  	if isServiceRestart(w.Config) {
   228  		_ = s.SetRecoveryActions([]mgr.RecoveryAction{
   229  			{
   230  				Type:  mgr.ServiceRestart,
   231  				Delay: 0,
   232  			},
   233  		}, 0)
   234  	}
   235  
   236  	return s.Start()
   237  }
   238  
   239  func (w *windowsService) Stop() error {
   240  	m, err := connect()
   241  	if err != nil {
   242  		return err
   243  	}
   244  	defer m.Disconnect()
   245  
   246  	s, err := m.OpenService(w.Name)
   247  	if err != nil {
   248  		return err
   249  	}
   250  	defer s.Close()
   251  
   252  	if isServiceRestart(w.Config) {
   253  		_ = s.SetRecoveryActions([]mgr.RecoveryAction{
   254  			{
   255  				Type:  mgr.NoAction,
   256  				Delay: 0,
   257  			},
   258  		}, 0)
   259  	}
   260  
   261  	return w.stopWait(s)
   262  }
   263  
   264  func (w *windowsService) Restart() error {
   265  	err := w.Stop()
   266  	if err != nil {
   267  		return err
   268  	}
   269  	return w.Start()
   270  }
   271  
   272  func (w *windowsService) Status() string {
   273  	m, err := connect()
   274  	if err != nil {
   275  		return "Unknown"
   276  	}
   277  	defer m.Disconnect()
   278  	s, err := m.OpenService(w.Name)
   279  	if err != nil {
   280  		return err.Error()
   281  	}
   282  	defer s.Close()
   283  	q, err := s.Query()
   284  	if err != nil {
   285  		return err.Error()
   286  	}
   287  	switch q.State {
   288  	case svc.Running:
   289  		return "Running"
   290  	case svc.StopPending:
   291  		return "StopPending"
   292  	case svc.Stopped:
   293  		return "Stop"
   294  	}
   295  	return strconv.Itoa(int(q.State))
   296  }
   297  
   298  func (w *windowsService) forceKeep(processId uint32) error {
   299  	ss := "taskkill /F /pid " + strconv.Itoa(int(processId))
   300  	_, _, _, err := zshell.Run(ss)
   301  	return err
   302  }
   303  
   304  func (w *windowsService) stopWait(s *mgr.Service) error {
   305  	status, err := s.Control(svc.Stop)
   306  	if err != nil {
   307  		if !strings.Contains(err.Error(), "not valid") {
   308  			return err
   309  		}
   310  		status, err = s.Query()
   311  		if err != nil {
   312  			return err
   313  		}
   314  		_ = w.forceKeep(status.ProcessId)
   315  	}
   316  
   317  	timeDuration := time.Millisecond * 100
   318  	timeout := time.After(getStopTimeout() + (timeDuration * 2))
   319  	tick := time.NewTicker(timeDuration)
   320  	defer tick.Stop()
   321  	for status.State != svc.Stopped {
   322  		select {
   323  		case <-tick.C:
   324  			status, err = s.Query()
   325  			if err != nil {
   326  				return err
   327  			}
   328  		case <-timeout:
   329  			_ = w.forceKeep(status.ProcessId)
   330  			break
   331  		}
   332  	}
   333  	return nil
   334  }
   335  
   336  func connect() (*mgr.Mgr, error) {
   337  	m, err := mgr.Connect()
   338  	if err != nil {
   339  		if strings.Contains(err.Error(), "Access is denied") {
   340  			err = ErrNotAnAdministrator
   341  		}
   342  	}
   343  	return m, err
   344  }
   345  func getStopTimeout() time.Duration {
   346  	// For default and paths see https://support.microsoft.com/en-us/kb/146092
   347  	defaultTimeout := time.Millisecond * 20000
   348  	key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ)
   349  	if err != nil {
   350  		return defaultTimeout
   351  	}
   352  	sv, _, err := key.GetStringValue("WaitToKillServiceTimeout")
   353  	if err != nil {
   354  		return defaultTimeout
   355  	}
   356  	v, err := strconv.Atoi(sv)
   357  	if err != nil {
   358  		return defaultTimeout
   359  	}
   360  	return time.Millisecond * time.Duration(v)
   361  }