github.com/Andyfoo/golang/x/sys@v0.0.0-20190901054642-57c1bf301704/windows/svc/mgr/mgr.go (about)

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build windows
     6  
     7  // Package mgr can be used to manage Windows service programs.
     8  // It can be used to install and remove them. It can also start,
     9  // stop and pause them. The package can query / change current
    10  // service state and config parameters.
    11  //
    12  package mgr
    13  
    14  import (
    15  	"syscall"
    16  	"time"
    17  	"unicode/utf16"
    18  	"unsafe"
    19  
    20  	"github.com/Andyfoo/golang/x/sys/windows"
    21  )
    22  
    23  // Mgr is used to manage Windows service.
    24  type Mgr struct {
    25  	Handle windows.Handle
    26  }
    27  
    28  // Connect establishes a connection to the service control manager.
    29  func Connect() (*Mgr, error) {
    30  	return ConnectRemote("")
    31  }
    32  
    33  // ConnectRemote establishes a connection to the
    34  // service control manager on computer named host.
    35  func ConnectRemote(host string) (*Mgr, error) {
    36  	var s *uint16
    37  	if host != "" {
    38  		s = syscall.StringToUTF16Ptr(host)
    39  	}
    40  	h, err := windows.OpenSCManager(s, nil, windows.SC_MANAGER_ALL_ACCESS)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	return &Mgr{Handle: h}, nil
    45  }
    46  
    47  // Disconnect closes connection to the service control manager m.
    48  func (m *Mgr) Disconnect() error {
    49  	return windows.CloseServiceHandle(m.Handle)
    50  }
    51  
    52  type LockStatus struct {
    53  	IsLocked bool          // Whether the SCM has been locked.
    54  	Age      time.Duration // For how long the SCM has been locked.
    55  	Owner    string        // The name of the user who has locked the SCM.
    56  }
    57  
    58  // LockStatus returns whether the service control manager is locked by
    59  // the system, for how long, and by whom. A locked SCM indicates that
    60  // most service actions will block until the system unlocks the SCM.
    61  func (m *Mgr) LockStatus() (*LockStatus, error) {
    62  	bytesNeeded := uint32(unsafe.Sizeof(windows.QUERY_SERVICE_LOCK_STATUS{}) + 1024)
    63  	for {
    64  		bytes := make([]byte, bytesNeeded)
    65  		lockStatus := (*windows.QUERY_SERVICE_LOCK_STATUS)(unsafe.Pointer(&bytes[0]))
    66  		err := windows.QueryServiceLockStatus(m.Handle, lockStatus, uint32(len(bytes)), &bytesNeeded)
    67  		if err == windows.ERROR_INSUFFICIENT_BUFFER && bytesNeeded >= uint32(unsafe.Sizeof(windows.QUERY_SERVICE_LOCK_STATUS{})) {
    68  			continue
    69  		}
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  		status := &LockStatus{
    74  			IsLocked: lockStatus.IsLocked != 0,
    75  			Age:      time.Duration(lockStatus.LockDuration) * time.Second,
    76  			Owner:    windows.UTF16ToString((*[(1 << 30) - 1]uint16)(unsafe.Pointer(lockStatus.LockOwner))[:]),
    77  		}
    78  		return status, nil
    79  	}
    80  }
    81  
    82  func toPtr(s string) *uint16 {
    83  	if len(s) == 0 {
    84  		return nil
    85  	}
    86  	return syscall.StringToUTF16Ptr(s)
    87  }
    88  
    89  // toStringBlock terminates strings in ss with 0, and then
    90  // concatenates them together. It also adds extra 0 at the end.
    91  func toStringBlock(ss []string) *uint16 {
    92  	if len(ss) == 0 {
    93  		return nil
    94  	}
    95  	t := ""
    96  	for _, s := range ss {
    97  		if s != "" {
    98  			t += s + "\x00"
    99  		}
   100  	}
   101  	if t == "" {
   102  		return nil
   103  	}
   104  	t += "\x00"
   105  	return &utf16.Encode([]rune(t))[0]
   106  }
   107  
   108  // CreateService installs new service name on the system.
   109  // The service will be executed by running exepath binary.
   110  // Use config c to specify service parameters.
   111  // Any args will be passed as command-line arguments when
   112  // the service is started; these arguments are distinct from
   113  // the arguments passed to Service.Start or via the "Start
   114  // parameters" field in the service's Properties dialog box.
   115  func (m *Mgr) CreateService(name, exepath string, c Config, args ...string) (*Service, error) {
   116  	if c.StartType == 0 {
   117  		c.StartType = StartManual
   118  	}
   119  	if c.ErrorControl == 0 {
   120  		c.ErrorControl = ErrorNormal
   121  	}
   122  	if c.ServiceType == 0 {
   123  		c.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
   124  	}
   125  	s := syscall.EscapeArg(exepath)
   126  	for _, v := range args {
   127  		s += " " + syscall.EscapeArg(v)
   128  	}
   129  	h, err := windows.CreateService(m.Handle, toPtr(name), toPtr(c.DisplayName),
   130  		windows.SERVICE_ALL_ACCESS, c.ServiceType,
   131  		c.StartType, c.ErrorControl, toPtr(s), toPtr(c.LoadOrderGroup),
   132  		nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName), toPtr(c.Password))
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	if c.SidType != windows.SERVICE_SID_TYPE_NONE {
   137  		err = updateSidType(h, c.SidType)
   138  		if err != nil {
   139  			windows.DeleteService(h)
   140  			windows.CloseHandle(h)
   141  			return nil, err
   142  		}
   143  	}
   144  	if c.Description != "" {
   145  		err = updateDescription(h, c.Description)
   146  		if err != nil {
   147  			windows.DeleteService(h)
   148  			windows.CloseHandle(h)
   149  			return nil, err
   150  		}
   151  	}
   152  	if c.DelayedAutoStart {
   153  		err = updateStartUp(h, c.DelayedAutoStart)
   154  		if err != nil {
   155  			windows.DeleteService(h)
   156  			windows.CloseHandle(h)
   157  			return nil, err
   158  		}
   159  	}
   160  	return &Service{Name: name, Handle: h}, nil
   161  }
   162  
   163  // OpenService retrieves access to service name, so it can
   164  // be interrogated and controlled.
   165  func (m *Mgr) OpenService(name string) (*Service, error) {
   166  	h, err := windows.OpenService(m.Handle, syscall.StringToUTF16Ptr(name), windows.SERVICE_ALL_ACCESS)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	return &Service{Name: name, Handle: h}, nil
   171  }
   172  
   173  // ListServices enumerates services in the specified
   174  // service control manager database m.
   175  // If the caller does not have the SERVICE_QUERY_STATUS
   176  // access right to a service, the service is silently
   177  // omitted from the list of services returned.
   178  func (m *Mgr) ListServices() ([]string, error) {
   179  	var err error
   180  	var bytesNeeded, servicesReturned uint32
   181  	var buf []byte
   182  	for {
   183  		var p *byte
   184  		if len(buf) > 0 {
   185  			p = &buf[0]
   186  		}
   187  		err = windows.EnumServicesStatusEx(m.Handle, windows.SC_ENUM_PROCESS_INFO,
   188  			windows.SERVICE_WIN32, windows.SERVICE_STATE_ALL,
   189  			p, uint32(len(buf)), &bytesNeeded, &servicesReturned, nil, nil)
   190  		if err == nil {
   191  			break
   192  		}
   193  		if err != syscall.ERROR_MORE_DATA {
   194  			return nil, err
   195  		}
   196  		if bytesNeeded <= uint32(len(buf)) {
   197  			return nil, err
   198  		}
   199  		buf = make([]byte, bytesNeeded)
   200  	}
   201  	if servicesReturned == 0 {
   202  		return nil, nil
   203  	}
   204  	services := (*[1 << 20]windows.ENUM_SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buf[0]))[:servicesReturned]
   205  	var names []string
   206  	for _, s := range services {
   207  		name := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(s.ServiceName))[:])
   208  		names = append(names, name)
   209  	}
   210  	return names, nil
   211  }