1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// +build windows
6
7// Package svc provides everything required to build Windows service.
8//
9package svc
10
11import (
12	"errors"
13	"runtime"
14	"syscall"
15	"unsafe"
16
17	"golang.org/x/sys/windows"
18)
19
20// State describes service execution state (Stopped, Running and so on).
21type State uint32
22
23const (
24	Stopped         = State(windows.SERVICE_STOPPED)
25	StartPending    = State(windows.SERVICE_START_PENDING)
26	StopPending     = State(windows.SERVICE_STOP_PENDING)
27	Running         = State(windows.SERVICE_RUNNING)
28	ContinuePending = State(windows.SERVICE_CONTINUE_PENDING)
29	PausePending    = State(windows.SERVICE_PAUSE_PENDING)
30	Paused          = State(windows.SERVICE_PAUSED)
31)
32
33// Cmd represents service state change request. It is sent to a service
34// by the service manager, and should be actioned upon by the service.
35type Cmd uint32
36
37const (
38	Stop                  = Cmd(windows.SERVICE_CONTROL_STOP)
39	Pause                 = Cmd(windows.SERVICE_CONTROL_PAUSE)
40	Continue              = Cmd(windows.SERVICE_CONTROL_CONTINUE)
41	Interrogate           = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
42	Shutdown              = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
43	ParamChange           = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE)
44	NetBindAdd            = Cmd(windows.SERVICE_CONTROL_NETBINDADD)
45	NetBindRemove         = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE)
46	NetBindEnable         = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE)
47	NetBindDisable        = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE)
48	DeviceEvent           = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT)
49	HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE)
50	PowerEvent            = Cmd(windows.SERVICE_CONTROL_POWEREVENT)
51	SessionChange         = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE)
52)
53
54// Accepted is used to describe commands accepted by the service.
55// Note that Interrogate is always accepted.
56type Accepted uint32
57
58const (
59	AcceptStop                  = Accepted(windows.SERVICE_ACCEPT_STOP)
60	AcceptShutdown              = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
61	AcceptPauseAndContinue      = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
62	AcceptParamChange           = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)
63	AcceptNetBindChange         = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE)
64	AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE)
65	AcceptPowerEvent            = Accepted(windows.SERVICE_ACCEPT_POWEREVENT)
66	AcceptSessionChange         = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE)
67)
68
69// Status combines State and Accepted commands to fully describe running service.
70type Status struct {
71	State      State
72	Accepts    Accepted
73	CheckPoint uint32 // used to report progress during a lengthy operation
74	WaitHint   uint32 // estimated time required for a pending operation, in milliseconds
75	ProcessId  uint32 // if the service is running, the process identifier of it, and otherwise zero
76}
77
78// ChangeRequest is sent to the service Handler to request service status change.
79type ChangeRequest struct {
80	Cmd           Cmd
81	EventType     uint32
82	EventData     uintptr
83	CurrentStatus Status
84	Context       uintptr
85}
86
87// Handler is the interface that must be implemented to build Windows service.
88type Handler interface {
89
90	// Execute will be called by the package code at the start of
91	// the service, and the service will exit once Execute completes.
92	// Inside Execute you must read service change requests from r and
93	// act accordingly. You must keep service control manager up to date
94	// about state of your service by writing into s as required.
95	// args contains service name followed by argument strings passed
96	// to the service.
97	// You can provide service exit code in exitCode return parameter,
98	// with 0 being "no error". You can also indicate if exit code,
99	// if any, is service specific or not by using svcSpecificEC
100	// parameter.
101	Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
102}
103
104var (
105	// These are used by asm code.
106	goWaitsH                       uintptr
107	cWaitsH                        uintptr
108	ssHandle                       uintptr
109	sName                          *uint16
110	sArgc                          uintptr
111	sArgv                          **uint16
112	ctlHandlerExProc               uintptr
113	cSetEvent                      uintptr
114	cWaitForSingleObject           uintptr
115	cRegisterServiceCtrlHandlerExW uintptr
116)
117
118func init() {
119	k := windows.NewLazySystemDLL("kernel32.dll")
120	cSetEvent = k.NewProc("SetEvent").Addr()
121	cWaitForSingleObject = k.NewProc("WaitForSingleObject").Addr()
122	a := windows.NewLazySystemDLL("advapi32.dll")
123	cRegisterServiceCtrlHandlerExW = a.NewProc("RegisterServiceCtrlHandlerExW").Addr()
124}
125
126type ctlEvent struct {
127	cmd       Cmd
128	eventType uint32
129	eventData uintptr
130	context   uintptr
131	errno     uint32
132}
133
134// service provides access to windows service api.
135type service struct {
136	name    string
137	h       windows.Handle
138	cWaits  *event
139	goWaits *event
140	c       chan ctlEvent
141	handler Handler
142}
143
144func newService(name string, handler Handler) (*service, error) {
145	var s service
146	var err error
147	s.name = name
148	s.c = make(chan ctlEvent)
149	s.handler = handler
150	s.cWaits, err = newEvent()
151	if err != nil {
152		return nil, err
153	}
154	s.goWaits, err = newEvent()
155	if err != nil {
156		s.cWaits.Close()
157		return nil, err
158	}
159	return &s, nil
160}
161
162func (s *service) close() error {
163	s.cWaits.Close()
164	s.goWaits.Close()
165	return nil
166}
167
168type exitCode struct {
169	isSvcSpecific bool
170	errno         uint32
171}
172
173func (s *service) updateStatus(status *Status, ec *exitCode) error {
174	if s.h == 0 {
175		return errors.New("updateStatus with no service status handle")
176	}
177	var t windows.SERVICE_STATUS
178	t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
179	t.CurrentState = uint32(status.State)
180	if status.Accepts&AcceptStop != 0 {
181		t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
182	}
183	if status.Accepts&AcceptShutdown != 0 {
184		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
185	}
186	if status.Accepts&AcceptPauseAndContinue != 0 {
187		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
188	}
189	if status.Accepts&AcceptParamChange != 0 {
190		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
191	}
192	if status.Accepts&AcceptNetBindChange != 0 {
193		t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
194	}
195	if status.Accepts&AcceptHardwareProfileChange != 0 {
196		t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
197	}
198	if status.Accepts&AcceptPowerEvent != 0 {
199		t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
200	}
201	if status.Accepts&AcceptSessionChange != 0 {
202		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
203	}
204	if ec.errno == 0 {
205		t.Win32ExitCode = windows.NO_ERROR
206		t.ServiceSpecificExitCode = windows.NO_ERROR
207	} else if ec.isSvcSpecific {
208		t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
209		t.ServiceSpecificExitCode = ec.errno
210	} else {
211		t.Win32ExitCode = ec.errno
212		t.ServiceSpecificExitCode = windows.NO_ERROR
213	}
214	t.CheckPoint = status.CheckPoint
215	t.WaitHint = status.WaitHint
216	return windows.SetServiceStatus(s.h, &t)
217}
218
219const (
220	sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota
221	sysErrNewThreadInCallback
222)
223
224func (s *service) run() {
225	s.goWaits.Wait()
226	s.h = windows.Handle(ssHandle)
227	argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc]
228	args := make([]string, len(argv))
229	for i, a := range argv {
230		args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:])
231	}
232
233	cmdsToHandler := make(chan ChangeRequest)
234	changesFromHandler := make(chan Status)
235	exitFromHandler := make(chan exitCode)
236
237	go func() {
238		ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler)
239		exitFromHandler <- exitCode{ss, errno}
240	}()
241
242	ec := exitCode{isSvcSpecific: true, errno: 0}
243	outcr := ChangeRequest{
244		CurrentStatus: Status{State: Stopped},
245	}
246	var outch chan ChangeRequest
247	inch := s.c
248loop:
249	for {
250		select {
251		case r := <-inch:
252			if r.errno != 0 {
253				ec.errno = r.errno
254				break loop
255			}
256			inch = nil
257			outch = cmdsToHandler
258			outcr.Cmd = r.cmd
259			outcr.EventType = r.eventType
260			outcr.EventData = r.eventData
261			outcr.Context = r.context
262		case outch <- outcr:
263			inch = s.c
264			outch = nil
265		case c := <-changesFromHandler:
266			err := s.updateStatus(&c, &ec)
267			if err != nil {
268				// best suitable error number
269				ec.errno = sysErrSetServiceStatusFailed
270				if err2, ok := err.(syscall.Errno); ok {
271					ec.errno = uint32(err2)
272				}
273				break loop
274			}
275			outcr.CurrentStatus = c
276		case ec = <-exitFromHandler:
277			break loop
278		}
279	}
280
281	s.updateStatus(&Status{State: Stopped}, &ec)
282	s.cWaits.Set()
283}
284
285func newCallback(fn interface{}) (cb uintptr, err error) {
286	defer func() {
287		r := recover()
288		if r == nil {
289			return
290		}
291		cb = 0
292		switch v := r.(type) {
293		case string:
294			err = errors.New(v)
295		case error:
296			err = v
297		default:
298			err = errors.New("unexpected panic in syscall.NewCallback")
299		}
300	}()
301	return syscall.NewCallback(fn), nil
302}
303
304// BUG(brainman): There is no mechanism to run multiple services
305// inside one single executable. Perhaps, it can be overcome by
306// using RegisterServiceCtrlHandlerEx Windows api.
307
308// Run executes service name by calling appropriate handler function.
309func Run(name string, handler Handler) error {
310	runtime.LockOSThread()
311
312	tid := windows.GetCurrentThreadId()
313
314	s, err := newService(name, handler)
315	if err != nil {
316		return err
317	}
318
319	ctlHandler := func(ctl, evtype, evdata, context uintptr) uintptr {
320		e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: context}
321		// We assume that this callback function is running on
322		// the same thread as Run. Nowhere in MS documentation
323		// I could find statement to guarantee that. So putting
324		// check here to verify, otherwise things will go bad
325		// quickly, if ignored.
326		i := windows.GetCurrentThreadId()
327		if i != tid {
328			e.errno = sysErrNewThreadInCallback
329		}
330		s.c <- e
331		// Always return NO_ERROR (0) for now.
332		return windows.NO_ERROR
333	}
334
335	var svcmain uintptr
336	getServiceMain(&svcmain)
337	t := []windows.SERVICE_TABLE_ENTRY{
338		{ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain},
339		{ServiceName: nil, ServiceProc: 0},
340	}
341
342	goWaitsH = uintptr(s.goWaits.h)
343	cWaitsH = uintptr(s.cWaits.h)
344	sName = t[0].ServiceName
345	ctlHandlerExProc, err = newCallback(ctlHandler)
346	if err != nil {
347		return err
348	}
349
350	go s.run()
351
352	err = windows.StartServiceCtrlDispatcher(&t[0])
353	if err != nil {
354		return err
355	}
356	return nil
357}
358
359// StatusHandle returns service status handle. It is safe to call this function
360// from inside the Handler.Execute because then it is guaranteed to be set.
361// This code will have to change once multiple services are possible per process.
362func StatusHandle() windows.Handle {
363	return windows.Handle(ssHandle)
364}
365