mirror of
https://github.com/golang/sys.git
synced 2026-02-09 04:06:04 +03:00
From MSDN on CreateServiceW: The returned handle is only valid for the process that called CreateService. It can be closed by calling the CloseServiceHandle function. This isn't an actual kernel object to be closed with CloseHandle. Change-Id: Iee225a666576d57a6c4864abef20206d54cbbce2 Reviewed-on: https://go-review.googlesource.com/c/sys/+/198298 Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Alex Brainman <alex.brainman@gmail.com> Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
212 lines
6.0 KiB
Go
212 lines
6.0 KiB
Go
// Copyright 2012 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build windows
|
|
|
|
// Package mgr can be used to manage Windows service programs.
|
|
// It can be used to install and remove them. It can also start,
|
|
// stop and pause them. The package can query / change current
|
|
// service state and config parameters.
|
|
//
|
|
package mgr
|
|
|
|
import (
|
|
"syscall"
|
|
"time"
|
|
"unicode/utf16"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// Mgr is used to manage Windows service.
|
|
type Mgr struct {
|
|
Handle windows.Handle
|
|
}
|
|
|
|
// Connect establishes a connection to the service control manager.
|
|
func Connect() (*Mgr, error) {
|
|
return ConnectRemote("")
|
|
}
|
|
|
|
// ConnectRemote establishes a connection to the
|
|
// service control manager on computer named host.
|
|
func ConnectRemote(host string) (*Mgr, error) {
|
|
var s *uint16
|
|
if host != "" {
|
|
s = syscall.StringToUTF16Ptr(host)
|
|
}
|
|
h, err := windows.OpenSCManager(s, nil, windows.SC_MANAGER_ALL_ACCESS)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Mgr{Handle: h}, nil
|
|
}
|
|
|
|
// Disconnect closes connection to the service control manager m.
|
|
func (m *Mgr) Disconnect() error {
|
|
return windows.CloseServiceHandle(m.Handle)
|
|
}
|
|
|
|
type LockStatus struct {
|
|
IsLocked bool // Whether the SCM has been locked.
|
|
Age time.Duration // For how long the SCM has been locked.
|
|
Owner string // The name of the user who has locked the SCM.
|
|
}
|
|
|
|
// LockStatus returns whether the service control manager is locked by
|
|
// the system, for how long, and by whom. A locked SCM indicates that
|
|
// most service actions will block until the system unlocks the SCM.
|
|
func (m *Mgr) LockStatus() (*LockStatus, error) {
|
|
bytesNeeded := uint32(unsafe.Sizeof(windows.QUERY_SERVICE_LOCK_STATUS{}) + 1024)
|
|
for {
|
|
bytes := make([]byte, bytesNeeded)
|
|
lockStatus := (*windows.QUERY_SERVICE_LOCK_STATUS)(unsafe.Pointer(&bytes[0]))
|
|
err := windows.QueryServiceLockStatus(m.Handle, lockStatus, uint32(len(bytes)), &bytesNeeded)
|
|
if err == windows.ERROR_INSUFFICIENT_BUFFER && bytesNeeded >= uint32(unsafe.Sizeof(windows.QUERY_SERVICE_LOCK_STATUS{})) {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
status := &LockStatus{
|
|
IsLocked: lockStatus.IsLocked != 0,
|
|
Age: time.Duration(lockStatus.LockDuration) * time.Second,
|
|
Owner: windows.UTF16ToString((*[(1 << 30) - 1]uint16)(unsafe.Pointer(lockStatus.LockOwner))[:]),
|
|
}
|
|
return status, nil
|
|
}
|
|
}
|
|
|
|
func toPtr(s string) *uint16 {
|
|
if len(s) == 0 {
|
|
return nil
|
|
}
|
|
return syscall.StringToUTF16Ptr(s)
|
|
}
|
|
|
|
// toStringBlock terminates strings in ss with 0, and then
|
|
// concatenates them together. It also adds extra 0 at the end.
|
|
func toStringBlock(ss []string) *uint16 {
|
|
if len(ss) == 0 {
|
|
return nil
|
|
}
|
|
t := ""
|
|
for _, s := range ss {
|
|
if s != "" {
|
|
t += s + "\x00"
|
|
}
|
|
}
|
|
if t == "" {
|
|
return nil
|
|
}
|
|
t += "\x00"
|
|
return &utf16.Encode([]rune(t))[0]
|
|
}
|
|
|
|
// CreateService installs new service name on the system.
|
|
// The service will be executed by running exepath binary.
|
|
// Use config c to specify service parameters.
|
|
// Any args will be passed as command-line arguments when
|
|
// the service is started; these arguments are distinct from
|
|
// the arguments passed to Service.Start or via the "Start
|
|
// parameters" field in the service's Properties dialog box.
|
|
func (m *Mgr) CreateService(name, exepath string, c Config, args ...string) (*Service, error) {
|
|
if c.StartType == 0 {
|
|
c.StartType = StartManual
|
|
}
|
|
if c.ErrorControl == 0 {
|
|
c.ErrorControl = ErrorNormal
|
|
}
|
|
if c.ServiceType == 0 {
|
|
c.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
|
|
}
|
|
s := syscall.EscapeArg(exepath)
|
|
for _, v := range args {
|
|
s += " " + syscall.EscapeArg(v)
|
|
}
|
|
h, err := windows.CreateService(m.Handle, toPtr(name), toPtr(c.DisplayName),
|
|
windows.SERVICE_ALL_ACCESS, c.ServiceType,
|
|
c.StartType, c.ErrorControl, toPtr(s), toPtr(c.LoadOrderGroup),
|
|
nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName), toPtr(c.Password))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if c.SidType != windows.SERVICE_SID_TYPE_NONE {
|
|
err = updateSidType(h, c.SidType)
|
|
if err != nil {
|
|
windows.DeleteService(h)
|
|
windows.CloseServiceHandle(h)
|
|
return nil, err
|
|
}
|
|
}
|
|
if c.Description != "" {
|
|
err = updateDescription(h, c.Description)
|
|
if err != nil {
|
|
windows.DeleteService(h)
|
|
windows.CloseServiceHandle(h)
|
|
return nil, err
|
|
}
|
|
}
|
|
if c.DelayedAutoStart {
|
|
err = updateStartUp(h, c.DelayedAutoStart)
|
|
if err != nil {
|
|
windows.DeleteService(h)
|
|
windows.CloseServiceHandle(h)
|
|
return nil, err
|
|
}
|
|
}
|
|
return &Service{Name: name, Handle: h}, nil
|
|
}
|
|
|
|
// OpenService retrieves access to service name, so it can
|
|
// be interrogated and controlled.
|
|
func (m *Mgr) OpenService(name string) (*Service, error) {
|
|
h, err := windows.OpenService(m.Handle, syscall.StringToUTF16Ptr(name), windows.SERVICE_ALL_ACCESS)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Service{Name: name, Handle: h}, nil
|
|
}
|
|
|
|
// ListServices enumerates services in the specified
|
|
// service control manager database m.
|
|
// If the caller does not have the SERVICE_QUERY_STATUS
|
|
// access right to a service, the service is silently
|
|
// omitted from the list of services returned.
|
|
func (m *Mgr) ListServices() ([]string, error) {
|
|
var err error
|
|
var bytesNeeded, servicesReturned uint32
|
|
var buf []byte
|
|
for {
|
|
var p *byte
|
|
if len(buf) > 0 {
|
|
p = &buf[0]
|
|
}
|
|
err = windows.EnumServicesStatusEx(m.Handle, windows.SC_ENUM_PROCESS_INFO,
|
|
windows.SERVICE_WIN32, windows.SERVICE_STATE_ALL,
|
|
p, uint32(len(buf)), &bytesNeeded, &servicesReturned, nil, nil)
|
|
if err == nil {
|
|
break
|
|
}
|
|
if err != syscall.ERROR_MORE_DATA {
|
|
return nil, err
|
|
}
|
|
if bytesNeeded <= uint32(len(buf)) {
|
|
return nil, err
|
|
}
|
|
buf = make([]byte, bytesNeeded)
|
|
}
|
|
if servicesReturned == 0 {
|
|
return nil, nil
|
|
}
|
|
services := (*[1 << 20]windows.ENUM_SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buf[0]))[:servicesReturned]
|
|
var names []string
|
|
for _, s := range services {
|
|
name := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(s.ServiceName))[:])
|
|
names = append(names, name)
|
|
}
|
|
return names, nil
|
|
}
|