1// +build windows
2
3/*
4Copyright 2017 The Kubernetes Authors.
5
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS,
14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15See the License for the specific language governing permissions and
16limitations under the License.
17*/
18
19package initsystem
20
21import (
22	"fmt"
23	"time"
24
25	"golang.org/x/sys/windows/svc"
26	"golang.org/x/sys/windows/svc/mgr"
27)
28
29// WindowsInitSystem is the windows implementation of InitSystem
30type WindowsInitSystem struct{}
31
32// EnableCommand return a string describing how to enable a service
33func (sysd WindowsInitSystem) EnableCommand(service string) string {
34	return fmt.Sprintf("Set-Service '%s' -StartupType Automatic", service)
35}
36
37// ServiceStart tries to start a specific service
38// Following Windows documentation: https://docs.microsoft.com/en-us/windows/desktop/Services/starting-a-service
39func (sysd WindowsInitSystem) ServiceStart(service string) error {
40	m, err := mgr.Connect()
41	if err != nil {
42		return err
43	}
44	defer m.Disconnect()
45
46	s, err := m.OpenService(service)
47	if err != nil {
48		return fmt.Errorf("could not access service %s: %v", service, err)
49	}
50	defer s.Close()
51
52	// Check if service is already started
53	status, err := s.Query()
54	if err != nil {
55		return fmt.Errorf("could not query service %s: %v", service, err)
56	}
57
58	if status.State != svc.Stopped && status.State != svc.StopPending {
59		return nil
60	}
61
62	timeout := time.Now().Add(10 * time.Second)
63	for status.State != svc.Stopped {
64		if timeout.Before(time.Now()) {
65			return fmt.Errorf("timeout waiting for %s service to stop", service)
66		}
67		time.Sleep(300 * time.Millisecond)
68		status, err = s.Query()
69		if err != nil {
70			return fmt.Errorf("could not retrieve %s service status: %v", service, err)
71		}
72	}
73
74	// Start the service
75	err = s.Start("is", "manual-started")
76	if err != nil {
77		return fmt.Errorf("could not start service %s: %v", service, err)
78	}
79
80	// Check that the start was successful
81	status, err = s.Query()
82	if err != nil {
83		return fmt.Errorf("could not query service %s: %v", service, err)
84	}
85	timeout = time.Now().Add(10 * time.Second)
86	for status.State != svc.Running {
87		if timeout.Before(time.Now()) {
88			return fmt.Errorf("timeout waiting for %s service to start", service)
89		}
90		time.Sleep(300 * time.Millisecond)
91		status, err = s.Query()
92		if err != nil {
93			return fmt.Errorf("could not retrieve %s service status: %v", service, err)
94		}
95	}
96	return nil
97}
98
99// ServiceRestart tries to reload the environment and restart the specific service
100func (sysd WindowsInitSystem) ServiceRestart(service string) error {
101	if err := sysd.ServiceStop(service); err != nil {
102		return fmt.Errorf("couldn't stop service %s: %v", service, err)
103	}
104	if err := sysd.ServiceStart(service); err != nil {
105		return fmt.Errorf("couldn't start service %s: %v", service, err)
106	}
107
108	return nil
109}
110
111// ServiceStop tries to stop a specific service
112// Following Windows documentation: https://docs.microsoft.com/en-us/windows/desktop/Services/stopping-a-service
113func (sysd WindowsInitSystem) ServiceStop(service string) error {
114	m, err := mgr.Connect()
115	if err != nil {
116		return err
117	}
118	defer m.Disconnect()
119
120	s, err := m.OpenService(service)
121	if err != nil {
122		return fmt.Errorf("could not access service %s: %v", service, err)
123	}
124	defer s.Close()
125
126	// Check if service is already stopped
127	status, err := s.Query()
128	if err != nil {
129		return fmt.Errorf("could not query service %s: %v", service, err)
130	}
131
132	if status.State == svc.Stopped {
133		return nil
134	}
135
136	// If StopPending, check that service eventually stops
137	if status.State == svc.StopPending {
138		timeout := time.Now().Add(10 * time.Second)
139		for status.State != svc.Stopped {
140			if timeout.Before(time.Now()) {
141				return fmt.Errorf("timeout waiting for %s service to stop", service)
142			}
143			time.Sleep(300 * time.Millisecond)
144			status, err = s.Query()
145			if err != nil {
146				return fmt.Errorf("could not retrieve %s service status: %v", service, err)
147			}
148		}
149		return nil
150	}
151
152	// Stop the service
153	status, err = s.Control(svc.Stop)
154	if err != nil {
155		return fmt.Errorf("could not stop service %s: %v", service, err)
156	}
157
158	// Check that the stop was successful
159	status, err = s.Query()
160	if err != nil {
161		return fmt.Errorf("could not query service %s: %v", service, err)
162	}
163	timeout := time.Now().Add(10 * time.Second)
164	for status.State != svc.Stopped {
165		if timeout.Before(time.Now()) {
166			return fmt.Errorf("timeout waiting for %s service to stop", service)
167		}
168		time.Sleep(300 * time.Millisecond)
169		status, err = s.Query()
170		if err != nil {
171			return fmt.Errorf("could not retrieve %s service status: %v", service, err)
172		}
173	}
174	return nil
175}
176
177// ServiceExists ensures the service is defined for this init system.
178func (sysd WindowsInitSystem) ServiceExists(service string) bool {
179	m, err := mgr.Connect()
180	if err != nil {
181		return false
182	}
183	defer m.Disconnect()
184	s, err := m.OpenService(service)
185	if err != nil {
186		return false
187	}
188	defer s.Close()
189
190	return true
191}
192
193// ServiceIsEnabled ensures the service is enabled to start on each boot.
194func (sysd WindowsInitSystem) ServiceIsEnabled(service string) bool {
195	m, err := mgr.Connect()
196	if err != nil {
197		return false
198	}
199	defer m.Disconnect()
200
201	s, err := m.OpenService(service)
202	if err != nil {
203		return false
204	}
205	defer s.Close()
206
207	c, err := s.Config()
208	if err != nil {
209		return false
210	}
211
212	return c.StartType != mgr.StartDisabled
213}
214
215// ServiceIsActive ensures the service is running, or attempting to run. (crash looping in the case of kubelet)
216func (sysd WindowsInitSystem) ServiceIsActive(service string) bool {
217	m, err := mgr.Connect()
218	if err != nil {
219		return false
220	}
221	defer m.Disconnect()
222	s, err := m.OpenService(service)
223	if err != nil {
224		return false
225	}
226	defer s.Close()
227
228	status, err := s.Query()
229	if err != nil {
230		return false
231	}
232	return status.State == svc.Running
233}
234
235// GetInitSystem returns an InitSystem for the current system, or nil
236// if we cannot detect a supported init system.
237// This indicates we will skip init system checks, not an error.
238func GetInitSystem() (InitSystem, error) {
239	m, err := mgr.Connect()
240	if err != nil {
241		return nil, fmt.Errorf("no supported init system detected: %v", err)
242	}
243	defer m.Disconnect()
244	return &WindowsInitSystem{}, nil
245}
246