gitee.com/quant1x/gox@v1.21.2/daemon/daemon_windows.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package daemon windows version
     6  package daemon
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"os/exec"
    12  	"strconv"
    13  	"syscall"
    14  	"time"
    15  	"unicode/utf16"
    16  	"unsafe"
    17  
    18  	"golang.org/x/sys/windows"
    19  	"golang.org/x/sys/windows/registry"
    20  	"golang.org/x/sys/windows/svc"
    21  	"golang.org/x/sys/windows/svc/mgr"
    22  )
    23  
    24  // windowsRecord - standard record (struct) for windows version of daemon package
    25  type windowsRecord struct {
    26  	name         string
    27  	description  string
    28  	kind         Kind
    29  	dependencies []string
    30  }
    31  
    32  func newDaemon(name, description string, kind Kind, dependencies []string) (Daemon, error) {
    33  	return &windowsRecord{name, description, kind, dependencies}, nil
    34  }
    35  
    36  func lowPrivMgr() (*mgr.Mgr, error) {
    37  	h, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_CONNECT|windows.SC_MANAGER_ENUMERATE_SERVICE)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	return &mgr.Mgr{Handle: h}, nil
    42  }
    43  
    44  func lowPrivSvc(m *mgr.Mgr, name string) (*mgr.Service, error) {
    45  	h, err := windows.OpenService(
    46  		m.Handle, syscall.StringToUTF16Ptr(name),
    47  		windows.SERVICE_QUERY_CONFIG|windows.SERVICE_QUERY_STATUS|windows.SERVICE_START|windows.SERVICE_STOP)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	return &mgr.Service{Handle: h, Name: name}, nil
    52  }
    53  
    54  // Install the service
    55  func (windows *windowsRecord) Install(args ...string) (string, error) {
    56  	initWindows()
    57  	installAction := "Install " + windows.description + ":"
    58  
    59  	execp, err := execPath()
    60  	if err != nil {
    61  		return installAction + failed, err
    62  	}
    63  
    64  	m, err := mgr.Connect()
    65  	if err != nil {
    66  		return installAction + failed, err
    67  	}
    68  	defer m.Disconnect()
    69  
    70  	s, err := m.OpenService(windows.name)
    71  	if err == nil {
    72  		s.Close()
    73  		return installAction + failed, ErrAlreadyRunning
    74  	}
    75  
    76  	// u, err := user.Current()
    77  	// if err != nil {
    78  	// 	return installAction + failed, err
    79  	// }
    80  
    81  	s, err = m.CreateService(windows.name, execp, mgr.Config{
    82  		DisplayName:  windows.name,
    83  		Description:  windows.description,
    84  		StartType:    mgr.StartAutomatic,
    85  		Dependencies: windows.dependencies,
    86  		// ServiceStartName: u.Username,
    87  		// Password:         "",
    88  	}, args...)
    89  	if err != nil {
    90  		return installAction + failed, err
    91  	}
    92  	defer s.Close()
    93  
    94  	// set recovery action for service
    95  	// restart after 5 seconds for the first 3 times
    96  	// restart after 1 minute, otherwise
    97  	r := []mgr.RecoveryAction{
    98  		mgr.RecoveryAction{
    99  			Type:  mgr.ServiceRestart,
   100  			Delay: 5000 * time.Millisecond,
   101  		},
   102  		mgr.RecoveryAction{
   103  			Type:  mgr.ServiceRestart,
   104  			Delay: 5000 * time.Millisecond,
   105  		},
   106  		mgr.RecoveryAction{
   107  			Type:  mgr.ServiceRestart,
   108  			Delay: 5000 * time.Millisecond,
   109  		},
   110  		mgr.RecoveryAction{
   111  			Type:  mgr.ServiceRestart,
   112  			Delay: 60000 * time.Millisecond,
   113  		},
   114  	}
   115  	// set reset period as a day
   116  	s.SetRecoveryActions(r, uint32(86400))
   117  
   118  	return installAction + " completed.", nil
   119  }
   120  
   121  // Remove the service
   122  func (windows *windowsRecord) Remove() (string, error) {
   123  	removeAction := "Removing " + windows.description + ":"
   124  
   125  	m, err := mgr.Connect()
   126  	if err != nil {
   127  		return removeAction + failed, getWindowsError(err)
   128  	}
   129  	defer m.Disconnect()
   130  	s, err := m.OpenService(windows.name)
   131  	if err != nil {
   132  		return removeAction + failed, getWindowsError(err)
   133  	}
   134  	defer s.Close()
   135  	err = s.Delete()
   136  	if err != nil {
   137  		return removeAction + failed, getWindowsError(err)
   138  	}
   139  
   140  	return removeAction + " completed.", nil
   141  }
   142  
   143  // Start the service
   144  func (windows *windowsRecord) Start() (string, error) {
   145  	startAction := "Starting " + windows.description + ":"
   146  
   147  	m, err := mgr.Connect()
   148  	if err != nil {
   149  		return startAction + failed, getWindowsError(err)
   150  	}
   151  	defer m.Disconnect()
   152  	s, err := m.OpenService(windows.name)
   153  	if err != nil {
   154  		return startAction + failed, getWindowsError(err)
   155  	}
   156  	defer s.Close()
   157  	if err = s.Start(); err != nil {
   158  		return startAction + failed, getWindowsError(err)
   159  	}
   160  
   161  	return startAction + " completed.", nil
   162  }
   163  
   164  // Stop the service
   165  func (windows *windowsRecord) Stop() (string, error) {
   166  	stopAction := "Stopping " + windows.description + ":"
   167  
   168  	m, err := mgr.Connect()
   169  	if err != nil {
   170  		return stopAction + failed, getWindowsError(err)
   171  	}
   172  	defer m.Disconnect()
   173  	s, err := m.OpenService(windows.name)
   174  	if err != nil {
   175  		return stopAction + failed, getWindowsError(err)
   176  	}
   177  	defer s.Close()
   178  	if err := stopAndWait(s); err != nil {
   179  		return stopAction + failed, getWindowsError(err)
   180  	}
   181  
   182  	return stopAction + " completed.", nil
   183  }
   184  
   185  func stopAndWait(s *mgr.Service) error {
   186  	// First stop the service. Then wait for the service to
   187  	// actually stop before starting it.
   188  	status, err := s.Control(svc.Stop)
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	timeDuration := time.Millisecond * 50
   194  
   195  	timeout := time.After(getStopTimeout() + (timeDuration * 2))
   196  	tick := time.NewTicker(timeDuration)
   197  	defer tick.Stop()
   198  
   199  	for status.State != svc.Stopped {
   200  		select {
   201  		case <-tick.C:
   202  			status, err = s.Query()
   203  			if err != nil {
   204  				return err
   205  			}
   206  		case <-timeout:
   207  			break
   208  		}
   209  	}
   210  	return nil
   211  }
   212  
   213  func getStopTimeout() time.Duration {
   214  	// For default and paths see https://support.microsoft.com/en-us/kb/146092
   215  	defaultTimeout := time.Millisecond * 20000
   216  	key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ)
   217  	if err != nil {
   218  		return defaultTimeout
   219  	}
   220  	sv, _, err := key.GetStringValue("WaitToKillServiceTimeout")
   221  	if err != nil {
   222  		return defaultTimeout
   223  	}
   224  	v, err := strconv.Atoi(sv)
   225  	if err != nil {
   226  		return defaultTimeout
   227  	}
   228  	return time.Millisecond * time.Duration(v)
   229  }
   230  
   231  // Status - Get service status
   232  func (windows *windowsRecord) Status() (string, error) {
   233  	m, err := mgr.Connect()
   234  	if err != nil {
   235  		return "Getting status:" + failed, getWindowsError(err)
   236  	}
   237  	defer m.Disconnect()
   238  	s, err := m.OpenService(windows.name)
   239  	if err != nil {
   240  		return "Getting status:" + failed, getWindowsError(err)
   241  	}
   242  	defer s.Close()
   243  	status, err := s.Query()
   244  	if err != nil {
   245  		return "Getting status:" + failed, getWindowsError(err)
   246  	}
   247  
   248  	return "Status: " + getWindowsServiceStateFromUint32(status.State), nil
   249  }
   250  
   251  // Get executable path
   252  func execPath() (string, error) {
   253  	var n uint32
   254  	b := make([]uint16, syscall.MAX_PATH)
   255  	size := uint32(len(b))
   256  
   257  	r0, _, e1 := syscall.MustLoadDLL(
   258  		"kernel32.dll",
   259  	).MustFindProc(
   260  		"GetModuleFileNameW",
   261  	).Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size))
   262  	n = uint32(r0)
   263  	if n == 0 {
   264  		return "", e1
   265  	}
   266  	return string(utf16.Decode(b[0:n])), nil
   267  }
   268  
   269  // Get windows error
   270  func getWindowsError(inputError error) error {
   271  	if exiterr, ok := inputError.(*exec.ExitError); ok {
   272  		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
   273  			if sysErr, ok := WinErrCode[status.ExitStatus()]; ok {
   274  				return errors.New(fmt.Sprintf("\n %s: %s \n %s", sysErr.Title, sysErr.Description, sysErr.Action))
   275  			}
   276  		}
   277  	}
   278  
   279  	return inputError
   280  }
   281  
   282  // Get windows service state
   283  func getWindowsServiceStateFromUint32(state svc.State) string {
   284  	switch state {
   285  	case svc.Stopped:
   286  		return "SERVICE_STOPPED"
   287  	case svc.StartPending:
   288  		return "SERVICE_START_PENDING"
   289  	case svc.StopPending:
   290  		return "SERVICE_STOP_PENDING"
   291  	case svc.Running:
   292  		return "SERVICE_RUNNING"
   293  	case svc.ContinuePending:
   294  		return "SERVICE_CONTINUE_PENDING"
   295  	case svc.PausePending:
   296  		return "SERVICE_PAUSE_PENDING"
   297  	case svc.Paused:
   298  		return "SERVICE_PAUSED"
   299  	}
   300  	return "SERVICE_UNKNOWN"
   301  }
   302  
   303  type serviceHandler struct {
   304  	executable Executable
   305  }
   306  
   307  func (sh *serviceHandler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
   308  	const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
   309  	changes <- svc.Status{State: svc.StartPending}
   310  	fasttick := time.Tick(500 * time.Millisecond)
   311  	slowtick := time.Tick(2 * time.Second)
   312  	tick := fasttick
   313  	sh.executable.Start()
   314  	go sh.executable.Run()
   315  	changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
   316  loop:
   317  	for {
   318  		select {
   319  		case <-tick:
   320  			break
   321  		case c := <-r:
   322  			switch c.Cmd {
   323  			case svc.Interrogate:
   324  				changes <- c.CurrentStatus
   325  				// Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
   326  				time.Sleep(100 * time.Millisecond)
   327  				changes <- c.CurrentStatus
   328  			case svc.Stop, svc.Shutdown:
   329  				changes <- svc.Status{State: svc.StopPending}
   330  				sh.executable.Stop()
   331  				break loop
   332  			case svc.Pause:
   333  				changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
   334  				tick = slowtick
   335  			case svc.Continue:
   336  				changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
   337  				tick = fasttick
   338  			default:
   339  				continue loop
   340  			}
   341  		}
   342  	}
   343  	return
   344  }
   345  
   346  func (windows *windowsRecord) Run(e Executable) (string, error) {
   347  	runAction := "Running " + windows.description + ":"
   348  
   349  	interactive, err := svc.IsAnInteractiveSession()
   350  	if err != nil {
   351  		return runAction + failed, getWindowsError(err)
   352  	}
   353  	if !interactive {
   354  		// service called from windows service manager
   355  		// use API provided by golang.org/x/sys/windows
   356  		err = svc.Run(windows.name, &serviceHandler{
   357  			executable: e,
   358  		})
   359  		//err = svc.Run(windows.name, e)
   360  		if err != nil {
   361  			return runAction + failed, getWindowsError(err)
   362  		}
   363  	} else {
   364  		// otherwise, service should be called from terminal session
   365  		e.Run()
   366  	}
   367  
   368  	return runAction + " completed.", nil
   369  }
   370  
   371  // GetTemplate - gets service config template
   372  func (linux *windowsRecord) GetTemplate() string {
   373  	return ""
   374  }
   375  
   376  // SetTemplate - sets service config template
   377  func (linux *windowsRecord) SetTemplate(tplStr string) error {
   378  	return errors.New(fmt.Sprintf("templating is not supported for windows"))
   379  }