mixlib-shellout 1.0.0-x86-mingw32 → 1.1.0-x86-mingw32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +8 -0
- data/lib/mixlib/shellout.rb +8 -0
- data/lib/mixlib/shellout/exceptions.rb +0 -1
- data/lib/mixlib/shellout/unix.rb +27 -8
- data/lib/mixlib/shellout/version.rb +1 -1
- data/lib/mixlib/shellout/windows.rb +110 -398
- data/lib/mixlib/shellout/windows/core_ext.rb +385 -0
- metadata +37 -43
data/README.md
CHANGED
@@ -28,6 +28,14 @@ Run a command as the `www` user with no extra ENV settings from `/tmp`
|
|
28
28
|
cmd = Mixlib::ShellOut.new("apachectl", "start", :user => 'www', :env => nil, :cwd => '/tmp')
|
29
29
|
cmd.run_command # etc.
|
30
30
|
|
31
|
+
## STDIN Example
|
32
|
+
Invoke crontab to edit user cron:
|
33
|
+
|
34
|
+
# :input only supports simple strings
|
35
|
+
crontab_lines = [ "* * * * * /bin/true", "* * * * * touch /tmp/here" ]
|
36
|
+
crontab = Mixlib::ShellOut.new("crontab -l -u #{@new_resource.user}", :input => crontab_lines.join("\n"))
|
37
|
+
crontab.run_command
|
38
|
+
|
31
39
|
## Platform Support
|
32
40
|
Mixlib::ShellOut does a standard fork/exec on Unix, and uses the Win32
|
33
41
|
API on Windows. There is not currently support for JRuby.
|
data/lib/mixlib/shellout.rb
CHANGED
@@ -55,6 +55,11 @@ module Mixlib
|
|
55
55
|
# the command's output will be echoed to STDOUT.
|
56
56
|
attr_accessor :live_stream
|
57
57
|
|
58
|
+
# ShellOut will push data from :input down the stdin of the subprocss.
|
59
|
+
# Normally set via options passed to new.
|
60
|
+
# Default: nil
|
61
|
+
attr_accessor :input
|
62
|
+
|
58
63
|
# If a logger is set, ShellOut will log a message before it executes the
|
59
64
|
# command.
|
60
65
|
attr_accessor :logger
|
@@ -140,6 +145,7 @@ module Mixlib
|
|
140
145
|
def initialize(*command_args)
|
141
146
|
@stdout, @stderr = '', ''
|
142
147
|
@live_stream = nil
|
148
|
+
@input = nil
|
143
149
|
@log_level = :debug
|
144
150
|
@log_tag = nil
|
145
151
|
@environment = DEFAULT_ENVIRONMENT
|
@@ -267,6 +273,8 @@ module Mixlib
|
|
267
273
|
self.valid_exit_codes = Array(setting)
|
268
274
|
when 'live_stream'
|
269
275
|
self.live_stream = setting
|
276
|
+
when 'input'
|
277
|
+
self.input = setting
|
270
278
|
when 'logger'
|
271
279
|
self.logger = setting
|
272
280
|
when 'log_level'
|
data/lib/mixlib/shellout/unix.rb
CHANGED
@@ -40,6 +40,8 @@ module Mixlib
|
|
40
40
|
@result = nil
|
41
41
|
@execution_time = 0
|
42
42
|
|
43
|
+
write_to_child_stdin
|
44
|
+
|
43
45
|
# Ruby 1.8.7 and 1.8.6 from mid 2009 try to allocate objects during GC
|
44
46
|
# when calling IO.select and IO#read. Some OS Vendors are not interested
|
45
47
|
# in updating their ruby packages (Apple, *cough*) and we *have to*
|
@@ -114,10 +116,14 @@ module Mixlib
|
|
114
116
|
end
|
115
117
|
|
116
118
|
def initialize_ipc
|
117
|
-
@stdout_pipe, @stderr_pipe, @process_status_pipe = IO.pipe, IO.pipe, IO.pipe
|
119
|
+
@stdin_pipe, @stdout_pipe, @stderr_pipe, @process_status_pipe = IO.pipe, IO.pipe, IO.pipe, IO.pipe
|
118
120
|
@process_status_pipe.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
119
121
|
end
|
120
122
|
|
123
|
+
def child_stdin
|
124
|
+
@stdin_pipe[1]
|
125
|
+
end
|
126
|
+
|
121
127
|
def child_stdout
|
122
128
|
@stdout_pipe[0]
|
123
129
|
end
|
@@ -131,23 +137,25 @@ module Mixlib
|
|
131
137
|
end
|
132
138
|
|
133
139
|
def close_all_pipes
|
140
|
+
child_stdin.close unless child_stdin.closed?
|
134
141
|
child_stdout.close unless child_stdout.closed?
|
135
142
|
child_stderr.close unless child_stderr.closed?
|
136
143
|
child_process_status.close unless child_process_status.closed?
|
137
144
|
end
|
138
145
|
|
139
|
-
#
|
140
|
-
# reader side of the error marshaling side channel.
|
141
|
-
#
|
146
|
+
# Replace stdout, and stderr with pipes to the parent, and close the
|
147
|
+
# reader side of the error marshaling side channel.
|
148
|
+
#
|
149
|
+
# If there is no input, close STDIN so when we exec,
|
150
|
+
# the new program will know it's never getting input ever.
|
142
151
|
def configure_subprocess_file_descriptors
|
143
152
|
process_status_pipe.first.close
|
144
153
|
|
145
154
|
# HACK: for some reason, just STDIN.close isn't good enough when running
|
146
155
|
# under ruby 1.9.2, so make it good enough:
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
stdin_reader.close
|
156
|
+
stdin_pipe.last.close
|
157
|
+
STDIN.reopen stdin_pipe.first
|
158
|
+
stdin_pipe.first.close unless input
|
151
159
|
|
152
160
|
stdout_pipe.first.close
|
153
161
|
STDOUT.reopen stdout_pipe.last
|
@@ -158,14 +166,18 @@ module Mixlib
|
|
158
166
|
stderr_pipe.last.close
|
159
167
|
|
160
168
|
STDOUT.sync = STDERR.sync = true
|
169
|
+
STDIN.sync = true if input
|
161
170
|
end
|
162
171
|
|
163
172
|
def configure_parent_process_file_descriptors
|
164
173
|
# Close the sides of the pipes we don't care about
|
174
|
+
stdin_pipe.first.close
|
175
|
+
stdin_pipe.last.close unless input
|
165
176
|
stdout_pipe.last.close
|
166
177
|
stderr_pipe.last.close
|
167
178
|
process_status_pipe.last.close
|
168
179
|
# Get output as it happens rather than buffered
|
180
|
+
child_stdin.sync = true if input
|
169
181
|
child_stdout.sync = true
|
170
182
|
child_stderr.sync = true
|
171
183
|
|
@@ -178,6 +190,13 @@ module Mixlib
|
|
178
190
|
@open_pipes ||= [child_stdout, child_stderr]
|
179
191
|
end
|
180
192
|
|
193
|
+
# Keep this unbuffered for now
|
194
|
+
def write_to_child_stdin
|
195
|
+
return unless input
|
196
|
+
child_stdin << input
|
197
|
+
child_stdin.close # Kick things off
|
198
|
+
end
|
199
|
+
|
181
200
|
def read_stdout_to_buffer
|
182
201
|
while chunk = child_stdout.read_nonblock(READ_SIZE)
|
183
202
|
@stdout << chunk
|
@@ -1,7 +1,8 @@
|
|
1
1
|
#--
|
2
2
|
# Author:: Daniel DeLeo (<dan@opscode.com>)
|
3
3
|
# Author:: John Keiser (<jkeiser@opscode.com>)
|
4
|
-
#
|
4
|
+
# Author:: Ho-Sheng Hsiao (<hosh@opscode.com>)
|
5
|
+
# Copyright:: Copyright (c) 2011, 2012 Opscode, Inc.
|
5
6
|
# License:: Apache License, Version 2.0
|
6
7
|
#
|
7
8
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -22,6 +23,8 @@ require 'windows/handle'
|
|
22
23
|
require 'windows/process'
|
23
24
|
require 'windows/synchronize'
|
24
25
|
|
26
|
+
require 'mixlib/shellout/windows/core_ext'
|
27
|
+
|
25
28
|
module Mixlib
|
26
29
|
class ShellOut
|
27
30
|
module Windows
|
@@ -50,7 +53,7 @@ module Mixlib
|
|
50
53
|
#
|
51
54
|
# Set cwd, environment, appname, etc.
|
52
55
|
#
|
53
|
-
app_name, command_line = command_to_run
|
56
|
+
app_name, command_line = command_to_run(self.command)
|
54
57
|
create_process_args = {
|
55
58
|
:app_name => app_name,
|
56
59
|
:command_line => command_line,
|
@@ -69,6 +72,11 @@ module Mixlib
|
|
69
72
|
#
|
70
73
|
process = Process.create(create_process_args)
|
71
74
|
begin
|
75
|
+
# Start pushing data into input
|
76
|
+
stdin_write << input if input
|
77
|
+
|
78
|
+
# Close pipe to kick things off
|
79
|
+
stdin_write.close
|
72
80
|
|
73
81
|
#
|
74
82
|
# Wait for the process to finish, consuming output as we go
|
@@ -90,7 +98,7 @@ module Mixlib
|
|
90
98
|
when WAIT_TIMEOUT
|
91
99
|
# Kill the process
|
92
100
|
if (Time.now - start_wait) > timeout
|
93
|
-
raise Mixlib::ShellOut::
|
101
|
+
raise Mixlib::ShellOut::CommandTimeout, "command timed out:\n#{format_for_exception}"
|
94
102
|
end
|
95
103
|
|
96
104
|
consume_output(open_streams, stdout_read, stderr_read)
|
@@ -154,33 +162,52 @@ module Mixlib
|
|
154
162
|
return true
|
155
163
|
end
|
156
164
|
|
157
|
-
IS_BATCH_FILE = /\.bat
|
165
|
+
IS_BATCH_FILE = /\.bat"?$|\.cmd"?$/i
|
158
166
|
|
159
|
-
def command_to_run
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
else
|
164
|
-
# Otherwise check everything up to the first space
|
165
|
-
candidate = command[0,command.index(/\s/) || command.length].strip
|
166
|
-
end
|
167
|
+
def command_to_run(command)
|
168
|
+
return _run_under_cmd(command) if Utils.should_run_under_cmd?(command)
|
169
|
+
|
170
|
+
candidate = candidate_executable_for_command(command)
|
167
171
|
|
168
172
|
# Don't do searching for empty commands. Let it fail when it runs.
|
169
|
-
if candidate.length == 0
|
170
|
-
return [ nil, command ]
|
171
|
-
end
|
173
|
+
return [ nil, command ] if candidate.length == 0
|
172
174
|
|
173
175
|
# Check if the exe exists directly. Otherwise, search PATH.
|
174
|
-
exe =
|
175
|
-
if exe.nil? && exe !~ /[\\\/]/
|
176
|
-
exe = which(command[0,command.index(/\s/) || command.length])
|
177
|
-
end
|
176
|
+
exe = Utils.find_executable(candidate)
|
177
|
+
exe = Utils.which(unquoted_executable_path(command)) if exe.nil? && exe !~ /[\\\/]/
|
178
178
|
|
179
|
+
# Batch files MUST use cmd; and if we couldn't find the command we're looking for,
|
180
|
+
# we assume it must be a cmd builtin.
|
179
181
|
if exe.nil? || exe =~ IS_BATCH_FILE
|
180
|
-
|
181
|
-
|
182
|
+
_run_under_cmd(command)
|
183
|
+
else
|
184
|
+
_run_directly(command, exe)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
# cmd does not parse multiple quotes well unless the whole thing is wrapped up in quotes.
|
190
|
+
# https://github.com/opscode/mixlib-shellout/pull/2#issuecomment-4837859
|
191
|
+
# http://ss64.com/nt/syntax-esc.html
|
192
|
+
def _run_under_cmd(command)
|
193
|
+
[ ENV['COMSPEC'], "cmd /c \"#{command}\"" ]
|
194
|
+
end
|
195
|
+
|
196
|
+
def _run_directly(command, exe)
|
197
|
+
[ exe, command ]
|
198
|
+
end
|
199
|
+
|
200
|
+
def unquoted_executable_path(command)
|
201
|
+
command[0,command.index(/\s/) || command.length]
|
202
|
+
end
|
203
|
+
|
204
|
+
def candidate_executable_for_command(command)
|
205
|
+
if command =~ /^\s*"(.*?)"/
|
206
|
+
# If we have quotes, do an exact match
|
207
|
+
$1
|
182
208
|
else
|
183
|
-
|
209
|
+
# Otherwise check everything up to the first space
|
210
|
+
unquoted_executable_path(command).strip
|
184
211
|
end
|
185
212
|
end
|
186
213
|
|
@@ -200,389 +227,74 @@ module Mixlib
|
|
200
227
|
result
|
201
228
|
end
|
202
229
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
230
|
+
module Utils
|
231
|
+
# api: semi-private
|
232
|
+
# If there are special characters parsable by cmd.exe (such as file redirection), then
|
233
|
+
# this method should return true.
|
234
|
+
#
|
235
|
+
# This parser is based on
|
236
|
+
# https://github.com/ruby/ruby/blob/9073db5cb1d3173aff62be5b48d00f0fb2890991/win32/win32.c#L1437
|
237
|
+
def self.should_run_under_cmd?(command)
|
238
|
+
return true if command =~ /^@/
|
239
|
+
|
240
|
+
quote = nil
|
241
|
+
env = false
|
242
|
+
env_first_char = false
|
243
|
+
|
244
|
+
command.dup.each_char do |c|
|
245
|
+
case c
|
246
|
+
when "'", '"'
|
247
|
+
if (!quote)
|
248
|
+
quote = c
|
249
|
+
elsif quote == c
|
250
|
+
quote = nil
|
251
|
+
end
|
252
|
+
next
|
253
|
+
when '>', '<', '|', '&', "\n"
|
254
|
+
return true unless quote
|
255
|
+
when '%'
|
256
|
+
return true if env
|
257
|
+
env = env_first_char = true
|
258
|
+
next
|
259
|
+
else
|
260
|
+
next unless env
|
261
|
+
if env_first_char
|
262
|
+
env_first_char = false
|
263
|
+
env = false and next if c !~ /[A-Za-z_]/
|
264
|
+
end
|
265
|
+
env = false if c !~ /[A-Za-z1-9_]/
|
266
|
+
end
|
267
|
+
end
|
268
|
+
return false
|
220
269
|
end
|
221
|
-
return nil
|
222
|
-
end
|
223
|
-
end # class
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
#
|
228
|
-
# Override module Windows::Process.CreateProcess to fix bug when
|
229
|
-
# using both app_name and command_line
|
230
|
-
#
|
231
|
-
module Windows
|
232
|
-
module Process
|
233
|
-
API.new('CreateProcess', 'SPPPLLLPPP', 'B')
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
#
|
238
|
-
# Override Win32::Process.create to take a proper environment hash
|
239
|
-
# so that variables can contain semicolons
|
240
|
-
# (submitted patch to owner)
|
241
|
-
#
|
242
|
-
module Process
|
243
|
-
def create(args)
|
244
|
-
unless args.kind_of?(Hash)
|
245
|
-
raise TypeError, 'Expecting hash-style keyword arguments'
|
246
|
-
end
|
247
|
-
|
248
|
-
valid_keys = %w/
|
249
|
-
app_name command_line inherit creation_flags cwd environment
|
250
|
-
startup_info thread_inherit process_inherit close_handles with_logon
|
251
|
-
domain password
|
252
|
-
/
|
253
|
-
|
254
|
-
valid_si_keys = %/
|
255
|
-
startf_flags desktop title x y x_size y_size x_count_chars
|
256
|
-
y_count_chars fill_attribute sw_flags stdin stdout stderr
|
257
|
-
/
|
258
|
-
|
259
|
-
# Set default values
|
260
|
-
hash = {
|
261
|
-
'app_name' => nil,
|
262
|
-
'creation_flags' => 0,
|
263
|
-
'close_handles' => true
|
264
|
-
}
|
265
|
-
|
266
|
-
# Validate the keys, and convert symbols and case to lowercase strings.
|
267
|
-
args.each{ |key, val|
|
268
|
-
key = key.to_s.downcase
|
269
|
-
unless valid_keys.include?(key)
|
270
|
-
raise ArgumentError, "invalid key '#{key}'"
|
271
|
-
end
|
272
|
-
hash[key] = val
|
273
|
-
}
|
274
270
|
|
275
|
-
|
276
|
-
|
277
|
-
# If the startup_info key is present, validate its subkeys
|
278
|
-
if hash['startup_info']
|
279
|
-
hash['startup_info'].each{ |key, val|
|
280
|
-
key = key.to_s.downcase
|
281
|
-
unless valid_si_keys.include?(key)
|
282
|
-
raise ArgumentError, "invalid startup_info key '#{key}'"
|
283
|
-
end
|
284
|
-
si_hash[key] = val
|
285
|
-
}
|
286
|
-
end
|
287
|
-
|
288
|
-
# The +command_line+ key is mandatory unless the +app_name+ key
|
289
|
-
# is specified.
|
290
|
-
unless hash['command_line']
|
291
|
-
if hash['app_name']
|
292
|
-
hash['command_line'] = hash['app_name']
|
293
|
-
hash['app_name'] = nil
|
294
|
-
else
|
295
|
-
raise ArgumentError, 'command_line or app_name must be specified'
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
# The environment string should be passed as an array of A=B paths, or
|
300
|
-
# as a string of ';' separated paths.
|
301
|
-
if hash['environment']
|
302
|
-
env = hash['environment']
|
303
|
-
if !env.respond_to?(:join)
|
304
|
-
# Backwards compat for ; separated paths
|
305
|
-
env = hash['environment'].split(File::PATH_SEPARATOR)
|
306
|
-
end
|
307
|
-
# The argument format is a series of null-terminated strings, with an additional null terminator.
|
308
|
-
env = env.map { |e| e + "\0" }.join("") + "\0"
|
309
|
-
if hash['with_logon']
|
310
|
-
env = env.multi_to_wide(e)
|
311
|
-
end
|
312
|
-
env = [env].pack('p*').unpack('L').first
|
313
|
-
else
|
314
|
-
env = nil
|
315
|
-
end
|
316
|
-
|
317
|
-
startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
318
|
-
startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
|
319
|
-
procinfo = [0,0,0,0].pack('LLLL')
|
320
|
-
|
321
|
-
# Process SECURITY_ATTRIBUTE structure
|
322
|
-
process_security = 0
|
323
|
-
if hash['process_inherit']
|
324
|
-
process_security = [0,0,0].pack('LLL')
|
325
|
-
process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
|
326
|
-
process_security[8,4] = [1].pack('L') # TRUE
|
327
|
-
end
|
328
|
-
|
329
|
-
# Thread SECURITY_ATTRIBUTE structure
|
330
|
-
thread_security = 0
|
331
|
-
if hash['thread_inherit']
|
332
|
-
thread_security = [0,0,0].pack('LLL')
|
333
|
-
thread_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
|
334
|
-
thread_security[8,4] = [1].pack('L') # TRUE
|
335
|
-
end
|
336
|
-
|
337
|
-
# Automatically handle stdin, stdout and stderr as either IO objects
|
338
|
-
# or file descriptors. This won't work for StringIO, however.
|
339
|
-
['stdin', 'stdout', 'stderr'].each{ |io|
|
340
|
-
if si_hash[io]
|
341
|
-
if si_hash[io].respond_to?(:fileno)
|
342
|
-
handle = get_osfhandle(si_hash[io].fileno)
|
343
|
-
else
|
344
|
-
handle = get_osfhandle(si_hash[io])
|
271
|
+
def self.pathext
|
272
|
+
@pathext ||= ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') + [''] : ['']
|
345
273
|
end
|
346
274
|
|
347
|
-
|
348
|
-
|
275
|
+
# which() mimicks the Unix which command
|
276
|
+
# FIXME: it is not working
|
277
|
+
def self.which(cmd)
|
278
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
279
|
+
exe = find_executable("#{path}/#{cmd}")
|
280
|
+
return exe if exe
|
281
|
+
end
|
282
|
+
return nil
|
349
283
|
end
|
350
284
|
|
351
|
-
#
|
352
|
-
#
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
HANDLE_FLAG_INHERIT
|
357
|
-
)
|
285
|
+
# Windows has a different notion of what "executable" means
|
286
|
+
# The OS will search through valid the extensions and look
|
287
|
+
# for a binary there.
|
288
|
+
def self.find_executable(path)
|
289
|
+
return path if File.executable? path
|
358
290
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
291
|
+
pathext.each do |ext|
|
292
|
+
exe = "#{path}#{ext}"
|
293
|
+
return exe if File.executable? exe
|
294
|
+
end
|
295
|
+
return nil
|
296
|
+
end
|
365
297
|
end
|
366
|
-
|
367
|
-
|
368
|
-
# The bytes not covered here are reserved (null)
|
369
|
-
unless si_hash.empty?
|
370
|
-
startinfo[0,4] = [startinfo.size].pack('L')
|
371
|
-
startinfo[8,4] = [si_hash['desktop']].pack('p*') if si_hash['desktop']
|
372
|
-
startinfo[12,4] = [si_hash['title']].pack('p*') if si_hash['title']
|
373
|
-
startinfo[16,4] = [si_hash['x']].pack('L') if si_hash['x']
|
374
|
-
startinfo[20,4] = [si_hash['y']].pack('L') if si_hash['y']
|
375
|
-
startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
|
376
|
-
startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
|
377
|
-
startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
|
378
|
-
startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
|
379
|
-
startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
|
380
|
-
startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
|
381
|
-
startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
|
382
|
-
startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
|
383
|
-
startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
|
384
|
-
startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
|
385
|
-
end
|
386
|
-
|
387
|
-
if hash['with_logon']
|
388
|
-
logon = multi_to_wide(hash['with_logon'])
|
389
|
-
domain = multi_to_wide(hash['domain'])
|
390
|
-
app = hash['app_name'].nil? ? nil : multi_to_wide(hash['app_name'])
|
391
|
-
cmd = hash['command_line'].nil? ? nil : multi_to_wide(hash['command_line'])
|
392
|
-
cwd = multi_to_wide(hash['cwd'])
|
393
|
-
passwd = multi_to_wide(hash['password'])
|
394
|
-
|
395
|
-
hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
|
396
|
-
|
397
|
-
process_ran = CreateProcessWithLogonW(
|
398
|
-
logon, # User
|
399
|
-
domain, # Domain
|
400
|
-
passwd, # Password
|
401
|
-
LOGON_WITH_PROFILE, # Logon flags
|
402
|
-
app, # App name
|
403
|
-
cmd, # Command line
|
404
|
-
hash['creation_flags'], # Creation flags
|
405
|
-
env, # Environment
|
406
|
-
cwd, # Working directory
|
407
|
-
startinfo, # Startup Info
|
408
|
-
procinfo # Process Info
|
409
|
-
)
|
410
|
-
else
|
411
|
-
process_ran = CreateProcess(
|
412
|
-
hash['app_name'], # App name
|
413
|
-
hash['command_line'], # Command line
|
414
|
-
process_security, # Process attributes
|
415
|
-
thread_security, # Thread attributes
|
416
|
-
hash['inherit'], # Inherit handles?
|
417
|
-
hash['creation_flags'], # Creation flags
|
418
|
-
env, # Environment
|
419
|
-
hash['cwd'], # Working directory
|
420
|
-
startinfo, # Startup Info
|
421
|
-
procinfo # Process Info
|
422
|
-
)
|
423
|
-
end
|
424
|
-
|
425
|
-
# TODO: Close stdin, stdout and stderr handles in the si_hash unless
|
426
|
-
# they're pointing to one of the standard handles already. [Maybe]
|
427
|
-
if !process_ran
|
428
|
-
raise_last_error("CreateProcess()")
|
429
|
-
end
|
430
|
-
|
431
|
-
# Automatically close the process and thread handles in the
|
432
|
-
# PROCESS_INFORMATION struct unless explicitly told not to.
|
433
|
-
if hash['close_handles']
|
434
|
-
CloseHandle(procinfo[0,4].unpack('L').first)
|
435
|
-
CloseHandle(procinfo[4,4].unpack('L').first)
|
436
|
-
end
|
437
|
-
|
438
|
-
ProcessInfo.new(
|
439
|
-
procinfo[0,4].unpack('L').first, # hProcess
|
440
|
-
procinfo[4,4].unpack('L').first, # hThread
|
441
|
-
procinfo[8,4].unpack('L').first, # hProcessId
|
442
|
-
procinfo[12,4].unpack('L').first # hThreadId
|
443
|
-
)
|
444
|
-
end
|
445
|
-
|
446
|
-
def self.raise_last_error(operation)
|
447
|
-
error_string = "#{operation} failed: #{get_last_error}"
|
448
|
-
last_error_code = GetLastError()
|
449
|
-
if ERROR_CODE_MAP.has_key?(last_error_code)
|
450
|
-
raise ERROR_CODE_MAP[last_error_code], error_string
|
451
|
-
else
|
452
|
-
raise Error, error_string
|
453
|
-
end
|
298
|
+
end # class
|
454
299
|
end
|
455
|
-
|
456
|
-
# List from ruby/win32/win32.c
|
457
|
-
ERROR_CODE_MAP = {
|
458
|
-
ERROR_INVALID_FUNCTION => Errno::EINVAL,
|
459
|
-
ERROR_FILE_NOT_FOUND => Errno::ENOENT,
|
460
|
-
ERROR_PATH_NOT_FOUND => Errno::ENOENT,
|
461
|
-
ERROR_TOO_MANY_OPEN_FILES => Errno::EMFILE,
|
462
|
-
ERROR_ACCESS_DENIED => Errno::EACCES,
|
463
|
-
ERROR_INVALID_HANDLE => Errno::EBADF,
|
464
|
-
ERROR_ARENA_TRASHED => Errno::ENOMEM,
|
465
|
-
ERROR_NOT_ENOUGH_MEMORY => Errno::ENOMEM,
|
466
|
-
ERROR_INVALID_BLOCK => Errno::ENOMEM,
|
467
|
-
ERROR_BAD_ENVIRONMENT => Errno::E2BIG,
|
468
|
-
ERROR_BAD_FORMAT => Errno::ENOEXEC,
|
469
|
-
ERROR_INVALID_ACCESS => Errno::EINVAL,
|
470
|
-
ERROR_INVALID_DATA => Errno::EINVAL,
|
471
|
-
ERROR_INVALID_DRIVE => Errno::ENOENT,
|
472
|
-
ERROR_CURRENT_DIRECTORY => Errno::EACCES,
|
473
|
-
ERROR_NOT_SAME_DEVICE => Errno::EXDEV,
|
474
|
-
ERROR_NO_MORE_FILES => Errno::ENOENT,
|
475
|
-
ERROR_WRITE_PROTECT => Errno::EROFS,
|
476
|
-
ERROR_BAD_UNIT => Errno::ENODEV,
|
477
|
-
ERROR_NOT_READY => Errno::ENXIO,
|
478
|
-
ERROR_BAD_COMMAND => Errno::EACCES,
|
479
|
-
ERROR_CRC => Errno::EACCES,
|
480
|
-
ERROR_BAD_LENGTH => Errno::EACCES,
|
481
|
-
ERROR_SEEK => Errno::EIO,
|
482
|
-
ERROR_NOT_DOS_DISK => Errno::EACCES,
|
483
|
-
ERROR_SECTOR_NOT_FOUND => Errno::EACCES,
|
484
|
-
ERROR_OUT_OF_PAPER => Errno::EACCES,
|
485
|
-
ERROR_WRITE_FAULT => Errno::EIO,
|
486
|
-
ERROR_READ_FAULT => Errno::EIO,
|
487
|
-
ERROR_GEN_FAILURE => Errno::EACCES,
|
488
|
-
ERROR_LOCK_VIOLATION => Errno::EACCES,
|
489
|
-
ERROR_SHARING_VIOLATION => Errno::EACCES,
|
490
|
-
ERROR_WRONG_DISK => Errno::EACCES,
|
491
|
-
ERROR_SHARING_BUFFER_EXCEEDED => Errno::EACCES,
|
492
|
-
# ERROR_BAD_NETPATH => Errno::ENOENT,
|
493
|
-
# ERROR_NETWORK_ACCESS_DENIED => Errno::EACCES,
|
494
|
-
# ERROR_BAD_NET_NAME => Errno::ENOENT,
|
495
|
-
ERROR_FILE_EXISTS => Errno::EEXIST,
|
496
|
-
ERROR_CANNOT_MAKE => Errno::EACCES,
|
497
|
-
ERROR_FAIL_I24 => Errno::EACCES,
|
498
|
-
ERROR_INVALID_PARAMETER => Errno::EINVAL,
|
499
|
-
ERROR_NO_PROC_SLOTS => Errno::EAGAIN,
|
500
|
-
ERROR_DRIVE_LOCKED => Errno::EACCES,
|
501
|
-
ERROR_BROKEN_PIPE => Errno::EPIPE,
|
502
|
-
ERROR_DISK_FULL => Errno::ENOSPC,
|
503
|
-
ERROR_INVALID_TARGET_HANDLE => Errno::EBADF,
|
504
|
-
ERROR_INVALID_HANDLE => Errno::EINVAL,
|
505
|
-
ERROR_WAIT_NO_CHILDREN => Errno::ECHILD,
|
506
|
-
ERROR_CHILD_NOT_COMPLETE => Errno::ECHILD,
|
507
|
-
ERROR_DIRECT_ACCESS_HANDLE => Errno::EBADF,
|
508
|
-
ERROR_NEGATIVE_SEEK => Errno::EINVAL,
|
509
|
-
ERROR_SEEK_ON_DEVICE => Errno::EACCES,
|
510
|
-
ERROR_DIR_NOT_EMPTY => Errno::ENOTEMPTY,
|
511
|
-
# ERROR_DIRECTORY => Errno::ENOTDIR,
|
512
|
-
ERROR_NOT_LOCKED => Errno::EACCES,
|
513
|
-
ERROR_BAD_PATHNAME => Errno::ENOENT,
|
514
|
-
ERROR_MAX_THRDS_REACHED => Errno::EAGAIN,
|
515
|
-
# ERROR_LOCK_FAILED => Errno::EACCES,
|
516
|
-
ERROR_ALREADY_EXISTS => Errno::EEXIST,
|
517
|
-
ERROR_INVALID_STARTING_CODESEG => Errno::ENOEXEC,
|
518
|
-
ERROR_INVALID_STACKSEG => Errno::ENOEXEC,
|
519
|
-
ERROR_INVALID_MODULETYPE => Errno::ENOEXEC,
|
520
|
-
ERROR_INVALID_EXE_SIGNATURE => Errno::ENOEXEC,
|
521
|
-
ERROR_EXE_MARKED_INVALID => Errno::ENOEXEC,
|
522
|
-
ERROR_BAD_EXE_FORMAT => Errno::ENOEXEC,
|
523
|
-
ERROR_ITERATED_DATA_EXCEEDS_64k => Errno::ENOEXEC,
|
524
|
-
ERROR_INVALID_MINALLOCSIZE => Errno::ENOEXEC,
|
525
|
-
ERROR_DYNLINK_FROM_INVALID_RING => Errno::ENOEXEC,
|
526
|
-
ERROR_IOPL_NOT_ENABLED => Errno::ENOEXEC,
|
527
|
-
ERROR_INVALID_SEGDPL => Errno::ENOEXEC,
|
528
|
-
ERROR_AUTODATASEG_EXCEEDS_64k => Errno::ENOEXEC,
|
529
|
-
ERROR_RING2SEG_MUST_BE_MOVABLE => Errno::ENOEXEC,
|
530
|
-
ERROR_RELOC_CHAIN_XEEDS_SEGLIM => Errno::ENOEXEC,
|
531
|
-
ERROR_INFLOOP_IN_RELOC_CHAIN => Errno::ENOEXEC,
|
532
|
-
ERROR_FILENAME_EXCED_RANGE => Errno::ENOENT,
|
533
|
-
ERROR_NESTING_NOT_ALLOWED => Errno::EAGAIN,
|
534
|
-
# ERROR_PIPE_LOCAL => Errno::EPIPE,
|
535
|
-
ERROR_BAD_PIPE => Errno::EPIPE,
|
536
|
-
ERROR_PIPE_BUSY => Errno::EAGAIN,
|
537
|
-
ERROR_NO_DATA => Errno::EPIPE,
|
538
|
-
ERROR_PIPE_NOT_CONNECTED => Errno::EPIPE,
|
539
|
-
ERROR_OPERATION_ABORTED => Errno::EINTR,
|
540
|
-
# ERROR_NOT_ENOUGH_QUOTA => Errno::ENOMEM,
|
541
|
-
ERROR_MOD_NOT_FOUND => Errno::ENOENT,
|
542
|
-
WSAEINTR => Errno::EINTR,
|
543
|
-
WSAEBADF => Errno::EBADF,
|
544
|
-
# WSAEACCES => Errno::EACCES,
|
545
|
-
WSAEFAULT => Errno::EFAULT,
|
546
|
-
WSAEINVAL => Errno::EINVAL,
|
547
|
-
WSAEMFILE => Errno::EMFILE,
|
548
|
-
WSAEWOULDBLOCK => Errno::EWOULDBLOCK,
|
549
|
-
WSAEINPROGRESS => Errno::EINPROGRESS,
|
550
|
-
WSAEALREADY => Errno::EALREADY,
|
551
|
-
WSAENOTSOCK => Errno::ENOTSOCK,
|
552
|
-
WSAEDESTADDRREQ => Errno::EDESTADDRREQ,
|
553
|
-
WSAEMSGSIZE => Errno::EMSGSIZE,
|
554
|
-
WSAEPROTOTYPE => Errno::EPROTOTYPE,
|
555
|
-
WSAENOPROTOOPT => Errno::ENOPROTOOPT,
|
556
|
-
WSAEPROTONOSUPPORT => Errno::EPROTONOSUPPORT,
|
557
|
-
WSAESOCKTNOSUPPORT => Errno::ESOCKTNOSUPPORT,
|
558
|
-
WSAEOPNOTSUPP => Errno::EOPNOTSUPP,
|
559
|
-
WSAEPFNOSUPPORT => Errno::EPFNOSUPPORT,
|
560
|
-
WSAEAFNOSUPPORT => Errno::EAFNOSUPPORT,
|
561
|
-
WSAEADDRINUSE => Errno::EADDRINUSE,
|
562
|
-
WSAEADDRNOTAVAIL => Errno::EADDRNOTAVAIL,
|
563
|
-
WSAENETDOWN => Errno::ENETDOWN,
|
564
|
-
WSAENETUNREACH => Errno::ENETUNREACH,
|
565
|
-
WSAENETRESET => Errno::ENETRESET,
|
566
|
-
WSAECONNABORTED => Errno::ECONNABORTED,
|
567
|
-
WSAECONNRESET => Errno::ECONNRESET,
|
568
|
-
WSAENOBUFS => Errno::ENOBUFS,
|
569
|
-
WSAEISCONN => Errno::EISCONN,
|
570
|
-
WSAENOTCONN => Errno::ENOTCONN,
|
571
|
-
WSAESHUTDOWN => Errno::ESHUTDOWN,
|
572
|
-
WSAETOOMANYREFS => Errno::ETOOMANYREFS,
|
573
|
-
# WSAETIMEDOUT => Errno::ETIMEDOUT,
|
574
|
-
WSAECONNREFUSED => Errno::ECONNREFUSED,
|
575
|
-
WSAELOOP => Errno::ELOOP,
|
576
|
-
WSAENAMETOOLONG => Errno::ENAMETOOLONG,
|
577
|
-
WSAEHOSTDOWN => Errno::EHOSTDOWN,
|
578
|
-
WSAEHOSTUNREACH => Errno::EHOSTUNREACH,
|
579
|
-
# WSAEPROCLIM => Errno::EPROCLIM,
|
580
|
-
# WSAENOTEMPTY => Errno::ENOTEMPTY,
|
581
|
-
WSAEUSERS => Errno::EUSERS,
|
582
|
-
WSAEDQUOT => Errno::EDQUOT,
|
583
|
-
WSAESTALE => Errno::ESTALE,
|
584
|
-
WSAEREMOTE => Errno::EREMOTE
|
585
|
-
}
|
586
|
-
|
587
|
-
module_function :create
|
588
300
|
end
|
@@ -0,0 +1,385 @@
|
|
1
|
+
#--
|
2
|
+
# Author:: Daniel DeLeo (<dan@opscode.com>)
|
3
|
+
# Author:: John Keiser (<jkeiser@opscode.com>)
|
4
|
+
# Copyright:: Copyright (c) 2011, 2012 Opscode, Inc.
|
5
|
+
# License:: Apache License, Version 2.0
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'win32/process'
|
21
|
+
require 'windows/handle'
|
22
|
+
require 'windows/process'
|
23
|
+
require 'windows/synchronize'
|
24
|
+
|
25
|
+
# Override module Windows::Process.CreateProcess to fix bug when
|
26
|
+
# using both app_name and command_line
|
27
|
+
#
|
28
|
+
module Windows
|
29
|
+
module Process
|
30
|
+
API.new('CreateProcess', 'SPPPLLLPPP', 'B')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Override Win32::Process.create to take a proper environment hash
|
36
|
+
# so that variables can contain semicolons
|
37
|
+
# (submitted patch to owner)
|
38
|
+
#
|
39
|
+
module Process
|
40
|
+
def create(args)
|
41
|
+
unless args.kind_of?(Hash)
|
42
|
+
raise TypeError, 'Expecting hash-style keyword arguments'
|
43
|
+
end
|
44
|
+
|
45
|
+
valid_keys = %w/
|
46
|
+
app_name command_line inherit creation_flags cwd environment
|
47
|
+
startup_info thread_inherit process_inherit close_handles with_logon
|
48
|
+
domain password
|
49
|
+
/
|
50
|
+
|
51
|
+
valid_si_keys = %/
|
52
|
+
startf_flags desktop title x y x_size y_size x_count_chars
|
53
|
+
y_count_chars fill_attribute sw_flags stdin stdout stderr
|
54
|
+
/
|
55
|
+
|
56
|
+
# Set default values
|
57
|
+
hash = {
|
58
|
+
'app_name' => nil,
|
59
|
+
'creation_flags' => 0,
|
60
|
+
'close_handles' => true
|
61
|
+
}
|
62
|
+
|
63
|
+
# Validate the keys, and convert symbols and case to lowercase strings.
|
64
|
+
args.each{ |key, val|
|
65
|
+
key = key.to_s.downcase
|
66
|
+
unless valid_keys.include?(key)
|
67
|
+
raise ArgumentError, "invalid key '#{key}'"
|
68
|
+
end
|
69
|
+
hash[key] = val
|
70
|
+
}
|
71
|
+
|
72
|
+
si_hash = {}
|
73
|
+
|
74
|
+
# If the startup_info key is present, validate its subkeys
|
75
|
+
if hash['startup_info']
|
76
|
+
hash['startup_info'].each{ |key, val|
|
77
|
+
key = key.to_s.downcase
|
78
|
+
unless valid_si_keys.include?(key)
|
79
|
+
raise ArgumentError, "invalid startup_info key '#{key}'"
|
80
|
+
end
|
81
|
+
si_hash[key] = val
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
# The +command_line+ key is mandatory unless the +app_name+ key
|
86
|
+
# is specified.
|
87
|
+
unless hash['command_line']
|
88
|
+
if hash['app_name']
|
89
|
+
hash['command_line'] = hash['app_name']
|
90
|
+
hash['app_name'] = nil
|
91
|
+
else
|
92
|
+
raise ArgumentError, 'command_line or app_name must be specified'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# The environment string should be passed as an array of A=B paths, or
|
97
|
+
# as a string of ';' separated paths.
|
98
|
+
if hash['environment']
|
99
|
+
env = hash['environment']
|
100
|
+
if !env.respond_to?(:join)
|
101
|
+
# Backwards compat for ; separated paths
|
102
|
+
env = hash['environment'].split(File::PATH_SEPARATOR)
|
103
|
+
end
|
104
|
+
# The argument format is a series of null-terminated strings, with an additional null terminator.
|
105
|
+
env = env.map { |e| e + "\0" }.join("") + "\0"
|
106
|
+
if hash['with_logon']
|
107
|
+
env = env.multi_to_wide(e)
|
108
|
+
end
|
109
|
+
env = [env].pack('p*').unpack('L').first
|
110
|
+
else
|
111
|
+
env = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
115
|
+
startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
|
116
|
+
procinfo = [0,0,0,0].pack('LLLL')
|
117
|
+
|
118
|
+
# Process SECURITY_ATTRIBUTE structure
|
119
|
+
process_security = 0
|
120
|
+
if hash['process_inherit']
|
121
|
+
process_security = [0,0,0].pack('LLL')
|
122
|
+
process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
|
123
|
+
process_security[8,4] = [1].pack('L') # TRUE
|
124
|
+
end
|
125
|
+
|
126
|
+
# Thread SECURITY_ATTRIBUTE structure
|
127
|
+
thread_security = 0
|
128
|
+
if hash['thread_inherit']
|
129
|
+
thread_security = [0,0,0].pack('LLL')
|
130
|
+
thread_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
|
131
|
+
thread_security[8,4] = [1].pack('L') # TRUE
|
132
|
+
end
|
133
|
+
|
134
|
+
# Automatically handle stdin, stdout and stderr as either IO objects
|
135
|
+
# or file descriptors. This won't work for StringIO, however.
|
136
|
+
['stdin', 'stdout', 'stderr'].each{ |io|
|
137
|
+
if si_hash[io]
|
138
|
+
if si_hash[io].respond_to?(:fileno)
|
139
|
+
handle = get_osfhandle(si_hash[io].fileno)
|
140
|
+
else
|
141
|
+
handle = get_osfhandle(si_hash[io])
|
142
|
+
end
|
143
|
+
|
144
|
+
if handle == INVALID_HANDLE_VALUE
|
145
|
+
raise Error, get_last_error
|
146
|
+
end
|
147
|
+
|
148
|
+
# Most implementations of Ruby on Windows create inheritable
|
149
|
+
# handles by default, but some do not. RF bug #26988.
|
150
|
+
bool = SetHandleInformation(
|
151
|
+
handle,
|
152
|
+
HANDLE_FLAG_INHERIT,
|
153
|
+
HANDLE_FLAG_INHERIT
|
154
|
+
)
|
155
|
+
|
156
|
+
raise Error, get_last_error unless bool
|
157
|
+
|
158
|
+
si_hash[io] = handle
|
159
|
+
si_hash['startf_flags'] ||= 0
|
160
|
+
si_hash['startf_flags'] |= STARTF_USESTDHANDLES
|
161
|
+
hash['inherit'] = true
|
162
|
+
end
|
163
|
+
}
|
164
|
+
|
165
|
+
# The bytes not covered here are reserved (null)
|
166
|
+
unless si_hash.empty?
|
167
|
+
startinfo[0,4] = [startinfo.size].pack('L')
|
168
|
+
startinfo[8,4] = [si_hash['desktop']].pack('p*') if si_hash['desktop']
|
169
|
+
startinfo[12,4] = [si_hash['title']].pack('p*') if si_hash['title']
|
170
|
+
startinfo[16,4] = [si_hash['x']].pack('L') if si_hash['x']
|
171
|
+
startinfo[20,4] = [si_hash['y']].pack('L') if si_hash['y']
|
172
|
+
startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
|
173
|
+
startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
|
174
|
+
startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
|
175
|
+
startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
|
176
|
+
startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
|
177
|
+
startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
|
178
|
+
startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
|
179
|
+
startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
|
180
|
+
startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
|
181
|
+
startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
|
182
|
+
end
|
183
|
+
|
184
|
+
if hash['with_logon']
|
185
|
+
logon = multi_to_wide(hash['with_logon'])
|
186
|
+
domain = multi_to_wide(hash['domain'])
|
187
|
+
app = hash['app_name'].nil? ? nil : multi_to_wide(hash['app_name'])
|
188
|
+
cmd = hash['command_line'].nil? ? nil : multi_to_wide(hash['command_line'])
|
189
|
+
cwd = multi_to_wide(hash['cwd'])
|
190
|
+
passwd = multi_to_wide(hash['password'])
|
191
|
+
|
192
|
+
hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
|
193
|
+
|
194
|
+
process_ran = CreateProcessWithLogonW(
|
195
|
+
logon, # User
|
196
|
+
domain, # Domain
|
197
|
+
passwd, # Password
|
198
|
+
LOGON_WITH_PROFILE, # Logon flags
|
199
|
+
app, # App name
|
200
|
+
cmd, # Command line
|
201
|
+
hash['creation_flags'], # Creation flags
|
202
|
+
env, # Environment
|
203
|
+
cwd, # Working directory
|
204
|
+
startinfo, # Startup Info
|
205
|
+
procinfo # Process Info
|
206
|
+
)
|
207
|
+
else
|
208
|
+
process_ran = CreateProcess(
|
209
|
+
hash['app_name'], # App name
|
210
|
+
hash['command_line'], # Command line
|
211
|
+
process_security, # Process attributes
|
212
|
+
thread_security, # Thread attributes
|
213
|
+
hash['inherit'], # Inherit handles?
|
214
|
+
hash['creation_flags'], # Creation flags
|
215
|
+
env, # Environment
|
216
|
+
hash['cwd'], # Working directory
|
217
|
+
startinfo, # Startup Info
|
218
|
+
procinfo # Process Info
|
219
|
+
)
|
220
|
+
end
|
221
|
+
|
222
|
+
# TODO: Close stdin, stdout and stderr handles in the si_hash unless
|
223
|
+
# they're pointing to one of the standard handles already. [Maybe]
|
224
|
+
if !process_ran
|
225
|
+
raise_last_error("CreateProcess()")
|
226
|
+
end
|
227
|
+
|
228
|
+
# Automatically close the process and thread handles in the
|
229
|
+
# PROCESS_INFORMATION struct unless explicitly told not to.
|
230
|
+
if hash['close_handles']
|
231
|
+
CloseHandle(procinfo[0,4].unpack('L').first)
|
232
|
+
CloseHandle(procinfo[4,4].unpack('L').first)
|
233
|
+
end
|
234
|
+
|
235
|
+
ProcessInfo.new(
|
236
|
+
procinfo[0,4].unpack('L').first, # hProcess
|
237
|
+
procinfo[4,4].unpack('L').first, # hThread
|
238
|
+
procinfo[8,4].unpack('L').first, # hProcessId
|
239
|
+
procinfo[12,4].unpack('L').first # hThreadId
|
240
|
+
)
|
241
|
+
end
|
242
|
+
|
243
|
+
def self.raise_last_error(operation)
|
244
|
+
error_string = "#{operation} failed: #{get_last_error}"
|
245
|
+
last_error_code = GetLastError()
|
246
|
+
if ERROR_CODE_MAP.has_key?(last_error_code)
|
247
|
+
raise ERROR_CODE_MAP[last_error_code], error_string
|
248
|
+
else
|
249
|
+
raise Error, error_string
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# List from ruby/win32/win32.c
|
254
|
+
ERROR_CODE_MAP = {
|
255
|
+
ERROR_INVALID_FUNCTION => Errno::EINVAL,
|
256
|
+
ERROR_FILE_NOT_FOUND => Errno::ENOENT,
|
257
|
+
ERROR_PATH_NOT_FOUND => Errno::ENOENT,
|
258
|
+
ERROR_TOO_MANY_OPEN_FILES => Errno::EMFILE,
|
259
|
+
ERROR_ACCESS_DENIED => Errno::EACCES,
|
260
|
+
ERROR_INVALID_HANDLE => Errno::EBADF,
|
261
|
+
ERROR_ARENA_TRASHED => Errno::ENOMEM,
|
262
|
+
ERROR_NOT_ENOUGH_MEMORY => Errno::ENOMEM,
|
263
|
+
ERROR_INVALID_BLOCK => Errno::ENOMEM,
|
264
|
+
ERROR_BAD_ENVIRONMENT => Errno::E2BIG,
|
265
|
+
ERROR_BAD_FORMAT => Errno::ENOEXEC,
|
266
|
+
ERROR_INVALID_ACCESS => Errno::EINVAL,
|
267
|
+
ERROR_INVALID_DATA => Errno::EINVAL,
|
268
|
+
ERROR_INVALID_DRIVE => Errno::ENOENT,
|
269
|
+
ERROR_CURRENT_DIRECTORY => Errno::EACCES,
|
270
|
+
ERROR_NOT_SAME_DEVICE => Errno::EXDEV,
|
271
|
+
ERROR_NO_MORE_FILES => Errno::ENOENT,
|
272
|
+
ERROR_WRITE_PROTECT => Errno::EROFS,
|
273
|
+
ERROR_BAD_UNIT => Errno::ENODEV,
|
274
|
+
ERROR_NOT_READY => Errno::ENXIO,
|
275
|
+
ERROR_BAD_COMMAND => Errno::EACCES,
|
276
|
+
ERROR_CRC => Errno::EACCES,
|
277
|
+
ERROR_BAD_LENGTH => Errno::EACCES,
|
278
|
+
ERROR_SEEK => Errno::EIO,
|
279
|
+
ERROR_NOT_DOS_DISK => Errno::EACCES,
|
280
|
+
ERROR_SECTOR_NOT_FOUND => Errno::EACCES,
|
281
|
+
ERROR_OUT_OF_PAPER => Errno::EACCES,
|
282
|
+
ERROR_WRITE_FAULT => Errno::EIO,
|
283
|
+
ERROR_READ_FAULT => Errno::EIO,
|
284
|
+
ERROR_GEN_FAILURE => Errno::EACCES,
|
285
|
+
ERROR_LOCK_VIOLATION => Errno::EACCES,
|
286
|
+
ERROR_SHARING_VIOLATION => Errno::EACCES,
|
287
|
+
ERROR_WRONG_DISK => Errno::EACCES,
|
288
|
+
ERROR_SHARING_BUFFER_EXCEEDED => Errno::EACCES,
|
289
|
+
# ERROR_BAD_NETPATH => Errno::ENOENT,
|
290
|
+
# ERROR_NETWORK_ACCESS_DENIED => Errno::EACCES,
|
291
|
+
# ERROR_BAD_NET_NAME => Errno::ENOENT,
|
292
|
+
ERROR_FILE_EXISTS => Errno::EEXIST,
|
293
|
+
ERROR_CANNOT_MAKE => Errno::EACCES,
|
294
|
+
ERROR_FAIL_I24 => Errno::EACCES,
|
295
|
+
ERROR_INVALID_PARAMETER => Errno::EINVAL,
|
296
|
+
ERROR_NO_PROC_SLOTS => Errno::EAGAIN,
|
297
|
+
ERROR_DRIVE_LOCKED => Errno::EACCES,
|
298
|
+
ERROR_BROKEN_PIPE => Errno::EPIPE,
|
299
|
+
ERROR_DISK_FULL => Errno::ENOSPC,
|
300
|
+
ERROR_INVALID_TARGET_HANDLE => Errno::EBADF,
|
301
|
+
ERROR_INVALID_HANDLE => Errno::EINVAL,
|
302
|
+
ERROR_WAIT_NO_CHILDREN => Errno::ECHILD,
|
303
|
+
ERROR_CHILD_NOT_COMPLETE => Errno::ECHILD,
|
304
|
+
ERROR_DIRECT_ACCESS_HANDLE => Errno::EBADF,
|
305
|
+
ERROR_NEGATIVE_SEEK => Errno::EINVAL,
|
306
|
+
ERROR_SEEK_ON_DEVICE => Errno::EACCES,
|
307
|
+
ERROR_DIR_NOT_EMPTY => Errno::ENOTEMPTY,
|
308
|
+
# ERROR_DIRECTORY => Errno::ENOTDIR,
|
309
|
+
ERROR_NOT_LOCKED => Errno::EACCES,
|
310
|
+
ERROR_BAD_PATHNAME => Errno::ENOENT,
|
311
|
+
ERROR_MAX_THRDS_REACHED => Errno::EAGAIN,
|
312
|
+
# ERROR_LOCK_FAILED => Errno::EACCES,
|
313
|
+
ERROR_ALREADY_EXISTS => Errno::EEXIST,
|
314
|
+
ERROR_INVALID_STARTING_CODESEG => Errno::ENOEXEC,
|
315
|
+
ERROR_INVALID_STACKSEG => Errno::ENOEXEC,
|
316
|
+
ERROR_INVALID_MODULETYPE => Errno::ENOEXEC,
|
317
|
+
ERROR_INVALID_EXE_SIGNATURE => Errno::ENOEXEC,
|
318
|
+
ERROR_EXE_MARKED_INVALID => Errno::ENOEXEC,
|
319
|
+
ERROR_BAD_EXE_FORMAT => Errno::ENOEXEC,
|
320
|
+
ERROR_ITERATED_DATA_EXCEEDS_64k => Errno::ENOEXEC,
|
321
|
+
ERROR_INVALID_MINALLOCSIZE => Errno::ENOEXEC,
|
322
|
+
ERROR_DYNLINK_FROM_INVALID_RING => Errno::ENOEXEC,
|
323
|
+
ERROR_IOPL_NOT_ENABLED => Errno::ENOEXEC,
|
324
|
+
ERROR_INVALID_SEGDPL => Errno::ENOEXEC,
|
325
|
+
ERROR_AUTODATASEG_EXCEEDS_64k => Errno::ENOEXEC,
|
326
|
+
ERROR_RING2SEG_MUST_BE_MOVABLE => Errno::ENOEXEC,
|
327
|
+
ERROR_RELOC_CHAIN_XEEDS_SEGLIM => Errno::ENOEXEC,
|
328
|
+
ERROR_INFLOOP_IN_RELOC_CHAIN => Errno::ENOEXEC,
|
329
|
+
ERROR_FILENAME_EXCED_RANGE => Errno::ENOENT,
|
330
|
+
ERROR_NESTING_NOT_ALLOWED => Errno::EAGAIN,
|
331
|
+
# ERROR_PIPE_LOCAL => Errno::EPIPE,
|
332
|
+
ERROR_BAD_PIPE => Errno::EPIPE,
|
333
|
+
ERROR_PIPE_BUSY => Errno::EAGAIN,
|
334
|
+
ERROR_NO_DATA => Errno::EPIPE,
|
335
|
+
ERROR_PIPE_NOT_CONNECTED => Errno::EPIPE,
|
336
|
+
ERROR_OPERATION_ABORTED => Errno::EINTR,
|
337
|
+
# ERROR_NOT_ENOUGH_QUOTA => Errno::ENOMEM,
|
338
|
+
ERROR_MOD_NOT_FOUND => Errno::ENOENT,
|
339
|
+
WSAEINTR => Errno::EINTR,
|
340
|
+
WSAEBADF => Errno::EBADF,
|
341
|
+
# WSAEACCES => Errno::EACCES,
|
342
|
+
WSAEFAULT => Errno::EFAULT,
|
343
|
+
WSAEINVAL => Errno::EINVAL,
|
344
|
+
WSAEMFILE => Errno::EMFILE,
|
345
|
+
WSAEWOULDBLOCK => Errno::EWOULDBLOCK,
|
346
|
+
WSAEINPROGRESS => Errno::EINPROGRESS,
|
347
|
+
WSAEALREADY => Errno::EALREADY,
|
348
|
+
WSAENOTSOCK => Errno::ENOTSOCK,
|
349
|
+
WSAEDESTADDRREQ => Errno::EDESTADDRREQ,
|
350
|
+
WSAEMSGSIZE => Errno::EMSGSIZE,
|
351
|
+
WSAEPROTOTYPE => Errno::EPROTOTYPE,
|
352
|
+
WSAENOPROTOOPT => Errno::ENOPROTOOPT,
|
353
|
+
WSAEPROTONOSUPPORT => Errno::EPROTONOSUPPORT,
|
354
|
+
WSAESOCKTNOSUPPORT => Errno::ESOCKTNOSUPPORT,
|
355
|
+
WSAEOPNOTSUPP => Errno::EOPNOTSUPP,
|
356
|
+
WSAEPFNOSUPPORT => Errno::EPFNOSUPPORT,
|
357
|
+
WSAEAFNOSUPPORT => Errno::EAFNOSUPPORT,
|
358
|
+
WSAEADDRINUSE => Errno::EADDRINUSE,
|
359
|
+
WSAEADDRNOTAVAIL => Errno::EADDRNOTAVAIL,
|
360
|
+
WSAENETDOWN => Errno::ENETDOWN,
|
361
|
+
WSAENETUNREACH => Errno::ENETUNREACH,
|
362
|
+
WSAENETRESET => Errno::ENETRESET,
|
363
|
+
WSAECONNABORTED => Errno::ECONNABORTED,
|
364
|
+
WSAECONNRESET => Errno::ECONNRESET,
|
365
|
+
WSAENOBUFS => Errno::ENOBUFS,
|
366
|
+
WSAEISCONN => Errno::EISCONN,
|
367
|
+
WSAENOTCONN => Errno::ENOTCONN,
|
368
|
+
WSAESHUTDOWN => Errno::ESHUTDOWN,
|
369
|
+
WSAETOOMANYREFS => Errno::ETOOMANYREFS,
|
370
|
+
# WSAETIMEDOUT => Errno::ETIMEDOUT,
|
371
|
+
WSAECONNREFUSED => Errno::ECONNREFUSED,
|
372
|
+
WSAELOOP => Errno::ELOOP,
|
373
|
+
WSAENAMETOOLONG => Errno::ENAMETOOLONG,
|
374
|
+
WSAEHOSTDOWN => Errno::EHOSTDOWN,
|
375
|
+
WSAEHOSTUNREACH => Errno::EHOSTUNREACH,
|
376
|
+
# WSAEPROCLIM => Errno::EPROCLIM,
|
377
|
+
# WSAENOTEMPTY => Errno::ENOTEMPTY,
|
378
|
+
WSAEUSERS => Errno::EUSERS,
|
379
|
+
WSAEDQUOT => Errno::EDQUOT,
|
380
|
+
WSAESTALE => Errno::ESTALE,
|
381
|
+
WSAEREMOTE => Errno::EREMOTE
|
382
|
+
}
|
383
|
+
|
384
|
+
module_function :create
|
385
|
+
end
|
metadata
CHANGED
@@ -1,82 +1,76 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: mixlib-shellout
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.0
|
4
5
|
prerelease:
|
5
|
-
version: 1.0.0
|
6
6
|
platform: x86-mingw32
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- Opscode
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-08-06 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
16
15
|
name: rspec
|
17
|
-
|
18
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &11516480 !ruby/object:Gem::Requirement
|
19
17
|
none: false
|
20
|
-
requirements:
|
21
|
-
- -
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version:
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
24
22
|
type: :development
|
25
|
-
version_requirements: *id001
|
26
|
-
- !ruby/object:Gem::Dependency
|
27
|
-
name: win32-process
|
28
23
|
prerelease: false
|
29
|
-
|
24
|
+
version_requirements: *11516480
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: win32-process
|
27
|
+
requirement: &11586780 !ruby/object:Gem::Requirement
|
30
28
|
none: false
|
31
|
-
requirements:
|
29
|
+
requirements:
|
32
30
|
- - ~>
|
33
|
-
- !ruby/object:Gem::Version
|
31
|
+
- !ruby/object:Gem::Version
|
34
32
|
version: 0.6.5
|
35
33
|
type: :runtime
|
36
|
-
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *11586780
|
37
36
|
description: Run external commands on Unix or Windows
|
38
37
|
email: info@opscode.com
|
39
38
|
executables: []
|
40
|
-
|
41
39
|
extensions: []
|
42
|
-
|
43
|
-
extra_rdoc_files:
|
40
|
+
extra_rdoc_files:
|
44
41
|
- README.md
|
45
42
|
- LICENSE
|
46
|
-
files:
|
43
|
+
files:
|
47
44
|
- LICENSE
|
48
45
|
- README.md
|
46
|
+
- lib/mixlib/shellout.rb
|
47
|
+
- lib/mixlib/shellout/windows/core_ext.rb
|
49
48
|
- lib/mixlib/shellout/exceptions.rb
|
50
|
-
- lib/mixlib/shellout/unix.rb
|
51
49
|
- lib/mixlib/shellout/version.rb
|
50
|
+
- lib/mixlib/shellout/unix.rb
|
52
51
|
- lib/mixlib/shellout/windows.rb
|
53
|
-
- lib/mixlib/shellout.rb
|
54
52
|
homepage: http://wiki.opscode.com/
|
55
53
|
licenses: []
|
56
|
-
|
57
54
|
post_install_message:
|
58
55
|
rdoc_options: []
|
59
|
-
|
60
|
-
require_paths:
|
56
|
+
require_paths:
|
61
57
|
- lib
|
62
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
59
|
none: false
|
64
|
-
requirements:
|
65
|
-
- -
|
66
|
-
- !ruby/object:Gem::Version
|
67
|
-
version:
|
68
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
65
|
none: false
|
70
|
-
requirements:
|
71
|
-
- -
|
72
|
-
- !ruby/object:Gem::Version
|
73
|
-
version:
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
74
70
|
requirements: []
|
75
|
-
|
76
71
|
rubyforge_project:
|
77
|
-
rubygems_version: 1.8.
|
72
|
+
rubygems_version: 1.8.17
|
78
73
|
signing_key:
|
79
74
|
specification_version: 3
|
80
75
|
summary: Run external commands on Unix or Windows
|
81
76
|
test_files: []
|
82
|
-
|