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//go:build windows
6// +build windows
7
8// Package svc provides everything required to build Windows service.
9//
10package svc
11
12import (
13	"errors"
14	"sync"
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// StartReason is the reason that the service was started.
84type StartReason uint32
85
86const (
87	StartReasonDemand           = StartReason(windows.SERVICE_START_REASON_DEMAND)
88	StartReasonAuto             = StartReason(windows.SERVICE_START_REASON_AUTO)
89	StartReasonTrigger          = StartReason(windows.SERVICE_START_REASON_TRIGGER)
90	StartReasonRestartOnFailure = StartReason(windows.SERVICE_START_REASON_RESTART_ON_FAILURE)
91	StartReasonDelayedAuto      = StartReason(windows.SERVICE_START_REASON_DELAYEDAUTO)
92)
93
94// ChangeRequest is sent to the service Handler to request service status change.
95type ChangeRequest struct {
96	Cmd           Cmd
97	EventType     uint32
98	EventData     uintptr
99	CurrentStatus Status
100	Context       uintptr
101}
102
103// Handler is the interface that must be implemented to build Windows service.
104type Handler interface {
105	// Execute will be called by the package code at the start of
106	// the service, and the service will exit once Execute completes.
107	// Inside Execute you must read service change requests from r and
108	// act accordingly. You must keep service control manager up to date
109	// about state of your service by writing into s as required.
110	// args contains service name followed by argument strings passed
111	// to the service.
112	// You can provide service exit code in exitCode return parameter,
113	// with 0 being "no error". You can also indicate if exit code,
114	// if any, is service specific or not by using svcSpecificEC
115	// parameter.
116	Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
117}
118
119type ctlEvent struct {
120	cmd       Cmd
121	eventType uint32
122	eventData uintptr
123	context   uintptr
124	errno     uint32
125}
126
127// service provides access to windows service api.
128type service struct {
129	name    string
130	h       windows.Handle
131	c       chan ctlEvent
132	handler Handler
133}
134
135type exitCode struct {
136	isSvcSpecific bool
137	errno         uint32
138}
139
140func (s *service) updateStatus(status *Status, ec *exitCode) error {
141	if s.h == 0 {
142		return errors.New("updateStatus with no service status handle")
143	}
144	var t windows.SERVICE_STATUS
145	t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
146	t.CurrentState = uint32(status.State)
147	if status.Accepts&AcceptStop != 0 {
148		t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
149	}
150	if status.Accepts&AcceptShutdown != 0 {
151		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
152	}
153	if status.Accepts&AcceptPauseAndContinue != 0 {
154		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
155	}
156	if status.Accepts&AcceptParamChange != 0 {
157		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
158	}
159	if status.Accepts&AcceptNetBindChange != 0 {
160		t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
161	}
162	if status.Accepts&AcceptHardwareProfileChange != 0 {
163		t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
164	}
165	if status.Accepts&AcceptPowerEvent != 0 {
166		t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
167	}
168	if status.Accepts&AcceptSessionChange != 0 {
169		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
170	}
171	if status.Accepts&AcceptPreShutdown != 0 {
172		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PRESHUTDOWN
173	}
174	if ec.errno == 0 {
175		t.Win32ExitCode = windows.NO_ERROR
176		t.ServiceSpecificExitCode = windows.NO_ERROR
177	} else if ec.isSvcSpecific {
178		t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
179		t.ServiceSpecificExitCode = ec.errno
180	} else {
181		t.Win32ExitCode = ec.errno
182		t.ServiceSpecificExitCode = windows.NO_ERROR
183	}
184	t.CheckPoint = status.CheckPoint
185	t.WaitHint = status.WaitHint
186	return windows.SetServiceStatus(s.h, &t)
187}
188
189var (
190	initCallbacks       sync.Once
191	ctlHandlerCallback  uintptr
192	serviceMainCallback uintptr
193)
194
195func ctlHandler(ctl, evtype, evdata, context uintptr) uintptr {
196	s := (*service)(unsafe.Pointer(context))
197	e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: 123456} // Set context to 123456 to test issue #25660.
198	s.c <- e
199	return 0
200}
201
202var theService service // This is, unfortunately, a global, which means only one service per process.
203
204// serviceMain is the entry point called by the service manager, registered earlier by
205// the call to StartServiceCtrlDispatcher.
206func serviceMain(argc uint32, argv **uint16) uintptr {
207	handle, err := windows.RegisterServiceCtrlHandlerEx(windows.StringToUTF16Ptr(theService.name), ctlHandlerCallback, uintptr(unsafe.Pointer(&theService)))
208	if sysErr, ok := err.(windows.Errno); ok {
209		return uintptr(sysErr)
210	} else if err != nil {
211		return uintptr(windows.ERROR_UNKNOWN_EXCEPTION)
212	}
213	theService.h = handle
214	defer func() {
215		theService.h = 0
216	}()
217	var args16 []*uint16
218	hdr := (*unsafeheader.Slice)(unsafe.Pointer(&args16))
219	hdr.Data = unsafe.Pointer(argv)
220	hdr.Len = int(argc)
221	hdr.Cap = int(argc)
222
223	args := make([]string, len(args16))
224	for i, a := range args16 {
225		args[i] = windows.UTF16PtrToString(a)
226	}
227
228	cmdsToHandler := make(chan ChangeRequest)
229	changesFromHandler := make(chan Status)
230	exitFromHandler := make(chan exitCode)
231
232	go func() {
233		ss, errno := theService.handler.Execute(args, cmdsToHandler, changesFromHandler)
234		exitFromHandler <- exitCode{ss, errno}
235	}()
236
237	ec := exitCode{isSvcSpecific: true, errno: 0}
238	outcr := ChangeRequest{
239		CurrentStatus: Status{State: Stopped},
240	}
241	var outch chan ChangeRequest
242	inch := theService.c
243loop:
244	for {
245		select {
246		case r := <-inch:
247			if r.errno != 0 {
248				ec.errno = r.errno
249				break loop
250			}
251			inch = nil
252			outch = cmdsToHandler
253			outcr.Cmd = r.cmd
254			outcr.EventType = r.eventType
255			outcr.EventData = r.eventData
256			outcr.Context = r.context
257		case outch <- outcr:
258			inch = theService.c
259			outch = nil
260		case c := <-changesFromHandler:
261			err := theService.updateStatus(&c, &ec)
262			if err != nil {
263				ec.errno = uint32(windows.ERROR_EXCEPTION_IN_SERVICE)
264				if err2, ok := err.(windows.Errno); ok {
265					ec.errno = uint32(err2)
266				}
267				break loop
268			}
269			outcr.CurrentStatus = c
270		case ec = <-exitFromHandler:
271			break loop
272		}
273	}
274
275	theService.updateStatus(&Status{State: Stopped}, &ec)
276
277	return windows.NO_ERROR
278}
279
280// Run executes service name by calling appropriate handler function.
281func Run(name string, handler Handler) error {
282	initCallbacks.Do(func() {
283		ctlHandlerCallback = windows.NewCallback(ctlHandler)
284		serviceMainCallback = windows.NewCallback(serviceMain)
285	})
286	theService.name = name
287	theService.handler = handler
288	theService.c = make(chan ctlEvent)
289	t := []windows.SERVICE_TABLE_ENTRY{
290		{ServiceName: windows.StringToUTF16Ptr(theService.name), ServiceProc: serviceMainCallback},
291		{ServiceName: nil, ServiceProc: 0},
292	}
293	return windows.StartServiceCtrlDispatcher(&t[0])
294}
295
296// StatusHandle returns service status handle. It is safe to call this function
297// from inside the Handler.Execute because then it is guaranteed to be set.
298func StatusHandle() windows.Handle {
299	return theService.h
300}
301
302// DynamicStartReason returns the reason why the service was started. It is safe
303// to call this function from inside the Handler.Execute because then it is
304// guaranteed to be set.
305func DynamicStartReason() (StartReason, error) {
306	var allocReason *uint32
307	err := windows.QueryServiceDynamicInformation(theService.h, windows.SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON, unsafe.Pointer(&allocReason))
308	if err != nil {
309		return 0, err
310	}
311	reason := StartReason(*allocReason)
312	windows.LocalFree(windows.Handle(unsafe.Pointer(allocReason)))
313	return reason, nil
314}
315