mixlib-shellout 1.0.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ module Mixlib
2
+ class ShellOut
3
+ class ShellCommandFailed < RuntimeError; end
4
+ class CommandTimeout < RuntimeError; end
5
+ class InvalidCommandOption < RuntimeError; end
6
+ end
7
+ end
8
+
@@ -0,0 +1,241 @@
1
+ #--
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2010, 2011 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module Mixlib
20
+ class ShellOut
21
+ module Unix
22
+
23
+ # Run the command, writing the command's standard out and standard error
24
+ # to +stdout+ and +stderr+, and saving its exit status object to +status+
25
+ # === Returns
26
+ # returns +self+; +stdout+, +stderr+, +status+, and +exitstatus+ will be
27
+ # populated with results of the command
28
+ # === Raises
29
+ # * Errno::EACCES when you are not privileged to execute the command
30
+ # * Errno::ENOENT when the command is not available on the system (or not
31
+ # in the current $PATH)
32
+ # * Chef::Exceptions::CommandTimeout when the command does not complete
33
+ # within +timeout+ seconds (default: 60s)
34
+ def run_command
35
+ @child_pid = fork_subprocess
36
+
37
+ configure_parent_process_file_descriptors
38
+ propagate_pre_exec_failure
39
+
40
+ @result = nil
41
+ @execution_time = 0
42
+
43
+ # Ruby 1.8.7 and 1.8.6 from mid 2009 try to allocate objects during GC
44
+ # when calling IO.select and IO#read. Some OS Vendors are not interested
45
+ # in updating their ruby packages (Apple, *cough*) and we *have to*
46
+ # make it work. So I give you this epic hack:
47
+ GC.disable
48
+ until @status
49
+ ready = IO.select(open_pipes, nil, nil, READ_WAIT_TIME)
50
+ unless ready
51
+ @execution_time += READ_WAIT_TIME
52
+ if @execution_time >= timeout && !@result
53
+ raise CommandTimeout, "command timed out:\n#{format_for_exception}"
54
+ end
55
+ end
56
+
57
+ if ready && ready.first.include?(child_stdout)
58
+ read_stdout_to_buffer
59
+ end
60
+ if ready && ready.first.include?(child_stderr)
61
+ read_stderr_to_buffer
62
+ end
63
+
64
+ unless @status
65
+ # make one more pass to get the last of the output after the
66
+ # child process dies
67
+ if results = Process.waitpid2(@child_pid, Process::WNOHANG)
68
+ @status = results.last
69
+ redo
70
+ end
71
+ end
72
+ end
73
+ self
74
+ rescue Exception
75
+ # do our best to kill zombies
76
+ Process.waitpid2(@child_pid, Process::WNOHANG) rescue nil
77
+ raise
78
+ ensure
79
+ # no matter what happens, turn the GC back on, and hope whatever busted
80
+ # version of ruby we're on doesn't allocate some objects during the next
81
+ # GC run.
82
+ GC.enable
83
+ close_all_pipes
84
+ end
85
+
86
+ private
87
+
88
+ def set_user
89
+ if user
90
+ Process.euid = uid
91
+ Process.uid = uid
92
+ end
93
+ end
94
+
95
+ def set_group
96
+ if group
97
+ Process.egid = gid
98
+ Process.gid = gid
99
+ end
100
+ end
101
+
102
+ def set_environment
103
+ environment.each do |env_var,value|
104
+ ENV[env_var] = value
105
+ end
106
+ end
107
+
108
+ def set_umask
109
+ File.umask(umask) if umask
110
+ end
111
+
112
+ def set_cwd
113
+ Dir.chdir(cwd) if cwd
114
+ end
115
+
116
+ def initialize_ipc
117
+ @stdout_pipe, @stderr_pipe, @process_status_pipe = IO.pipe, IO.pipe, IO.pipe
118
+ @process_status_pipe.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
119
+ end
120
+
121
+ def child_stdout
122
+ @stdout_pipe[0]
123
+ end
124
+
125
+ def child_stderr
126
+ @stderr_pipe[0]
127
+ end
128
+
129
+ def child_process_status
130
+ @process_status_pipe[0]
131
+ end
132
+
133
+ def close_all_pipes
134
+ child_stdout.close unless child_stdout.closed?
135
+ child_stderr.close unless child_stderr.closed?
136
+ child_process_status.close unless child_process_status.closed?
137
+ end
138
+
139
+ # replace stdout, and stderr with pipes to the parent, and close the
140
+ # reader side of the error marshaling side channel. Close STDIN so when we
141
+ # exec, the new program will know it's never getting input ever.
142
+ def configure_subprocess_file_descriptors
143
+ process_status_pipe.first.close
144
+
145
+ # HACK: for some reason, just STDIN.close isn't good enough when running
146
+ # under ruby 1.9.2, so make it good enough:
147
+ stdin_reader, stdin_writer = IO.pipe
148
+ stdin_writer.close
149
+ STDIN.reopen stdin_reader
150
+ stdin_reader.close
151
+
152
+ stdout_pipe.first.close
153
+ STDOUT.reopen stdout_pipe.last
154
+ stdout_pipe.last.close
155
+
156
+ stderr_pipe.first.close
157
+ STDERR.reopen stderr_pipe.last
158
+ stderr_pipe.last.close
159
+
160
+ STDOUT.sync = STDERR.sync = true
161
+ end
162
+
163
+ def configure_parent_process_file_descriptors
164
+ # Close the sides of the pipes we don't care about
165
+ stdout_pipe.last.close
166
+ stderr_pipe.last.close
167
+ process_status_pipe.last.close
168
+ # Get output as it happens rather than buffered
169
+ child_stdout.sync = true
170
+ child_stderr.sync = true
171
+
172
+ true
173
+ end
174
+
175
+ # Some patch levels of ruby in wide use (in particular the ruby 1.8.6 on OSX)
176
+ # segfault when you IO.select a pipe that's reached eof. Weak sauce.
177
+ def open_pipes
178
+ @open_pipes ||= [child_stdout, child_stderr]
179
+ end
180
+
181
+ def read_stdout_to_buffer
182
+ while chunk = child_stdout.read_nonblock(READ_SIZE)
183
+ @stdout << chunk
184
+ @live_stream << chunk if @live_stream
185
+ end
186
+ rescue Errno::EAGAIN
187
+ rescue EOFError
188
+ open_pipes.delete_at(0)
189
+ end
190
+
191
+ def read_stderr_to_buffer
192
+ while chunk = child_stderr.read_nonblock(READ_SIZE)
193
+ @stderr << chunk
194
+ end
195
+ rescue Errno::EAGAIN
196
+ rescue EOFError
197
+ open_pipes.delete_at(1)
198
+ end
199
+
200
+ def fork_subprocess
201
+ initialize_ipc
202
+
203
+ fork do
204
+ configure_subprocess_file_descriptors
205
+
206
+ set_group
207
+ set_user
208
+ set_environment
209
+ set_umask
210
+ set_cwd
211
+
212
+ begin
213
+ command.kind_of?(Array) ? exec(*command) : exec(command)
214
+
215
+ raise 'forty-two' # Should never get here
216
+ rescue Exception => e
217
+ Marshal.dump(e, process_status_pipe.last)
218
+ process_status_pipe.last.flush
219
+ end
220
+ process_status_pipe.last.close unless (process_status_pipe.last.closed?)
221
+ exit!
222
+ end
223
+ end
224
+
225
+ # Attempt to get a Marshaled error from the side-channel.
226
+ # If it's there, un-marshal it and raise. If it's not there,
227
+ # assume everything went well.
228
+ def propagate_pre_exec_failure
229
+ begin
230
+ e = Marshal.load child_process_status
231
+ raise(Exception === e ? e : "unknown failure: #{e.inspect}")
232
+ rescue EOFError # If we get an EOF error, then the exec was successful
233
+ true
234
+ ensure
235
+ child_process_status.close
236
+ end
237
+ end
238
+
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,5 @@
1
+ module Mixlib
2
+ class ShellOut
3
+ VERSION = "1.0.0.rc.0"
4
+ end
5
+ end
@@ -0,0 +1,554 @@
1
+ #--
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Author:: John Keiser (<jkeiser@opscode.com>)
4
+ # Copyright:: Copyright (c) 2011 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
+ module Mixlib
26
+ class ShellOut
27
+ module Windows
28
+
29
+ include ::Windows::Handle
30
+ include ::Windows::Process
31
+ include ::Windows::Synchronize
32
+
33
+ TIME_SLICE = 0.05
34
+
35
+ #--
36
+ # Missing lots of features from the UNIX version, such as
37
+ # uid, etc.
38
+ def run_command
39
+
40
+ #
41
+ # Create pipes to capture stdout and stderr,
42
+ #
43
+ stdout_read, stdout_write = IO.pipe
44
+ stderr_read, stderr_write = IO.pipe
45
+ open_streams = [ stdout_read, stderr_read ]
46
+
47
+ begin
48
+
49
+ #
50
+ # Set cwd, environment, appname, etc.
51
+ #
52
+ app_name, command_line = command_to_run
53
+ create_process_args = {
54
+ :app_name => app_name,
55
+ :command_line => command_line,
56
+ :startup_info => {
57
+ :stdout => stdout_write,
58
+ :stderr => stderr_write
59
+ },
60
+ :environment => inherit_environment.map { |k,v| "#{k}=#{v}" },
61
+ :close_handles => false
62
+ }
63
+ create_process_args[:cwd] = cwd if cwd
64
+
65
+ #
66
+ # Start the process
67
+ #
68
+ process = Process.create(create_process_args)
69
+ begin
70
+
71
+ #
72
+ # Wait for the process to finish, consuming output as we go
73
+ #
74
+ start_wait = Time.now
75
+ while true
76
+ wait_status = WaitForSingleObject(process.process_handle, 0)
77
+ case wait_status
78
+ when WAIT_OBJECT_0
79
+ # Get process exit code
80
+ exit_code = [0].pack('l')
81
+ unless GetExitCodeProcess(process.process_handle, exit_code)
82
+ raise get_last_error
83
+ end
84
+ @status = ThingThatLooksSortOfLikeAProcessStatus.new
85
+ @status.exitstatus = exit_code.unpack('l').first
86
+
87
+ return self
88
+ when WAIT_TIMEOUT
89
+ # Kill the process
90
+ if (Time.now - start_wait) > timeout
91
+ raise Mixlib::ShellOut::CommandTimeout, "command timed out:\n#{format_for_exception}"
92
+ end
93
+
94
+ consume_output(open_streams, stdout_read, stderr_read)
95
+ else
96
+ raise "Unknown response from WaitForSingleObject(#{process.process_handle}, #{timeout*1000}): #{wait_status}"
97
+ end
98
+
99
+ end
100
+
101
+ ensure
102
+ CloseHandle(process.thread_handle)
103
+ CloseHandle(process.process_handle)
104
+ end
105
+
106
+ ensure
107
+ #
108
+ # Consume all remaining data from the pipes until they are closed
109
+ #
110
+ stdout_write.close
111
+ stderr_write.close
112
+
113
+ while consume_output(open_streams, stdout_read, stderr_read)
114
+ end
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ class ThingThatLooksSortOfLikeAProcessStatus
121
+ attr_accessor :exitstatus
122
+ end
123
+
124
+ def consume_output(open_streams, stdout_read, stderr_read)
125
+ return false if open_streams.length == 0
126
+ ready = IO.select(open_streams, nil, nil, READ_WAIT_TIME)
127
+ return true if ! ready
128
+
129
+ if ready.first.include?(stdout_read)
130
+ begin
131
+ next_chunk = stdout_read.readpartial(READ_SIZE)
132
+ @stdout << next_chunk
133
+ @live_stream << next_chunk if @live_stream
134
+ rescue EOFError
135
+ stdout_read.close
136
+ open_streams.delete(stdout_read)
137
+ end
138
+ end
139
+
140
+ if ready.first.include?(stderr_read)
141
+ begin
142
+ @stderr << stderr_read.readpartial(READ_SIZE)
143
+ rescue EOFError
144
+ stderr_read.close
145
+ open_streams.delete(stderr_read)
146
+ end
147
+ end
148
+
149
+ return true
150
+ end
151
+
152
+ SHOULD_USE_CMD = /['"<>|&%]|\b(?:assoc|break|call|cd|chcp|chdir|cls|color|copy|ctty|date|del|dir|echo|endlocal|erase|exit|for|ftype|goto|if|lfnfor|lh|lock|md|mkdir|move|path|pause|popd|prompt|pushd|rd|rem|ren|rename|rmdir|set|setlocal|shift|start|time|title|truename|type|unlock|ver|verify|vol)\b/
153
+
154
+ def command_to_run
155
+ if command =~ SHOULD_USE_CMD
156
+ [ ENV['COMSPEC'], "cmd /c #{command}" ]
157
+ else
158
+ [ which(command[0,command.index(/\s/) || command.length]), command ]
159
+ end
160
+ end
161
+
162
+ def inherit_environment
163
+ result = {}
164
+ ENV.each_pair do |k,v|
165
+ result[k] = v
166
+ end
167
+
168
+ environment.each_pair do |k,v|
169
+ if v == nil
170
+ result.delete(k)
171
+ else
172
+ result[k] = v
173
+ end
174
+ end
175
+ result
176
+ end
177
+
178
+ def which(cmd)
179
+ return cmd if File.executable? cmd
180
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') + [''] : ['']
181
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
182
+ exts.each { |ext|
183
+ exe = "#{path}/#{cmd}#{ext}"
184
+ return exe if File.executable? exe
185
+ }
186
+ end
187
+ return nil
188
+ end
189
+ end # class
190
+ end
191
+ end
192
+
193
+ #
194
+ # Override module Windows::Process.CreateProcess to fix bug when
195
+ # using both app_name and command_line
196
+ #
197
+ module Windows
198
+ module Process
199
+ API.new('CreateProcess', 'SPPPLLLPPP', 'B')
200
+ end
201
+ end
202
+
203
+ #
204
+ # Override Win32::Process.create to take a proper environment hash
205
+ # so that variables can contain semicolons
206
+ # (submitted patch to owner)
207
+ #
208
+ module Process
209
+ def create(args)
210
+ unless args.kind_of?(Hash)
211
+ raise TypeError, 'Expecting hash-style keyword arguments'
212
+ end
213
+
214
+ valid_keys = %w/
215
+ app_name command_line inherit creation_flags cwd environment
216
+ startup_info thread_inherit process_inherit close_handles with_logon
217
+ domain password
218
+ /
219
+
220
+ valid_si_keys = %/
221
+ startf_flags desktop title x y x_size y_size x_count_chars
222
+ y_count_chars fill_attribute sw_flags stdin stdout stderr
223
+ /
224
+
225
+ # Set default values
226
+ hash = {
227
+ 'app_name' => nil,
228
+ 'creation_flags' => 0,
229
+ 'close_handles' => true
230
+ }
231
+
232
+ # Validate the keys, and convert symbols and case to lowercase strings.
233
+ args.each{ |key, val|
234
+ key = key.to_s.downcase
235
+ unless valid_keys.include?(key)
236
+ raise ArgumentError, "invalid key '#{key}'"
237
+ end
238
+ hash[key] = val
239
+ }
240
+
241
+ si_hash = {}
242
+
243
+ # If the startup_info key is present, validate its subkeys
244
+ if hash['startup_info']
245
+ hash['startup_info'].each{ |key, val|
246
+ key = key.to_s.downcase
247
+ unless valid_si_keys.include?(key)
248
+ raise ArgumentError, "invalid startup_info key '#{key}'"
249
+ end
250
+ si_hash[key] = val
251
+ }
252
+ end
253
+
254
+ # The +command_line+ key is mandatory unless the +app_name+ key
255
+ # is specified.
256
+ unless hash['command_line']
257
+ if hash['app_name']
258
+ hash['command_line'] = hash['app_name']
259
+ hash['app_name'] = nil
260
+ else
261
+ raise ArgumentError, 'command_line or app_name must be specified'
262
+ end
263
+ end
264
+
265
+ # The environment string should be passed as an array of A=B paths, or
266
+ # as a string of ';' separated paths.
267
+ if hash['environment']
268
+ env = hash['environment']
269
+ if !env.respond_to?(:join)
270
+ # Backwards compat for ; separated paths
271
+ env = hash['environment'].split(File::PATH_SEPARATOR)
272
+ end
273
+ # The argument format is a series of null-terminated strings, with an additional null terminator.
274
+ env = env.map { |e| e + "\0" }.join("") + "\0"
275
+ if hash['with_logon']
276
+ env = env.multi_to_wide(e)
277
+ end
278
+ env = [env].pack('p*').unpack('L').first
279
+ else
280
+ env = nil
281
+ end
282
+
283
+ startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
284
+ startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
285
+ procinfo = [0,0,0,0].pack('LLLL')
286
+
287
+ # Process SECURITY_ATTRIBUTE structure
288
+ process_security = 0
289
+ if hash['process_inherit']
290
+ process_security = [0,0,0].pack('LLL')
291
+ process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
292
+ process_security[8,4] = [1].pack('L') # TRUE
293
+ end
294
+
295
+ # Thread SECURITY_ATTRIBUTE structure
296
+ thread_security = 0
297
+ if hash['thread_inherit']
298
+ thread_security = [0,0,0].pack('LLL')
299
+ thread_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
300
+ thread_security[8,4] = [1].pack('L') # TRUE
301
+ end
302
+
303
+ # Automatically handle stdin, stdout and stderr as either IO objects
304
+ # or file descriptors. This won't work for StringIO, however.
305
+ ['stdin', 'stdout', 'stderr'].each{ |io|
306
+ if si_hash[io]
307
+ if si_hash[io].respond_to?(:fileno)
308
+ handle = get_osfhandle(si_hash[io].fileno)
309
+ else
310
+ handle = get_osfhandle(si_hash[io])
311
+ end
312
+
313
+ if handle == INVALID_HANDLE_VALUE
314
+ raise Error, get_last_error
315
+ end
316
+
317
+ # Most implementations of Ruby on Windows create inheritable
318
+ # handles by default, but some do not. RF bug #26988.
319
+ bool = SetHandleInformation(
320
+ handle,
321
+ HANDLE_FLAG_INHERIT,
322
+ HANDLE_FLAG_INHERIT
323
+ )
324
+
325
+ raise Error, get_last_error unless bool
326
+
327
+ si_hash[io] = handle
328
+ si_hash['startf_flags'] ||= 0
329
+ si_hash['startf_flags'] |= STARTF_USESTDHANDLES
330
+ hash['inherit'] = true
331
+ end
332
+ }
333
+
334
+ # The bytes not covered here are reserved (null)
335
+ unless si_hash.empty?
336
+ startinfo[0,4] = [startinfo.size].pack('L')
337
+ startinfo[8,4] = [si_hash['desktop']].pack('p*') if si_hash['desktop']
338
+ startinfo[12,4] = [si_hash['title']].pack('p*') if si_hash['title']
339
+ startinfo[16,4] = [si_hash['x']].pack('L') if si_hash['x']
340
+ startinfo[20,4] = [si_hash['y']].pack('L') if si_hash['y']
341
+ startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
342
+ startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
343
+ startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
344
+ startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
345
+ startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
346
+ startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
347
+ startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
348
+ startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
349
+ startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
350
+ startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
351
+ end
352
+
353
+ if hash['with_logon']
354
+ logon = multi_to_wide(hash['with_logon'])
355
+ domain = multi_to_wide(hash['domain'])
356
+ app = hash['app_name'].nil? ? nil : multi_to_wide(hash['app_name'])
357
+ cmd = hash['command_line'].nil? ? nil : multi_to_wide(hash['command_line'])
358
+ cwd = multi_to_wide(hash['cwd'])
359
+ passwd = multi_to_wide(hash['password'])
360
+
361
+ hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
362
+
363
+ process_ran = CreateProcessWithLogonW(
364
+ logon, # User
365
+ domain, # Domain
366
+ passwd, # Password
367
+ LOGON_WITH_PROFILE, # Logon flags
368
+ app, # App name
369
+ cmd, # Command line
370
+ hash['creation_flags'], # Creation flags
371
+ env, # Environment
372
+ cwd, # Working directory
373
+ startinfo, # Startup Info
374
+ procinfo # Process Info
375
+ )
376
+ else
377
+ process_ran = CreateProcess(
378
+ hash['app_name'], # App name
379
+ hash['command_line'], # Command line
380
+ process_security, # Process attributes
381
+ thread_security, # Thread attributes
382
+ hash['inherit'], # Inherit handles?
383
+ hash['creation_flags'], # Creation flags
384
+ env, # Environment
385
+ hash['cwd'], # Working directory
386
+ startinfo, # Startup Info
387
+ procinfo # Process Info
388
+ )
389
+ end
390
+
391
+ # TODO: Close stdin, stdout and stderr handles in the si_hash unless
392
+ # they're pointing to one of the standard handles already. [Maybe]
393
+ if !process_ran
394
+ raise_last_error("CreateProcess()")
395
+ end
396
+
397
+ # Automatically close the process and thread handles in the
398
+ # PROCESS_INFORMATION struct unless explicitly told not to.
399
+ if hash['close_handles']
400
+ CloseHandle(procinfo[0,4].unpack('L').first)
401
+ CloseHandle(procinfo[4,4].unpack('L').first)
402
+ end
403
+
404
+ ProcessInfo.new(
405
+ procinfo[0,4].unpack('L').first, # hProcess
406
+ procinfo[4,4].unpack('L').first, # hThread
407
+ procinfo[8,4].unpack('L').first, # hProcessId
408
+ procinfo[12,4].unpack('L').first # hThreadId
409
+ )
410
+ end
411
+
412
+ def self.raise_last_error(operation)
413
+ error_string = "#{operation} failed: #{get_last_error}"
414
+ last_error_code = GetLastError()
415
+ if ERROR_CODE_MAP.has_key?(last_error_code)
416
+ raise ERROR_CODE_MAP[last_error_code], error_string
417
+ else
418
+ raise Error, error_string
419
+ end
420
+ end
421
+
422
+ # List from ruby/win32/win32.c
423
+ ERROR_CODE_MAP = {
424
+ ERROR_INVALID_FUNCTION => Errno::EINVAL,
425
+ ERROR_FILE_NOT_FOUND => Errno::ENOENT,
426
+ ERROR_PATH_NOT_FOUND => Errno::ENOENT,
427
+ ERROR_TOO_MANY_OPEN_FILES => Errno::EMFILE,
428
+ ERROR_ACCESS_DENIED => Errno::EACCES,
429
+ ERROR_INVALID_HANDLE => Errno::EBADF,
430
+ ERROR_ARENA_TRASHED => Errno::ENOMEM,
431
+ ERROR_NOT_ENOUGH_MEMORY => Errno::ENOMEM,
432
+ ERROR_INVALID_BLOCK => Errno::ENOMEM,
433
+ ERROR_BAD_ENVIRONMENT => Errno::E2BIG,
434
+ ERROR_BAD_FORMAT => Errno::ENOEXEC,
435
+ ERROR_INVALID_ACCESS => Errno::EINVAL,
436
+ ERROR_INVALID_DATA => Errno::EINVAL,
437
+ ERROR_INVALID_DRIVE => Errno::ENOENT,
438
+ ERROR_CURRENT_DIRECTORY => Errno::EACCES,
439
+ ERROR_NOT_SAME_DEVICE => Errno::EXDEV,
440
+ ERROR_NO_MORE_FILES => Errno::ENOENT,
441
+ ERROR_WRITE_PROTECT => Errno::EROFS,
442
+ ERROR_BAD_UNIT => Errno::ENODEV,
443
+ ERROR_NOT_READY => Errno::ENXIO,
444
+ ERROR_BAD_COMMAND => Errno::EACCES,
445
+ ERROR_CRC => Errno::EACCES,
446
+ ERROR_BAD_LENGTH => Errno::EACCES,
447
+ ERROR_SEEK => Errno::EIO,
448
+ ERROR_NOT_DOS_DISK => Errno::EACCES,
449
+ ERROR_SECTOR_NOT_FOUND => Errno::EACCES,
450
+ ERROR_OUT_OF_PAPER => Errno::EACCES,
451
+ ERROR_WRITE_FAULT => Errno::EIO,
452
+ ERROR_READ_FAULT => Errno::EIO,
453
+ ERROR_GEN_FAILURE => Errno::EACCES,
454
+ ERROR_LOCK_VIOLATION => Errno::EACCES,
455
+ ERROR_SHARING_VIOLATION => Errno::EACCES,
456
+ ERROR_WRONG_DISK => Errno::EACCES,
457
+ ERROR_SHARING_BUFFER_EXCEEDED => Errno::EACCES,
458
+ # ERROR_BAD_NETPATH => Errno::ENOENT,
459
+ # ERROR_NETWORK_ACCESS_DENIED => Errno::EACCES,
460
+ # ERROR_BAD_NET_NAME => Errno::ENOENT,
461
+ ERROR_FILE_EXISTS => Errno::EEXIST,
462
+ ERROR_CANNOT_MAKE => Errno::EACCES,
463
+ ERROR_FAIL_I24 => Errno::EACCES,
464
+ ERROR_INVALID_PARAMETER => Errno::EINVAL,
465
+ ERROR_NO_PROC_SLOTS => Errno::EAGAIN,
466
+ ERROR_DRIVE_LOCKED => Errno::EACCES,
467
+ ERROR_BROKEN_PIPE => Errno::EPIPE,
468
+ ERROR_DISK_FULL => Errno::ENOSPC,
469
+ ERROR_INVALID_TARGET_HANDLE => Errno::EBADF,
470
+ ERROR_INVALID_HANDLE => Errno::EINVAL,
471
+ ERROR_WAIT_NO_CHILDREN => Errno::ECHILD,
472
+ ERROR_CHILD_NOT_COMPLETE => Errno::ECHILD,
473
+ ERROR_DIRECT_ACCESS_HANDLE => Errno::EBADF,
474
+ ERROR_NEGATIVE_SEEK => Errno::EINVAL,
475
+ ERROR_SEEK_ON_DEVICE => Errno::EACCES,
476
+ ERROR_DIR_NOT_EMPTY => Errno::ENOTEMPTY,
477
+ # ERROR_DIRECTORY => Errno::ENOTDIR,
478
+ ERROR_NOT_LOCKED => Errno::EACCES,
479
+ ERROR_BAD_PATHNAME => Errno::ENOENT,
480
+ ERROR_MAX_THRDS_REACHED => Errno::EAGAIN,
481
+ # ERROR_LOCK_FAILED => Errno::EACCES,
482
+ ERROR_ALREADY_EXISTS => Errno::EEXIST,
483
+ ERROR_INVALID_STARTING_CODESEG => Errno::ENOEXEC,
484
+ ERROR_INVALID_STACKSEG => Errno::ENOEXEC,
485
+ ERROR_INVALID_MODULETYPE => Errno::ENOEXEC,
486
+ ERROR_INVALID_EXE_SIGNATURE => Errno::ENOEXEC,
487
+ ERROR_EXE_MARKED_INVALID => Errno::ENOEXEC,
488
+ ERROR_BAD_EXE_FORMAT => Errno::ENOEXEC,
489
+ ERROR_ITERATED_DATA_EXCEEDS_64k => Errno::ENOEXEC,
490
+ ERROR_INVALID_MINALLOCSIZE => Errno::ENOEXEC,
491
+ ERROR_DYNLINK_FROM_INVALID_RING => Errno::ENOEXEC,
492
+ ERROR_IOPL_NOT_ENABLED => Errno::ENOEXEC,
493
+ ERROR_INVALID_SEGDPL => Errno::ENOEXEC,
494
+ ERROR_AUTODATASEG_EXCEEDS_64k => Errno::ENOEXEC,
495
+ ERROR_RING2SEG_MUST_BE_MOVABLE => Errno::ENOEXEC,
496
+ ERROR_RELOC_CHAIN_XEEDS_SEGLIM => Errno::ENOEXEC,
497
+ ERROR_INFLOOP_IN_RELOC_CHAIN => Errno::ENOEXEC,
498
+ ERROR_FILENAME_EXCED_RANGE => Errno::ENOENT,
499
+ ERROR_NESTING_NOT_ALLOWED => Errno::EAGAIN,
500
+ # ERROR_PIPE_LOCAL => Errno::EPIPE,
501
+ ERROR_BAD_PIPE => Errno::EPIPE,
502
+ ERROR_PIPE_BUSY => Errno::EAGAIN,
503
+ ERROR_NO_DATA => Errno::EPIPE,
504
+ ERROR_PIPE_NOT_CONNECTED => Errno::EPIPE,
505
+ ERROR_OPERATION_ABORTED => Errno::EINTR,
506
+ # ERROR_NOT_ENOUGH_QUOTA => Errno::ENOMEM,
507
+ ERROR_MOD_NOT_FOUND => Errno::ENOENT,
508
+ WSAEINTR => Errno::EINTR,
509
+ WSAEBADF => Errno::EBADF,
510
+ # WSAEACCES => Errno::EACCES,
511
+ WSAEFAULT => Errno::EFAULT,
512
+ WSAEINVAL => Errno::EINVAL,
513
+ WSAEMFILE => Errno::EMFILE,
514
+ WSAEWOULDBLOCK => Errno::EWOULDBLOCK,
515
+ WSAEINPROGRESS => Errno::EINPROGRESS,
516
+ WSAEALREADY => Errno::EALREADY,
517
+ WSAENOTSOCK => Errno::ENOTSOCK,
518
+ WSAEDESTADDRREQ => Errno::EDESTADDRREQ,
519
+ WSAEMSGSIZE => Errno::EMSGSIZE,
520
+ WSAEPROTOTYPE => Errno::EPROTOTYPE,
521
+ WSAENOPROTOOPT => Errno::ENOPROTOOPT,
522
+ WSAEPROTONOSUPPORT => Errno::EPROTONOSUPPORT,
523
+ WSAESOCKTNOSUPPORT => Errno::ESOCKTNOSUPPORT,
524
+ WSAEOPNOTSUPP => Errno::EOPNOTSUPP,
525
+ WSAEPFNOSUPPORT => Errno::EPFNOSUPPORT,
526
+ WSAEAFNOSUPPORT => Errno::EAFNOSUPPORT,
527
+ WSAEADDRINUSE => Errno::EADDRINUSE,
528
+ WSAEADDRNOTAVAIL => Errno::EADDRNOTAVAIL,
529
+ WSAENETDOWN => Errno::ENETDOWN,
530
+ WSAENETUNREACH => Errno::ENETUNREACH,
531
+ WSAENETRESET => Errno::ENETRESET,
532
+ WSAECONNABORTED => Errno::ECONNABORTED,
533
+ WSAECONNRESET => Errno::ECONNRESET,
534
+ WSAENOBUFS => Errno::ENOBUFS,
535
+ WSAEISCONN => Errno::EISCONN,
536
+ WSAENOTCONN => Errno::ENOTCONN,
537
+ WSAESHUTDOWN => Errno::ESHUTDOWN,
538
+ WSAETOOMANYREFS => Errno::ETOOMANYREFS,
539
+ # WSAETIMEDOUT => Errno::ETIMEDOUT,
540
+ WSAECONNREFUSED => Errno::ECONNREFUSED,
541
+ WSAELOOP => Errno::ELOOP,
542
+ WSAENAMETOOLONG => Errno::ENAMETOOLONG,
543
+ WSAEHOSTDOWN => Errno::EHOSTDOWN,
544
+ WSAEHOSTUNREACH => Errno::EHOSTUNREACH,
545
+ # WSAEPROCLIM => Errno::EPROCLIM,
546
+ # WSAENOTEMPTY => Errno::ENOTEMPTY,
547
+ WSAEUSERS => Errno::EUSERS,
548
+ WSAEDQUOT => Errno::EDQUOT,
549
+ WSAESTALE => Errno::ESTALE,
550
+ WSAEREMOTE => Errno::EREMOTE
551
+ }
552
+
553
+ module_function :create
554
+ end