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}
76
77// ChangeRequest is sent to the service Handler to request service status change.
78type ChangeRequest struct {
79	Cmd           Cmd
80	EventType     uint32
81	EventData     uintptr
82	CurrentStatus Status
83	Context       uintptr
84}
85
86// Handler is the interface that must be implemented to build Windows service.
87type Handler interface {
88
89	// Execute will be called by the package code at the start of
90	// the service, and the service will exit once Execute completes.
91	// Inside Execute you must read service change requests from r and
92	// act accordingly. You must keep service control manager up to date
93	// about state of your service by writing into s as required.
94	// args contains service name followed by argument strings passed
95	// to the service.
96	// You can provide service exit code in exitCode return parameter,
97	// with 0 being "no error". You can also indicate if exit code,
98	// if any, is service specific or not by using svcSpecificEC
99	// parameter.
100	Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
101}
102
103var (
104	// These are used by asm code.
105	goWaitsH                       uintptr
106	cWaitsH                        uintptr
107	ssHandle                       uintptr
108	sName                          *uint16
109	sArgc                          uintptr
110	sArgv                          **uint16
111	ctlHandlerExProc               uintptr
112	cSetEvent                      uintptr
113	cWaitForSingleObject           uintptr
114	cRegisterServiceCtrlHandlerExW uintptr
115)
116
117func init() {
118	k := windows.NewLazySystemDLL("kernel32.dll")
119	cSetEvent = k.NewProc("SetEvent").Addr()
120	cWaitForSingleObject = k.NewProc("WaitForSingleObject").Addr()
121	a := windows.NewLazySystemDLL("advapi32.dll")
122	cRegisterServiceCtrlHandlerExW = a.NewProc("RegisterServiceCtrlHandlerExW").Addr()
123}
124
125type ctlEvent struct {
126	cmd       Cmd
127	eventType uint32
128	eventData uintptr
129	context   uintptr
130	errno     uint32
131}
132
133// service provides access to windows service api.
134type service struct {
135	name    string
136	h       windows.Handle
137	cWaits  *event
138	goWaits *event
139	c       chan ctlEvent
140	handler Handler
141}
142
143func newService(name string, handler Handler) (*service, error) {
144	var s service
145	var err error
146	s.name = name
147	s.c = make(chan ctlEvent)
148	s.handler = handler
149	s.cWaits, err = newEvent()
150	if err != nil {
151		return nil, err
152	}
153	s.goWaits, err = newEvent()
154	if err != nil {
155		s.cWaits.Close()
156		return nil, err
157	}
158	return &s, nil
159}
160
161func (s *service) close() error {
162	s.cWaits.Close()
163	s.goWaits.Close()
164	return nil
165}
166
167type exitCode struct {
168	isSvcSpecific bool
169	errno         uint32
170}
171
172func (s *service) updateStatus(status *Status, ec *exitCode) error {
173	if s.h == 0 {
174		return errors.New("updateStatus with no service status handle")
175	}
176	var t windows.SERVICE_STATUS
177	t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
178	t.CurrentState = uint32(status.State)
179	if status.Accepts&AcceptStop != 0 {
180		t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
181	}
182	if status.Accepts&AcceptShutdown != 0 {
183		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
184	}
185	if status.Accepts&AcceptPauseAndContinue != 0 {
186		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
187	}
188	if status.Accepts&AcceptParamChange != 0 {
189		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
190	}
191	if status.Accepts&AcceptNetBindChange != 0 {
192		t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
193	}
194	if status.Accepts&AcceptHardwareProfileChange != 0 {
195		t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
196	}
197	if status.Accepts&AcceptPowerEvent != 0 {
198		t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
199	}
200	if status.Accepts&AcceptSessionChange != 0 {
201		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
202	}
203	if ec.errno == 0 {
204		t.Win32ExitCode = windows.NO_ERROR
205		t.ServiceSpecificExitCode = windows.NO_ERROR
206	} else if ec.isSvcSpecific {
207		t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
208		t.ServiceSpecificExitCode = ec.errno
209	} else {
210		t.Win32ExitCode = ec.errno
211		t.ServiceSpecificExitCode = windows.NO_ERROR
212	}
213	t.CheckPoint = status.CheckPoint
214	t.WaitHint = status.WaitHint
215	return windows.SetServiceStatus(s.h, &t)
216}
217
218const (
219	sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota
220	sysErrNewThreadInCallback
221)
222
223func (s *service) run() {
224	s.goWaits.Wait()
225	s.h = windows.Handle(ssHandle)
226	argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc]
227	args := make([]string, len(argv))
228	for i, a := range argv {
229		args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:])
230	}
231
232	cmdsToHandler := make(chan ChangeRequest)
233	changesFromHandler := make(chan Status)
234	exitFromHandler := make(chan exitCode)
235
236	go func() {
237		ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler)
238		exitFromHandler <- exitCode{ss, errno}
239	}()
240
241	ec := exitCode{isSvcSpecific: true, errno: 0}
242	outcr := ChangeRequest{
243		CurrentStatus: Status{State: Stopped},
244	}
245	var outch chan ChangeRequest
246	inch := s.c
247loop:
248	for {
249		select {
250		case r := <-inch:
251			if r.errno != 0 {
252				ec.errno = r.errno
253				break loop
254			}
255			inch = nil
256			outch = cmdsToHandler
257			outcr.Cmd = r.cmd
258			outcr.EventType = r.eventType
259			outcr.EventData = r.eventData
260			outcr.Context = r.context
261		case outch <- outcr:
262			inch = s.c
263			outch = nil
264		case c := <-changesFromHandler:
265			err := s.updateStatus(&c, &ec)
266			if err != nil {
267				// best suitable error number
268				ec.errno = sysErrSetServiceStatusFailed
269				if err2, ok := err.(syscall.Errno); ok {
270					ec.errno = uint32(err2)
271				}
272				break loop
273			}
274			outcr.CurrentStatus = c
275		case ec = <-exitFromHandler:
276			break loop
277		}
278	}
279
280	s.updateStatus(&Status{State: Stopped}, &ec)
281	s.cWaits.Set()
282}
283
284func newCallback(fn interface{}) (cb uintptr, err error) {
285	defer func() {
286		r := recover()
287		if r == nil {
288			return
289		}
290		cb = 0
291		switch v := r.(type) {
292		case string:
293			err = errors.New(v)
294		case error:
295			err = v
296		default:
297			err = errors.New("unexpected panic in syscall.NewCallback")
298		}
299	}()
300	return syscall.NewCallback(fn), nil
301}
302
303// BUG(brainman): There is no mechanism to run multiple services
304// inside one single executable. Perhaps, it can be overcome by
305// using RegisterServiceCtrlHandlerEx Windows api.
306
307// Run executes service name by calling appropriate handler function.
308func Run(name string, handler Handler) error {
309	runtime.LockOSThread()
310
311	tid := windows.GetCurrentThreadId()
312
313	s, err := newService(name, handler)
314	if err != nil {
315		return err
316	}
317
318	ctlHandler := func(ctl, evtype, evdata, context uintptr) uintptr {
319		e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: context}
320		// We assume that this callback function is running on
321		// the same thread as Run. Nowhere in MS documentation
322		// I could find statement to guarantee that. So putting
323		// check here to verify, otherwise things will go bad
324		// quickly, if ignored.
325		i := windows.GetCurrentThreadId()
326		if i != tid {
327			e.errno = sysErrNewThreadInCallback
328		}
329		s.c <- e
330		// Always return NO_ERROR (0) for now.
331		return 0
332	}
333
334	var svcmain uintptr
335	getServiceMain(&svcmain)
336	t := []windows.SERVICE_TABLE_ENTRY{
337		{ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain},
338		{ServiceName: nil, ServiceProc: 0},
339	}
340
341	goWaitsH = uintptr(s.goWaits.h)
342	cWaitsH = uintptr(s.cWaits.h)
343	sName = t[0].ServiceName
344	ctlHandlerExProc, err = newCallback(ctlHandler)
345	if err != nil {
346		return err
347	}
348
349	go s.run()
350
351	err = windows.StartServiceCtrlDispatcher(&t[0])
352	if err != nil {
353		return err
354	}
355	return nil
356}
357
358// StatusHandle returns service status handle. It is safe to call this function
359// from inside the Handler.Execute because then it is guaranteed to be set.
360// This code will have to change once multiple services are possible per process.
361func StatusHandle() windows.Handle {
362	return windows.Handle(ssHandle)
363}
364