[sys] windows/svc: add new package to help create and manage Windows services

296 views
Skip to first unread message

Alex Brainman (Gerrit)

unread,
Apr 19, 2015, 7:26:23 AM4/19/15
to Rob Pike, Ian Lance Taylor, golang-co...@googlegroups.com
Alex Brainman uploaded a change:
https://go-review.googlesource.com/9104

windows/svc: add new package to help create and manage Windows services

Change-Id: I58bb446aaa387b31d8a9ff4217793a170b96a7e2
---
A windows/eventlog.go
M windows/security_windows.go
A windows/service.go
A windows/svc/debug/log.go
A windows/svc/debug/service.go
A windows/svc/event.go
A windows/svc/eventlog/install.go
A windows/svc/eventlog/log.go
A windows/svc/eventlog/log_test.go
A windows/svc/example/beep.go
A windows/svc/example/install.go
A windows/svc/example/main.go
A windows/svc/example/manage.go
A windows/svc/example/service.go
A windows/svc/go12.c
A windows/svc/go12.go
A windows/svc/go13.go
A windows/svc/mgr/config.go
A windows/svc/mgr/mgr.go
A windows/svc/mgr/mgr_test.go
A windows/svc/mgr/service.go
A windows/svc/security.go
A windows/svc/service.go
A windows/svc/svc_test.go
A windows/svc/sys_386.s
A windows/svc/sys_amd64.s
M windows/syscall_windows.go
M windows/zsyscall_windows.go
M windows/ztypes_windows.go
29 files changed, 2,362 insertions(+), 23 deletions(-)



diff --git a/windows/eventlog.go b/windows/eventlog.go
new file mode 100644
index 0000000..40af946
--- /dev/null
+++ b/windows/eventlog.go
@@ -0,0 +1,20 @@
+// 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 windows
+
+const (
+ EVENTLOG_SUCCESS = 0
+ EVENTLOG_ERROR_TYPE = 1
+ EVENTLOG_WARNING_TYPE = 2
+ EVENTLOG_INFORMATION_TYPE = 4
+ EVENTLOG_AUDIT_SUCCESS = 8
+ EVENTLOG_AUDIT_FAILURE = 16
+)
+
+//sys RegisterEventSource(uncServerName *uint16, sourceName *uint16)
(handle Handle, err error) [failretval==0] = advapi32.RegisterEventSourceW
+//sys DeregisterEventSource(handle Handle) (err error) =
advapi32.DeregisterEventSource
+//sys ReportEvent(log Handle, etype uint16, category uint16, eventId
uint32, usrSId uintptr, numStrings uint16, dataSize uint32, strings
**uint16, rawData *byte) (err error) = advapi32.ReportEventW
diff --git a/windows/security_windows.go b/windows/security_windows.go
index 8fe3737..ca09bdd 100644
--- a/windows/security_windows.go
+++ b/windows/security_windows.go
@@ -91,12 +91,56 @@
SidTypeLabel
)

+type SidIdentifierAuthority struct {
+ Value [6]byte
+}
+
+var (
+ SECURITY_NULL_SID_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0,
0, 0, 0, 0}}
+ SECURITY_WORLD_SID_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0,
0, 0, 0, 1}}
+ SECURITY_LOCAL_SID_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0,
0, 0, 0, 2}}
+ SECURITY_CREATOR_SID_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0,
0, 0, 0, 3}}
+ SECURITY_NON_UNIQUE_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0,
0, 0, 0, 4}}
+ SECURITY_NT_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0,
0, 0, 0, 5}}
+ SECURITY_MANDATORY_LABEL_AUTHORITY = SidIdentifierAuthority{[6]byte{0, 0,
0, 0, 0, 16}}
+)
+
+const (
+ SECURITY_NULL_RID = 0
+ SECURITY_WORLD_RID = 0
+ SECURITY_LOCAL_RID = 0
+ SECURITY_CREATOR_OWNER_RID = 0
+ SECURITY_CREATOR_GROUP_RID = 1
+ SECURITY_DIALUP_RID = 1
+ SECURITY_NETWORK_RID = 2
+ SECURITY_BATCH_RID = 3
+ SECURITY_INTERACTIVE_RID = 4
+ SECURITY_LOGON_IDS_RID = 5
+ SECURITY_SERVICE_RID = 6
+ SECURITY_LOCAL_SYSTEM_RID = 18
+ SECURITY_BUILTIN_DOMAIN_RID = 32
+ SECURITY_PRINCIPAL_SELF_RID = 10
+ SECURITY_CREATOR_OWNER_SERVER_RID = 0x2
+ SECURITY_CREATOR_GROUP_SERVER_RID = 0x3
+ SECURITY_LOGON_IDS_RID_COUNT = 0x3
+ SECURITY_ANONYMOUS_LOGON_RID = 0x7
+ SECURITY_PROXY_RID = 0x8
+ SECURITY_ENTERPRISE_CONTROLLERS_RID = 0x9
+ SECURITY_SERVER_LOGON_RID = SECURITY_ENTERPRISE_CONTROLLERS_RID
+ SECURITY_AUTHENTICATED_USER_RID = 0xb
+ SECURITY_RESTRICTED_CODE_RID = 0xc
+ SECURITY_NT_NON_UNIQUE_RID = 0x15
+)
+
//sys LookupAccountSid(systemName *uint16, sid *SID, name *uint16, nameLen
*uint32, refdDomainName *uint16, refdDomainNameLen *uint32, use *uint32)
(err error) = advapi32.LookupAccountSidW
//sys LookupAccountName(systemName *uint16, accountName *uint16, sid *SID,
sidLen *uint32, refdDomainName *uint16, refdDomainNameLen *uint32, use
*uint32) (err error) = advapi32.LookupAccountNameW
//sys ConvertSidToStringSid(sid *SID, stringSid **uint16) (err error) =
advapi32.ConvertSidToStringSidW
//sys ConvertStringSidToSid(stringSid *uint16, sid **SID) (err error) =
advapi32.ConvertStringSidToSidW
//sys GetLengthSid(sid *SID) (len uint32) = advapi32.GetLengthSid
//sys CopySid(destSidLen uint32, destSid *SID, srcSid *SID) (err error) =
advapi32.CopySid
+//sys AllocateAndInitializeSid(identAuth *SidIdentifierAuthority, subAuth
byte, subAuth0 uint32, subAuth1 uint32, subAuth2 uint32, subAuth3 uint32,
subAuth4 uint32, subAuth5 uint32, subAuth6 uint32, subAuth7 uint32, sid
**SID) (err error) = advapi32.AllocateAndInitializeSid
+//sys FreeSid(sid *SID) (err error) [failretval!=0] = advapi32.FreeSid
+//sys EqualSid(sid1 *SID, sid2 *SID) (isEqual bool) = advapi32.EqualSid

// The security identifier (SID) structure is a variable-length
// structure used to uniquely identify users or groups.
@@ -286,6 +330,11 @@
PrimaryGroup *SID
}

+type Tokengroups struct {
+ GroupCount uint32
+ Groups [1]SIDAndAttributes
+}
+
//sys OpenProcessToken(h Handle, access uint32, token *Token) (err error)
= advapi32.OpenProcessToken
//sys GetTokenInformation(t Token, infoClass uint32, info *byte, infoLen
uint32, returnedLen *uint32) (err error) = advapi32.GetTokenInformation
//sys GetUserProfileDirectory(t Token, dir *uint16, dirLen *uint32) (err
error) = userenv.GetUserProfileDirectoryW
@@ -346,6 +395,15 @@
return (*Tokenuser)(i), nil
}

