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