puppet 5.5.6-universal-darwin → 5.5.7-universal-darwin
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +3 -1
- data/Gemfile.lock +12 -12
- data/Rakefile +9 -0
- data/lib/puppet/application.rb +5 -0
- data/lib/puppet/application/apply.rb +1 -0
- data/lib/puppet/application/master.rb +9 -7
- data/lib/puppet/application/script.rb +1 -1
- data/lib/puppet/defaults.rb +51 -31
- data/lib/puppet/etc.rb +20 -0
- data/lib/puppet/file_serving/fileset.rb +1 -1
- data/lib/puppet/functions.rb +123 -0
- data/lib/puppet/functions/new.rb +37 -53
- data/lib/puppet/functions/warning.rb +1 -1
- data/lib/puppet/loaders.rb +1 -0
- data/lib/puppet/parser/functions.rb +3 -1
- data/lib/puppet/parser/functions/sprintf.rb +12 -1
- data/lib/puppet/pops/evaluator/runtime3_converter.rb +16 -0
- data/lib/puppet/pops/evaluator/runtime3_support.rb +3 -4
- data/lib/puppet/pops/issues.rb +8 -0
- data/lib/puppet/pops/loader/loader.rb +2 -2
- data/lib/puppet/pops/loader/loader_paths.rb +3 -1
- data/lib/puppet/pops/loader/module_loaders.rb +1 -1
- data/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb +62 -0
- data/lib/puppet/pops/loaders.rb +5 -21
- data/lib/puppet/pops/parser/heredoc_support.rb +1 -2
- data/lib/puppet/pops/parser/lexer2.rb +1 -1
- data/lib/puppet/pops/validation/checker4_0.rb +31 -6
- data/lib/puppet/pops/validation/validator_factory_4_0.rb +1 -0
- data/lib/puppet/property/keyvalue.rb +70 -8
- data/lib/puppet/provider/aix_object.rb +483 -0
- data/lib/puppet/provider/exec.rb +54 -57
- data/lib/puppet/provider/group/aix.rb +40 -115
- data/lib/puppet/provider/group/pw.rb +4 -8
- data/lib/puppet/provider/group/windows_adsi.rb +7 -4
- data/lib/puppet/provider/nameservice.rb +1 -25
- data/lib/puppet/provider/nameservice/directoryservice.rb +5 -3
- data/lib/puppet/provider/package/portage.rb +2 -2
- data/lib/puppet/provider/package/windows.rb +2 -2
- data/lib/puppet/provider/package/windows/exe_package.rb +3 -10
- data/lib/puppet/provider/package/zypper.rb +1 -1
- data/lib/puppet/provider/service/launchd.rb +19 -3
- data/lib/puppet/provider/service/windows.rb +49 -40
- data/lib/puppet/provider/user/aix.rb +180 -246
- data/lib/puppet/provider/user/windows_adsi.rb +9 -1
- data/lib/puppet/resource/catalog.rb +1 -5
- data/lib/puppet/type/augeas.rb +1 -1
- data/lib/puppet/type/exec.rb +16 -14
- data/lib/puppet/type/file.rb +2 -2
- data/lib/puppet/type/file/source.rb +9 -5
- data/lib/puppet/type/group.rb +65 -23
- data/lib/puppet/type/k5login.rb +2 -2
- data/lib/puppet/type/notify.rb +1 -1
- data/lib/puppet/type/package.rb +3 -6
- data/lib/puppet/type/resources.rb +12 -2
- data/lib/puppet/type/schedule.rb +8 -1
- data/lib/puppet/type/selboolean.rb +2 -2
- data/lib/puppet/type/selmodule.rb +3 -4
- data/lib/puppet/type/service.rb +2 -5
- data/lib/puppet/type/tidy.rb +1 -1
- data/lib/puppet/type/user.rb +15 -20
- data/lib/puppet/type/yumrepo.rb +2 -2
- data/lib/puppet/type/zone.rb +2 -2
- data/lib/puppet/util.rb +7 -3
- data/lib/puppet/util/execution.rb +15 -1
- data/lib/puppet/util/posix.rb +15 -0
- data/lib/puppet/util/storage.rb +12 -0
- data/lib/puppet/util/windows.rb +4 -2
- data/lib/puppet/util/windows/adsi.rb +235 -205
- data/lib/puppet/util/windows/process.rb +23 -3
- data/lib/puppet/util/windows/security.rb +14 -0
- data/lib/puppet/util/windows/service.rb +977 -0
- data/lib/puppet/util/windows/user.rb +3 -5
- data/lib/puppet/version.rb +1 -1
- data/locales/ja/puppet.po +705 -374
- data/locales/puppet.pot +485 -261
- data/man/man5/puppet.conf.5 +36 -15
- data/man/man8/puppet-agent.8 +1 -1
- data/man/man8/puppet-apply.8 +1 -1
- data/man/man8/puppet-ca.8 +1 -1
- data/man/man8/puppet-catalog.8 +1 -1
- data/man/man8/puppet-cert.8 +1 -1
- data/man/man8/puppet-certificate.8 +1 -1
- data/man/man8/puppet-certificate_request.8 +1 -1
- data/man/man8/puppet-certificate_revocation_list.8 +1 -1
- data/man/man8/puppet-config.8 +1 -1
- data/man/man8/puppet-describe.8 +1 -1
- data/man/man8/puppet-device.8 +1 -1
- data/man/man8/puppet-doc.8 +1 -1
- data/man/man8/puppet-epp.8 +1 -1
- data/man/man8/puppet-facts.8 +1 -1
- data/man/man8/puppet-filebucket.8 +1 -1
- data/man/man8/puppet-generate.8 +1 -1
- data/man/man8/puppet-help.8 +1 -1
- data/man/man8/puppet-key.8 +1 -1
- data/man/man8/puppet-lookup.8 +1 -1
- data/man/man8/puppet-man.8 +1 -1
- data/man/man8/puppet-master.8 +1 -1
- data/man/man8/puppet-module.8 +1 -1
- data/man/man8/puppet-node.8 +1 -1
- data/man/man8/puppet-parser.8 +1 -1
- data/man/man8/puppet-plugin.8 +1 -1
- data/man/man8/puppet-report.8 +1 -1
- data/man/man8/puppet-resource.8 +1 -1
- data/man/man8/puppet-script.8 +1 -1
- data/man/man8/puppet-status.8 +1 -1
- data/man/man8/puppet.8 +2 -2
- data/spec/fixtures/unit/provider/aix_object/aix_colon_list_real_world_input.out +1 -0
- data/spec/fixtures/unit/provider/aix_object/aix_colon_list_real_world_output.out +1 -0
- data/spec/fixtures/unit/provider/user/aix/aix_passwd_file.out +32 -0
- data/spec/integration/parser/collection_spec.rb +4 -8
- data/spec/integration/provider/service/windows_spec.rb +5 -5
- data/spec/integration/type/file_spec.rb +6 -6
- data/spec/integration/util/windows/adsi_spec.rb +6 -5
- data/spec/integration/util/windows/security_spec.rb +10 -7
- data/spec/integration/util/windows/user_spec.rb +37 -17
- data/spec/spec_helper.rb +0 -1
- data/spec/unit/application/apply_spec.rb +41 -2
- data/spec/unit/application/master_spec.rb +7 -0
- data/spec/unit/application_spec.rb +21 -3
- data/spec/unit/defaults_spec.rb +20 -0
- data/spec/unit/etc_spec.rb +25 -0
- data/spec/unit/file_serving/fileset_spec.rb +11 -11
- data/spec/unit/gettext/config_spec.rb +1 -1
- data/spec/unit/pops/evaluator/evaluating_parser_spec.rb +6 -6
- data/spec/unit/pops/loaders/loaders_spec.rb +40 -7
- data/spec/unit/pops/parser/parse_heredoc_spec.rb +16 -0
- data/spec/unit/pops/validator/validator_spec.rb +129 -10
- data/spec/unit/property/keyvalue_spec.rb +97 -6
- data/spec/unit/provider/aix_object_spec.rb +805 -0
- data/spec/unit/provider/group/aix_spec.rb +57 -0
- data/spec/unit/provider/group/pw_spec.rb +0 -6
- data/spec/unit/provider/group/windows_adsi_spec.rb +34 -35
- data/spec/unit/provider/nameservice/directoryservice_spec.rb +2 -2
- data/spec/unit/provider/package/windows/exe_package_spec.rb +3 -3
- data/spec/unit/provider/package/windows_spec.rb +4 -4
- data/spec/unit/provider/service/launchd_spec.rb +19 -0
- data/spec/unit/provider/service/windows_spec.rb +71 -78
- data/spec/unit/provider/user/aix_spec.rb +162 -116
- data/spec/unit/provider/user/windows_adsi_spec.rb +4 -4
- data/spec/unit/resource/catalog_spec.rb +2 -2
- data/spec/unit/ssl/certificate_authority_spec.rb +0 -1
- data/spec/unit/type/group_spec.rb +111 -13
- data/spec/unit/type/resources_spec.rb +18 -0
- data/spec/unit/util/execution_spec.rb +77 -0
- data/spec/unit/util/posix_spec.rb +28 -0
- data/spec/unit/util/storage_spec.rb +107 -0
- data/spec/unit/util/windows/adsi_spec.rb +108 -13
- data/spec/unit/util/windows/service_spec.rb +669 -0
- metadata +17 -5
- data/lib/puppet/provider/aixobject.rb +0 -392
- data/spec/unit/provider/aixobject_spec.rb +0 -101
@@ -7,15 +7,35 @@ module Puppet::Util::Windows::Process
|
|
7
7
|
extend FFI::Library
|
8
8
|
|
9
9
|
WAIT_TIMEOUT = 0x102
|
10
|
+
WAIT_INTERVAL = 200
|
11
|
+
# https://docs.microsoft.com/en-us/windows/desktop/ProcThread/process-creation-flags
|
12
|
+
CREATE_NO_WINDOW = 0x08000000
|
10
13
|
|
11
14
|
def execute(command, arguments, stdin, stdout, stderr)
|
12
|
-
|
15
|
+
create_args = {
|
16
|
+
:command_line => command,
|
17
|
+
:startup_info => {
|
18
|
+
:stdin => stdin,
|
19
|
+
:stdout => stdout,
|
20
|
+
:stderr => stderr
|
21
|
+
},
|
22
|
+
:close_handles => false,
|
23
|
+
}
|
24
|
+
if arguments[:suppress_window]
|
25
|
+
create_args[:creation_flags] = CREATE_NO_WINDOW
|
26
|
+
end
|
27
|
+
cwd = arguments[:cwd]
|
28
|
+
if cwd
|
29
|
+
Dir.chdir(cwd) { Process.create(create_args) }
|
30
|
+
else
|
31
|
+
Process.create(create_args)
|
32
|
+
end
|
13
33
|
end
|
14
34
|
module_function :execute
|
15
35
|
|
16
36
|
def wait_process(handle)
|
17
|
-
while WaitForSingleObject(handle,
|
18
|
-
sleep(
|
37
|
+
while WaitForSingleObject(handle, WAIT_INTERVAL) == WAIT_TIMEOUT
|
38
|
+
sleep(0)
|
19
39
|
end
|
20
40
|
|
21
41
|
exit_status = -1
|
@@ -324,6 +324,20 @@ module Puppet::Util::Windows::Security
|
|
324
324
|
# we don't check S_ISYSTEM_MISSING bit, but automatically carry over existing SYSTEM perms
|
325
325
|
# by default set SYSTEM perms to full
|
326
326
|
system_allow = FILE::FILE_ALL_ACCESS
|
327
|
+
else
|
328
|
+
# It is possible to set SYSTEM with a mode other than Full Control (7) however this makes no sense and in practical terms
|
329
|
+
# should not be done. We can trap these instances and correct them before being applied.
|
330
|
+
if (sd.owner == well_known_system_sid) && (owner_allow != FILE::FILE_ALL_ACCESS)
|
331
|
+
#TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
|
332
|
+
Puppet.debug _("An attempt to set mode %{mode} on item %{path} would result in the owner, SYSTEM, to have less than Full Control rights. This attempt has been corrected to Full Control") % { mode: mode.to_s(8), path: path }
|
333
|
+
owner_allow = FILE::FILE_ALL_ACCESS
|
334
|
+
end
|
335
|
+
|
336
|
+
if (sd.group == well_known_system_sid) && (group_allow != FILE::FILE_ALL_ACCESS)
|
337
|
+
#TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
|
338
|
+
Puppet.debug _("An attempt to set mode %{mode} on item %{path} would result in the group, SYSTEM, to have less than Full Control rights. This attempt has been corrected to Full Control") % { mode: mode.to_s(8), path: path }
|
339
|
+
group_allow = FILE::FILE_ALL_ACCESS
|
340
|
+
end
|
327
341
|
end
|
328
342
|
|
329
343
|
# even though FILE_DELETE_CHILD only applies to directories, it can be set on files
|
@@ -0,0 +1,977 @@
|
|
1
|
+
require 'puppet/util/windows'
|
2
|
+
require 'ffi'
|
3
|
+
|
4
|
+
module Puppet::Util::Windows
|
5
|
+
# This module is designed to provide an API between the windows system and puppet for
|
6
|
+
# service management.
|
7
|
+
#
|
8
|
+
# for an overview of the service state transitions see: https://docs.microsoft.com/en-us/windows/desktop/Services/service-status-transitions
|
9
|
+
module Service
|
10
|
+
extend FFI::Library
|
11
|
+
extend Puppet::Util::Windows::String
|
12
|
+
|
13
|
+
FILE = Puppet::Util::Windows::File
|
14
|
+
|
15
|
+
# integer value of the floor for timeouts when waiting for service pending states.
|
16
|
+
# puppet will wait the length of dwWaitHint if it is longer than this value, but
|
17
|
+
# no shorter
|
18
|
+
DEFAULT_TIMEOUT = 30
|
19
|
+
|
20
|
+
# Service error codes
|
21
|
+
# https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--1000-1299-
|
22
|
+
ERROR_SERVICE_DOES_NOT_EXIST = 0x00000424
|
23
|
+
|
24
|
+
# Service control codes
|
25
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-controlserviceexw
|
26
|
+
SERVICE_CONTROL_STOP = 0x00000001
|
27
|
+
SERVICE_CONTROL_PAUSE = 0x00000002
|
28
|
+
SERVICE_CONTROL_CONTINUE = 0x00000003
|
29
|
+
SERVICE_CONTROL_INTERROGATE = 0x00000004
|
30
|
+
SERVICE_CONTROL_SHUTDOWN = 0x00000005
|
31
|
+
SERVICE_CONTROL_PARAMCHANGE = 0x00000006
|
32
|
+
SERVICE_CONTROL_NETBINDADD = 0x00000007
|
33
|
+
SERVICE_CONTROL_NETBINDREMOVE = 0x00000008
|
34
|
+
SERVICE_CONTROL_NETBINDENABLE = 0x00000009
|
35
|
+
SERVICE_CONTROL_NETBINDDISABLE = 0x0000000A
|
36
|
+
SERVICE_CONTROL_DEVICEEVENT = 0x0000000B
|
37
|
+
SERVICE_CONTROL_HARDWAREPROFILECHANGE = 0x0000000C
|
38
|
+
SERVICE_CONTROL_POWEREVENT = 0x0000000D
|
39
|
+
SERVICE_CONTROL_SESSIONCHANGE = 0x0000000E
|
40
|
+
SERVICE_CONTROL_PRESHUTDOWN = 0x0000000F
|
41
|
+
SERVICE_CONTROL_TIMECHANGE = 0x00000010
|
42
|
+
SERVICE_CONTROL_TRIGGEREVENT = 0x00000020
|
43
|
+
SERVICE_CONTROL_SIGNALS = {
|
44
|
+
SERVICE_CONTROL_STOP => :SERVICE_CONTROL_STOP,
|
45
|
+
SERVICE_CONTROL_PAUSE => :SERVICE_CONTROL_PAUSE,
|
46
|
+
SERVICE_CONTROL_CONTINUE => :SERVICE_CONTROL_CONTINUE,
|
47
|
+
SERVICE_CONTROL_INTERROGATE => :SERVICE_CONTROL_INTERROGATE,
|
48
|
+
SERVICE_CONTROL_SHUTDOWN => :SERVICE_CONTROL_SHUTDOWN,
|
49
|
+
SERVICE_CONTROL_PARAMCHANGE => :SERVICE_CONTROL_PARAMCHANGE,
|
50
|
+
SERVICE_CONTROL_NETBINDADD => :SERVICE_CONTROL_NETBINDADD,
|
51
|
+
SERVICE_CONTROL_NETBINDREMOVE => :SERVICE_CONTROL_NETBINDREMOVE,
|
52
|
+
SERVICE_CONTROL_NETBINDENABLE => :SERVICE_CONTROL_NETBINDENABLE,
|
53
|
+
SERVICE_CONTROL_NETBINDDISABLE => :SERVICE_CONTROL_NETBINDDISABLE,
|
54
|
+
SERVICE_CONTROL_DEVICEEVENT => :SERVICE_CONTROL_DEVICEEVENT,
|
55
|
+
SERVICE_CONTROL_HARDWAREPROFILECHANGE => :SERVICE_CONTROL_HARDWAREPROFILECHANGE,
|
56
|
+
SERVICE_CONTROL_POWEREVENT => :SERVICE_CONTROL_POWEREVENT,
|
57
|
+
SERVICE_CONTROL_SESSIONCHANGE => :SERVICE_CONTROL_SESSIONCHANGE,
|
58
|
+
SERVICE_CONTROL_PRESHUTDOWN => :SERVICE_CONTROL_PRESHUTDOWN,
|
59
|
+
SERVICE_CONTROL_TIMECHANGE => :SERVICE_CONTROL_TIMECHANGE,
|
60
|
+
SERVICE_CONTROL_TRIGGEREVENT => :SERVICE_CONTROL_TRIGGEREVENT
|
61
|
+
}
|
62
|
+
|
63
|
+
|
64
|
+
# Service start type codes
|
65
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-changeserviceconfigw
|
66
|
+
SERVICE_AUTO_START = 0x00000002
|
67
|
+
SERVICE_BOOT_START = 0x00000000
|
68
|
+
SERVICE_DEMAND_START = 0x00000003
|
69
|
+
SERVICE_DISABLED = 0x00000004
|
70
|
+
SERVICE_SYSTEM_START = 0x00000001
|
71
|
+
SERVICE_START_TYPES = {
|
72
|
+
SERVICE_AUTO_START => :SERVICE_AUTO_START,
|
73
|
+
SERVICE_BOOT_START => :SERVICE_BOOT_START,
|
74
|
+
SERVICE_DEMAND_START => :SERVICE_DEMAND_START,
|
75
|
+
SERVICE_DISABLED => :SERVICE_DISABLED,
|
76
|
+
SERVICE_SYSTEM_START => :SERVICE_SYSTEM_START,
|
77
|
+
}
|
78
|
+
|
79
|
+
# Service type codes
|
80
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-changeserviceconfigw
|
81
|
+
SERVICE_FILE_SYSTEM_DRIVER = 0x00000002
|
82
|
+
SERVICE_KERNEL_DRIVER = 0x00000001
|
83
|
+
SERVICE_WIN32_OWN_PROCESS = 0x00000010
|
84
|
+
SERVICE_WIN32_SHARE_PROCESS = 0x00000020
|
85
|
+
SERVICE_USER_OWN_PROCESS = 0x00000050
|
86
|
+
SERVICE_USER_SHARE_PROCESS = 0x00000060
|
87
|
+
# Available only if service is also SERVICE_WIN32_OWN_PROCESS or SERVICE_WIN32_SHARE_PROCESS
|
88
|
+
SERVICE_INTERACTIVE_PROCESS = 0x00000100
|
89
|
+
ALL_SERVICE_TYPES =
|
90
|
+
SERVICE_FILE_SYSTEM_DRIVER |
|
91
|
+
SERVICE_KERNEL_DRIVER |
|
92
|
+
SERVICE_WIN32_OWN_PROCESS |
|
93
|
+
SERVICE_WIN32_SHARE_PROCESS
|
94
|
+
|
95
|
+
# Current state codes
|
96
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_status_process
|
97
|
+
SERVICE_CONTINUE_PENDING = 0x00000005
|
98
|
+
SERVICE_PAUSE_PENDING = 0x00000006
|
99
|
+
SERVICE_PAUSED = 0x00000007
|
100
|
+
SERVICE_RUNNING = 0x00000004
|
101
|
+
SERVICE_START_PENDING = 0x00000002
|
102
|
+
SERVICE_STOP_PENDING = 0x00000003
|
103
|
+
SERVICE_STOPPED = 0x00000001
|
104
|
+
UNSAFE_PENDING_STATES = [SERVICE_START_PENDING, SERVICE_STOP_PENDING]
|
105
|
+
FINAL_STATES = {
|
106
|
+
SERVICE_CONTINUE_PENDING => SERVICE_RUNNING,
|
107
|
+
SERVICE_PAUSE_PENDING => SERVICE_PAUSED,
|
108
|
+
SERVICE_START_PENDING => SERVICE_RUNNING,
|
109
|
+
SERVICE_STOP_PENDING => SERVICE_STOPPED
|
110
|
+
}
|
111
|
+
SERVICE_STATES = {
|
112
|
+
SERVICE_CONTINUE_PENDING => :SERVICE_CONTINUE_PENDING,
|
113
|
+
SERVICE_PAUSE_PENDING => :SERVICE_PAUSE_PENDING,
|
114
|
+
SERVICE_PAUSED => :SERVICE_PAUSED,
|
115
|
+
SERVICE_RUNNING => :SERVICE_RUNNING,
|
116
|
+
SERVICE_START_PENDING => :SERVICE_START_PENDING,
|
117
|
+
SERVICE_STOP_PENDING => :SERVICE_STOP_PENDING,
|
118
|
+
SERVICE_STOPPED => :SERVICE_STOPPED,
|
119
|
+
}
|
120
|
+
|
121
|
+
# Service accepts control codes
|
122
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_status_process
|
123
|
+
SERVICE_ACCEPT_STOP = 0x00000001
|
124
|
+
SERVICE_ACCEPT_PAUSE_CONTINUE = 0x00000002
|
125
|
+
SERVICE_ACCEPT_SHUTDOWN = 0x00000004
|
126
|
+
SERVICE_ACCEPT_PARAMCHANGE = 0x00000008
|
127
|
+
SERVICE_ACCEPT_NETBINDCHANGE = 0x00000010
|
128
|
+
SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 0x00000020
|
129
|
+
SERVICE_ACCEPT_POWEREVENT = 0x00000040
|
130
|
+
SERVICE_ACCEPT_SESSIONCHANGE = 0x00000080
|
131
|
+
SERVICE_ACCEPT_PRESHUTDOWN = 0x00000100
|
132
|
+
SERVICE_ACCEPT_TIMECHANGE = 0x00000200
|
133
|
+
SERVICE_ACCEPT_TRIGGEREVENT = 0x00000400
|
134
|
+
SERVICE_ACCEPT_USER_LOGOFF = 0x00000800
|
135
|
+
|
136
|
+
# Service manager access codes
|
137
|
+
# https://docs.microsoft.com/en-us/windows/desktop/Services/service-security-and-access-rights
|
138
|
+
SC_MANAGER_CREATE_SERVICE = 0x00000002
|
139
|
+
SC_MANAGER_CONNECT = 0x00000001
|
140
|
+
SC_MANAGER_ENUMERATE_SERVICE = 0x00000004
|
141
|
+
SC_MANAGER_LOCK = 0x00000008
|
142
|
+
SC_MANAGER_MODIFY_BOOT_CONFIG = 0x00000020
|
143
|
+
SC_MANAGER_QUERY_LOCK_STATUS = 0x00000010
|
144
|
+
SC_MANAGER_ALL_ACCESS =
|
145
|
+
FILE::STANDARD_RIGHTS_REQUIRED |
|
146
|
+
SC_MANAGER_CREATE_SERVICE |
|
147
|
+
SC_MANAGER_CONNECT |
|
148
|
+
SC_MANAGER_ENUMERATE_SERVICE |
|
149
|
+
SC_MANAGER_LOCK |
|
150
|
+
SC_MANAGER_MODIFY_BOOT_CONFIG |
|
151
|
+
SC_MANAGER_QUERY_LOCK_STATUS
|
152
|
+
|
153
|
+
|
154
|
+
# Service access codes
|
155
|
+
# https://docs.microsoft.com/en-us/windows/desktop/Services/service-security-and-access-rights
|
156
|
+
SERVICE_CHANGE_CONFIG = 0x0002
|
157
|
+
SERVICE_ENUMERATE_DEPENDENTS = 0x0008
|
158
|
+
SERVICE_INTERROGATE = 0x0080
|
159
|
+
SERVICE_PAUSE_CONTINUE = 0x0040
|
160
|
+
SERVICE_QUERY_STATUS = 0x0004
|
161
|
+
SERVICE_QUERY_CONFIG = 0x0001
|
162
|
+
SERVICE_START = 0x0010
|
163
|
+
SERVICE_STOP = 0x0020
|
164
|
+
SERVICE_USER_DEFINED_CONTROL = 0x0100
|
165
|
+
SERVICE_ALL_ACCESS =
|
166
|
+
FILE::STANDARD_RIGHTS_REQUIRED |
|
167
|
+
SERVICE_CHANGE_CONFIG |
|
168
|
+
SERVICE_ENUMERATE_DEPENDENTS |
|
169
|
+
SERVICE_INTERROGATE |
|
170
|
+
SERVICE_PAUSE_CONTINUE |
|
171
|
+
SERVICE_QUERY_STATUS |
|
172
|
+
SERVICE_QUERY_CONFIG |
|
173
|
+
SERVICE_START |
|
174
|
+
SERVICE_STOP |
|
175
|
+
SERVICE_USER_DEFINED_CONTROL
|
176
|
+
|
177
|
+
# Service config codes
|
178
|
+
# From the windows 10 SDK:
|
179
|
+
# //
|
180
|
+
# // Value to indicate no change to an optional parameter
|
181
|
+
# //
|
182
|
+
# #define SERVICE_NO_CHANGE 0xffffffff
|
183
|
+
SERVICE_NO_CHANGE = 0xffffffff
|
184
|
+
|
185
|
+
# Service enum codes
|
186
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-enumservicesstatusexa
|
187
|
+
SERVICE_ACTIVE = 0x00000001
|
188
|
+
SERVICE_INACTIVE = 0x00000002
|
189
|
+
SERVICE_STATE_ALL =
|
190
|
+
SERVICE_ACTIVE |
|
191
|
+
SERVICE_INACTIVE
|
192
|
+
|
193
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_enum_service_status_processw
|
194
|
+
SERVICENAME_MAX = 256
|
195
|
+
|
196
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_status_process
|
197
|
+
# typedef struct _SERVICE_STATUS_PROCESS {
|
198
|
+
# DWORD dwServiceType;
|
199
|
+
# DWORD dwCurrentState;
|
200
|
+
# DWORD dwControlsAccepted;
|
201
|
+
# DWORD dwWin32ExitCode;
|
202
|
+
# DWORD dwServiceSpecificExitCode;
|
203
|
+
# DWORD dwCheckPoint;
|
204
|
+
# DWORD dwWaitHint;
|
205
|
+
# DWORD dwProcessId;
|
206
|
+
# DWORD dwServiceFlags;
|
207
|
+
# } SERVICE_STATUS_PROCESS, *LPSERVICE_STATUS_PROCESS;
|
208
|
+
class SERVICE_STATUS_PROCESS < FFI::Struct
|
209
|
+
layout(
|
210
|
+
:dwServiceType, :dword,
|
211
|
+
:dwCurrentState, :dword,
|
212
|
+
:dwControlsAccepted, :dword,
|
213
|
+
:dwWin32ExitCode, :dword,
|
214
|
+
:dwServiceSpecificExitCode, :dword,
|
215
|
+
:dwCheckPoint, :dword,
|
216
|
+
:dwWaitHint, :dword,
|
217
|
+
:dwProcessId, :dword,
|
218
|
+
:dwServiceFlags, :dword
|
219
|
+
)
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_enum_service_status_processw
|
224
|
+
# typedef struct _ENUM_SERVICE_STATUS_PROCESSW {
|
225
|
+
# LPWSTR lpServiceName;
|
226
|
+
# LPWSTR lpDisplayName;
|
227
|
+
# SERVICE_STATUS_PROCESS ServiceStatusProcess;
|
228
|
+
# } ENUM_SERVICE_STATUS_PROCESSW, *LPENUM_SERVICE_STATUS_PROCESSW;
|
229
|
+
class ENUM_SERVICE_STATUS_PROCESSW < FFI::Struct
|
230
|
+
layout(
|
231
|
+
:lpServiceName, :pointer,
|
232
|
+
:lpDisplayName, :pointer,
|
233
|
+
:ServiceStatusProcess, SERVICE_STATUS_PROCESS
|
234
|
+
)
|
235
|
+
end
|
236
|
+
|
237
|
+
# typedef struct _SERVICE_STATUS {
|
238
|
+
# DWORD dwServiceType;
|
239
|
+
# DWORD dwCurrentState;
|
240
|
+
# DWORD dwControlsAccepted;
|
241
|
+
# DWORD dwWin32ExitCode;
|
242
|
+
# DWORD dwServiceSpecificExitCode;
|
243
|
+
# DWORD dwCheckPoint;
|
244
|
+
# DWORD dwWaitHint;
|
245
|
+
# } SERVICE_STATUS, *LPSERVICE_STATUS;
|
246
|
+
class SERVICE_STATUS < FFI::Struct
|
247
|
+
layout(
|
248
|
+
:dwServiceType, :dword,
|
249
|
+
:dwCurrentState, :dword,
|
250
|
+
:dwControlsAccepted, :dword,
|
251
|
+
:dwWin32ExitCode, :dword,
|
252
|
+
:dwServiceSpecificExitCode, :dword,
|
253
|
+
:dwCheckPoint, :dword,
|
254
|
+
:dwWaitHint, :dword,
|
255
|
+
)
|
256
|
+
end
|
257
|
+
|
258
|
+
# typedef struct _QUERY_SERVICE_CONFIGW {
|
259
|
+
# DWORD dwServiceType;
|
260
|
+
# DWORD dwStartType;
|
261
|
+
# DWORD dwErrorControl;
|
262
|
+
# LPWSTR lpBinaryPathName;
|
263
|
+
# LPWSTR lpLoadOrderGroup;
|
264
|
+
# DWORD dwTagId;
|
265
|
+
# LPWSTR lpDependencies;
|
266
|
+
# LPWSTR lpServiceStartName;
|
267
|
+
# LPWSTR lpDisplayName;
|
268
|
+
# } QUERY_SERVICE_CONFIGW, *LPQUERY_SERVICE_CONFIGW;
|
269
|
+
class QUERY_SERVICE_CONFIGW < FFI::Struct
|
270
|
+
layout(
|
271
|
+
:dwServiceType, :dword,
|
272
|
+
:dwStartType, :dword,
|
273
|
+
:dwErrorControl, :dword,
|
274
|
+
:lpBinaryPathName, :pointer,
|
275
|
+
:lpLoadOrderGroup, :pointer,
|
276
|
+
:dwTagId, :dword,
|
277
|
+
:lpDependencies, :pointer,
|
278
|
+
:lpServiceStartName, :pointer,
|
279
|
+
:lpDisplayName, :pointer,
|
280
|
+
)
|
281
|
+
end
|
282
|
+
|
283
|
+
# Returns true if the service exists, false otherwise.
|
284
|
+
#
|
285
|
+
# @param [:string] service_name name of the service
|
286
|
+
def exists?(service_name)
|
287
|
+
open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |_|
|
288
|
+
true
|
289
|
+
end
|
290
|
+
rescue Puppet::Util::Windows::Error => e
|
291
|
+
return false if e.code == ERROR_SERVICE_DOES_NOT_EXIST
|
292
|
+
raise e
|
293
|
+
end
|
294
|
+
module_function :exists?
|
295
|
+
|
296
|
+
# Start a windows service
|
297
|
+
#
|
298
|
+
# @param [:string] service_name name of the service to start
|
299
|
+
def start(service_name)
|
300
|
+
Puppet.debug _("Starting the %{service_name} service") % { service_name: service_name }
|
301
|
+
|
302
|
+
valid_initial_states = [
|
303
|
+
SERVICE_STOP_PENDING,
|
304
|
+
SERVICE_STOPPED,
|
305
|
+
SERVICE_START_PENDING
|
306
|
+
]
|
307
|
+
|
308
|
+
transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING) do |service|
|
309
|
+
if StartServiceW(service, 0, FFI::Pointer::NULL) == FFI::WIN32_FALSE
|
310
|
+
raise Puppet::Util::Windows::Error, _("Failed to start the service")
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
Puppet.debug _("Successfully started the %{service_name} service") % { service_name: service_name }
|
315
|
+
end
|
316
|
+
module_function :start
|
317
|
+
|
318
|
+
# Stop a windows service
|
319
|
+
#
|
320
|
+
# @param [:string] service_name name of the service to stop
|
321
|
+
def stop(service_name)
|
322
|
+
Puppet.debug _("Stopping the %{service_name} service") % { service_name: service_name }
|
323
|
+
|
324
|
+
valid_initial_states = SERVICE_STATES.keys - [SERVICE_STOPPED]
|
325
|
+
|
326
|
+
transition_service_state(service_name, valid_initial_states, SERVICE_STOPPED) do |service|
|
327
|
+
send_service_control_signal(service, SERVICE_CONTROL_STOP)
|
328
|
+
end
|
329
|
+
|
330
|
+
Puppet.debug _("Successfully stopped the %{service_name} service") % { service_name: service_name }
|
331
|
+
end
|
332
|
+
module_function :stop
|
333
|
+
|
334
|
+
# Resume a paused windows service
|
335
|
+
#
|
336
|
+
# @param [:string] service_name name of the service to resume
|
337
|
+
def resume(service_name)
|
338
|
+
Puppet.debug _("Resuming the %{service_name} service") % { service_name: service_name }
|
339
|
+
|
340
|
+
valid_initial_states = [
|
341
|
+
SERVICE_PAUSE_PENDING,
|
342
|
+
SERVICE_PAUSED,
|
343
|
+
SERVICE_CONTINUE_PENDING
|
344
|
+
]
|
345
|
+
|
346
|
+
transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING) do |service|
|
347
|
+
# The SERVICE_CONTROL_CONTINUE signal can only be sent when
|
348
|
+
# the service is in the SERVICE_PAUSED state
|
349
|
+
wait_on_pending_state(service, SERVICE_PAUSE_PENDING)
|
350
|
+
|
351
|
+
send_service_control_signal(service, SERVICE_CONTROL_CONTINUE)
|
352
|
+
end
|
353
|
+
|
354
|
+
Puppet.debug _("Successfully resumed the %{service_name} service") % { service_name: service_name }
|
355
|
+
end
|
356
|
+
module_function :resume
|
357
|
+
|
358
|
+
# Query the state of a service using QueryServiceStatusEx
|
359
|
+
#
|
360
|
+
# @param [:string] service_name name of the service to query
|
361
|
+
# @return [string] the status of the service
|
362
|
+
def service_state(service_name)
|
363
|
+
status = nil
|
364
|
+
open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_STATUS) do |service|
|
365
|
+
status = query_status(service)
|
366
|
+
end
|
367
|
+
state = SERVICE_STATES[status[:dwCurrentState]]
|
368
|
+
if state.nil?
|
369
|
+
raise Puppet::Error.new(_("Unknown Service state '%{current_state}' for '%{service_name}'") % { current_state: status[:dwCurrentState].to_s, service_name: service_name})
|
370
|
+
end
|
371
|
+
state
|
372
|
+
end
|
373
|
+
module_function :service_state
|
374
|
+
|
375
|
+
# Query the configuration of a service using QueryServiceConfigW
|
376
|
+
#
|
377
|
+
# @param [:string] service_name name of the service to query
|
378
|
+
# @return [QUERY_SERVICE_CONFIGW.struct] the configuration of the service
|
379
|
+
def service_start_type(service_name)
|
380
|
+
config = nil
|
381
|
+
open_service(service_name, SC_MANAGER_CONNECT, SERVICE_QUERY_CONFIG) do |service|
|
382
|
+
config = query_config(service)
|
383
|
+
end
|
384
|
+
start_type = SERVICE_START_TYPES[config[:dwStartType]]
|
385
|
+
if start_type.nil?
|
386
|
+
raise Puppet::Error.new(_("Unknown start type '%{start_type}' for '%{service_name}'") % { start_type: config[:dwStartType].to_s, service_name: service_name})
|
387
|
+
end
|
388
|
+
start_type
|
389
|
+
end
|
390
|
+
module_function :service_start_type
|
391
|
+
|
392
|
+
# Change the startup mode of a windows service
|
393
|
+
#
|
394
|
+
# @param [string] service_name the name of the service to modify
|
395
|
+
# @param [Int] startup_type a code corresponding to a start type for
|
396
|
+
# windows service, see the "Service start type codes" section in the
|
397
|
+
# Puppet::Util::Windows::Service file for the list of available codes
|
398
|
+
def set_startup_mode(service_name, startup_type)
|
399
|
+
startup_code = SERVICE_START_TYPES.key(startup_type)
|
400
|
+
if startup_code.nil?
|
401
|
+
raise Puppet::Error.new(_("Unknown start type %{start_type}") % {startup_type: startup_type.to_s})
|
402
|
+
end
|
403
|
+
open_service(service_name, SC_MANAGER_CONNECT, SERVICE_CHANGE_CONFIG) do |service|
|
404
|
+
# Currently the only thing puppet's API can really manage
|
405
|
+
# in this list is dwStartType (the third param). Thus no
|
406
|
+
# generic function was written to make use of all the params
|
407
|
+
# since the API as-is couldn't use them anyway
|
408
|
+
success = ChangeServiceConfigW(
|
409
|
+
service,
|
410
|
+
SERVICE_NO_CHANGE, # dwServiceType
|
411
|
+
startup_code, # dwStartType
|
412
|
+
SERVICE_NO_CHANGE, # dwErrorControl
|
413
|
+
FFI::Pointer::NULL, # lpBinaryPathName
|
414
|
+
FFI::Pointer::NULL, # lpLoadOrderGroup
|
415
|
+
FFI::Pointer::NULL, # lpdwTagId
|
416
|
+
FFI::Pointer::NULL, # lpDependencies
|
417
|
+
FFI::Pointer::NULL, # lpServiceStartName
|
418
|
+
FFI::Pointer::NULL, # lpPassword
|
419
|
+
FFI::Pointer::NULL # lpDisplayName
|
420
|
+
)
|
421
|
+
if success == FFI::WIN32_FALSE
|
422
|
+
raise Puppet::Util::Windows::Error.new(_("Failed to update service configuration"))
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
module_function :set_startup_mode
|
427
|
+
|
428
|
+
# enumerate over all services in all states and return them as a hash
|
429
|
+
#
|
430
|
+
# @return [Hash] a hash containing services:
|
431
|
+
# { 'service name' => {
|
432
|
+
# 'display_name' => 'display name',
|
433
|
+
# 'service_status_process' => SERVICE_STATUS_PROCESS struct
|
434
|
+
# }
|
435
|
+
# }
|
436
|
+
def services
|
437
|
+
services = {}
|
438
|
+
open_scm(SC_MANAGER_ENUMERATE_SERVICE) do |scm|
|
439
|
+
size_required = 0
|
440
|
+
services_returned = 0
|
441
|
+
FFI::MemoryPointer.new(:dword) do |bytes_pointer|
|
442
|
+
FFI::MemoryPointer.new(:dword) do |svcs_ret_ptr|
|
443
|
+
FFI::MemoryPointer.new(:dword) do |resume_ptr|
|
444
|
+
resume_ptr.write_dword(0)
|
445
|
+
# Fetch the bytes of memory required to be allocated
|
446
|
+
# for QueryServiceConfigW to return succesfully. This
|
447
|
+
# is done by sending NULL and 0 for the pointer and size
|
448
|
+
# respectively, letting the command fail, then reading the
|
449
|
+
# value of pcbBytesNeeded
|
450
|
+
#
|
451
|
+
# return value will be false from this call, since it's designed
|
452
|
+
# to fail. Just ignore it
|
453
|
+
EnumServicesStatusExW(
|
454
|
+
scm,
|
455
|
+
:SC_ENUM_PROCESS_INFO,
|
456
|
+
ALL_SERVICE_TYPES,
|
457
|
+
SERVICE_STATE_ALL,
|
458
|
+
FFI::Pointer::NULL,
|
459
|
+
0,
|
460
|
+
bytes_pointer,
|
461
|
+
svcs_ret_ptr,
|
462
|
+
resume_ptr,
|
463
|
+
FFI::Pointer::NULL
|
464
|
+
)
|
465
|
+
size_required = bytes_pointer.read_dword
|
466
|
+
FFI::MemoryPointer.new(size_required) do |buffer_ptr|
|
467
|
+
resume_ptr.write_dword(0)
|
468
|
+
svcs_ret_ptr.write_dword(0)
|
469
|
+
success = EnumServicesStatusExW(
|
470
|
+
scm,
|
471
|
+
:SC_ENUM_PROCESS_INFO,
|
472
|
+
ALL_SERVICE_TYPES,
|
473
|
+
SERVICE_STATE_ALL,
|
474
|
+
buffer_ptr,
|
475
|
+
buffer_ptr.size,
|
476
|
+
bytes_pointer,
|
477
|
+
svcs_ret_ptr,
|
478
|
+
resume_ptr,
|
479
|
+
FFI::Pointer::NULL
|
480
|
+
)
|
481
|
+
if success == FFI::WIN32_FALSE
|
482
|
+
raise Puppet::Util::Windows::Error.new(_("Failed to fetch services"))
|
483
|
+
end
|
484
|
+
# Now that the buffer is populated with services
|
485
|
+
# we pull the data from memory using pointer arithmetic:
|
486
|
+
# the number of services returned by the function is
|
487
|
+
# available to be read from svcs_ret_ptr, and we iterate
|
488
|
+
# that many times moving the cursor pointer the length of
|
489
|
+
# ENUM_SERVICE_STATUS_PROCESSW.size. This should iterate
|
490
|
+
# over the buffer and extract each struct.
|
491
|
+
services_returned = svcs_ret_ptr.read_dword
|
492
|
+
cursor_ptr = FFI::Pointer.new(ENUM_SERVICE_STATUS_PROCESSW, buffer_ptr)
|
493
|
+
0.upto(services_returned - 1) do |index|
|
494
|
+
service = ENUM_SERVICE_STATUS_PROCESSW.new(cursor_ptr[index])
|
495
|
+
services[service[:lpServiceName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX)] = {
|
496
|
+
:display_name => service[:lpDisplayName].read_arbitrary_wide_string_up_to(SERVICENAME_MAX),
|
497
|
+
:service_status_process => service[:ServiceStatusProcess]
|
498
|
+
}
|
499
|
+
end
|
500
|
+
end # buffer_ptr
|
501
|
+
end # resume_ptr
|
502
|
+
end # scvs_ret_ptr
|
503
|
+
end # bytes_ptr
|
504
|
+
end # open_scm
|
505
|
+
services
|
506
|
+
end
|
507
|
+
module_function :services
|
508
|
+
|
509
|
+
class << self
|
510
|
+
# @api private
|
511
|
+
# Opens a connection to the SCManager on windows then uses that
|
512
|
+
# handle to create a handle to a specific service in windows
|
513
|
+
# corresponding to service_name
|
514
|
+
#
|
515
|
+
# this function takes a block that executes within the context of
|
516
|
+
# the open service handler, and will close the service and SCManager
|
517
|
+
# handles once the block finishes
|
518
|
+
#
|
519
|
+
# @param [string] service_name the name of the service to open
|
520
|
+
# @param [Integer] scm_access code corresponding to the access type requested for the scm
|
521
|
+
# @param [Integer] service_access code corresponding to the access type requested for the service
|
522
|
+
# @yieldparam [:handle] service the windows native handle used to access
|
523
|
+
# the service
|
524
|
+
# @return the result of the block
|
525
|
+
def open_service(service_name, scm_access, service_access, &block)
|
526
|
+
service = FFI::Pointer::NULL_HANDLE
|
527
|
+
|
528
|
+
result = nil
|
529
|
+
open_scm(scm_access) do |scm|
|
530
|
+
service = OpenServiceW(scm, wide_string(service_name), service_access)
|
531
|
+
raise Puppet::Util::Windows::Error.new(_("Failed to open a handle to the service")) if service == FFI::Pointer::NULL_HANDLE
|
532
|
+
result = yield service
|
533
|
+
end
|
534
|
+
|
535
|
+
result
|
536
|
+
ensure
|
537
|
+
CloseServiceHandle(service)
|
538
|
+
end
|
539
|
+
private :open_service
|
540
|
+
|
541
|
+
# @api private
|
542
|
+
#
|
543
|
+
# Opens a handle to the service control manager
|
544
|
+
#
|
545
|
+
# @param [Integer] scm_access code corresponding to the access type requested for the scm
|
546
|
+
def open_scm(scm_access, &block)
|
547
|
+
scm = OpenSCManagerW(FFI::Pointer::NULL, FFI::Pointer::NULL, scm_access)
|
548
|
+
raise Puppet::Util::Windows::Error.new(_("Failed to open a handle to the service control manager")) if scm == FFI::Pointer::NULL_HANDLE
|
549
|
+
yield scm
|
550
|
+
ensure
|
551
|
+
CloseServiceHandle(scm)
|
552
|
+
end
|
553
|
+
private :open_scm
|
554
|
+
|
555
|
+
# @api private
|
556
|
+
# Transition the service to the specified state. The block should perform
|
557
|
+
# the actual transition.
|
558
|
+
#
|
559
|
+
# @param [String] service_name the name of the service to transition
|
560
|
+
# @param [[Integer]] valid_initial_states an array of valid states that the service can transition from
|
561
|
+
# @param [:Integer] final_state the state that the service will transition to
|
562
|
+
def transition_service_state(service_name, valid_initial_states, final_state, &block)
|
563
|
+
service_access = SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_QUERY_STATUS
|
564
|
+
open_service(service_name, SC_MANAGER_CONNECT, service_access) do |service|
|
565
|
+
status = query_status(service)
|
566
|
+
initial_state = status[:dwCurrentState]
|
567
|
+
|
568
|
+
# If the service is already in the final_state, then
|
569
|
+
# no further work needs to be done
|
570
|
+
if initial_state == final_state
|
571
|
+
Puppet.debug _("The service is already in the %{final_state} state. No further work needs to be done.") % { final_state: SERVICE_STATES[final_state] }
|
572
|
+
|
573
|
+
next
|
574
|
+
end
|
575
|
+
|
576
|
+
# Check that initial_state corresponds to a valid
|
577
|
+
# initial state
|
578
|
+
unless valid_initial_states.include?(initial_state)
|
579
|
+
valid_initial_states_str = valid_initial_states.map do |state|
|
580
|
+
SERVICE_STATES[state]
|
581
|
+
end.join(", ")
|
582
|
+
|
583
|
+
raise Puppet::Error, _("The service must be in one of the %{valid_initial_states} states to perform this transition. It is currently in the %{current_state} state.") % { valid_initial_states: valid_initial_states_str, current_state: SERVICE_STATES[initial_state] }
|
584
|
+
end
|
585
|
+
|
586
|
+
# Check if there's a pending transition to the final_state. If so, then wait for
|
587
|
+
# that transition to finish.
|
588
|
+
possible_pending_states = FINAL_STATES.keys.select do |pending_state|
|
589
|
+
# SERVICE_RUNNING has two pending states, SERVICE_START_PENDING and
|
590
|
+
# SERVICE_CONTINUE_PENDING. That is why we need the #select here
|
591
|
+
FINAL_STATES[pending_state] == final_state
|
592
|
+
end
|
593
|
+
if possible_pending_states.include?(initial_state)
|
594
|
+
Puppet.debug _("There is already a pending transition to the %{final_state} state for the %{service_name} service.") % { final_state: SERVICE_STATES[final_state], service_name: service_name }
|
595
|
+
wait_on_pending_state(service, initial_state)
|
596
|
+
|
597
|
+
next
|
598
|
+
end
|
599
|
+
|
600
|
+
# If we are in an unsafe pending state like SERVICE_START_PENDING
|
601
|
+
# or SERVICE_STOP_PENDING, then we want to wait for that pending
|
602
|
+
# transition to finish before transitioning the service state.
|
603
|
+
# The reason we do this is because SERVICE_START_PENDING is when
|
604
|
+
# the service thread is being created and initialized, while
|
605
|
+
# SERVICE_STOP_PENDING is when the service thread is being cleaned
|
606
|
+
# up and destroyed. Thus there is a chance that when the service is
|
607
|
+
# in either of these states, its service thread may not yet be ready
|
608
|
+
# to perform the state transition (it may not even exist).
|
609
|
+
if UNSAFE_PENDING_STATES.include?(initial_state)
|
610
|
+
Puppet.debug _("The service is in the %{pending_state} state, which is an unsafe pending state.") % { pending_state: SERVICE_STATES[initial_state] }
|
611
|
+
wait_on_pending_state(service, initial_state)
|
612
|
+
initial_state = FINAL_STATES[initial_state]
|
613
|
+
end
|
614
|
+
|
615
|
+
Puppet.debug _("Transitioning the %{service_name} service from %{initial_state} to %{final_state}") % { service_name: service_name, initial_state: SERVICE_STATES[initial_state], final_state: SERVICE_STATES[final_state] }
|
616
|
+
|
617
|
+
yield service
|
618
|
+
|
619
|
+
Puppet.debug _("Waiting for the transition to finish")
|
620
|
+
wait_on_state_transition(service, initial_state, final_state)
|
621
|
+
end
|
622
|
+
rescue => detail
|
623
|
+
raise Puppet::Error, _("Failed to transition the %{service_name} service to the %{final_state} state. Detail: %{detail}") % { service_name: service_name, final_state: SERVICE_STATES[final_state], detail: detail }, detail.backtrace
|
624
|
+
end
|
625
|
+
private :transition_service_state
|
626
|
+
|
627
|
+
# @api private
|
628
|
+
# perform QueryServiceStatusEx on a windows service and return the
|
629
|
+
# result
|
630
|
+
#
|
631
|
+
# @param [:handle] service handle of the service to query
|
632
|
+
# @return [SERVICE_STATUS_PROCESS struct] the result of the query
|
633
|
+
def query_status(service)
|
634
|
+
size_required = nil
|
635
|
+
status = nil
|
636
|
+
# Fetch the bytes of memory required to be allocated
|
637
|
+
# for QueryServiceConfigW to return succesfully. This
|
638
|
+
# is done by sending NULL and 0 for the pointer and size
|
639
|
+
# respectively, letting the command fail, then reading the
|
640
|
+
# value of pcbBytesNeeded
|
641
|
+
FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
|
642
|
+
# return value will be false from this call, since it's designed
|
643
|
+
# to fail. Just ignore it
|
644
|
+
QueryServiceStatusEx(
|
645
|
+
service,
|
646
|
+
:SC_STATUS_PROCESS_INFO,
|
647
|
+
FFI::Pointer::NULL,
|
648
|
+
0,
|
649
|
+
bytes_pointer
|
650
|
+
)
|
651
|
+
size_required = bytes_pointer.read_dword
|
652
|
+
FFI::MemoryPointer.new(size_required) do |ssp_ptr|
|
653
|
+
status = SERVICE_STATUS_PROCESS.new(ssp_ptr)
|
654
|
+
success = QueryServiceStatusEx(
|
655
|
+
service,
|
656
|
+
:SC_STATUS_PROCESS_INFO,
|
657
|
+
ssp_ptr,
|
658
|
+
size_required,
|
659
|
+
bytes_pointer
|
660
|
+
)
|
661
|
+
if success == FFI::WIN32_FALSE
|
662
|
+
raise Puppet::Util::Windows::Error.new(_("Service query failed"))
|
663
|
+
end
|
664
|
+
end
|
665
|
+
end
|
666
|
+
status
|
667
|
+
end
|
668
|
+
private :query_status
|
669
|
+
|
670
|
+
# @api private
|
671
|
+
# perform QueryServiceConfigW on a windows service and return the
|
672
|
+
# result
|
673
|
+
#
|
674
|
+
# @param [:handle] service handle of the service to query
|
675
|
+
# @return [QUERY_SERVICE_CONFIGW struct] the result of the query
|
676
|
+
def query_config(service)
|
677
|
+
config = nil
|
678
|
+
size_required = nil
|
679
|
+
# Fetch the bytes of memory required to be allocated
|
680
|
+
# for QueryServiceConfigW to return succesfully. This
|
681
|
+
# is done by sending NULL and 0 for the pointer and size
|
682
|
+
# respectively, letting the command fail, then reading the
|
683
|
+
# value of pcbBytesNeeded
|
684
|
+
FFI::MemoryPointer.new(:lpword) do |bytes_pointer|
|
685
|
+
# return value will be false from this call, since it's designed
|
686
|
+
# to fail. Just ignore it
|
687
|
+
QueryServiceConfigW(service, FFI::Pointer::NULL, 0, bytes_pointer)
|
688
|
+
size_required = bytes_pointer.read_dword
|
689
|
+
FFI::MemoryPointer.new(size_required) do |ssp_ptr|
|
690
|
+
config = QUERY_SERVICE_CONFIGW.new(ssp_ptr)
|
691
|
+
success = QueryServiceConfigW(
|
692
|
+
service,
|
693
|
+
ssp_ptr,
|
694
|
+
size_required,
|
695
|
+
bytes_pointer
|
696
|
+
)
|
697
|
+
if success == FFI::WIN32_FALSE
|
698
|
+
raise Puppet::Util::Windows::Error.new(_("Service query failed"))
|
699
|
+
end
|
700
|
+
end
|
701
|
+
end
|
702
|
+
config
|
703
|
+
end
|
704
|
+
private :query_config
|
705
|
+
|
706
|
+
# @api private
|
707
|
+
# Sends a service control signal to a service
|
708
|
+
#
|
709
|
+
# @param [:handle] service handle to the service
|
710
|
+
# @param [Integer] signal the service control signal to send
|
711
|
+
def send_service_control_signal(service, signal)
|
712
|
+
FFI::MemoryPointer.new(SERVICE_STATUS.size) do |status_ptr|
|
713
|
+
status = SERVICE_STATUS.new(status_ptr)
|
714
|
+
if ControlService(service, signal, status) == FFI::WIN32_FALSE
|
715
|
+
raise Puppet::Util::Windows::Error, _("Failed to send the %{control_signal} signal to the service. Its current state is %{current_state}. Failed with") % { control_signal: SERVICE_CONTROL_SIGNALS[signal], current_state: SERVICE_STATES[status[:dwCurrentState]] }
|
716
|
+
end
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
# @api private
|
721
|
+
# Waits for a service to transition from one state to
|
722
|
+
# another state.
|
723
|
+
#
|
724
|
+
# @param [:handle] service handle to the service to wait on
|
725
|
+
# @param [Integer] initial_state the state that the service is transitioning from.
|
726
|
+
# @param [Integer] final_state the state that the service is transitioning to
|
727
|
+
def wait_on_state_transition(service, initial_state, final_state)
|
728
|
+
# Get the pending state for this transition. Note that SERVICE_RUNNING
|
729
|
+
# has two possible pending states, which is why we need this logic.
|
730
|
+
if final_state != SERVICE_RUNNING
|
731
|
+
pending_state = FINAL_STATES.key(final_state)
|
732
|
+
elsif initial_state == SERVICE_STOPPED
|
733
|
+
# SERVICE_STOPPED => SERVICE_RUNNING
|
734
|
+
pending_state = SERVICE_START_PENDING
|
735
|
+
else
|
736
|
+
# SERVICE_PAUSED => SERVICE_RUNNING
|
737
|
+
pending_state = SERVICE_CONTINUE_PENDING
|
738
|
+
end
|
739
|
+
|
740
|
+
# Wait for the transition to finish
|
741
|
+
state = nil
|
742
|
+
elapsed_time = 0
|
743
|
+
while elapsed_time <= DEFAULT_TIMEOUT
|
744
|
+
status = query_status(service)
|
745
|
+
state = status[:dwCurrentState]
|
746
|
+
|
747
|
+
return if state == final_state
|
748
|
+
if state == pending_state
|
749
|
+
Puppet.debug _("The service transitioned to the %{pending_state} state.") % { pending_state: SERVICE_STATES[pending_state] }
|
750
|
+
wait_on_pending_state(service, pending_state)
|
751
|
+
return
|
752
|
+
end
|
753
|
+
|
754
|
+
sleep(1)
|
755
|
+
elapsed_time += 1
|
756
|
+
end
|
757
|
+
|
758
|
+
# Timed out while waiting for the transition to finish. Raise an error
|
759
|
+
raise Puppet::Error, _("Timed out while waiting for the service to transition from %{initial_state} to %{final_state} OR from %{initial_state} to %{pending_state} to %{final_state}. The service's current state is %{current_state}.") % { initial_state: SERVICE_STATES[initial_state], final_state: SERVICE_STATES[final_state], pending_state: SERVICE_STATES[pending_state], current_state: SERVICE_STATES[state] }
|
760
|
+
end
|
761
|
+
private :wait_on_state_transition
|
762
|
+
|
763
|
+
# @api private
|
764
|
+
# Waits for a service to finish transitioning from
|
765
|
+
# a pending state. The service must be in the pending state
|
766
|
+
# before invoking this routine.
|
767
|
+
#
|
768
|
+
# @param [:handle] service handle to the service to wait on
|
769
|
+
# @param [Integer] pending_state the pending state
|
770
|
+
def wait_on_pending_state(service, pending_state)
|
771
|
+
final_state = FINAL_STATES[pending_state]
|
772
|
+
|
773
|
+
Puppet.debug _("Waiting for the pending transition to the %{final_state} state to finish.") % { final_state: SERVICE_STATES[final_state] }
|
774
|
+
|
775
|
+
elapsed_time = 0
|
776
|
+
last_checkpoint = -1
|
777
|
+
loop do
|
778
|
+
status = query_status(service)
|
779
|
+
state = status[:dwCurrentState]
|
780
|
+
|
781
|
+
# Check if our service has finished transitioning to
|
782
|
+
# the final_state OR if an unexpected transition
|
783
|
+
# has occurred
|
784
|
+
return if state == final_state
|
785
|
+
unless state == pending_state
|
786
|
+
raise Puppet::Error, _("Unexpected transition to the %{current_state} state while waiting for the pending transition from %{pending_state} to %{final_state} to finish.") % { current_state: SERVICE_STATES[state], pending_state: SERVICE_STATES[pending_state], final_state: SERVICE_STATES[final_state] }
|
787
|
+
end
|
788
|
+
|
789
|
+
# Check if any progress has been made since our last sleep
|
790
|
+
# using the dwCheckPoint. If no progress has been made then
|
791
|
+
# check if we've timed out, and raise an error if so
|
792
|
+
if status[:dwCheckPoint] > last_checkpoint
|
793
|
+
elapsed_time = 0
|
794
|
+
last_checkpoint = status[:dwCheckPoint]
|
795
|
+
else
|
796
|
+
wait_hint = milliseconds_to_seconds(status[:dwWaitHint])
|
797
|
+
timeout = wait_hint < DEFAULT_TIMEOUT ? DEFAULT_TIMEOUT : wait_hint
|
798
|
+
|
799
|
+
if elapsed_time >= timeout
|
800
|
+
raise Puppet::Error, _("Timed out while waiting for the pending transition from %{pending_state} to %{final_state} to finish. The current state is %{current_state}.") % { pending_state: SERVICE_STATES[pending_state], final_state: SERVICE_STATES[final_state], current_state: SERVICE_STATES[state] }
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
804
|
+
# Wait a bit before rechecking the service's state
|
805
|
+
wait_time = wait_hint_to_wait_time(status[:dwWaitHint])
|
806
|
+
sleep(wait_time)
|
807
|
+
elapsed_time += wait_time
|
808
|
+
end
|
809
|
+
end
|
810
|
+
private :wait_on_pending_state
|
811
|
+
|
812
|
+
# @api private
|
813
|
+
#
|
814
|
+
# create a usable wait time to wait between querying the service.
|
815
|
+
#
|
816
|
+
# @param [Integer] wait_hint the wait hint of a service in milliseconds
|
817
|
+
# @return [Integer] the time to wait in seconds between querying the service
|
818
|
+
def wait_hint_to_wait_time(wait_hint)
|
819
|
+
# Wait 1/10th the wait_hint, but no less than 1 and
|
820
|
+
# no more than 10 seconds
|
821
|
+
wait_time = milliseconds_to_seconds(wait_hint) / 10;
|
822
|
+
wait_time = 1 if wait_time < 1
|
823
|
+
wait_time = 10 if wait_time > 10
|
824
|
+
wait_time
|
825
|
+
end
|
826
|
+
private :wait_hint_to_wait_time
|
827
|
+
|
828
|
+
# @api private
|
829
|
+
#
|
830
|
+
# process the wait hint listed by a service to something
|
831
|
+
# usable by ruby sleep
|
832
|
+
#
|
833
|
+
# @param [Integer] wait_hint the wait hint of a service in milliseconds
|
834
|
+
# @return [Integer] wait_hint in seconds
|
835
|
+
def milliseconds_to_seconds(wait_hint)
|
836
|
+
wait_hint / 1000;
|
837
|
+
end
|
838
|
+
private :milliseconds_to_seconds
|
839
|
+
end
|
840
|
+
|
841
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-openscmanagerw
|
842
|
+
# SC_HANDLE OpenSCManagerW(
|
843
|
+
# LPCWSTR lpMachineName,
|
844
|
+
# LPCWSTR lpDatabaseName,
|
845
|
+
# DWORD dwDesiredAccess
|
846
|
+
# );
|
847
|
+
ffi_lib :advapi32
|
848
|
+
attach_function_private :OpenSCManagerW,
|
849
|
+
[:lpcwstr, :lpcwstr, :dword], :handle
|
850
|
+
|
851
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-openservicew
|
852
|
+
# SC_HANDLE OpenServiceW(
|
853
|
+
# SC_HANDLE hSCManager,
|
854
|
+
# LPCWSTR lpServiceName,
|
855
|
+
# DWORD dwDesiredAccess
|
856
|
+
# );
|
857
|
+
ffi_lib :advapi32
|
858
|
+
attach_function_private :OpenServiceW,
|
859
|
+
[:handle, :lpcwstr, :dword], :handle
|
860
|
+
|
861
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-closeservicehandle
|
862
|
+
# BOOL CloseServiceHandle(
|
863
|
+
# SC_HANDLE hSCObject
|
864
|
+
# );
|
865
|
+
ffi_lib :advapi32
|
866
|
+
attach_function_private :CloseServiceHandle,
|
867
|
+
[:handle], :win32_bool
|
868
|
+
|
869
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-queryservicestatusex
|
870
|
+
# BOOL QueryServiceStatusEx(
|
871
|
+
# SC_HANDLE hService,
|
872
|
+
# SC_STATUS_TYPE InfoLevel,
|
873
|
+
# LPBYTE lpBuffer,
|
874
|
+
# DWORD cbBufSize,
|
875
|
+
# LPDWORD pcbBytesNeeded
|
876
|
+
# );
|
877
|
+
SC_STATUS_TYPE = enum(
|
878
|
+
:SC_STATUS_PROCESS_INFO, 0,
|
879
|
+
)
|
880
|
+
ffi_lib :advapi32
|
881
|
+
attach_function_private :QueryServiceStatusEx,
|
882
|
+
[:handle, SC_STATUS_TYPE, :lpbyte, :dword, :lpdword], :win32_bool
|
883
|
+
|
884
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-queryserviceconfigw
|
885
|
+
# BOOL QueryServiceConfigW(
|
886
|
+
# SC_HANDLE hService,
|
887
|
+
# LPQUERY_SERVICE_CONFIGW lpServiceConfig,
|
888
|
+
# DWORD cbBufSize,
|
889
|
+
# LPDWORD pcbBytesNeeded
|
890
|
+
# );
|
891
|
+
ffi_lib :advapi32
|
892
|
+
attach_function_private :QueryServiceConfigW,
|
893
|
+
[:handle, :lpbyte, :dword, :lpdword], :win32_bool
|
894
|
+
|
895
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-startservicew
|
896
|
+
# BOOL StartServiceW(
|
897
|
+
# SC_HANDLE hService,
|
898
|
+
# DWORD dwNumServiceArgs,
|
899
|
+
# LPCWSTR *lpServiceArgVectors
|
900
|
+
# );
|
901
|
+
ffi_lib :advapi32
|
902
|
+
attach_function_private :StartServiceW,
|
903
|
+
[:handle, :dword, :pointer], :win32_bool
|
904
|
+
|
905
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-controlservice
|
906
|
+
# BOOL ControlService(
|
907
|
+
# SC_HANDLE hService,
|
908
|
+
# DWORD dwControl,
|
909
|
+
# LPSERVICE_STATUS lpServiceStatus
|
910
|
+
# );
|
911
|
+
ffi_lib :advapi32
|
912
|
+
attach_function_private :ControlService,
|
913
|
+
[:handle, :dword, :pointer], :win32_bool
|
914
|
+
|
915
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-changeserviceconfigw
|
916
|
+
# BOOL ChangeServiceConfigW(
|
917
|
+
# SC_HANDLE hService,
|
918
|
+
# DWORD dwServiceType,
|
919
|
+
# DWORD dwStartType,
|
920
|
+
# DWORD dwErrorControl,
|
921
|
+
# LPCWSTR lpBinaryPathName,
|
922
|
+
# LPCWSTR lpLoadOrderGroup,
|
923
|
+
# LPDWORD lpdwTagId,
|
924
|
+
# LPCWSTR lpDependencies,
|
925
|
+
# LPCWSTR lpServiceStartName,
|
926
|
+
# LPCWSTR lpPassword,
|
927
|
+
# LPCWSTR lpDisplayName
|
928
|
+
# );
|
929
|
+
ffi_lib :advapi32
|
930
|
+
attach_function_private :ChangeServiceConfigW,
|
931
|
+
[
|
932
|
+
:handle,
|
933
|
+
:dword,
|
934
|
+
:dword,
|
935
|
+
:dword,
|
936
|
+
:lpcwstr,
|
937
|
+
:lpcwstr,
|
938
|
+
:lpdword,
|
939
|
+
:lpcwstr,
|
940
|
+
:lpcwstr,
|
941
|
+
:lpcwstr,
|
942
|
+
:lpcwstr
|
943
|
+
], :win32_bool
|
944
|
+
|
945
|
+
|
946
|
+
# https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-enumservicesstatusexw
|
947
|
+
# BOOL EnumServicesStatusExW(
|
948
|
+
# SC_HANDLE hSCManager,
|
949
|
+
# SC_ENUM_TYPE InfoLevel,
|
950
|
+
# DWORD dwServiceType,
|
951
|
+
# DWORD dwServiceState,
|
952
|
+
# LPBYTE lpServices,
|
953
|
+
# DWORD cbBufSize,
|
954
|
+
# LPDWORD pcbBytesNeeded,
|
955
|
+
# LPDWORD lpServicesReturned,
|
956
|
+
# LPDWORD lpResumeHandle,
|
957
|
+
# LPCWSTR pszGroupName
|
958
|
+
# );
|
959
|
+
SC_ENUM_TYPE = enum(
|
960
|
+
:SC_ENUM_PROCESS_INFO, 0,
|
961
|
+
)
|
962
|
+
ffi_lib :advapi32
|
963
|
+
attach_function_private :EnumServicesStatusExW,
|
964
|
+
[
|
965
|
+
:handle,
|
966
|
+
SC_ENUM_TYPE,
|
967
|
+
:dword,
|
968
|
+
:dword,
|
969
|
+
:lpbyte,
|
970
|
+
:dword,
|
971
|
+
:lpdword,
|
972
|
+
:lpdword,
|
973
|
+
:lpdword,
|
974
|
+
:lpcwstr
|
975
|
+
], :win32_bool
|
976
|
+
end
|
977
|
+
end
|