+// GetTokenGroups retrieves group accounts associated with access token t.
+func (t Token) GetTokenGroups() (*Tokengroups, error) {
+ i, e := t.getInfo(TokenGroups, 50)
+ if e != nil {
+ return nil, e
+ }
+ return (*Tokengroups)(i), nil
+}
+
// GetTokenPrimaryGroup retrieves access token t primary group information.
// A pointer to a SID structure representing a group that will become
// the primary group of any objects created by a process using this access
token.
diff --git a/windows/service.go b/windows/service.go
new file mode 100644
index 0000000..1c11d39
--- /dev/null
+++ b/windows/service.go
@@ -0,0 +1,143 @@
+// 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 windows
+
+const (
+ SC_MANAGER_CONNECT = 1
+ SC_MANAGER_CREATE_SERVICE = 2
+ SC_MANAGER_ENUMERATE_SERVICE = 4
+ SC_MANAGER_LOCK = 8
+ SC_MANAGER_QUERY_LOCK_STATUS = 16
+ SC_MANAGER_MODIFY_BOOT_CONFIG = 32
+ SC_MANAGER_ALL_ACCESS = 0xf003f
+)
+
+//sys OpenSCManager(machineName *uint16, databaseName *uint16, access
uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenSCManagerW
+
+const (
+ SERVICE_KERNEL_DRIVER = 1
+ SERVICE_FILE_SYSTEM_DRIVER = 2
+ SERVICE_ADAPTER = 4
+ SERVICE_RECOGNIZER_DRIVER = 8
+ SERVICE_WIN32_OWN_PROCESS = 16
+ SERVICE_WIN32_SHARE_PROCESS = 32
+ SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS |
SERVICE_WIN32_SHARE_PROCESS
+ SERVICE_INTERACTIVE_PROCESS = 256
+ SERVICE_DRIVER = SERVICE_KERNEL_DRIVER |
SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER
+ SERVICE_TYPE_ALL = SERVICE_WIN32 | SERVICE_ADAPTER |
SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS
+
+ SERVICE_BOOT_START = 0
+ SERVICE_SYSTEM_START = 1
+ SERVICE_AUTO_START = 2
+ SERVICE_DEMAND_START = 3
+ SERVICE_DISABLED = 4
+
+ SERVICE_ERROR_IGNORE = 0
+ SERVICE_ERROR_NORMAL = 1
+ SERVICE_ERROR_SEVERE = 2
+ SERVICE_ERROR_CRITICAL = 3
+
+ SC_STATUS_PROCESS_INFO = 0
+
+ SERVICE_STOPPED = 1
+ SERVICE_START_PENDING = 2
+ SERVICE_STOP_PENDING = 3
+ SERVICE_RUNNING = 4
+ SERVICE_CONTINUE_PENDING = 5
+ SERVICE_PAUSE_PENDING = 6
+ SERVICE_PAUSED = 7
+ SERVICE_NO_CHANGE = 0xffffffff
+
+ SERVICE_ACCEPT_STOP = 1
+ SERVICE_ACCEPT_PAUSE_CONTINUE = 2
+ SERVICE_ACCEPT_SHUTDOWN = 4
+ SERVICE_ACCEPT_PARAMCHANGE = 8
+ SERVICE_ACCEPT_NETBINDCHANGE = 16
+ SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 32
+ SERVICE_ACCEPT_POWEREVENT = 64
+ SERVICE_ACCEPT_SESSIONCHANGE = 128
+
+ SERVICE_CONTROL_STOP = 1
+ SERVICE_CONTROL_PAUSE = 2
+ SERVICE_CONTROL_CONTINUE = 3
+ SERVICE_CONTROL_INTERROGATE = 4
+ SERVICE_CONTROL_SHUTDOWN = 5
+ SERVICE_CONTROL_PARAMCHANGE = 6
+ SERVICE_CONTROL_NETBINDADD = 7
+ SERVICE_CONTROL_NETBINDREMOVE = 8
+ SERVICE_CONTROL_NETBINDENABLE = 9
+ SERVICE_CONTROL_NETBINDDISABLE = 10
+ SERVICE_CONTROL_DEVICEEVENT = 11
+ SERVICE_CONTROL_HARDWAREPROFILECHANGE = 12
+ SERVICE_CONTROL_POWEREVENT = 13
+ SERVICE_CONTROL_SESSIONCHANGE = 14
+
+ SERVICE_ACTIVE = 1
+ SERVICE_INACTIVE = 2
+ SERVICE_STATE_ALL = 3
+
+ SERVICE_QUERY_CONFIG = 1
+ SERVICE_CHANGE_CONFIG = 2
+ SERVICE_QUERY_STATUS = 4
+ SERVICE_ENUMERATE_DEPENDENTS = 8
+ SERVICE_START = 16
+ SERVICE_STOP = 32
+ SERVICE_PAUSE_CONTINUE = 64
+ SERVICE_INTERROGATE = 128
+ SERVICE_USER_DEFINED_CONTROL = 256
+ SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS |
SERVICE_ENUMERATE_DEPENDENTS | SERVICE_START | SERVICE_STOP |
SERVICE_PAUSE_CONTINUE | SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL
+ SERVICE_RUNS_IN_SYSTEM_PROCESS = 1
+ SERVICE_CONFIG_DESCRIPTION = 1
+ SERVICE_CONFIG_FAILURE_ACTIONS = 2
+
+ NO_ERROR = 0
+)
+
+type SERVICE_STATUS struct {
+ ServiceType uint32
+ CurrentState uint32
+ ControlsAccepted uint32
+ Win32ExitCode uint32
+ ServiceSpecificExitCode uint32
+ CheckPoint uint32
+ WaitHint uint32
+}
+
+type SERVICE_TABLE_ENTRY struct {
+ ServiceName *uint16
+ ServiceProc uintptr
+}
+
+type QUERY_SERVICE_CONFIG struct {
+ ServiceType uint32
+ StartType uint32
+ ErrorControl uint32
+ BinaryPathName *uint16
+ LoadOrderGroup *uint16
+ TagId uint32
+ Dependencies *uint16
+ ServiceStartName *uint16
+ DisplayName *uint16
+}
+
+type SERVICE_DESCRIPTION struct {
+ Description *uint16
+}
+
+//sys CloseServiceHandle(handle Handle) (err error) =
advapi32.CloseServiceHandle
+//sys CreateService(mgr Handle, serviceName *uint16, displayName *uint16,
access uint32, srvType uint32, startType uint32, errCtl uint32, pathName
*uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16,
serviceStartName *uint16, password *uint16) (handle Handle, err error)
[failretval==0] = advapi32.CreateServiceW
+//sys OpenService(mgr Handle, serviceName *uint16, access uint32) (handle
Handle, err error) [failretval==0] = advapi32.OpenServiceW
+//sys DeleteService(service Handle) (err error) = advapi32.DeleteService
+//sys StartService(service Handle, numArgs uint32, argVectors **uint16)
(err error) = advapi32.StartServiceW
+//sys QueryServiceStatus(service Handle, status *SERVICE_STATUS) (err
error) = advapi32.QueryServiceStatus
+//sys ControlService(service Handle, control uint32, status
*SERVICE_STATUS) (err error) = advapi32.ControlService
+//sys StartServiceCtrlDispatcher(serviceTable *SERVICE_TABLE_ENTRY) (err
error) = advapi32.StartServiceCtrlDispatcherW
+//sys SetServiceStatus(service Handle, serviceStatus *SERVICE_STATUS) (err
error) = advapi32.SetServiceStatus
+//sys ChangeServiceConfig(service Handle, serviceType uint32, startType
uint32, errorControl uint32, binaryPathName *uint16, loadOrderGroup
*uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16,
password *uint16, displayName *uint16) (err error) =
advapi32.ChangeServiceConfigW
+//sys QueryServiceConfig(service Handle, serviceConfig
*QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) =
advapi32.QueryServiceConfigW
+//sys ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte)
(err error) = advapi32.ChangeServiceConfig2W
+//sys QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte,
buffSize uint32, bytesNeeded *uint32) (err error) =
advapi32.QueryServiceConfig2W
diff --git a/windows/svc/debug/log.go b/windows/svc/debug/log.go
new file mode 100644
index 0000000..e51ab42
--- /dev/null
+++ b/windows/svc/debug/log.go
@@ -0,0 +1,56 @@
+// 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 debug
+
+import (
+ "os"
+ "strconv"
+)
+
+// Log interface allows different log implementations to be used.
+type Log interface {
+ Close() error
+ Info(eid uint32, msg string) error
+ Warning(eid uint32, msg string) error
+ Error(eid uint32, msg string) error
+}
+
+// ConsoleLog provides access to the console.
+type ConsoleLog struct {
+ Name string
+}
+
+// New creates new ConsoleLog.
+func New(source string) *ConsoleLog {
+ return &ConsoleLog{Name: source}
+}
+
+// Close closes console log l.
+func (l *ConsoleLog) Close() error {
+ return nil
+}
+
+func (l *ConsoleLog) report(kind string, eid uint32, msg string) error {
+ s := l.Name + "." + kind + "(" + strconv.Itoa(int(eid)) + "): " + msg
+ "\n"
+ _, err := os.Stdout.Write([]byte(s))
+ return err
+}
+
+// Info writes an information event msg with event id eid to the console l.
+func (l *ConsoleLog) Info(eid uint32, msg string) error {
+ return l.report("info", eid, msg)
+}
+
+// Warning writes an warning event msg with event id eid to the console l.
+func (l *ConsoleLog) Warning(eid uint32, msg string) error {
+ return l.report("warn", eid, msg)
+}
+
+// Error writes an error event msg with event id eid to the console l.
+func (l *ConsoleLog) Error(eid uint32, msg string) error {
+ return l.report("error", eid, msg)
+}
diff --git a/windows/svc/debug/service.go b/windows/svc/debug/service.go
new file mode 100644
index 0000000..d5ab94b
--- /dev/null
+++ b/windows/svc/debug/service.go
@@ -0,0 +1,45 @@
+// 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 debug provides facilities to execute svc.Handler on console.
+//
+package debug
+
+import (
+ "os"
+ "os/signal"
+ "syscall"
+
+ "golang.org/x/sys/windows/svc"
+)
+
+// Run executes service name by calling appropriate handler function.
+// The process is running on console, unlike real service. Use Ctrl+C to
+// send "Stop" command to your service.
+func Run(name string, handler svc.Handler) error {
+ cmds := make(chan svc.ChangeRequest)
+ changes := make(chan svc.Status)
+
+ sig := make(chan os.Signal)
+ signal.Notify(sig)
+
+ go func() {
+ status := svc.Status{State: svc.Stopped}
+ for {
+ select {
+ case <-sig:
+ cmds <- svc.ChangeRequest{svc.Stop, status}
+ case status = <-changes:
+ }
+ }
+ }()
+
+ _, errno := handler.Execute([]string{name}, cmds, changes)
+ if errno != 0 {
+ return syscall.Errno(errno)
+ }
+ return nil
+}
diff --git a/windows/svc/event.go b/windows/svc/event.go
new file mode 100644
index 0000000..0508e22
--- /dev/null
+++ b/windows/svc/event.go
@@ -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
+}
diff --git a/windows/svc/eventlog/install.go
b/windows/svc/eventlog/install.go
new file mode 100644
index 0000000..c76a376
--- /dev/null
+++ b/windows/svc/eventlog/install.go
@@ -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)
+}
diff --git a/windows/svc/eventlog/log.go b/windows/svc/eventlog/log.go
new file mode 100644
index 0000000..46e5153
--- /dev/null
+++ b/windows/svc/eventlog/log.go
@@ -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)
+}
diff --git a/windows/svc/eventlog/log_test.go
b/windows/svc/eventlog/log_test.go
new file mode 100644
index 0000000..4dd8ad9
--- /dev/null
+++ b/windows/svc/eventlog/log_test.go
@@ -0,0 +1,51 @@
+// 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_test
+
+import (
+ "testing"
+
+ "golang.org/x/sys/windows/svc/eventlog"
+)
+
+func TestLog(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping test in short mode - it modifies system logs")
+ }
+
+ const name = "mylog"
+ const supports = eventlog.Error | eventlog.Warning | eventlog.Info
+ err := eventlog.InstallAsEventCreate(name, supports)
+ if err != nil {
+ t.Fatalf("Install failed: %s", err)
+ }
+ defer func() {
+ err = eventlog.Remove(name)
+ if err != nil {
+ t.Fatalf("Remove failed: %s", err)
+ }
+ }()
+
+ l, err := eventlog.Open(name)
+ if err != nil {
+ t.Fatalf("Open failed: %s", err)
+ }
+ defer l.Close()
+
+ err = l.Info(1, "info")
+ if err != nil {
+ t.Fatalf("Info failed: %s", err)
+ }
+ err = l.Warning(2, "warning")
+ if err != nil {
+ t.Fatalf("Warning failed: %s", err)
+ }
+ err = l.Error(3, "error")
+ if err != nil {
+ t.Fatalf("Error failed: %s", err)
+ }
+}
diff --git a/windows/svc/example/beep.go b/windows/svc/example/beep.go
new file mode 100644
index 0000000..dcf2340
--- /dev/null
+++ b/windows/svc/example/beep.go
@@ -0,0 +1,22 @@
+// 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 main
+
+import (
+ "syscall"
+)
+
+// BUG(brainman): MessageBeep Windows api is broken on Windows 7,
+// so this example does not beep when runs as service on Windows 7.
+
+var (
+ beepFunc = syscall.MustLoadDLL("user32.dll").MustFindProc("MessageBeep")
+)
+
+func beep() {
+ beepFunc.Call(0xffffffff)
+}
diff --git a/windows/svc/example/install.go b/windows/svc/example/install.go
new file mode 100644
index 0000000..0d2d0cd
--- /dev/null
+++ b/windows/svc/example/install.go
@@ -0,0 +1,92 @@
+// 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 main
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "golang.org/x/sys/windows/svc/eventlog"
+ "golang.org/x/sys/windows/svc/mgr"
+)
+
+func exePath() (string, error) {
+ prog := os.Args[0]
+ p, err := filepath.Abs(prog)
+ if err != nil {
+ return "", err
+ }
+ fi, err := os.Stat(p)
+ if err == nil {
+ if !fi.Mode().IsDir() {
+ return p, nil
+ }
+ err = fmt.Errorf("%s is directory", p)
+ }
+ if filepath.Ext(p) == "" {
+ p += ".exe"
+ fi, err := os.Stat(p)
+ if err == nil {
+ if !fi.Mode().IsDir() {
+ return p, nil
+ }
+ err = fmt.Errorf("%s is directory", p)
+ }
+ }
+ return "", err
+}
+
+func installService(name, desc string) error {
+ exepath, err := exePath()
+ if err != nil {
+ return err
+ }
+ m, err := mgr.Connect()
+ if err != nil {
+ return err
+ }
+ defer m.Disconnect()
+ s, err := m.OpenService(name)
+ if err == nil {
+ s.Close()
+ return fmt.Errorf("service %s already exists", name)
+ }
+ s, err = m.CreateService(name, exepath, mgr.Config{DisplayName: desc})
+ if err != nil {
+ return err
+ }
+ defer s.Close()
+ err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|
eventlog.Info)
+ if err != nil {
+ s.Delete()
+ return fmt.Errorf("SetupEventLogSource() failed: %s", err)
+ }
+ return nil
+}
+
+func removeService(name string) error {
+ m, err := mgr.Connect()
+ if err != nil {
+ return err
+ }
+ defer m.Disconnect()
+ s, err := m.OpenService(name)
+ if err != nil {
+ return fmt.Errorf("service %s is not installed", name)
+ }
+ defer s.Close()
+ err = s.Delete()
+ if err != nil {
+ return err
+ }
+ err = eventlog.Remove(name)
+ if err != nil {
+ return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
+ }
+ return nil
+}
diff --git a/windows/svc/example/main.go b/windows/svc/example/main.go
new file mode 100644
index 0000000..dc96c08
--- /dev/null
+++ b/windows/svc/example/main.go
@@ -0,0 +1,76 @@
+// 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
+
+// Example service program that beeps.
+//
+// The program demonstrates how to create Windows service and
+// install / remove it on a computer. It also shows how to
+// stop / start / pause / continue any service, and how to
+// write to event log. It also shows how to use debug
+// facilities available in debug package.
+//
+package main
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "strings"
+
+ "golang.org/x/sys/windows/svc"
+)
+
+func usage(errmsg string) {
+ fmt.Fprintf(os.Stderr,
+ "%s\n\n"+
+ "usage: %s <command>\n"+
+ " where <command> is one of\n"+
+ " install, remove, debug, start, stop, pause or continue.\n",
+ errmsg, os.Args[0])
+ os.Exit(2)
+}
+
+func main() {
+ const svcName = "myservice"
+
+ isIntSess, err := svc.IsAnInteractiveSession()
+ if err != nil {
+ log.Fatalf("failed to determine if we are running in an interactive
session: %v", err)
+ }
+ if !isIntSess {
+ runService(svcName, false)
+ return
+ }
+
+ if len(os.Args) < 2 {
+ usage("no command specified")
+ }
+
+ cmd := strings.ToLower(os.Args[1])
+ switch cmd {
+ case "debug":
+ runService(svcName, true)
+ return
+ case "install":
+ err = installService(svcName, "my service")
+ case "remove":
+ err = removeService(svcName)
+ case "start":
+ err = startService(svcName)
+ case "stop":
+ err = controlService(svcName, svc.Stop, svc.Stopped)
+ case "pause":
+ err = controlService(svcName, svc.Pause, svc.Paused)
+ case "continue":
+ err = controlService(svcName, svc.Continue, svc.Running)
+ default:
+ usage(fmt.Sprintf("invalid command %s", cmd))
+ }
+ if err != nil {
+ log.Fatalf("failed to %s %s: %v", cmd, svcName, err)
+ }
+ return
+}
diff --git a/windows/svc/example/manage.go b/windows/svc/example/manage.go
new file mode 100644
index 0000000..c270db2
--- /dev/null
+++ b/windows/svc/example/manage.go
@@ -0,0 +1,62 @@
+// 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 main
+
+import (
+ "fmt"
+ "time"
+
+ "golang.org/x/sys/windows/svc"
+ "golang.org/x/sys/windows/svc/mgr"
+)
+
+func startService(name string) error {
+ m, err := mgr.Connect()
+ if err != nil {
+ return err
+ }
+ defer m.Disconnect()
+ s, err := m.OpenService(name)
+ if err != nil {
+ return fmt.Errorf("could not access service: %v", err)
+ }
+ defer s.Close()
+ err = s.Start([]string{"p1", "p2", "p3"})
+ if err != nil {
+ return fmt.Errorf("could not start service: %v", err)
+ }
+ return nil
+}
+
+func controlService(name string, c svc.Cmd, to svc.State) error {
+ m, err := mgr.Connect()
+ if err != nil {
+ return err
+ }
+ defer m.Disconnect()
+ s, err := m.OpenService(name)
+ if err != nil {
+ return fmt.Errorf("could not access service: %v", err)
+ }
+ defer s.Close()
+ status, err := s.Control(c)
+ if err != nil {
+ return fmt.Errorf("could not send control=%d: %v", c, err)
+ }
+ timeout := time.Now().Add(10 * time.Second)
+ for status.State != to {
+ if timeout.Before(time.Now()) {
+ return fmt.Errorf("timeout waiting for service to go to state=%d", to)
+ }
+ time.Sleep(300 * time.Millisecond)
+ status, err = s.Query()
+ if err != nil {
+ return fmt.Errorf("could not retrieve service status: %v", err)
+ }
+ }
+ return nil
+}
diff --git a/windows/svc/example/service.go b/windows/svc/example/service.go
new file mode 100644
index 0000000..4a0ac42
--- /dev/null
+++ b/windows/svc/example/service.go
@@ -0,0 +1,81 @@
+// 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 main
+
+import (
+ "fmt"
+ "time"
+
+ "golang.org/x/sys/windows/svc"
+ "golang.org/x/sys/windows/svc/debug"
+ "golang.org/x/sys/windows/svc/eventlog"
+)
+
+var elog debug.Log
+
+type myservice struct{}
+
+func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest,
changes chan<- svc.Status) (ssec bool, errno uint32) {
+ const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown |
svc.AcceptPauseAndContinue
+ changes <- svc.Status{State: svc.StartPending}
+ fasttick := time.Tick(500 * time.Millisecond)
+ slowtick := time.Tick(2 * time.Second)
+ tick := fasttick
+ changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
+loop:
+ for {
+ select {
+ case <-tick:
+ beep()
+ case c := <-r:
+ switch c.Cmd {
+ case svc.Interrogate:
+ changes <- c.CurrentStatus
+ // Testing deadlock from
https://code.google.com/p/winsvc/issues/detail?id=4
+ time.Sleep(100 * time.Millisecond)
+ changes <- c.CurrentStatus
+ case svc.Stop, svc.Shutdown:
+ break loop
+ case svc.Pause:
+ changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
+ tick = slowtick
+ case svc.Continue:
+ changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
+ tick = fasttick
+ default:
+ elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
+ }
+ }
+ }
+ changes <- svc.Status{State: svc.StopPending}
+ return
+}
+
+func runService(name string, isDebug bool) {
+ var err error
+ if isDebug {
+ elog = debug.New(name)
+ } else {
+ elog, err = eventlog.Open(name)
+ if err != nil {
+ return
+ }
+ }
+ defer elog.Close()
+
+ elog.Info(1, fmt.Sprintf("starting %s service", name))
+ run := svc.Run
+ if isDebug {
+ run = debug.Run
+ }
+ err = run(name, &myservice{})
+ if err != nil {
+ elog.Error(1, fmt.Sprintf("%s service failed: %v", name, err))
+ return
+ }
+ elog.Info(1, fmt.Sprintf("%s service stopped", name))
+}
diff --git a/windows/svc/go12.c b/windows/svc/go12.c
new file mode 100644
index 0000000..6f1be1f
--- /dev/null
+++ b/windows/svc/go12.c
@@ -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;
+}
diff --git a/windows/svc/go12.go b/windows/svc/go12.go
new file mode 100644
index 0000000..6f0a924
--- /dev/null
+++ b/windows/svc/go12.go
@@ -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)
diff --git a/windows/svc/go13.go b/windows/svc/go13.go
new file mode 100644
index 0000000..432a9e7
--- /dev/null
+++ b/windows/svc/go13.go
@@ -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)
+}
diff --git a/windows/svc/mgr/config.go b/windows/svc/mgr/config.go
new file mode 100644
index 0000000..9b1f6e7
--- /dev/null
+++ b/windows/svc/mgr/config.go
@@ -0,0 +1,139 @@
+// 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"
+ "unicode/utf16"
+ "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
+ LoadOrderGroup string
+ TagId uint32
+ Dependencies []string
+ ServiceStartName string // name of the account under which the service
should run
+ DisplayName string
+ Password string
+ Description string
+}
+
+func toString(p *uint16) string {
+ if p == nil {
+ return ""
+ }
+ return syscall.UTF16ToString((*[4096]uint16)(unsafe.Pointer(p))[:])
+}
+
+func toStringSlice(ps *uint16) []string {
+ if ps == nil {
+ return nil
+ }
+ r := make([]string, 0)
+ for from, i, p := 0, 0, (*[1 << 24]uint16)(unsafe.Pointer(ps)); true; i++
{
+ if p[i] == 0 {
+ // empty string marks the end
+ if i <= from {
+ break
+ }
+ r = append(r, string(utf16.Decode(p[from:i])))
+ from = i + 1
+ }
+ }
+ 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
+ }
+ }
+
+ var p2 *windows.SERVICE_DESCRIPTION
+ n = uint32(1024)
+ for {
+ b := make([]byte, n)
+ p2 = (*windows.SERVICE_DESCRIPTION)(unsafe.Pointer(&b[0]))
+ err := windows.QueryServiceConfig2(s.Handle,
+ windows.SERVICE_CONFIG_DESCRIPTION, &b[0], 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
+ }
+ }
+
+ return Config{
+ ServiceType: p.ServiceType,
+ StartType: p.StartType,
+ ErrorControl: p.ErrorControl,
+ BinaryPathName: toString(p.BinaryPathName),
+ LoadOrderGroup: toString(p.LoadOrderGroup),
+ TagId: p.TagId,
+ Dependencies: toStringSlice(p.Dependencies),
+ ServiceStartName: toString(p.ServiceStartName),
+ DisplayName: toString(p.DisplayName),
+ Description: toString(p2.Description),
+ }, nil
+}
+
+func updateDescription(handle windows.Handle, desc string) error {
+ d := windows.SERVICE_DESCRIPTION{toPtr(desc)}
+ return windows.ChangeServiceConfig2(handle,
+ windows.SERVICE_CONFIG_DESCRIPTION, (*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
+ }
+ return updateDescription(s.Handle, c.Description)
+}
diff --git a/windows/svc/mgr/mgr.go b/windows/svc/mgr/mgr.go
new file mode 100644
index 0000000..be46881
--- /dev/null
+++ b/windows/svc/mgr/mgr.go
@@ -0,0 +1,111 @@
+// 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"
+ "unicode/utf16"
+
+ "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)
+}
+
+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.
+func (m *Mgr) CreateService(name, exepath string, c Config) (*Service,
error) {
+ if c.StartType == 0 {
+ c.StartType = StartManual
+ }
+ if c.ErrorControl == 0 {
+ c.ErrorControl = ErrorNormal
+ }
+ c.BinaryPathName = exepath // execpath is important, do not rely on
BinaryPathName field to be set
+ h, err := windows.CreateService(m.Handle, toPtr(name),
toPtr(c.DisplayName),
+ windows.SERVICE_ALL_ACCESS, windows.SERVICE_WIN32_OWN_PROCESS,
+ c.StartType, c.ErrorControl, toPtr(exepath), toPtr(c.LoadOrderGroup),
+ nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName),
toPtr(c.Password))
+ if err != nil {
+ return nil, err
+ }
+ if c.Description != "" {
+ err = updateDescription(h, c.Description)
+ if err != nil {
+ 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
+}
diff --git a/windows/svc/mgr/mgr_test.go b/windows/svc/mgr/mgr_test.go
new file mode 100644
index 0000000..deb9fac
--- /dev/null
+++ b/windows/svc/mgr/mgr_test.go
@@ -0,0 +1,147 @@
+// 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_test
+
+import (
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/windows/svc/mgr"
+)
+
+func TestOpenLanManServer(t *testing.T) {
+ m, err := mgr.Connect()
+ if err != nil {
+ t.Fatalf("SCM connection failed: %s", err)
+ }
+ defer m.Disconnect()
+
+ s, err := m.OpenService("LanmanServer")
+ if err != nil {
+ t.Fatalf("OpenService(lanmanserver) failed: %s", err)
+ }
+ defer s.Close()
+
+ _, err = s.Config()
+ if err != nil {
+ t.Fatalf("Config failed: %s", err)
+ }
+}
+
+func install(t *testing.T, m *mgr.Mgr, name, exepath string, c mgr.Config)
{
+ // Sometimes it takes a while for the service to get
+ // removed after previous test run.
+ for i := 0; ; i++ {
+ s, err := m.OpenService(name)
+ if err != nil {
+ break
+ }
+ s.Close()
+
+ if i > 10 {
+ t.Fatalf("service %s already exists", name)
+ }
+ time.Sleep(300 * time.Millisecond)
+ }
+
+ s, err := m.CreateService(name, exepath, c)
+ if err != nil {
+ t.Fatalf("CreateService(%s) failed: %v", name, err)
+ }
+ defer s.Close()
+}
+
+func depString(d []string) string {
+ if len(d) == 0 {
+ return ""
+ }
+ for i := range d {
+ d[i] = strings.ToLower(d[i])
+ }
+ ss := sort.StringSlice(d)
+ ss.Sort()
+ return strings.Join([]string(ss), " ")
+}
+
+func testConfig(t *testing.T, s *mgr.Service, should mgr.Config)
mgr.Config {
+ is, err := s.Config()
+ if err != nil {
+ t.Fatalf("Config failed: %s", err)
+ }
+ if should.DisplayName != is.DisplayName {
+ t.Fatalf("config mismatch: DisplayName is %q, but should have %q",
is.DisplayName, should.DisplayName)
+ }
+ if should.StartType != is.StartType {
+ t.Fatalf("config mismatch: StartType is %v, but should have %v",
is.StartType, should.StartType)
+ }
+ if should.Description != is.Description {
+ t.Fatalf("config mismatch: Description is %q, but should have %q",
is.Description, should.Description)
+ }
+ if depString(should.Dependencies) != depString(is.Dependencies) {
+ t.Fatalf("config mismatch: Dependencies is %v, but should have %v",
is.Dependencies, should.Dependencies)
+ }
+ return is
+}
+
+func remove(t *testing.T, s *mgr.Service) {
+ err := s.Delete()
+ if err != nil {
+ t.Fatalf("Delete failed: %s", err)
+ }
+}
+
+func TestMyService(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping test in short mode - it modifies system services")
+ }
+
+ const name = "myservice"
+
+ m, err := mgr.Connect()
+ if err != nil {
+ t.Fatalf("SCM connection failed: %s", err)
+ }
+ defer m.Disconnect()
+
+ c := mgr.Config{
+ StartType: mgr.StartDisabled,
+ DisplayName: "my service",
+ Description: "my service is just a test",
+ Dependencies: []string{"LanmanServer", "W32Time"},
+ }
+
+ exename := os.Args[0]
+ exepath, err := filepath.Abs(exename)
+ if err != nil {
+ t.Fatalf("filepath.Abs(%s) failed: %s", exename, err)
+ }
+
+ install(t, m, name, exepath, c)
+
+ s, err := m.OpenService(name)
+ if err != nil {
+ t.Fatalf("service %s is not installed", name)
+ }
+ defer s.Close()
+
+ c.BinaryPathName = exepath
+ c = testConfig(t, s, c)
+
+ c.StartType = mgr.StartManual
+ err = s.UpdateConfig(c)
+ if err != nil {
+ t.Fatalf("UpdateConfig failed: %v", err)
+ }
+
+ testConfig(t, s, c)
+
+ remove(t, s)
+}
diff --git a/windows/svc/mgr/service.go b/windows/svc/mgr/service.go
new file mode 100644
index 0000000..9ae0baf
--- /dev/null
+++ b/windows/svc/mgr/service.go
@@ -0,0 +1,73 @@
+// 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"
+
+ "golang.org/x/sys/windows"
+ "golang.org/x/sys/windows/svc"
+)
+
+// TODO(brainman): Use EnumDependentServices to enumerate dependent
services.
+
+// TODO(brainman): Use EnumServicesStatus to enumerate services in the
specified service control manager database.
+
+// 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.
+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
+ err := windows.QueryServiceStatus(s.Handle, &t)
+ if err != nil {
+ return svc.Status{}, err
+ }
+ return svc.Status{
+ State: svc.State(t.CurrentState),
+ Accepts: svc.Accepted(t.ControlsAccepted),
+ }, nil
+}
diff --git a/windows/svc/security.go b/windows/svc/security.go
new file mode 100644
index 0000000..6fbc923
--- /dev/null
+++ b/windows/svc/security.go
@@ -0,0 +1,62 @@
+// 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 (
+ "unsafe"
+
+ "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
+ }
+ p := unsafe.Pointer(&gs.Groups[0])
+ groups := (*[2 << 20]windows.SIDAndAttributes)(p)[:gs.GroupCount]
+ for _, g := range groups {
+ if windows.EqualSid(g.Sid, interSid) {
+ return true, nil
+ }
+ if windows.EqualSid(g.Sid, serviceSid) {
+ return false, nil
+ }
+ }
+ return false, nil
+}
diff --git a/windows/svc/service.go b/windows/svc/service.go
new file mode 100644
index 0000000..70a94d4
--- /dev/null
+++ b/windows/svc/service.go
@@ -0,0 +1,315 @@
+// 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/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)
+)
+
+// 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)
+)
+
+// 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
+}
+
+// ChangeRequest is sent to the service Handler to request service status
change.
+type ChangeRequest struct {
+ Cmd Cmd
+ CurrentStatus Status
+}
+
+// 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 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
+ ctlHandlerProc uintptr
+ cSetEvent uintptr
+ cWaitForSingleObject uintptr
+ cRegisterServiceCtrlHandlerW uintptr
+)
+
+func init() {
+ k := syscall.MustLoadDLL("kernel32.dll")
+ cSetEvent = k.MustFindProc("SetEvent").Addr()
+ cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr()
+ a := syscall.MustLoadDLL("advapi32.dll")
+ cRegisterServiceCtrlHandlerW =
a.MustFindProc("RegisterServiceCtrlHandlerW").Addr()
+}
+
+type ctlEvent struct {
+ cmd Cmd
+ 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 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)
+ argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc]
+ args := make([]string, len(argv))
+ for i, a := range argv {
+ args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(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}
+ }()
+
+ status := Status{State: Stopped}
+ ec := exitCode{isSvcSpecific: true, errno: 0}
+ var outch chan ChangeRequest
+ inch := s.c
+ var cmd Cmd
+loop:
+ for {
+ select {
+ case r := <-inch:
+ if r.errno != 0 {
+ ec.errno = r.errno
+ break loop
+ }
+ inch = nil
+ outch = cmdsToHandler
+ cmd = r.cmd
+ case outch <- ChangeRequest{cmd, status}:
+ 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
+ }
+ status = 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 uint32) uintptr {
+ e := ctlEvent{cmd: Cmd(ctl)}
+ // 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
+ return 0
+ }
+
+ var svcmain uintptr
+ getServiceMain(&svcmain)
+ t := []windows.SERVICE_TABLE_ENTRY{
+ {syscall.StringToUTF16Ptr(s.name), svcmain},
+ {nil, 0},
+ }
+
+ goWaitsH = uintptr(s.goWaits.h)
+ cWaitsH = uintptr(s.cWaits.h)
+ sName = t[0].ServiceName
+ ctlHandlerProc, err = newCallback(ctlHandler)
+ if err != nil {
+ return err
+ }
+
+ go s.run()
+
+ err = windows.StartServiceCtrlDispatcher(&t[0])
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/windows/svc/svc_test.go b/windows/svc/svc_test.go
new file mode 100644
index 0000000..4dcf540
--- /dev/null
+++ b/windows/svc/svc_test.go
@@ -0,0 +1,118 @@
+// 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_test
+
+import (
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/windows/svc"
+ "golang.org/x/sys/windows/svc/mgr"
+)
+
+func getState(t *testing.T, s *mgr.Service) svc.State {
+ status, err := s.Query()
+ if err != nil {
+ t.Fatalf("Query(%s) failed: %s", s.Name, err)
+ }
+ return status.State
+}
+
+func testState(t *testing.T, s *mgr.Service, want svc.State) {
+ have := getState(t, s)
+ if have != want {
+ t.Fatalf("%s state is=%d want=%d", s.Name, have, want)
+ }
+}
+
+func waitState(t *testing.T, s *mgr.Service, want svc.State) {
+ for i := 0; ; i++ {
+ have := getState(t, s)
+ if have == want {
+ return
+ }
+ if i > 10 {
+ t.Fatalf("%s state is=%d, waiting timeout", s.Name, have)
+ }
+ time.Sleep(300 * time.Millisecond)
+ }
+}
+
+func TestExample(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping test in short mode - it modifies system services")
+ }
+
+ const name = "myservice"
+
+ m, err := mgr.Connect()
+ if err != nil {
+ t.Fatalf("SCM connection failed: %s", err)
+ }
+ defer m.Disconnect()
+
+ dir, err := ioutil.TempDir("", "svc")
+ if err != nil {
+ t.Fatalf("failed to create temp directory: %v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ exepath := filepath.Join(dir, "a.exe")
+ o, err := exec.Command("go", "build", "-o",
exepath, "golang.org/x/sys/windows/svc/example").CombinedOutput()
+ if err != nil {
+ t.Fatalf("failed to build service program: %v\n%v", err, string(o))
+ }
+
+ s, err := m.OpenService(name)
+ if err == nil {
+ err = s.Delete()
+ if err != nil {
+ s.Close()
+ t.Fatalf("Delete failed: %s", err)
+ }
+ s.Close()
+ }
+ s, err = m.CreateService(name, exepath, mgr.Config{DisplayName: "my
service"})
+ if err != nil {
+ t.Fatalf("CreateService(%s) failed: %v", name, err)
+ }
+ defer s.Close()
+
+ testState(t, s, svc.Stopped)
+ err = s.Start(nil)
+ if err != nil {
+ t.Fatalf("Start(%s) failed: %s", s.Name, err)
+ }
+ waitState(t, s, svc.Running)
+ time.Sleep(1 * time.Second)
+
+ // testing deadlock from issues 4.
+ _, err = s.Control(svc.Interrogate)
+ if err != nil {
+ t.Fatalf("Control(%s) failed: %s", s.Name, err)
+ }
+ _, err = s.Control(svc.Interrogate)
+ if err != nil {
+ t.Fatalf("Control(%s) failed: %s", s.Name, err)
+ }
+ time.Sleep(1 * time.Second)
+
+ _, err = s.Control(svc.Stop)
+ if err != nil {
+ t.Fatalf("Control(%s) failed: %s", s.Name, err)
+ }
+ waitState(t, s, svc.Stopped)
+
+ err = s.Delete()
+ if err != nil {
+ t.Fatalf("Delete failed: %s", err)
+ }
+}
diff --git a/windows/svc/sys_386.s b/windows/svc/sys_386.s
new file mode 100644
index 0000000..5e11bfa
--- /dev/null
+++ b/windows/svc/sys_386.s
@@ -0,0 +1,67 @@
+// 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)
+ MOVL ·cRegisterServiceCtrlHandlerW(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) uintptr
+TEXT ·servicectlhandler(SB),7,$0
+ MOVL ·ctlHandlerProc(SB), CX
+ JMP CX
diff --git a/windows/svc/sys_amd64.s b/windows/svc/sys_amd64.s
new file mode 100644
index 0000000..87dbec8
--- /dev/null
+++ b/windows/svc/sys_amd64.s
@@ -0,0 +1,41 @@
+// 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)
+ MOVL DX, ·sArgv(SB)
+
+ SUBQ $32, SP // stack for the first 4 syscall params
+
+ MOVQ ·sName(SB), CX
+ MOVQ $·servicectlhandler(SB), DX
+ MOVQ ·cRegisterServiceCtrlHandlerW(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) uintptr
+TEXT ·servicectlhandler(SB),7,$0
+ MOVQ ·ctlHandlerProc(SB), AX
+ JMP AX
diff --git a/windows/syscall_windows.go b/windows/syscall_windows.go
index 4256f45..2784f17 100644
--- a/windows/syscall_windows.go
+++ b/windows/syscall_windows.go
@@ -14,7 +14,7 @@
"unsafe"
)

-//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output
zsyscall_windows.go syscall_windows.go security_windows.go
+//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output
zsyscall_windows.go eventlog.go service.go syscall_windows.go
security_windows.go

type Handle uintptr

@@ -180,6 +180,9 @@
// This function returns 1 byte BOOLEAN rather than the 4 byte BOOL.
//sys CreateSymbolicLink(symlinkfilename *uint16, targetfilename *uint16,
flags uint32) (err error) [failretval&0xff==0] = CreateSymbolicLinkW
//sys CreateHardLink(filename *uint16, existingfilename *uint16, reserved
uintptr) (err error) [failretval&0xff==0] = CreateHardLinkW
+//sys GetCurrentThreadId() (id uint32)
+//sys CreateEvent(eventAttrs *syscall.SecurityAttributes, manualReset
uint32, initialState uint32, name *uint16) (handle Handle, err error) =
kernel32.CreateEventW
+//sys SetEvent(event Handle) (err error) = kernel32.SetEvent

