puppet 5.5.6 → 5.5.7

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.

Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Gemfile.lock +12 -12
  4. data/Rakefile +9 -0
  5. data/lib/puppet/application.rb +5 -0
  6. data/lib/puppet/application/apply.rb +1 -0
  7. data/lib/puppet/application/master.rb +9 -7
  8. data/lib/puppet/application/script.rb +1 -1
  9. data/lib/puppet/defaults.rb +51 -31
  10. data/lib/puppet/etc.rb +20 -0
  11. data/lib/puppet/file_serving/fileset.rb +1 -1
  12. data/lib/puppet/functions.rb +123 -0
  13. data/lib/puppet/functions/new.rb +37 -53
  14. data/lib/puppet/functions/warning.rb +1 -1
  15. data/lib/puppet/loaders.rb +1 -0
  16. data/lib/puppet/parser/functions.rb +3 -1
  17. data/lib/puppet/parser/functions/sprintf.rb +12 -1
  18. data/lib/puppet/pops/evaluator/runtime3_converter.rb +16 -0
  19. data/lib/puppet/pops/evaluator/runtime3_support.rb +3 -4
  20. data/lib/puppet/pops/issues.rb +8 -0
  21. data/lib/puppet/pops/loader/loader.rb +2 -2
  22. data/lib/puppet/pops/loader/loader_paths.rb +3 -1
  23. data/lib/puppet/pops/loader/module_loaders.rb +1 -1
  24. data/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb +62 -0
  25. data/lib/puppet/pops/loaders.rb +5 -21
  26. data/lib/puppet/pops/parser/heredoc_support.rb +1 -2
  27. data/lib/puppet/pops/parser/lexer2.rb +1 -1
  28. data/lib/puppet/pops/validation/checker4_0.rb +31 -6
  29. data/lib/puppet/pops/validation/validator_factory_4_0.rb +1 -0
  30. data/lib/puppet/property/keyvalue.rb +70 -8
  31. data/lib/puppet/provider/aix_object.rb +483 -0
  32. data/lib/puppet/provider/exec.rb +54 -57
  33. data/lib/puppet/provider/group/aix.rb +40 -115
  34. data/lib/puppet/provider/group/pw.rb +4 -8
  35. data/lib/puppet/provider/group/windows_adsi.rb +7 -4
  36. data/lib/puppet/provider/nameservice.rb +1 -25
  37. data/lib/puppet/provider/nameservice/directoryservice.rb +5 -3
  38. data/lib/puppet/provider/package/portage.rb +2 -2
  39. data/lib/puppet/provider/package/windows.rb +2 -2
  40. data/lib/puppet/provider/package/windows/exe_package.rb +3 -10
  41. data/lib/puppet/provider/package/zypper.rb +1 -1
  42. data/lib/puppet/provider/service/launchd.rb +19 -3
  43. data/lib/puppet/provider/service/windows.rb +49 -40
  44. data/lib/puppet/provider/user/aix.rb +180 -246
  45. data/lib/puppet/provider/user/windows_adsi.rb +9 -1
  46. data/lib/puppet/resource/catalog.rb +1 -5
  47. data/lib/puppet/type/augeas.rb +1 -1
  48. data/lib/puppet/type/exec.rb +16 -14
  49. data/lib/puppet/type/file.rb +2 -2
  50. data/lib/puppet/type/file/source.rb +9 -5
  51. data/lib/puppet/type/group.rb +65 -23
  52. data/lib/puppet/type/k5login.rb +2 -2
  53. data/lib/puppet/type/notify.rb +1 -1
  54. data/lib/puppet/type/package.rb +3 -6
  55. data/lib/puppet/type/resources.rb +12 -2
  56. data/lib/puppet/type/schedule.rb +8 -1
  57. data/lib/puppet/type/selboolean.rb +2 -2
  58. data/lib/puppet/type/selmodule.rb +3 -4
  59. data/lib/puppet/type/service.rb +2 -5
  60. data/lib/puppet/type/tidy.rb +1 -1
  61. data/lib/puppet/type/user.rb +15 -20
  62. data/lib/puppet/type/yumrepo.rb +2 -2
  63. data/lib/puppet/type/zone.rb +2 -2
  64. data/lib/puppet/util.rb +7 -3
  65. data/lib/puppet/util/execution.rb +15 -1
  66. data/lib/puppet/util/posix.rb +15 -0
  67. data/lib/puppet/util/storage.rb +12 -0
  68. data/lib/puppet/util/windows.rb +4 -2
  69. data/lib/puppet/util/windows/adsi.rb +235 -205
  70. data/lib/puppet/util/windows/process.rb +23 -3
  71. data/lib/puppet/util/windows/security.rb +14 -0
  72. data/lib/puppet/util/windows/service.rb +977 -0
  73. data/lib/puppet/util/windows/user.rb +3 -5
  74. data/lib/puppet/version.rb +1 -1
  75. data/locales/ja/puppet.po +705 -374
  76. data/locales/puppet.pot +485 -261
  77. data/man/man5/puppet.conf.5 +36 -15
  78. data/man/man8/puppet-agent.8 +1 -1
  79. data/man/man8/puppet-apply.8 +1 -1
  80. data/man/man8/puppet-ca.8 +1 -1
  81. data/man/man8/puppet-catalog.8 +1 -1
  82. data/man/man8/puppet-cert.8 +1 -1
  83. data/man/man8/puppet-certificate.8 +1 -1
  84. data/man/man8/puppet-certificate_request.8 +1 -1
  85. data/man/man8/puppet-certificate_revocation_list.8 +1 -1
  86. data/man/man8/puppet-config.8 +1 -1
  87. data/man/man8/puppet-describe.8 +1 -1
  88. data/man/man8/puppet-device.8 +1 -1
  89. data/man/man8/puppet-doc.8 +1 -1
  90. data/man/man8/puppet-epp.8 +1 -1
  91. data/man/man8/puppet-facts.8 +1 -1
  92. data/man/man8/puppet-filebucket.8 +1 -1
  93. data/man/man8/puppet-generate.8 +1 -1
  94. data/man/man8/puppet-help.8 +1 -1
  95. data/man/man8/puppet-key.8 +1 -1
  96. data/man/man8/puppet-lookup.8 +1 -1
  97. data/man/man8/puppet-man.8 +1 -1
  98. data/man/man8/puppet-master.8 +1 -1
  99. data/man/man8/puppet-module.8 +1 -1
  100. data/man/man8/puppet-node.8 +1 -1
  101. data/man/man8/puppet-parser.8 +1 -1
  102. data/man/man8/puppet-plugin.8 +1 -1
  103. data/man/man8/puppet-report.8 +1 -1
  104. data/man/man8/puppet-resource.8 +1 -1
  105. data/man/man8/puppet-script.8 +1 -1
  106. data/man/man8/puppet-status.8 +1 -1
  107. data/man/man8/puppet.8 +2 -2
  108. data/spec/fixtures/unit/provider/aix_object/aix_colon_list_real_world_input.out +1 -0
  109. data/spec/fixtures/unit/provider/aix_object/aix_colon_list_real_world_output.out +1 -0
  110. data/spec/fixtures/unit/provider/user/aix/aix_passwd_file.out +32 -0
  111. data/spec/integration/parser/collection_spec.rb +4 -8
  112. data/spec/integration/provider/service/windows_spec.rb +5 -5
  113. data/spec/integration/type/file_spec.rb +6 -6
  114. data/spec/integration/util/windows/adsi_spec.rb +6 -5
  115. data/spec/integration/util/windows/security_spec.rb +10 -7
  116. data/spec/integration/util/windows/user_spec.rb +37 -17
  117. data/spec/spec_helper.rb +0 -1
  118. data/spec/unit/application/apply_spec.rb +41 -2
  119. data/spec/unit/application/master_spec.rb +7 -0
  120. data/spec/unit/application_spec.rb +21 -3
  121. data/spec/unit/defaults_spec.rb +20 -0
  122. data/spec/unit/etc_spec.rb +25 -0
  123. data/spec/unit/file_serving/fileset_spec.rb +11 -11
  124. data/spec/unit/gettext/config_spec.rb +1 -1
  125. data/spec/unit/pops/evaluator/evaluating_parser_spec.rb +6 -6
  126. data/spec/unit/pops/loaders/loaders_spec.rb +40 -7
  127. data/spec/unit/pops/parser/parse_heredoc_spec.rb +16 -0
  128. data/spec/unit/pops/validator/validator_spec.rb +129 -10
  129. data/spec/unit/property/keyvalue_spec.rb +97 -6
  130. data/spec/unit/provider/aix_object_spec.rb +805 -0
  131. data/spec/unit/provider/group/aix_spec.rb +57 -0
  132. data/spec/unit/provider/group/pw_spec.rb +0 -6
  133. data/spec/unit/provider/group/windows_adsi_spec.rb +34 -35
  134. data/spec/unit/provider/nameservice/directoryservice_spec.rb +2 -2
  135. data/spec/unit/provider/package/windows/exe_package_spec.rb +3 -3
  136. data/spec/unit/provider/package/windows_spec.rb +4 -4
  137. data/spec/unit/provider/service/launchd_spec.rb +19 -0
  138. data/spec/unit/provider/service/windows_spec.rb +71 -78
  139. data/spec/unit/provider/user/aix_spec.rb +162 -116
  140. data/spec/unit/provider/user/windows_adsi_spec.rb +4 -4
  141. data/spec/unit/resource/catalog_spec.rb +2 -2
  142. data/spec/unit/ssl/certificate_authority_spec.rb +0 -1
  143. data/spec/unit/type/group_spec.rb +111 -13
  144. data/spec/unit/type/resources_spec.rb +18 -0
  145. data/spec/unit/util/execution_spec.rb +77 -0
  146. data/spec/unit/util/posix_spec.rb +28 -0
  147. data/spec/unit/util/storage_spec.rb +107 -0
  148. data/spec/unit/util/windows/adsi_spec.rb +108 -13
  149. data/spec/unit/util/windows/service_spec.rb +669 -0
  150. metadata +17 -5
  151. data/lib/puppet/provider/aixobject.rb +0 -392
  152. 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
- Process.create( :command_line => command, :startup_info => {:stdin => stdin, :stdout => stdout, :stderr => stderr}, :close_handles => false )
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, 0) == WAIT_TIMEOUT
18
- sleep(1)
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