vendor deps
This commit is contained in:
parent
1635cbad9b
commit
8d73901067
|
@ -0,0 +1,48 @@
|
||||||
|
// 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 svc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// event represents auto-reset, initially non-signaled Windows event.
|
||||||
|
// It is used to communicate between go and asm parts of this package.
|
||||||
|
type event struct {
|
||||||
|
h windows.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEvent() (*event, error) {
|
||||||
|
h, err := windows.CreateEvent(nil, 0, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &event{h: h}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *event) Close() error {
|
||||||
|
return windows.CloseHandle(e.h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *event) Set() error {
|
||||||
|
return windows.SetEvent(e.h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *event) Wait() error {
|
||||||
|
s, err := windows.WaitForSingleObject(e.h, windows.INFINITE)
|
||||||
|
switch s {
|
||||||
|
case windows.WAIT_OBJECT_0:
|
||||||
|
break
|
||||||
|
case windows.WAIT_FAILED:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return errors.New("unexpected result from WaitForSingleObject")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
// 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 eventlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Log levels.
|
||||||
|
Info = windows.EVENTLOG_INFORMATION_TYPE
|
||||||
|
Warning = windows.EVENTLOG_WARNING_TYPE
|
||||||
|
Error = windows.EVENTLOG_ERROR_TYPE
|
||||||
|
)
|
||||||
|
|
||||||
|
const addKeyName = `SYSTEM\CurrentControlSet\Services\EventLog\Application`
|
||||||
|
|
||||||
|
// Install modifies PC registry to allow logging with an event source src.
|
||||||
|
// It adds all required keys and values to the event log registry key.
|
||||||
|
// Install uses msgFile as the event message file. If useExpandKey is true,
|
||||||
|
// the event message file is installed as REG_EXPAND_SZ value,
|
||||||
|
// otherwise as REG_SZ. Use bitwise of log.Error, log.Warning and
|
||||||
|
// log.Info to specify events supported by the new event source.
|
||||||
|
func Install(src, msgFile string, useExpandKey bool, eventsSupported uint32) error {
|
||||||
|
appkey, err := registry.OpenKey(registry.LOCAL_MACHINE, addKeyName, registry.CREATE_SUB_KEY)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer appkey.Close()
|
||||||
|
|
||||||
|
sk, alreadyExist, err := registry.CreateKey(appkey, src, registry.SET_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sk.Close()
|
||||||
|
if alreadyExist {
|
||||||
|
return errors.New(addKeyName + `\` + src + " registry key already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sk.SetDWordValue("CustomSource", 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if useExpandKey {
|
||||||
|
err = sk.SetExpandStringValue("EventMessageFile", msgFile)
|
||||||
|
} else {
|
||||||
|
err = sk.SetStringValue("EventMessageFile", msgFile)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = sk.SetDWordValue("TypesSupported", eventsSupported)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallAsEventCreate is the same as Install, but uses
|
||||||
|
// %SystemRoot%\System32\EventCreate.exe as the event message file.
|
||||||
|
func InstallAsEventCreate(src string, eventsSupported uint32) error {
|
||||||
|
return Install(src, "%SystemRoot%\\System32\\EventCreate.exe", true, eventsSupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deletes all registry elements installed by the correspondent Install.
|
||||||
|
func Remove(src string) error {
|
||||||
|
appkey, err := registry.OpenKey(registry.LOCAL_MACHINE, addKeyName, registry.SET_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer appkey.Close()
|
||||||
|
return registry.DeleteKey(appkey, src)
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
// 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 eventlog implements access to Windows event log.
|
||||||
|
//
|
||||||
|
package eventlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log provides access to the system log.
|
||||||
|
type Log struct {
|
||||||
|
Handle windows.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open retrieves a handle to the specified event log.
|
||||||
|
func Open(source string) (*Log, error) {
|
||||||
|
return OpenRemote("", source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenRemote does the same as Open, but on different computer host.
|
||||||
|
func OpenRemote(host, source string) (*Log, error) {
|
||||||
|
if source == "" {
|
||||||
|
return nil, errors.New("Specify event log source")
|
||||||
|
}
|
||||||
|
var s *uint16
|
||||||
|
if host != "" {
|
||||||
|
s = syscall.StringToUTF16Ptr(host)
|
||||||
|
}
|
||||||
|
h, err := windows.RegisterEventSource(s, syscall.StringToUTF16Ptr(source))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Log{Handle: h}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes event log l.
|
||||||
|
func (l *Log) Close() error {
|
||||||
|
return windows.DeregisterEventSource(l.Handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Log) report(etype uint16, eid uint32, msg string) error {
|
||||||
|
ss := []*uint16{syscall.StringToUTF16Ptr(msg)}
|
||||||
|
return windows.ReportEvent(l.Handle, etype, 0, eid, 0, 1, 0, &ss[0], nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info writes an information event msg with event id eid to the end of event log l.
|
||||||
|
// When EventCreate.exe is used, eid must be between 1 and 1000.
|
||||||
|
func (l *Log) Info(eid uint32, msg string) error {
|
||||||
|
return l.report(windows.EVENTLOG_INFORMATION_TYPE, eid, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning writes an warning event msg with event id eid to the end of event log l.
|
||||||
|
// When EventCreate.exe is used, eid must be between 1 and 1000.
|
||||||
|
func (l *Log) Warning(eid uint32, msg string) error {
|
||||||
|
return l.report(windows.EVENTLOG_WARNING_TYPE, eid, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error writes an error event msg with event id eid to the end of event log l.
|
||||||
|
// When EventCreate.exe is used, eid must be between 1 and 1000.
|
||||||
|
func (l *Log) Error(eid uint32, msg string) error {
|
||||||
|
return l.report(windows.EVENTLOG_ERROR_TYPE, eid, msg)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// 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
|
||||||
|
// +build !go1.3
|
||||||
|
|
||||||
|
// copied from pkg/runtime
|
||||||
|
typedef unsigned int uint32;
|
||||||
|
typedef unsigned long long int uint64;
|
||||||
|
#ifdef _64BIT
|
||||||
|
typedef uint64 uintptr;
|
||||||
|
#else
|
||||||
|
typedef uint32 uintptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// from sys_386.s or sys_amd64.s
|
||||||
|
void ·servicemain(void);
|
||||||
|
|
||||||
|
void
|
||||||
|
·getServiceMain(uintptr *r)
|
||||||
|
{
|
||||||
|
*r = (uintptr)·servicemain;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright 2014 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
|
||||||
|
// +build !go1.3
|
||||||
|
|
||||||
|
package svc
|
||||||
|
|
||||||
|
// from go12.c
|
||||||
|
func getServiceMain(r *uintptr)
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2014 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
|
||||||
|
// +build go1.3
|
||||||
|
|
||||||
|
package svc
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
const ptrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const
|
||||||
|
|
||||||
|
// Should be a built-in for unsafe.Pointer?
|
||||||
|
func add(p unsafe.Pointer, x uintptr) unsafe.Pointer {
|
||||||
|
return unsafe.Pointer(uintptr(p) + x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcPC returns the entry PC of the function f.
|
||||||
|
// It assumes that f is a func value. Otherwise the behavior is undefined.
|
||||||
|
func funcPC(f interface{}) uintptr {
|
||||||
|
return **(**uintptr)(add(unsafe.Pointer(&f), ptrSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
// from sys_386.s and sys_amd64.s
|
||||||
|
func servicectlhandler(ctl uint32) uintptr
|
||||||
|
func servicemain(argc uint32, argv **uint16)
|
||||||
|
|
||||||
|
func getServiceMain(r *uintptr) {
|
||||||
|
*r = funcPC(servicemain)
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Service start types.
|
||||||
|
StartManual = windows.SERVICE_DEMAND_START // the service must be started manually
|
||||||
|
StartAutomatic = windows.SERVICE_AUTO_START // the service will start by itself whenever the computer reboots
|
||||||
|
StartDisabled = windows.SERVICE_DISABLED // the service cannot be started
|
||||||
|
|
||||||
|
// The severity of the error, and action taken,
|
||||||
|
// if this service fails to start.
|
||||||
|
ErrorCritical = windows.SERVICE_ERROR_CRITICAL
|
||||||
|
ErrorIgnore = windows.SERVICE_ERROR_IGNORE
|
||||||
|
ErrorNormal = windows.SERVICE_ERROR_NORMAL
|
||||||
|
ErrorSevere = windows.SERVICE_ERROR_SEVERE
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(brainman): Password is not returned by windows.QueryServiceConfig, not sure how to get it.
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ServiceType uint32
|
||||||
|
StartType uint32
|
||||||
|
ErrorControl uint32
|
||||||
|
BinaryPathName string // fully qualified path to the service binary file, can also include arguments for an auto-start service
|
||||||
|
LoadOrderGroup string
|
||||||
|
TagId uint32
|
||||||
|
Dependencies []string
|
||||||
|
ServiceStartName string // name of the account under which the service should run
|
||||||
|
DisplayName string
|
||||||
|
Password string
|
||||||
|
Description string
|
||||||
|
SidType uint32 // one of SERVICE_SID_TYPE, the type of sid to use for the service
|
||||||
|
DelayedAutoStart bool // the service is started after other auto-start services are started plus a short delay
|
||||||
|
}
|
||||||
|
|
||||||
|
func toStringSlice(ps *uint16) []string {
|
||||||
|
r := make([]string, 0)
|
||||||
|
p := unsafe.Pointer(ps)
|
||||||
|
|
||||||
|
for {
|
||||||
|
s := windows.UTF16PtrToString((*uint16)(p))
|
||||||
|
if len(s) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
r = append(r, s)
|
||||||
|
offset := unsafe.Sizeof(uint16(0)) * (uintptr)(len(s)+1)
|
||||||
|
p = unsafe.Pointer(uintptr(p) + offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config retrieves service s configuration paramteres.
|
||||||
|
func (s *Service) Config() (Config, error) {
|
||||||
|
var p *windows.QUERY_SERVICE_CONFIG
|
||||||
|
n := uint32(1024)
|
||||||
|
for {
|
||||||
|
b := make([]byte, n)
|
||||||
|
p = (*windows.QUERY_SERVICE_CONFIG)(unsafe.Pointer(&b[0]))
|
||||||
|
err := windows.QueryServiceConfig(s.Handle, p, n, &n)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err.(syscall.Errno) != syscall.ERROR_INSUFFICIENT_BUFFER {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
if n <= uint32(len(b)) {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_DESCRIPTION)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
p2 := (*windows.SERVICE_DESCRIPTION)(unsafe.Pointer(&b[0]))
|
||||||
|
|
||||||
|
b, err = s.queryServiceConfig2(windows.SERVICE_CONFIG_DELAYED_AUTO_START_INFO)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
p3 := (*windows.SERVICE_DELAYED_AUTO_START_INFO)(unsafe.Pointer(&b[0]))
|
||||||
|
delayedStart := false
|
||||||
|
if p3.IsDelayedAutoStartUp != 0 {
|
||||||
|
delayedStart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return Config{
|
||||||
|
ServiceType: p.ServiceType,
|
||||||
|
StartType: p.StartType,
|
||||||
|
ErrorControl: p.ErrorControl,
|
||||||
|
BinaryPathName: windows.UTF16PtrToString(p.BinaryPathName),
|
||||||
|
LoadOrderGroup: windows.UTF16PtrToString(p.LoadOrderGroup),
|
||||||
|
TagId: p.TagId,
|
||||||
|
Dependencies: toStringSlice(p.Dependencies),
|
||||||
|
ServiceStartName: windows.UTF16PtrToString(p.ServiceStartName),
|
||||||
|
DisplayName: windows.UTF16PtrToString(p.DisplayName),
|
||||||
|
Description: windows.UTF16PtrToString(p2.Description),
|
||||||
|
DelayedAutoStart: delayedStart,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateDescription(handle windows.Handle, desc string) error {
|
||||||
|
d := windows.SERVICE_DESCRIPTION{Description: toPtr(desc)}
|
||||||
|
return windows.ChangeServiceConfig2(handle,
|
||||||
|
windows.SERVICE_CONFIG_DESCRIPTION, (*byte)(unsafe.Pointer(&d)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSidType(handle windows.Handle, sidType uint32) error {
|
||||||
|
return windows.ChangeServiceConfig2(handle, windows.SERVICE_CONFIG_SERVICE_SID_INFO, (*byte)(unsafe.Pointer(&sidType)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStartUp(handle windows.Handle, isDelayed bool) error {
|
||||||
|
var d windows.SERVICE_DELAYED_AUTO_START_INFO
|
||||||
|
if isDelayed {
|
||||||
|
d.IsDelayedAutoStartUp = 1
|
||||||
|
}
|
||||||
|
return windows.ChangeServiceConfig2(handle,
|
||||||
|
windows.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (*byte)(unsafe.Pointer(&d)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfig updates service s configuration parameters.
|
||||||
|
func (s *Service) UpdateConfig(c Config) error {
|
||||||
|
err := windows.ChangeServiceConfig(s.Handle, c.ServiceType, c.StartType,
|
||||||
|
c.ErrorControl, toPtr(c.BinaryPathName), toPtr(c.LoadOrderGroup),
|
||||||
|
nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName),
|
||||||
|
toPtr(c.Password), toPtr(c.DisplayName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = updateSidType(s.Handle, c.SidType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = updateStartUp(s.Handle, c.DelayedAutoStart)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateDescription(s.Handle, c.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// queryServiceConfig2 calls Windows QueryServiceConfig2 with infoLevel parameter and returns retrieved service configuration information.
|
||||||
|
func (s *Service) queryServiceConfig2(infoLevel uint32) ([]byte, error) {
|
||||||
|
n := uint32(1024)
|
||||||
|
for {
|
||||||
|
b := make([]byte, n)
|
||||||
|
err := windows.QueryServiceConfig2(s.Handle, infoLevel, &b[0], n, &n)
|
||||||
|
if err == nil {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
if err.(syscall.Errno) != syscall.ERROR_INSUFFICIENT_BUFFER {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n <= uint32(len(b)) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,218 @@
|
||||||
|
// 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/internal/unsafeheader"
|
||||||
|
"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.UTF16PtrToString(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
|
||||||
|
}
|
||||||
|
|
||||||
|
var services []windows.ENUM_SERVICE_STATUS_PROCESS
|
||||||
|
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&services))
|
||||||
|
hdr.Data = unsafe.Pointer(&buf[0])
|
||||||
|
hdr.Len = int(servicesReturned)
|
||||||
|
hdr.Cap = int(servicesReturned)
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
for _, s := range services {
|
||||||
|
name := windows.UTF16PtrToString(s.ServiceName)
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names, nil
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
// Copyright 2018 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/internal/unsafeheader"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Possible recovery actions that the service control manager can perform.
|
||||||
|
NoAction = windows.SC_ACTION_NONE // no action
|
||||||
|
ComputerReboot = windows.SC_ACTION_REBOOT // reboot the computer
|
||||||
|
ServiceRestart = windows.SC_ACTION_RESTART // restart the service
|
||||||
|
RunCommand = windows.SC_ACTION_RUN_COMMAND // run a command
|
||||||
|
)
|
||||||
|
|
||||||
|
// RecoveryAction represents an action that the service control manager can perform when service fails.
|
||||||
|
// A service is considered failed when it terminates without reporting a status of SERVICE_STOPPED to the service controller.
|
||||||
|
type RecoveryAction struct {
|
||||||
|
Type int // one of NoAction, ComputerReboot, ServiceRestart or RunCommand
|
||||||
|
Delay time.Duration // the time to wait before performing the specified action
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRecoveryActions sets actions that service controller performs when service fails and
|
||||||
|
// the time after which to reset the service failure count to zero if there are no failures, in seconds.
|
||||||
|
// Specify INFINITE to indicate that service failure count should never be reset.
|
||||||
|
func (s *Service) SetRecoveryActions(recoveryActions []RecoveryAction, resetPeriod uint32) error {
|
||||||
|
if recoveryActions == nil {
|
||||||
|
return errors.New("recoveryActions cannot be nil")
|
||||||
|
}
|
||||||
|
actions := []windows.SC_ACTION{}
|
||||||
|
for _, a := range recoveryActions {
|
||||||
|
action := windows.SC_ACTION{
|
||||||
|
Type: uint32(a.Type),
|
||||||
|
Delay: uint32(a.Delay.Nanoseconds() / 1000000),
|
||||||
|
}
|
||||||
|
actions = append(actions, action)
|
||||||
|
}
|
||||||
|
rActions := windows.SERVICE_FAILURE_ACTIONS{
|
||||||
|
ActionsCount: uint32(len(actions)),
|
||||||
|
Actions: &actions[0],
|
||||||
|
ResetPeriod: resetPeriod,
|
||||||
|
}
|
||||||
|
return windows.ChangeServiceConfig2(s.Handle, windows.SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&rActions)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoveryActions returns actions that service controller performs when service fails.
|
||||||
|
// The service control manager counts the number of times service s has failed since the system booted.
|
||||||
|
// The count is reset to 0 if the service has not failed for ResetPeriod seconds.
|
||||||
|
// When the service fails for the Nth time, the service controller performs the action specified in element [N-1] of returned slice.
|
||||||
|
// If N is greater than slice length, the service controller repeats the last action in the slice.
|
||||||
|
func (s *Service) RecoveryActions() ([]RecoveryAction, error) {
|
||||||
|
b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_FAILURE_ACTIONS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p := (*windows.SERVICE_FAILURE_ACTIONS)(unsafe.Pointer(&b[0]))
|
||||||
|
if p.Actions == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var actions []windows.SC_ACTION
|
||||||
|
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&actions))
|
||||||
|
hdr.Data = unsafe.Pointer(p.Actions)
|
||||||
|
hdr.Len = int(p.ActionsCount)
|
||||||
|
hdr.Cap = int(p.ActionsCount)
|
||||||
|
|
||||||
|
var recoveryActions []RecoveryAction
|
||||||
|
for _, action := range actions {
|
||||||
|
recoveryActions = append(recoveryActions, RecoveryAction{Type: int(action.Type), Delay: time.Duration(action.Delay) * time.Millisecond})
|
||||||
|
}
|
||||||
|
return recoveryActions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetRecoveryActions deletes both reset period and array of failure actions.
|
||||||
|
func (s *Service) ResetRecoveryActions() error {
|
||||||
|
actions := make([]windows.SC_ACTION, 1)
|
||||||
|
rActions := windows.SERVICE_FAILURE_ACTIONS{
|
||||||
|
Actions: &actions[0],
|
||||||
|
}
|
||||||
|
return windows.ChangeServiceConfig2(s.Handle, windows.SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&rActions)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetPeriod is the time after which to reset the service failure
|
||||||
|
// count to zero if there are no failures, in seconds.
|
||||||
|
func (s *Service) ResetPeriod() (uint32, error) {
|
||||||
|
b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_FAILURE_ACTIONS)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
p := (*windows.SERVICE_FAILURE_ACTIONS)(unsafe.Pointer(&b[0]))
|
||||||
|
return p.ResetPeriod, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRebootMessage sets service s reboot message.
|
||||||
|
// If msg is "", the reboot message is deleted and no message is broadcast.
|
||||||
|
func (s *Service) SetRebootMessage(msg string) error {
|
||||||
|
rActions := windows.SERVICE_FAILURE_ACTIONS{
|
||||||
|
RebootMsg: syscall.StringToUTF16Ptr(msg),
|
||||||
|
}
|
||||||
|
return windows.ChangeServiceConfig2(s.Handle, windows.SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&rActions)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RebootMessage is broadcast to server users before rebooting in response to the ComputerReboot service controller action.
|
||||||
|
func (s *Service) RebootMessage() (string, error) {
|
||||||
|
b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_FAILURE_ACTIONS)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
p := (*windows.SERVICE_FAILURE_ACTIONS)(unsafe.Pointer(&b[0]))
|
||||||
|
return windows.UTF16PtrToString(p.RebootMsg), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRecoveryCommand sets the command line of the process to execute in response to the RunCommand service controller action.
|
||||||
|
// If cmd is "", the command is deleted and no program is run when the service fails.
|
||||||
|
func (s *Service) SetRecoveryCommand(cmd string) error {
|
||||||
|
rActions := windows.SERVICE_FAILURE_ACTIONS{
|
||||||
|
Command: syscall.StringToUTF16Ptr(cmd),
|
||||||
|
}
|
||||||
|
return windows.ChangeServiceConfig2(s.Handle, windows.SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&rActions)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoveryCommand is the command line of the process to execute in response to the RunCommand service controller action. This process runs under the same account as the service.
|
||||||
|
func (s *Service) RecoveryCommand() (string, error) {
|
||||||
|
b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_FAILURE_ACTIONS)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
p := (*windows.SERVICE_FAILURE_ACTIONS)(unsafe.Pointer(&b[0]))
|
||||||
|
return windows.UTF16PtrToString(p.Command), nil
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.org/x/sys/windows/svc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(brainman): Use EnumDependentServices to enumerate dependent services.
|
||||||
|
|
||||||
|
// Service is used to access Windows service.
|
||||||
|
type Service struct {
|
||||||
|
Name string
|
||||||
|
Handle windows.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete marks service s for deletion from the service control manager database.
|
||||||
|
func (s *Service) Delete() error {
|
||||||
|
return windows.DeleteService(s.Handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close relinquish access to the service s.
|
||||||
|
func (s *Service) Close() error {
|
||||||
|
return windows.CloseServiceHandle(s.Handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts service s.
|
||||||
|
// args will be passed to svc.Handler.Execute.
|
||||||
|
func (s *Service) Start(args ...string) error {
|
||||||
|
var p **uint16
|
||||||
|
if len(args) > 0 {
|
||||||
|
vs := make([]*uint16, len(args))
|
||||||
|
for i := range vs {
|
||||||
|
vs[i] = syscall.StringToUTF16Ptr(args[i])
|
||||||
|
}
|
||||||
|
p = &vs[0]
|
||||||
|
}
|
||||||
|
return windows.StartService(s.Handle, uint32(len(args)), p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Control sends state change request c to the servce s.
|
||||||
|
func (s *Service) Control(c svc.Cmd) (svc.Status, error) {
|
||||||
|
var t windows.SERVICE_STATUS
|
||||||
|
err := windows.ControlService(s.Handle, uint32(c), &t)
|
||||||
|
if err != nil {
|
||||||
|
return svc.Status{}, err
|
||||||
|
}
|
||||||
|
return svc.Status{
|
||||||
|
State: svc.State(t.CurrentState),
|
||||||
|
Accepts: svc.Accepted(t.ControlsAccepted),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns current status of service s.
|
||||||
|
func (s *Service) Query() (svc.Status, error) {
|
||||||
|
var t windows.SERVICE_STATUS_PROCESS
|
||||||
|
var needed uint32
|
||||||
|
err := windows.QueryServiceStatusEx(s.Handle, windows.SC_STATUS_PROCESS_INFO, (*byte)(unsafe.Pointer(&t)), uint32(unsafe.Sizeof(t)), &needed)
|
||||||
|
if err != nil {
|
||||||
|
return svc.Status{}, err
|
||||||
|
}
|
||||||
|
return svc.Status{
|
||||||
|
State: svc.State(t.CurrentState),
|
||||||
|
Accepts: svc.Accepted(t.ControlsAccepted),
|
||||||
|
ProcessId: t.ProcessId,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// 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 svc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func allocSid(subAuth0 uint32) (*windows.SID, error) {
|
||||||
|
var sid *windows.SID
|
||||||
|
err := windows.AllocateAndInitializeSid(&windows.SECURITY_NT_AUTHORITY,
|
||||||
|
1, subAuth0, 0, 0, 0, 0, 0, 0, 0, &sid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAnInteractiveSession determines if calling process is running interactively.
|
||||||
|
// It queries the process token for membership in the Interactive group.
|
||||||
|
// http://stackoverflow.com/questions/2668851/how-do-i-detect-that-my-application-is-running-as-service-or-in-an-interactive-s
|
||||||
|
func IsAnInteractiveSession() (bool, error) {
|
||||||
|
interSid, err := allocSid(windows.SECURITY_INTERACTIVE_RID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer windows.FreeSid(interSid)
|
||||||
|
|
||||||
|
serviceSid, err := allocSid(windows.SECURITY_SERVICE_RID)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer windows.FreeSid(serviceSid)
|
||||||
|
|
||||||
|
t, err := windows.OpenCurrentProcessToken()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer t.Close()
|
||||||
|
|
||||||
|
gs, err := t.GetTokenGroups()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, g := range gs.AllGroups() {
|
||||||
|
if windows.EqualSid(g.Sid, interSid) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if windows.EqualSid(g.Sid, serviceSid) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
|
@ -0,0 +1,371 @@
|
||||||
|
// 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 svc provides everything required to build Windows service.
|
||||||
|
//
|
||||||
|
package svc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/internal/unsafeheader"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State describes service execution state (Stopped, Running and so on).
|
||||||
|
type State uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Stopped = State(windows.SERVICE_STOPPED)
|
||||||
|
StartPending = State(windows.SERVICE_START_PENDING)
|
||||||
|
StopPending = State(windows.SERVICE_STOP_PENDING)
|
||||||
|
Running = State(windows.SERVICE_RUNNING)
|
||||||
|
ContinuePending = State(windows.SERVICE_CONTINUE_PENDING)
|
||||||
|
PausePending = State(windows.SERVICE_PAUSE_PENDING)
|
||||||
|
Paused = State(windows.SERVICE_PAUSED)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cmd represents service state change request. It is sent to a service
|
||||||
|
// by the service manager, and should be actioned upon by the service.
|
||||||
|
type Cmd uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Stop = Cmd(windows.SERVICE_CONTROL_STOP)
|
||||||
|
Pause = Cmd(windows.SERVICE_CONTROL_PAUSE)
|
||||||
|
Continue = Cmd(windows.SERVICE_CONTROL_CONTINUE)
|
||||||
|
Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
|
||||||
|
Shutdown = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
|
||||||
|
ParamChange = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE)
|
||||||
|
NetBindAdd = Cmd(windows.SERVICE_CONTROL_NETBINDADD)
|
||||||
|
NetBindRemove = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE)
|
||||||
|
NetBindEnable = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE)
|
||||||
|
NetBindDisable = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE)
|
||||||
|
DeviceEvent = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT)
|
||||||
|
HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE)
|
||||||
|
PowerEvent = Cmd(windows.SERVICE_CONTROL_POWEREVENT)
|
||||||
|
SessionChange = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Accepted is used to describe commands accepted by the service.
|
||||||
|
// Note that Interrogate is always accepted.
|
||||||
|
type Accepted uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
AcceptStop = Accepted(windows.SERVICE_ACCEPT_STOP)
|
||||||
|
AcceptShutdown = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
|
||||||
|
AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
|
||||||
|
AcceptParamChange = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)
|
||||||
|
AcceptNetBindChange = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE)
|
||||||
|
AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE)
|
||||||
|
AcceptPowerEvent = Accepted(windows.SERVICE_ACCEPT_POWEREVENT)
|
||||||
|
AcceptSessionChange = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Status combines State and Accepted commands to fully describe running service.
|
||||||
|
type Status struct {
|
||||||
|
State State
|
||||||
|
Accepts Accepted
|
||||||
|
CheckPoint uint32 // used to report progress during a lengthy operation
|
||||||
|
WaitHint uint32 // estimated time required for a pending operation, in milliseconds
|
||||||
|
ProcessId uint32 // if the service is running, the process identifier of it, and otherwise zero
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeRequest is sent to the service Handler to request service status change.
|
||||||
|
type ChangeRequest struct {
|
||||||
|
Cmd Cmd
|
||||||
|
EventType uint32
|
||||||
|
EventData uintptr
|
||||||
|
CurrentStatus Status
|
||||||
|
Context uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler is the interface that must be implemented to build Windows service.
|
||||||
|
type Handler interface {
|
||||||
|
|
||||||
|
// Execute will be called by the package code at the start of
|
||||||
|
// the service, and the service will exit once Execute completes.
|
||||||
|
// Inside Execute you must read service change requests from r and
|
||||||
|
// act accordingly. You must keep service control manager up to date
|
||||||
|
// about state of your service by writing into s as required.
|
||||||
|
// args contains service name followed by argument strings passed
|
||||||
|
// to the service.
|
||||||
|
// You can provide service exit code in exitCode return parameter,
|
||||||
|
// with 0 being "no error". You can also indicate if exit code,
|
||||||
|
// if any, is service specific or not by using svcSpecificEC
|
||||||
|
// parameter.
|
||||||
|
Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// These are used by asm code.
|
||||||
|
goWaitsH uintptr
|
||||||
|
cWaitsH uintptr
|
||||||
|
ssHandle uintptr
|
||||||
|
sName *uint16
|
||||||
|
sArgc uintptr
|
||||||
|
sArgv **uint16
|
||||||
|
ctlHandlerExProc uintptr
|
||||||
|
cSetEvent uintptr
|
||||||
|
cWaitForSingleObject uintptr
|
||||||
|
cRegisterServiceCtrlHandlerExW uintptr
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
k := windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
cSetEvent = k.NewProc("SetEvent").Addr()
|
||||||
|
cWaitForSingleObject = k.NewProc("WaitForSingleObject").Addr()
|
||||||
|
a := windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
cRegisterServiceCtrlHandlerExW = a.NewProc("RegisterServiceCtrlHandlerExW").Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ctlEvent struct {
|
||||||
|
cmd Cmd
|
||||||
|
eventType uint32
|
||||||
|
eventData uintptr
|
||||||
|
context uintptr
|
||||||
|
errno uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// service provides access to windows service api.
|
||||||
|
type service struct {
|
||||||
|
name string
|
||||||
|
h windows.Handle
|
||||||
|
cWaits *event
|
||||||
|
goWaits *event
|
||||||
|
c chan ctlEvent
|
||||||
|
handler Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func newService(name string, handler Handler) (*service, error) {
|
||||||
|
var s service
|
||||||
|
var err error
|
||||||
|
s.name = name
|
||||||
|
s.c = make(chan ctlEvent)
|
||||||
|
s.handler = handler
|
||||||
|
s.cWaits, err = newEvent()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.goWaits, err = newEvent()
|
||||||
|
if err != nil {
|
||||||
|
s.cWaits.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) close() error {
|
||||||
|
s.cWaits.Close()
|
||||||
|
s.goWaits.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type exitCode struct {
|
||||||
|
isSvcSpecific bool
|
||||||
|
errno uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) updateStatus(status *Status, ec *exitCode) error {
|
||||||
|
if s.h == 0 {
|
||||||
|
return errors.New("updateStatus with no service status handle")
|
||||||
|
}
|
||||||
|
var t windows.SERVICE_STATUS
|
||||||
|
t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
|
||||||
|
t.CurrentState = uint32(status.State)
|
||||||
|
if status.Accepts&AcceptStop != 0 {
|
||||||
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
|
||||||
|
}
|
||||||
|
if status.Accepts&AcceptShutdown != 0 {
|
||||||
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
|
||||||
|
}
|
||||||
|
if status.Accepts&AcceptPauseAndContinue != 0 {
|
||||||
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
|
||||||
|
}
|
||||||
|
if status.Accepts&AcceptParamChange != 0 {
|
||||||
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
|
||||||
|
}
|
||||||
|
if status.Accepts&AcceptNetBindChange != 0 {
|
||||||
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
|
||||||
|
}
|
||||||
|
if status.Accepts&AcceptHardwareProfileChange != 0 {
|
||||||
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
|
||||||
|
}
|
||||||
|
if status.Accepts&AcceptPowerEvent != 0 {
|
||||||
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
|
||||||
|
}
|
||||||
|
if status.Accepts&AcceptSessionChange != 0 {
|
||||||
|
t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
|
||||||
|
}
|
||||||
|
if ec.errno == 0 {
|
||||||
|
t.Win32ExitCode = windows.NO_ERROR
|
||||||
|
t.ServiceSpecificExitCode = windows.NO_ERROR
|
||||||
|
} else if ec.isSvcSpecific {
|
||||||
|
t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
|
||||||
|
t.ServiceSpecificExitCode = ec.errno
|
||||||
|
} else {
|
||||||
|
t.Win32ExitCode = ec.errno
|
||||||
|
t.ServiceSpecificExitCode = windows.NO_ERROR
|
||||||
|
}
|
||||||
|
t.CheckPoint = status.CheckPoint
|
||||||
|
t.WaitHint = status.WaitHint
|
||||||
|
return windows.SetServiceStatus(s.h, &t)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota
|
||||||
|
sysErrNewThreadInCallback
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *service) run() {
|
||||||
|
s.goWaits.Wait()
|
||||||
|
s.h = windows.Handle(ssHandle)
|
||||||
|
|
||||||
|
var argv []*uint16
|
||||||
|
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&argv))
|
||||||
|
hdr.Data = unsafe.Pointer(sArgv)
|
||||||
|
hdr.Len = int(sArgc)
|
||||||
|
hdr.Cap = int(sArgc)
|
||||||
|
|
||||||
|
args := make([]string, len(argv))
|
||||||
|
for i, a := range argv {
|
||||||
|
args[i] = windows.UTF16PtrToString(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdsToHandler := make(chan ChangeRequest)
|
||||||
|
changesFromHandler := make(chan Status)
|
||||||
|
exitFromHandler := make(chan exitCode)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler)
|
||||||
|
exitFromHandler <- exitCode{ss, errno}
|
||||||
|
}()
|
||||||
|
|
||||||
|
ec := exitCode{isSvcSpecific: true, errno: 0}
|
||||||
|
outcr := ChangeRequest{
|
||||||
|
CurrentStatus: Status{State: Stopped},
|
||||||
|
}
|
||||||
|
var outch chan ChangeRequest
|
||||||
|
inch := s.c
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case r := <-inch:
|
||||||
|
if r.errno != 0 {
|
||||||
|
ec.errno = r.errno
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
inch = nil
|
||||||
|
outch = cmdsToHandler
|
||||||
|
outcr.Cmd = r.cmd
|
||||||
|
outcr.EventType = r.eventType
|
||||||
|
outcr.EventData = r.eventData
|
||||||
|
outcr.Context = r.context
|
||||||
|
case outch <- outcr:
|
||||||
|
inch = s.c
|
||||||
|
outch = nil
|
||||||
|
case c := <-changesFromHandler:
|
||||||
|
err := s.updateStatus(&c, &ec)
|
||||||
|
if err != nil {
|
||||||
|
// best suitable error number
|
||||||
|
ec.errno = sysErrSetServiceStatusFailed
|
||||||
|
if err2, ok := err.(syscall.Errno); ok {
|
||||||
|
ec.errno = uint32(err2)
|
||||||
|
}
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
outcr.CurrentStatus = c
|
||||||
|
case ec = <-exitFromHandler:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.updateStatus(&Status{State: Stopped}, &ec)
|
||||||
|
s.cWaits.Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCallback(fn interface{}) (cb uintptr, err error) {
|
||||||
|
defer func() {
|
||||||
|
r := recover()
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cb = 0
|
||||||
|
switch v := r.(type) {
|
||||||
|
case string:
|
||||||
|
err = errors.New(v)
|
||||||
|
case error:
|
||||||
|
err = v
|
||||||
|
default:
|
||||||
|
err = errors.New("unexpected panic in syscall.NewCallback")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return syscall.NewCallback(fn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BUG(brainman): There is no mechanism to run multiple services
|
||||||
|
// inside one single executable. Perhaps, it can be overcome by
|
||||||
|
// using RegisterServiceCtrlHandlerEx Windows api.
|
||||||
|
|
||||||
|
// Run executes service name by calling appropriate handler function.
|
||||||
|
func Run(name string, handler Handler) error {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
tid := windows.GetCurrentThreadId()
|
||||||
|
|
||||||
|
s, err := newService(name, handler)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctlHandler := func(ctl, evtype, evdata, context uintptr) uintptr {
|
||||||
|
e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: context}
|
||||||
|
// We assume that this callback function is running on
|
||||||
|
// the same thread as Run. Nowhere in MS documentation
|
||||||
|
// I could find statement to guarantee that. So putting
|
||||||
|
// check here to verify, otherwise things will go bad
|
||||||
|
// quickly, if ignored.
|
||||||
|
i := windows.GetCurrentThreadId()
|
||||||
|
if i != tid {
|
||||||
|
e.errno = sysErrNewThreadInCallback
|
||||||
|
}
|
||||||
|
s.c <- e
|
||||||
|
// Always return NO_ERROR (0) for now.
|
||||||
|
return windows.NO_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
var svcmain uintptr
|
||||||
|
getServiceMain(&svcmain)
|
||||||
|
t := []windows.SERVICE_TABLE_ENTRY{
|
||||||
|
{ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain},
|
||||||
|
{ServiceName: nil, ServiceProc: 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
goWaitsH = uintptr(s.goWaits.h)
|
||||||
|
cWaitsH = uintptr(s.cWaits.h)
|
||||||
|
sName = t[0].ServiceName
|
||||||
|
ctlHandlerExProc, err = newCallback(ctlHandler)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.run()
|
||||||
|
|
||||||
|
err = windows.StartServiceCtrlDispatcher(&t[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusHandle returns service status handle. It is safe to call this function
|
||||||
|
// from inside the Handler.Execute because then it is guaranteed to be set.
|
||||||
|
// This code will have to change once multiple services are possible per process.
|
||||||
|
func StatusHandle() windows.Handle {
|
||||||
|
return windows.Handle(ssHandle)
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// func servicemain(argc uint32, argv **uint16)
|
||||||
|
TEXT ·servicemain(SB),7,$0
|
||||||
|
MOVL argc+0(FP), AX
|
||||||
|
MOVL AX, ·sArgc(SB)
|
||||||
|
MOVL argv+4(FP), AX
|
||||||
|
MOVL AX, ·sArgv(SB)
|
||||||
|
|
||||||
|
PUSHL BP
|
||||||
|
PUSHL BX
|
||||||
|
PUSHL SI
|
||||||
|
PUSHL DI
|
||||||
|
|
||||||
|
SUBL $12, SP
|
||||||
|
|
||||||
|
MOVL ·sName(SB), AX
|
||||||
|
MOVL AX, (SP)
|
||||||
|
MOVL $·servicectlhandler(SB), AX
|
||||||
|
MOVL AX, 4(SP)
|
||||||
|
// Set context to 123456 to test issue #25660.
|
||||||
|
MOVL $123456, 8(SP)
|
||||||
|
MOVL ·cRegisterServiceCtrlHandlerExW(SB), AX
|
||||||
|
MOVL SP, BP
|
||||||
|
CALL AX
|
||||||
|
MOVL BP, SP
|
||||||
|
CMPL AX, $0
|
||||||
|
JE exit
|
||||||
|
MOVL AX, ·ssHandle(SB)
|
||||||
|
|
||||||
|
MOVL ·goWaitsH(SB), AX
|
||||||
|
MOVL AX, (SP)
|
||||||
|
MOVL ·cSetEvent(SB), AX
|
||||||
|
MOVL SP, BP
|
||||||
|
CALL AX
|
||||||
|
MOVL BP, SP
|
||||||
|
|
||||||
|
MOVL ·cWaitsH(SB), AX
|
||||||
|
MOVL AX, (SP)
|
||||||
|
MOVL $-1, AX
|
||||||
|
MOVL AX, 4(SP)
|
||||||
|
MOVL ·cWaitForSingleObject(SB), AX
|
||||||
|
MOVL SP, BP
|
||||||
|
CALL AX
|
||||||
|
MOVL BP, SP
|
||||||
|
|
||||||
|
exit:
|
||||||
|
ADDL $12, SP
|
||||||
|
|
||||||
|
POPL DI
|
||||||
|
POPL SI
|
||||||
|
POPL BX
|
||||||
|
POPL BP
|
||||||
|
|
||||||
|
MOVL 0(SP), CX
|
||||||
|
ADDL $12, SP
|
||||||
|
JMP CX
|
||||||
|
|
||||||
|
// I do not know why, but this seems to be the only way to call
|
||||||
|
// ctlHandlerProc on Windows 7.
|
||||||
|
|
||||||
|
// func servicectlhandler(ctl uint32, evtype uint32, evdata uintptr, context uintptr) uintptr {
|
||||||
|
TEXT ·servicectlhandler(SB),7,$0
|
||||||
|
MOVL ·ctlHandlerExProc(SB), CX
|
||||||
|
JMP CX
|
|
@ -0,0 +1,44 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// func servicemain(argc uint32, argv **uint16)
|
||||||
|
TEXT ·servicemain(SB),7,$0
|
||||||
|
MOVL CX, ·sArgc(SB)
|
||||||
|
MOVQ DX, ·sArgv(SB)
|
||||||
|
|
||||||
|
SUBQ $32, SP // stack for the first 4 syscall params
|
||||||
|
|
||||||
|
MOVQ ·sName(SB), CX
|
||||||
|
MOVQ $·servicectlhandler(SB), DX
|
||||||
|
// BUG(pastarmovj): Figure out a way to pass in context in R8.
|
||||||
|
// Set context to 123456 to test issue #25660.
|
||||||
|
MOVQ $123456, R8
|
||||||
|
MOVQ ·cRegisterServiceCtrlHandlerExW(SB), AX
|
||||||
|
CALL AX
|
||||||
|
CMPQ AX, $0
|
||||||
|
JE exit
|
||||||
|
MOVQ AX, ·ssHandle(SB)
|
||||||
|
|
||||||
|
MOVQ ·goWaitsH(SB), CX
|
||||||
|
MOVQ ·cSetEvent(SB), AX
|
||||||
|
CALL AX
|
||||||
|
|
||||||
|
MOVQ ·cWaitsH(SB), CX
|
||||||
|
MOVQ $4294967295, DX
|
||||||
|
MOVQ ·cWaitForSingleObject(SB), AX
|
||||||
|
CALL AX
|
||||||
|
|
||||||
|
exit:
|
||||||
|
ADDQ $32, SP
|
||||||
|
RET
|
||||||
|
|
||||||
|
// I do not know why, but this seems to be the only way to call
|
||||||
|
// ctlHandlerProc on Windows 7.
|
||||||
|
|
||||||
|
// func ·servicectlhandler(ctl uint32, evtype uint32, evdata uintptr, context uintptr) uintptr {
|
||||||
|
TEXT ·servicectlhandler(SB),7,$0
|
||||||
|
MOVQ ·ctlHandlerExProc(SB), AX
|
||||||
|
JMP AX
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2018 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
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// func servicemain(argc uint32, argv **uint16)
|
||||||
|
TEXT ·servicemain(SB),NOSPLIT|NOFRAME,$0
|
||||||
|
MOVM.DB.W [R4, R14], (R13) // push {r4, lr}
|
||||||
|
MOVW R13, R4
|
||||||
|
BIC $0x7, R13 // alignment for ABI
|
||||||
|
|
||||||
|
MOVW R0, ·sArgc(SB)
|
||||||
|
MOVW R1, ·sArgv(SB)
|
||||||
|
|
||||||
|
MOVW ·sName(SB), R0
|
||||||
|
MOVW ·ctlHandlerExProc(SB), R1
|
||||||
|
MOVW $0, R2
|
||||||
|
MOVW ·cRegisterServiceCtrlHandlerExW(SB), R3
|
||||||
|
BL (R3)
|
||||||
|
CMP $0, R0
|
||||||
|
BEQ exit
|
||||||
|
MOVW R0, ·ssHandle(SB)
|
||||||
|
|
||||||
|
MOVW ·goWaitsH(SB), R0
|
||||||
|
MOVW ·cSetEvent(SB), R1
|
||||||
|
BL (R1)
|
||||||
|
|
||||||
|
MOVW ·cWaitsH(SB), R0
|
||||||
|
MOVW $-1, R1
|
||||||
|
MOVW ·cWaitForSingleObject(SB), R2
|
||||||
|
BL (R2)
|
||||||
|
|
||||||
|
exit:
|
||||||
|
MOVW R4, R13 // free extra stack space
|
||||||
|
MOVM.IA.W (R13), [R4, R15] // pop {r4, pc}
|
Loading…
Reference in New Issue