// syscall interface implementation for other packages

diff --git a/windows/zsyscall_windows.go b/windows/zsyscall_windows.go
index a0bcfa1..cec5d5a 100644
--- a/windows/zsyscall_windows.go
+++ b/windows/zsyscall_windows.go
@@ -8,8 +8,8 @@
var _ unsafe.Pointer

var (
- modkernel32 = syscall.NewLazyDLL("kernel32.dll")
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
+ modkernel32 = syscall.NewLazyDLL("kernel32.dll")
modshell32 = syscall.NewLazyDLL("shell32.dll")
modmswsock = syscall.NewLazyDLL("mswsock.dll")
modcrypt32 = syscall.NewLazyDLL("crypt32.dll")
@@ -20,6 +20,23 @@
modnetapi32 = syscall.NewLazyDLL("netapi32.dll")
moduserenv = syscall.NewLazyDLL("userenv.dll")

+ procRegisterEventSourceW =
modadvapi32.NewProc("RegisterEventSourceW")
+ procDeregisterEventSource =
modadvapi32.NewProc("DeregisterEventSource")
+ procReportEventW =
modadvapi32.NewProc("ReportEventW")
+ procOpenSCManagerW =
modadvapi32.NewProc("OpenSCManagerW")
+ procCloseServiceHandle =
modadvapi32.NewProc("CloseServiceHandle")
+ procCreateServiceW =
modadvapi32.NewProc("CreateServiceW")
+ procOpenServiceW =
modadvapi32.NewProc("OpenServiceW")
+ procDeleteService =
modadvapi32.NewProc("DeleteService")
+ procStartServiceW =
modadvapi32.NewProc("StartServiceW")
+ procQueryServiceStatus =
modadvapi32.NewProc("QueryServiceStatus")
+ procControlService =
modadvapi32.NewProc("ControlService")
+ procStartServiceCtrlDispatcherW =
modadvapi32.NewProc("StartServiceCtrlDispatcherW")
+ procSetServiceStatus =
modadvapi32.NewProc("SetServiceStatus")
+ procChangeServiceConfigW =
modadvapi32.NewProc("ChangeServiceConfigW")
+ procQueryServiceConfigW =
modadvapi32.NewProc("QueryServiceConfigW")
+ procChangeServiceConfig2W =
modadvapi32.NewProc("ChangeServiceConfig2W")
+ procQueryServiceConfig2W =
modadvapi32.NewProc("QueryServiceConfig2W")
procGetLastError =
modkernel32.NewProc("GetLastError")
procLoadLibraryW =
modkernel32.NewProc("LoadLibraryW")
procFreeLibrary =
modkernel32.NewProc("FreeLibrary")
@@ -117,6 +134,9 @@
procDeviceIoControl =
modkernel32.NewProc("DeviceIoControl")
procCreateSymbolicLinkW =
modkernel32.NewProc("CreateSymbolicLinkW")
procCreateHardLinkW =
modkernel32.NewProc("CreateHardLinkW")
+ procGetCurrentThreadId =
modkernel32.NewProc("GetCurrentThreadId")
+ procCreateEventW =
modkernel32.NewProc("CreateEventW")
+ procSetEvent = modkernel32.NewProc("SetEvent")
procWSAStartup = modws2_32.NewProc("WSAStartup")
procWSACleanup = modws2_32.NewProc("WSACleanup")
procWSAIoctl = modws2_32.NewProc("WSAIoctl")
@@ -160,10 +180,221 @@
procConvertStringSidToSidW =
modadvapi32.NewProc("ConvertStringSidToSidW")
procGetLengthSid =
modadvapi32.NewProc("GetLengthSid")
procCopySid = modadvapi32.NewProc("CopySid")
+ procAllocateAndInitializeSid =
modadvapi32.NewProc("AllocateAndInitializeSid")
+ procFreeSid = modadvapi32.NewProc("FreeSid")
+ procEqualSid = modadvapi32.NewProc("EqualSid")
procOpenProcessToken =
modadvapi32.NewProc("OpenProcessToken")
procGetTokenInformation =
modadvapi32.NewProc("GetTokenInformation")
procGetUserProfileDirectoryW =
moduserenv.NewProc("GetUserProfileDirectoryW")
)
+
+func RegisterEventSource(uncServerName *uint16, sourceName *uint16)
(handle Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procRegisterEventSourceW.Addr(), 2,
uintptr(unsafe.Pointer(uncServerName)),
uintptr(unsafe.Pointer(sourceName)), 0)
+ handle = Handle(r0)
+ if handle == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func DeregisterEventSource(handle Handle) (err error) {
+ r1, _, e1 := syscall.Syscall(procDeregisterEventSource.Addr(), 1,
uintptr(handle), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func ReportEvent(log Handle, etype uint16, category uint16, eventId
uint32, usrSId uintptr, numStrings uint16, dataSize uint32, strings
**uint16, rawData *byte) (err error) {
+ r1, _, e1 := syscall.Syscall9(procReportEventW.Addr(), 9, uintptr(log),
uintptr(etype), uintptr(category), uintptr(eventId), uintptr(usrSId),
uintptr(numStrings), uintptr(dataSize), uintptr(unsafe.Pointer(strings)),
uintptr(unsafe.Pointer(rawData)))
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func OpenSCManager(machineName *uint16, databaseName *uint16, access
uint32) (handle Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procOpenSCManagerW.Addr(), 3,
uintptr(unsafe.Pointer(machineName)),
uintptr(unsafe.Pointer(databaseName)), uintptr(access))
+ handle = Handle(r0)
+ if handle == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func CloseServiceHandle(handle Handle) (err error) {
+ r1, _, e1 := syscall.Syscall(procCloseServiceHandle.Addr(), 1,
uintptr(handle), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func CreateService(mgr Handle, serviceName *uint16, displayName *uint16,
access uint32, srvType uint32, startType uint32, errCtl uint32, pathName
*uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16,
serviceStartName *uint16, password *uint16) (handle Handle, err error) {
+ r0, _, e1 := syscall.Syscall15(procCreateServiceW.Addr(), 13,
uintptr(mgr), uintptr(unsafe.Pointer(serviceName)),
uintptr(unsafe.Pointer(displayName)), uintptr(access), uintptr(srvType),
uintptr(startType), uintptr(errCtl), uintptr(unsafe.Pointer(pathName)),
uintptr(unsafe.Pointer(loadOrderGroup)), uintptr(unsafe.Pointer(tagId)),
uintptr(unsafe.Pointer(dependencies)),
uintptr(unsafe.Pointer(serviceStartName)),
uintptr(unsafe.Pointer(password)), 0, 0)
+ handle = Handle(r0)
+ if handle == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func OpenService(mgr Handle, serviceName *uint16, access uint32) (handle
Handle, err error) {
+ r0, _, e1 := syscall.Syscall(procOpenServiceW.Addr(), 3, uintptr(mgr),
uintptr(unsafe.Pointer(serviceName)), uintptr(access))
+ handle = Handle(r0)
+ if handle == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func DeleteService(service Handle) (err error) {
+ r1, _, e1 := syscall.Syscall(procDeleteService.Addr(), 1,
uintptr(service), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func StartService(service Handle, numArgs uint32, argVectors **uint16)
(err error) {
+ r1, _, e1 := syscall.Syscall(procStartServiceW.Addr(), 3,
uintptr(service), uintptr(numArgs), uintptr(unsafe.Pointer(argVectors)))
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func QueryServiceStatus(service Handle, status *SERVICE_STATUS) (err
error) {
+ r1, _, e1 := syscall.Syscall(procQueryServiceStatus.Addr(), 2,
uintptr(service), uintptr(unsafe.Pointer(status)), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func ControlService(service Handle, control uint32, status
*SERVICE_STATUS) (err error) {
+ r1, _, e1 := syscall.Syscall(procControlService.Addr(), 3,
uintptr(service), uintptr(control), uintptr(unsafe.Pointer(status)))
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func StartServiceCtrlDispatcher(serviceTable *SERVICE_TABLE_ENTRY) (err
error) {
+ r1, _, e1 := syscall.Syscall(procStartServiceCtrlDispatcherW.Addr(), 1,
uintptr(unsafe.Pointer(serviceTable)), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func SetServiceStatus(service Handle, serviceStatus *SERVICE_STATUS) (err
error) {
+ r1, _, e1 := syscall.Syscall(procSetServiceStatus.Addr(), 2,
uintptr(service), uintptr(unsafe.Pointer(serviceStatus)), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func ChangeServiceConfig(service Handle, serviceType uint32, startType
uint32, errorControl uint32, binaryPathName *uint16, loadOrderGroup
*uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16,
password *uint16, displayName *uint16) (err error) {
+ r1, _, e1 := syscall.Syscall12(procChangeServiceConfigW.Addr(), 11,
uintptr(service), uintptr(serviceType), uintptr(startType),
uintptr(errorControl), uintptr(unsafe.Pointer(binaryPathName)),
uintptr(unsafe.Pointer(loadOrderGroup)), uintptr(unsafe.Pointer(tagId)),
uintptr(unsafe.Pointer(dependencies)),
uintptr(unsafe.Pointer(serviceStartName)),
uintptr(unsafe.Pointer(password)), uintptr(unsafe.Pointer(displayName)), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func QueryServiceConfig(service Handle, serviceConfig
*QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) {
+ r1, _, e1 := syscall.Syscall6(procQueryServiceConfigW.Addr(), 4,
uintptr(service), uintptr(unsafe.Pointer(serviceConfig)), uintptr(bufSize),
uintptr(unsafe.Pointer(bytesNeeded)), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte)
(err error) {
+ r1, _, e1 := syscall.Syscall(procChangeServiceConfig2W.Addr(), 3,
uintptr(service), uintptr(infoLevel), uintptr(unsafe.Pointer(info)))
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte,
buffSize uint32, bytesNeeded *uint32) (err error) {
+ r1, _, e1 := syscall.Syscall6(procQueryServiceConfig2W.Addr(), 5,
uintptr(service), uintptr(infoLevel), uintptr(unsafe.Pointer(buff)),
uintptr(buffSize), uintptr(unsafe.Pointer(bytesNeeded)), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}

func GetLastError() (lasterr error) {
r0, _, _ := syscall.Syscall(procGetLastError.Addr(), 0, 0, 0, 0)
@@ -1357,6 +1588,37 @@
return
}

+func GetCurrentThreadId() (id uint32) {
+ r0, _, _ := syscall.Syscall(procGetCurrentThreadId.Addr(), 0, 0, 0, 0)
+ id = uint32(r0)
+ return
+}
+
+func CreateEvent(eventAttrs *syscall.SecurityAttributes, manualReset
uint32, initialState uint32, name *uint16) (handle Handle, err error) {
+ r0, _, e1 := syscall.Syscall6(procCreateEventW.Addr(), 4,
uintptr(unsafe.Pointer(eventAttrs)), uintptr(manualReset),
uintptr(initialState), uintptr(unsafe.Pointer(name)), 0, 0)
+ handle = Handle(r0)
+ if handle == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func SetEvent(event Handle) (err error) {
+ r1, _, e1 := syscall.Syscall(procSetEvent.Addr(), 1, uintptr(event), 0, 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
func WSAStartup(verreq uint32, data *WSAData) (sockerr error) {
r0, _, _ := syscall.Syscall(procWSAStartup.Addr(), 2, uintptr(verreq),
uintptr(unsafe.Pointer(data)), 0)
if r0 != 0 {
@@ -1848,6 +2110,36 @@
return
}

+func AllocateAndInitializeSid(identAuth *SidIdentifierAuthority, subAuth
byte, subAuth0 uint32, subAuth1 uint32, subAuth2 uint32, subAuth3 uint32,
subAuth4 uint32, subAuth5 uint32, subAuth6 uint32, subAuth7 uint32, sid
**SID) (err error) {
+ r1, _, e1 := syscall.Syscall12(procAllocateAndInitializeSid.Addr(), 11,
uintptr(unsafe.Pointer(identAuth)), uintptr(subAuth), uintptr(subAuth0),
uintptr(subAuth1), uintptr(subAuth2), uintptr(subAuth3), uintptr(subAuth4),
uintptr(subAuth5), uintptr(subAuth6), uintptr(subAuth7),
uintptr(unsafe.Pointer(sid)), 0)
+ if r1 == 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func FreeSid(sid *SID) (err error) {
+ r1, _, e1 := syscall.Syscall(procFreeSid.Addr(), 1,
uintptr(unsafe.Pointer(sid)), 0, 0)
+ if r1 != 0 {
+ if e1 != 0 {
+ err = error(e1)
+ } else {
+ err = syscall.EINVAL
+ }
+ }
+ return
+}
+
+func EqualSid(sid1 *SID, sid2 *SID) (isEqual bool) {
+ r0, _, _ := syscall.Syscall(procEqualSid.Addr(), 2,
uintptr(unsafe.Pointer(sid1)), uintptr(unsafe.Pointer(sid2)), 0)
+ isEqual = r0 != 0
+ return
+}
+
func OpenProcessToken(h Handle, access uint32, token *Token) (err error) {
r1, _, e1 := syscall.Syscall(procOpenProcessToken.Addr(), 3, uintptr(h),
uintptr(access), uintptr(unsafe.Pointer(token)))
if r1 == 0 {
diff --git a/windows/ztypes_windows.go b/windows/ztypes_windows.go
index ee669ab..ea600f6 100644
--- a/windows/ztypes_windows.go
+++ b/windows/ztypes_windows.go
@@ -8,27 +8,28 @@

const (
// Windows errors.
- ERROR_FILE_NOT_FOUND syscall.Errno = 2
- ERROR_PATH_NOT_FOUND syscall.Errno = 3
- ERROR_ACCESS_DENIED syscall.Errno = 5
- ERROR_NO_MORE_FILES syscall.Errno = 18
- ERROR_HANDLE_EOF syscall.Errno = 38
- ERROR_NETNAME_DELETED syscall.Errno = 64
- ERROR_FILE_EXISTS syscall.Errno = 80
- ERROR_BROKEN_PIPE syscall.Errno = 109
- ERROR_BUFFER_OVERFLOW syscall.Errno = 111
- ERROR_INSUFFICIENT_BUFFER syscall.Errno = 122
- ERROR_MOD_NOT_FOUND syscall.Errno = 126
- ERROR_PROC_NOT_FOUND syscall.Errno = 127
- ERROR_ALREADY_EXISTS syscall.Errno = 183
- ERROR_ENVVAR_NOT_FOUND syscall.Errno = 203
- ERROR_MORE_DATA syscall.Errno = 234
- ERROR_OPERATION_ABORTED syscall.Errno = 995
- ERROR_IO_PENDING syscall.Errno = 997
- ERROR_NOT_FOUND syscall.Errno = 1168
- ERROR_PRIVILEGE_NOT_HELD syscall.Errno = 1314
- WSAEACCES syscall.Errno = 10013
- WSAECONNRESET syscall.Errno = 10054
+ ERROR_FILE_NOT_FOUND syscall.Errno = 2
+ ERROR_PATH_NOT_FOUND syscall.Errno = 3
+ ERROR_ACCESS_DENIED syscall.Errno = 5
+ ERROR_NO_MORE_FILES syscall.Errno = 18
+ ERROR_HANDLE_EOF syscall.Errno = 38
+ ERROR_NETNAME_DELETED syscall.Errno = 64
+ ERROR_FILE_EXISTS syscall.Errno = 80
+ ERROR_BROKEN_PIPE syscall.Errno = 109
+ ERROR_BUFFER_OVERFLOW syscall.Errno = 111
+ ERROR_INSUFFICIENT_BUFFER syscall.Errno = 122
+ ERROR_MOD_NOT_FOUND syscall.Errno = 126
+ ERROR_PROC_NOT_FOUND syscall.Errno = 127
+ ERROR_ALREADY_EXISTS syscall.Errno = 183
+ ERROR_ENVVAR_NOT_FOUND syscall.Errno = 203
+ ERROR_MORE_DATA syscall.Errno = 234
+ ERROR_OPERATION_ABORTED syscall.Errno = 995
+ ERROR_IO_PENDING syscall.Errno = 997
+ ERROR_SERVICE_SPECIFIC_ERROR syscall.Errno = 1066
+ ERROR_NOT_FOUND syscall.Errno = 1168
+ ERROR_PRIVILEGE_NOT_HELD syscall.Errno = 1314
+ WSAEACCES syscall.Errno = 10013
+ WSAECONNRESET syscall.Errno = 10054
)

const (

--
https://go-review.googlesource.com/9104

Daniel Theophanes (Gerrit)

unread,
Apr 19, 2015, 10:27:48 AM4/19/15
to Alex Brainman, golang-co...@googlegroups.com
Daniel Theophanes has posted comments on this change.

windows/svc: add new package to help create and manage Windows services

Patch Set 1:

(13 comments)

I'm not qualified to proof the asm, but if it is the same as previous code,
it has worked well for years.

https://go-review.googlesource.com/#/c/9104/1/windows/svc/debug/log.go
File windows/svc/debug/log.go:

Line 1: // Copyright 2012 The Go Authors. All rights reserved.
Would it be appropriate to update the year?


Line 15: Log
Could this interface be moved to the "eventlog" package and renamed Logger?


Line 23: type ConsoleLog struct {
If this package was called "console", this could be reduced to "Log".


Line 39: Stdout
I would prefer Stderr here.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/debug/service.go
File windows/svc/debug/service.go:

Line 9: debug
I think "console" would be make better names. Then we have console.Run. How
I use similar code isn't always for debug, but maybe for QA or just
different starting process.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/eventlog/log_test.go
File windows/svc/eventlog/log_test.go:

Line 7: package eventlog_test
Does this need to be in its own package?


https://go-review.googlesource.com/#/c/9104/1/windows/svc/example/beep.go
File windows/svc/example/beep.go:

Line 7: package main
I don't think we need this example package.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/example/install.go
File windows/svc/example/install.go:

Line 18: func exePath() (string, error) {
Windows services is where the osext.Executable call is really handy. What
would you think of proposing to add "github.com/kardianos/osext".Executable
to "x/sys"?


https://go-review.googlesource.com/#/c/9104/1/windows/svc/example/service.go
File windows/svc/example/service.go:

Line 33: beep()
I'd prefer something that is detectable on a remote server, say maybe
printing to the event log.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/mgr/config.go
File windows/svc/mgr/config.go:

Line 7: package mgr
I would propose merging mgr and svc. I'm fine with them separate, but I
think the distinction isn't really needed.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/mgr/mgr.go
File windows/svc/mgr/mgr.go:

Line 22: Mgr
In code this will read "mgr.Mgr". If move to svc package, it would
read "svc.Mgr". Also, Let's just name in Manager so: "svc.Manager".


Line 79: func (m *Mgr) CreateService(name, exepath string, c Config)
(*Service, error) {
exepath is interesting. It is possible to install a service with defined
arguments. See:
https://github.com/kardianos/service/blob/master/service_windows.go#L172

I'd like to either see this signature modified to be:
(name, execpath string, args []string, c Config) or to add a helper
function like:
func ArgsToBinpath(execpath string, args []string) string
(proably a better name).
.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/mgr/mgr_test.go
File windows/svc/mgr/mgr_test.go:

Line 7: package mgr_test
Does this need to be its own package?


--
https://go-review.googlesource.com/9104
Gerrit-Reviewer: Daniel Theophanes <kard...@gmail.com>
Gerrit-HasComments: Yes

Alex Brainman (Gerrit)

unread,
Apr 20, 2015, 3:49:40 AM4/20/15
to Daniel Theophanes, golang-co...@googlegroups.com
Alex Brainman has posted comments on this change.

windows/svc: add new package to help create and manage Windows services

Patch Set 1:

(13 comments)

Thank you for review.

Alex

https://go-review.googlesource.com/#/c/9104/1/windows/svc/debug/log.go
File windows/svc/debug/log.go:

Line 1: // Copyright 2012 The Go Authors. All rights reserved.
> Would it be appropriate to update the year?
Normally we don't change year when files move or change. I will see what
others will say.


Line 15: Log
> Could this interface be moved to the "eventlog" package and renamed
> Logger?
Why? I don't see use for this interface, but for debugging.


Line 23: type ConsoleLog struct {
> If this package was called "console", this could be reduced to "Log".
Sure. But lets leave it for later.


Line 39: Stdout
> I would prefer Stderr here.
Why Stderr? These are not just errors. I don't see problem here. Happy to
change, but need more convincing.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/debug/service.go
File windows/svc/debug/service.go:

Line 9: debug
> I think "console" would be make better names. Then we have console.Run.
> How
I disagree. The code in this package has very little purpose, but to debug
service based on svc package.

Lets keep this as is for now. Feel free to send change CL later, if you
think of better names.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/eventlog/log_test.go
File windows/svc/eventlog/log_test.go:

Line 7: package eventlog_test
> Does this need to be in its own package?
I like to see the code from user point of view. Helps me see problems with
names.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/example/beep.go
File windows/svc/example/beep.go:

Line 7: package main
> I don't think we need this example package.
Why do you think so? I disagree. It is very useful. svc and other packages
are quite complicated for new users. But example shows all functionality in
a couple of lines of code. I even use it sometimes as beginning of my real
programs. It has everything I need.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/example/install.go
File windows/svc/example/install.go:

Line 18: func exePath() (string, error) {
> Windows services is where the osext.Executable call is really handy. What
> w
I would not mind to have something like that. We failed before, but you
never know. Try it if you like.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/example/service.go
File windows/svc/example/service.go:

Line 33: beep()
> I'd prefer something that is detectable on a remote server, say maybe
> print
Done


https://go-review.googlesource.com/#/c/9104/1/windows/svc/mgr/config.go
File windows/svc/mgr/config.go:

Line 7: package mgr
> I would propose merging mgr and svc. I'm fine with them separate, but I
> thi
I need convincing that merging is a good idea. The reason I keep them
separate, is because they are doing different things. svc is to build the
service. mgr is to manage already built service. You can use either, or
both - they are not related to each other. One allows to see service from
inside. The other see service from outside. Some concepts are similar but
different. I even have Service type in both, but it, obviously, works
differently.

I suggest we leave this as is for this CL. But if you can make this simpler
(once CL is submitted), then I have nothing against it.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/mgr/mgr.go
File windows/svc/mgr/mgr.go:

Line 22: Mgr
> In code this will read "mgr.Mgr". If move to svc package, it would
> read "sv
I already answered this point (in config.go).


Line 79: func (m *Mgr) CreateService(name, exepath string, c Config)
(*Service, error) {
> exepath is interesting. It is possible to install a service with defined
> ar
I agree this needs to change. I just had issue raised
https://code.google.com/p/winsvc/issues/detail?id=15 just around this area.
I propose we add "args" variadic parameter at the end, so the user can omit
them. I also propose we change Service.Start args parameter to variadic to
match. See the code.


https://go-review.googlesource.com/#/c/9104/1/windows/svc/mgr/mgr_test.go
File windows/svc/mgr/mgr_test.go:

Line 7: package mgr_test
> Does this need to be its own package?
I like to see the code from user point of view. Helps me see problems with
names.


--
https://go-review.googlesource.com/9104
Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>

Alex Brainman (Gerrit)

unread,
Apr 20, 2015, 3:49:49 AM4/20/15
to Daniel Theophanes, golang-co...@googlegroups.com
Alex Brainman uploaded a new patch set:
29 files changed, 2,370 insertions(+), 23 deletions(-)

Daniel Theophanes (Gerrit)

unread,
Apr 20, 2015, 10:21:44 AM4/20/15
to Alex Brainman, golang-co...@googlegroups.com
Daniel Theophanes has posted comments on this change.

windows/svc: add new package to help create and manage Windows services

Patch Set 2: Code-Review+1

LGTM

--
https://go-review.googlesource.com/9104
Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
Gerrit-Reviewer: Daniel Theophanes <kard...@gmail.com>
Gerrit-HasComments: No

Chris Hines (Gerrit)

unread,
Apr 30, 2015, 2:04:18 PM4/30/15
to Alex Brainman, Daniel Theophanes, golang-co...@googlegroups.com
Chris Hines has posted comments on this change.

windows/svc: add new package to help create and manage Windows services

Patch Set 2: Code-Review+1

LGTM

--
https://go-review.googlesource.com/9104
Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
Gerrit-Reviewer: Chris Hines <chris....@gmail.com>

Rob Pike (Gerrit)

unread,
Apr 30, 2015, 6:12:27 PM4/30/15
to Alex Brainman, Rob Pike, Daniel Theophanes, Chris Hines, golang-co...@googlegroups.com
Rob Pike has posted comments on this change.

windows/svc: add new package to help create and manage Windows services

Patch Set 2: Code-Review+2

--
https://go-review.googlesource.com/9104
Gerrit-Reviewer: Alex Brainman <alex.b...@gmail.com>
Gerrit-Reviewer: Chris Hines <chris....@gmail.com>
Gerrit-Reviewer: Daniel Theophanes <kard...@gmail.com>
Gerrit-Reviewer: Rob Pike <r...@golang.org>
Gerrit-HasComments: No

Alex Brainman (Gerrit)

unread,
May 1, 2015, 1:27:02 AM5/1/15
to golang-...@googlegroups.com, Rob Pike, Daniel Theophanes, Chris Hines, golang-co...@googlegroups.com
Alex Brainman has submitted this change and it was merged.

windows/svc: add new package to help create and manage Windows services

Change-Id: I58bb446aaa387b31d8a9ff4217793a170b96a7e2
Reviewed-on: https://go-review.googlesource.com/9104
Reviewed-by: Rob Pike <r...@golang.org>
29 files changed, 2,370 insertions(+), 23 deletions(-)

Approvals:
Rob Pike: Looks good to me, approved
Reply all
Reply to author
Forward
0 new messages