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