win32-process 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES CHANGED
@@ -1,3 +1,13 @@
1
+ = 0.6.2 - 19-Dec-2009
2
+ * Fixed an issue where stdin, stdout and stderr might not be inheritable
3
+ even if the inherit option was set. Thanks go to Michael Buselli for the
4
+ spot and the patch.
5
+ * Added a basic implementation of Process.getrlimit.
6
+ * Added the Process.get_affinity method.
7
+ * Added test-unit 2.x and sys-proctable as development dependencies.
8
+ * Added the :uninstall and :build_gem Rake tasks to the Rakefile.
9
+ * Bumped required version of windows-pr to 1.0.6.
10
+
1
11
  = 0.6.1 - 16-Jul-2009
2
12
  * Added the Process.uid method. This method returns a user id (really, the RID
3
13
  of the SID) by default, but can also take an optional parameter to return
data/README CHANGED
@@ -1,12 +1,14 @@
1
1
  = Description
2
2
  This package provides the fork, wait, wait2, waitpid, and waitpid2 methods
3
3
  for MS Windows. In addition, it provides a different implementation of the
4
- kill method, and a proper implementation of the Process.ppid method.
4
+ kill method, a proper implementation of Process.ppid, and decent analogues
5
+ of Process.getpriority, Process.setpriority and Process.getrlimit.
5
6
 
6
7
  = Prerequisites
7
8
  Ruby 1.8.2 or later.
8
- The 'windows-pr' library, 0.8.6 or later.
9
- The 'sys-proctable' library, 0.7.6 or later (test suite only).
9
+ The windows-pr library, 0.8.6 or later.
10
+ The sys-proctable library, 0.7.6 or later (test suite only).
11
+ The test-unit library, 2.0.3 or later (test suite only).
10
12
 
11
13
  = Supported Platforms
12
14
  This library is supported on Windows 2000 or later.
@@ -100,10 +102,10 @@
100
102
  Other suggestions welcome.
101
103
 
102
104
  == License
103
- Ruby's
105
+ Artistic 2.0
104
106
 
105
107
  == Copyright
106
- (C) 2003-2008 Daniel J. Berger
108
+ (C) 2003-2009 Daniel J. Berger
107
109
  All Rights Reserved
108
110
 
109
111
  == Warranty
data/Rakefile CHANGED
@@ -3,20 +3,33 @@ require 'rake/testtask'
3
3
  require 'rbconfig'
4
4
  include Config
5
5
 
6
- desc 'Install the win32-process package (non-gem)'
6
+ desc 'Install the win32-process library (non-gem)'
7
7
  task :install do
8
- install_dir = File.join(CONFIG["sitelibdir"], 'win32')
9
- Dir.mkdir(install_dir) unless File.exists?(install_dir)
10
- cp 'lib/win32/process.rb', install_dir, :verbose => true
8
+ install_dir = File.join(CONFIG['sitelibdir'], 'win32')
9
+ Dir.mkdir(install_dir) unless File.exists?(install_dir)
10
+ cp 'lib/win32/process.rb', install_dir, :verbose => true
11
11
  end
12
12
 
13
- task :install_gem do
14
- ruby 'win32-process.gemspec'
15
- file = Dir["*.gem"].first
16
- sh "gem install #{file}"
13
+ desc 'Removes the win32-process library (non-gem)'
14
+ task :uninstall do
15
+ file = File.join(CONFIG['sitelibdir'], 'win32', 'process.rb')
16
+ if File.exists?(file)
17
+ rm_f file, :verbose => true
18
+ end
19
+ end
20
+
21
+ desc 'Builds a gem for the win32-process library'
22
+ task :gem do
23
+ spec = eval(IO.read('win32-process.gemspec'))
24
+ Gem::Builder.new(spec).build
25
+ end
26
+
27
+ task :install_gem => [:gem] do
28
+ file = Dir["*.gem"].first
29
+ sh "gem install #{file}"
17
30
  end
18
31
 
19
32
  Rake::TestTask.new do |t|
20
- t.verbose = true
21
- t.warning = true
33
+ t.verbose = true
34
+ t.warning = true
22
35
  end
@@ -12,824 +12,993 @@ require 'windows/security'
12
12
  require 'windows/msvcrt/string'
13
13
 
14
14
  module Process
15
- class Error < RuntimeError; end
15
+ # The Process::Error class is typically raised if any of the custom
16
+ # Process methods fail.
17
+ class Error < RuntimeError; end
16
18
 
17
- # Eliminates redefinition warnings.
18
- undef_method :getpriority, :kill, :ppid, :setpriority
19
- undef_method :wait, :wait2, :waitpid, :waitpid2, :uid
19
+ # Eliminates redefinition warnings.
20
+ undef_method :getpriority, :kill, :getrlimit, :ppid, :setpriority
21
+ undef_method :wait, :wait2, :waitpid, :waitpid2, :uid
20
22
 
21
- # The version of this library
22
- WIN32_PROCESS_VERSION = '0.6.1'
23
+ # The version of the win32-process library
24
+ WIN32_PROCESS_VERSION = '0.6.2'
25
+
26
+ include Windows::Process
27
+ include Windows::Thread
28
+ include Windows::Error
29
+ include Windows::Library
30
+ include Windows::Console
31
+ include Windows::Handle
32
+ include Windows::Security
33
+ include Windows::Synchronize
34
+ include Windows::Window
35
+ include Windows::Unicode
36
+ include Windows::ToolHelper
37
+ include Windows::MSVCRT::String
38
+
39
+ extend Windows::Error
40
+ extend Windows::Process
41
+ extend Windows::Thread
42
+ extend Windows::Security
43
+ extend Windows::Synchronize
44
+ extend Windows::Handle
45
+ extend Windows::Library
46
+ extend Windows::Console
47
+ extend Windows::Unicode
48
+ extend Windows::ToolHelper
49
+ extend Windows::MSVCRT::String
23
50
 
24
- include Windows::Process
25
- include Windows::Thread
26
- include Windows::Error
27
- include Windows::Library
28
- include Windows::Console
29
- include Windows::Handle
30
- include Windows::Security
31
- include Windows::Synchronize
32
- include Windows::Window
33
- include Windows::Unicode
34
- include Windows::ToolHelper
35
- include Windows::MSVCRT::String
36
-
37
- extend Windows::Error
38
- extend Windows::Process
39
- extend Windows::Thread
40
- extend Windows::Security
41
- extend Windows::Synchronize
42
- extend Windows::Handle
43
- extend Windows::Library
44
- extend Windows::Console
45
- extend Windows::Unicode
46
- extend Windows::ToolHelper
47
- extend Windows::MSVCRT::String
51
+ # :stopdoc:
48
52
 
49
- # Used by Process.create
50
- ProcessInfo = Struct.new("ProcessInfo",
51
- :process_handle,
52
- :thread_handle,
53
- :process_id,
54
- :thread_id
55
- )
53
+ # Used by Process.create
54
+ ProcessInfo = Struct.new("ProcessInfo",
55
+ :process_handle,
56
+ :thread_handle,
57
+ :process_id,
58
+ :thread_id
59
+ )
56
60
 
57
- @child_pids = [] # Static variable used for Process.fork
58
- @i = -1 # Static variable used for Process.fork
59
-
60
- # Retrieves the priority class for the specified process id +int+. Unlike
61
- # the default implementation, lower values do not necessarily correspond to
62
- # higher priority classes.
63
- #
64
- # The +kind+ parameter is ignored but present for API compatibility.
65
- # You can only retrieve process information, not process group or user
66
- # information, so it is effectively always Process::PRIO_PROCESS.
67
- #
68
- # Possible return values are:
69
- #
70
- # 32 - Process::NORMAL_PRIORITY_CLASS
71
- # 64 - Process::IDLE_PRIORITY_CLASS
72
- # 128 - Process::HIGH_PRIORITY_CLASS
73
- # 256 - Process::REALTIME_PRIORITY_CLASS
74
- # 16384 - Process::BELOW_NORMAL_PRIORITY_CLASS
75
- # 32768 - Process::ABOVE_NORMAL_PRIORITY_CLASS
76
- #
77
- def getpriority(kind = Process::PRIO_PROCESS, int = nil)
78
- raise ArgumentError unless int
79
-
80
- handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
81
-
82
- if handle == INVALID_HANDLE_VALUE
83
- raise Error, get_last_error
61
+ @child_pids = [] # Static variable used for Process.fork
62
+ @i = -1 # Static variable used for Process.fork
63
+
64
+ # These are probably not defined on MS Windows by default
65
+ unless defined? RLIMIT_CPU
66
+ RLIMIT_CPU = 0 # PerProcessUserTimeLimit
67
+ RLIMIT_FSIZE = 1 # Hard coded at 4TB - 64K (assumes NTFS)
68
+ RLIMIT_AS = 5 # ProcessMemoryLimit
69
+ RLIMIT_RSS = 5 # ProcessMemoryLimit
70
+ RLIMIT_VMEM = 5 # ProcessMemoryLimit
71
+ end
72
+
73
+ # :startdoc:
74
+
75
+ # Returns the process and system affinity mask for the given +pid+, or the
76
+ # current process if no pid is provided. The return value is a two element
77
+ # array, with the first containing the process affinity mask, and the second
78
+ # containing the system affinity mask. Both are decimal values.
79
+ #
80
+ # A process affinity mask is a bit vector indicating the processors that a
81
+ # process is allowed to run on. A system affinity mask is a bit vector in
82
+ # which each bit represents the processors that are configured into a
83
+ # system.
84
+ #
85
+ # Example:
86
+ #
87
+ # # System has 4 processors, current process is allowed to run on all
88
+ # Process.get_affinity # => [[15], [15]]
89
+ #
90
+ # # System has 4 processors, current process only allowed on 1 and 4 only
91
+ # Process.get_affinity # => [[9], [15]]
92
+ #
93
+ # If you want to convert a decimal bit vector into an array of 0's and 1's
94
+ # indicating the flag value of each processor, you can use something like
95
+ # this approach:
96
+ #
97
+ # mask = Process.get_affinity.first
98
+ # (0..mask).to_a.map{ |n| mask[n] }
99
+ #
100
+ def get_affinity(int = Process.pid)
101
+ pmask = 0.chr * 4
102
+ smask = 0.chr * 4
103
+
104
+ if int == Process.pid
105
+ unless GetProcessAffinityMask(GetCurrentProcess(), pmask, smask)
106
+ raise Error, get_last_error
84
107
  end
108
+ else
109
+ begin
110
+ handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
85
111
 
86
- priority_class = GetPriorityClass(handle)
112
+ if handle == INVALID_HANDLE_VALUE
113
+ raise Error, get_last_error
114
+ end
87
115
 
88
- if priority_class == 0
89
- raise Error, get_last_error
116
+ unless GetProcessAffinityMask(handle, pmask, smask)
117
+ raise Error, get_last_error
118
+ end
119
+ ensure
120
+ CloseHandle(handle) if handle != INVALID_HANDLE_VALUE
90
121
  end
91
-
92
- priority_class
93
- end
94
-
95
- # Sets the priority class for the specified process id +int+.
96
- #
97
- # The +kind+ parameter is ignored but present for API compatibility.
98
- # You can only retrieve process information, not process group or user
99
- # information, so it is effectively always Process::PRIO_PROCESS.
100
- #
101
- # Possible +int_priority+ values are:
102
- #
103
- # * Process::NORMAL_PRIORITY_CLASS
104
- # * Process::IDLE_PRIORITY_CLASS
105
- # * Process::HIGH_PRIORITY_CLASS
106
- # * Process::REALTIME_PRIORITY_CLASS
107
- # * Process::BELOW_NORMAL_PRIORITY_CLASS
108
- # * Process::ABOVE_NORMAL_PRIORITY_CLASS
109
- #
110
- def setpriority(kind = nil, int = nil, int_priority = nil)
111
- raise ArgumentError unless int
112
- raise ArgumentError unless int_priority
113
-
114
- handle = OpenProcess(PROCESS_SET_INFORMATION, 0 , int)
115
-
116
- if handle == INVALID_HANDLE_VALUE
117
- raise Error, get_last_error
122
+ end
123
+
124
+ pmask = pmask.unpack('L').first
125
+ smask = smask.unpack('L').first
126
+
127
+ [pmask, smask]
128
+ end
129
+
130
+ # Gets the resource limit of the current process. Only a limited number
131
+ # of flags are supported.
132
+ #
133
+ # Process::RLIMIT_CPU
134
+ # Process::RLIMIT_FSIZE
135
+ # Process::RLIMIT_AS
136
+ # Process::RLIMIT_RSS
137
+ # Process::RLIMIT_VMEM
138
+ #
139
+ # The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants
140
+ # all refer to the Process memory limit. The Process::RLIMIT_CPU constant
141
+ # refers to the per process user time limit. The Process::RLIMIT_FSIZE
142
+ # constant is hard coded to the maximum file size on an NTFS filesystem,
143
+ # approximately 4TB.
144
+ #
145
+ # While a two element array is returned in order to comply with the spec,
146
+ # there is no separate hard and soft limit. The values will always be the
147
+ # same.
148
+ #
149
+ # If [0,0] is returned then it means no limit has been set.
150
+ #
151
+ def getrlimit(resource)
152
+ # Strictly for API compatibility (actually 4 GB on FAT32)
153
+ # TODO: Check FS type. If FAT32, return 4 GB.
154
+ if resource == RLIMIT_FSIZE
155
+ return ((1024**4) * 4) - (1024 * 64)
156
+ end
157
+
158
+ # Put the current Ruby process in its own job unless it's already
159
+ # part of a job.
160
+ bool = 0.chr * 4
161
+ IsProcessInJob(GetCurrentProcess(), nil, bool)
162
+ bool = bool.unpack('L').first == 0 ? false : true
163
+
164
+ unless bool
165
+ job_name = 'ruby_' + Time.now.to_s
166
+
167
+ # Create a job object and add the current process to it
168
+ handle = CreateJobObject(nil, job_name)
169
+
170
+ if handle == 0
171
+ raise Error, get_last_error
118
172
  end
173
+ end
119
174
 
120
- unless SetPriorityClass(handle, int_priority)
121
- raise Error, get_last_error
175
+ begin
176
+ unless bool
177
+ unless AssignProcessToJobObject(handle, GetCurrentProcess())
178
+ raise Error, get_last_error
179
+ end
122
180
  end
123
181
 
124
- return 0 # Match the spec
125
- end
126
-
127
- # Returns the uid of the current process. Specifically, it returns the
128
- # RID of the SID associated with the owner of the process.
129
- #
130
- # If +sid+ is set to true, then a binary sid is returned. Otherwise, a
131
- # numeric id is returned (the default).
132
- #--
133
- # The Process.uid method in core Ruby always returns 0 on MS Windows.
134
- #
135
- def uid(sid = false)
136
- token = 0.chr * 4
137
-
138
- unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
139
- raise Error, get_last_error
140
- end
182
+ buf = 0.chr * 112 # sizeof(struct JOBJECT_EXTENDED_LIMIT_INFORMATION)
183
+ val = nil # value returned at end of method
141
184
 
142
- token = token.unpack('V')[0]
143
- rlength = 0.chr * 4
144
- tuser = 0.chr * 512
185
+ case resource
186
+ when RLIMIT_CPU
187
+ buf[16,4] = [JOB_OBJECT_LIMIT_PROCESS_TIME].pack('L')
188
+ when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
189
+ buf[16,4] = [JOB_OBJECT_LIMIT_PROCESS_MEMORY].pack('L')
190
+ end
145
191
 
146
- bool = GetTokenInformation(
147
- token,
148
- TokenUser,
149
- tuser,
150
- tuser.size,
151
- rlength
192
+ bool = QueryInformationJobObject(
193
+ handle,
194
+ JobObjectExtendedLimitInformation,
195
+ buf,
196
+ buf.size,
197
+ nil
152
198
  )
153
199
 
154
200
  unless bool
155
- raise Error, get_last_error
201
+ raise Error, get_last_error
156
202
  end
157
203
 
158
- lsid = tuser[8, (rlength.unpack('L').first - 8)]
159
-
160
- if sid
161
- lsid
162
- else
163
- sid_addr = [lsid].pack('p*').unpack('L')[0]
164
- sid_buf = 0.chr * 80
165
- sid_ptr = 0.chr * 4
166
-
167
- unless ConvertSidToStringSid(sid_addr, sid_ptr)
168
- raise Error, get_last_error
169
- end
170
-
171
- strcpy(sid_buf, sid_ptr.unpack('L')[0])
172
- sid_buf.strip.split('-').last.to_i
204
+ case resource
205
+ when Process::RLIMIT_CPU
206
+ val = buf[0,8].unpack('Q').first
207
+ when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
208
+ val = buf[96,4].unpack('L').first
173
209
  end
174
- end
175
-
176
- # Waits for the given child process to exit and returns that pid.
177
- #
178
- # Note that the $? (Process::Status) global variable is NOT set. This
179
- # may be addressed in a future release.
180
- #
181
- def waitpid(pid)
182
- exit_code = [0].pack('L')
183
- handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
184
-
185
- if handle == INVALID_HANDLE_VALUE
186
- raise Error, get_last_error
210
+ ensure
211
+ CloseHandle(handle)
212
+ end
213
+
214
+ [val, val] # Return an array of two values to comply with spec
215
+ end
216
+
217
+ # Retrieves the priority class for the specified process id +int+. Unlike
218
+ # the default implementation, lower values do not necessarily correspond to
219
+ # higher priority classes.
220
+ #
221
+ # The +kind+ parameter is ignored but present for API compatibility.
222
+ # You can only retrieve process information, not process group or user
223
+ # information, so it is effectively always Process::PRIO_PROCESS.
224
+ #
225
+ # Possible return values are:
226
+ #
227
+ # 32 - Process::NORMAL_PRIORITY_CLASS
228
+ # 64 - Process::IDLE_PRIORITY_CLASS
229
+ # 128 - Process::HIGH_PRIORITY_CLASS
230
+ # 256 - Process::REALTIME_PRIORITY_CLASS
231
+ # 16384 - Process::BELOW_NORMAL_PRIORITY_CLASS
232
+ # 32768 - Process::ABOVE_NORMAL_PRIORITY_CLASS
233
+ #
234
+ def getpriority(kind = Process::PRIO_PROCESS, int = nil)
235
+ raise ArgumentError unless int
236
+
237
+ handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
238
+
239
+ if handle == INVALID_HANDLE_VALUE
240
+ raise Error, get_last_error
241
+ end
242
+
243
+ priority_class = GetPriorityClass(handle)
244
+
245
+ if priority_class == 0
246
+ raise Error, get_last_error
247
+ end
248
+
249
+ priority_class
250
+ end
251
+
252
+ # Sets the priority class for the specified process id +int+.
253
+ #
254
+ # The +kind+ parameter is ignored but present for API compatibility.
255
+ # You can only retrieve process information, not process group or user
256
+ # information, so it is effectively always Process::PRIO_PROCESS.
257
+ #
258
+ # Possible +int_priority+ values are:
259
+ #
260
+ # * Process::NORMAL_PRIORITY_CLASS
261
+ # * Process::IDLE_PRIORITY_CLASS
262
+ # * Process::HIGH_PRIORITY_CLASS
263
+ # * Process::REALTIME_PRIORITY_CLASS
264
+ # * Process::BELOW_NORMAL_PRIORITY_CLASS
265
+ # * Process::ABOVE_NORMAL_PRIORITY_CLASS
266
+ #
267
+ def setpriority(kind = nil, int = nil, int_priority = nil)
268
+ raise ArgumentError unless int
269
+ raise ArgumentError unless int_priority
270
+
271
+ handle = OpenProcess(PROCESS_SET_INFORMATION, 0 , int)
272
+
273
+ if handle == INVALID_HANDLE_VALUE
274
+ raise Error, get_last_error
275
+ end
276
+
277
+ unless SetPriorityClass(handle, int_priority)
278
+ raise Error, get_last_error
279
+ end
280
+
281
+ return 0 # Match the spec
282
+ end
283
+
284
+ # Returns the uid of the current process. Specifically, it returns the
285
+ # RID of the SID associated with the owner of the process.
286
+ #
287
+ # If +sid+ is set to true, then a binary sid is returned. Otherwise, a
288
+ # numeric id is returned (the default).
289
+ #--
290
+ # The Process.uid method in core Ruby always returns 0 on MS Windows.
291
+ #
292
+ def uid(sid = false)
293
+ token = 0.chr * 4
294
+
295
+ unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
296
+ raise Error, get_last_error
297
+ end
298
+
299
+ token = token.unpack('V')[0]
300
+ rlength = 0.chr * 4
301
+ tuser = 0.chr * 512
302
+
303
+ bool = GetTokenInformation(
304
+ token,
305
+ TokenUser,
306
+ tuser,
307
+ tuser.size,
308
+ rlength
309
+ )
310
+
311
+ unless bool
312
+ raise Error, get_last_error
313
+ end
314
+
315
+ lsid = tuser[8, (rlength.unpack('L').first - 8)]
316
+
317
+ if sid
318
+ lsid
319
+ else
320
+ sid_addr = [lsid].pack('p*').unpack('L')[0]
321
+ sid_buf = 0.chr * 80
322
+ sid_ptr = 0.chr * 4
323
+
324
+ unless ConvertSidToStringSid(sid_addr, sid_ptr)
325
+ raise Error, get_last_error
187
326
  end
188
-
189
- # TODO: update the $? global variable (if/when possible)
190
- status = WaitForSingleObject(handle, INFINITE)
191
-
192
- begin
193
- unless GetExitCodeProcess(handle, exit_code)
194
- raise Error, get_last_error
195
- end
196
- ensure
197
- CloseHandle(handle)
327
+
328
+ strcpy(sid_buf, sid_ptr.unpack('L')[0])
329
+ sid_buf.strip.split('-').last.to_i
330
+ end
331
+ end
332
+
333
+ # Waits for the given child process to exit and returns that pid.
334
+ #
335
+ # Note that the $? (Process::Status) global variable is NOT set. This
336
+ # may be addressed in a future release.
337
+ #
338
+ def waitpid(pid)
339
+ exit_code = [0].pack('L')
340
+ handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
341
+
342
+ if handle == INVALID_HANDLE_VALUE
343
+ raise Error, get_last_error
344
+ end
345
+
346
+ # TODO: update the $? global variable (if/when possible)
347
+ status = WaitForSingleObject(handle, INFINITE)
348
+
349
+ begin
350
+ unless GetExitCodeProcess(handle, exit_code)
351
+ raise Error, get_last_error
198
352
  end
353
+ ensure
354
+ CloseHandle(handle)
355
+ end
199
356
 
200
- @child_pids.delete(pid)
357
+ @child_pids.delete(pid)
201
358
 
202
- # TODO: update the $? global variable (if/when possible)
203
- exit_code = exit_code.unpack('L').first
359
+ # TODO: update the $? global variable (if/when possible)
360
+ exit_code = exit_code.unpack('L').first
204
361
 
205
- pid
206
- end
362
+ pid
363
+ end
207
364
 
208
- # Waits for the given child process to exit and returns an array containing
209
- # the process id and the exit status.
210
- #
211
- # Note that the $? (Process::Status) global variable is NOT set. This
212
- # may be addressed in a future release if/when possible.
213
- #--
214
- # Ruby does not provide a way to hook into $? so there's no way for us
215
- # to set it.
216
- #
217
- def waitpid2(pid)
218
- exit_code = [0].pack('L')
219
- handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
220
-
221
- if handle == INVALID_HANDLE_VALUE
222
- raise Error, get_last_error
223
- end
224
-
225
- # TODO: update the $? global variable (if/when possible)
226
- status = WaitForSingleObject(handle, INFINITE)
227
-
228
- begin
229
- unless GetExitCodeProcess(handle, exit_code)
230
- raise Error, get_last_error
231
- end
232
- ensure
233
- CloseHandle(handle)
365
+ # Waits for the given child process to exit and returns an array containing
366
+ # the process id and the exit status.
367
+ #
368
+ # Note that the $? (Process::Status) global variable is NOT set. This
369
+ # may be addressed in a future release if/when possible.
370
+ #--
371
+ # Ruby does not provide a way to hook into $? so there's no way for us
372
+ # to set it.
373
+ #
374
+ def waitpid2(pid)
375
+ exit_code = [0].pack('L')
376
+ handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
377
+
378
+ if handle == INVALID_HANDLE_VALUE
379
+ raise Error, get_last_error
380
+ end
381
+
382
+ # TODO: update the $? global variable (if/when possible)
383
+ status = WaitForSingleObject(handle, INFINITE)
384
+
385
+ begin
386
+ unless GetExitCodeProcess(handle, exit_code)
387
+ raise Error, get_last_error
234
388
  end
235
-
236
- @child_pids.delete(pid)
237
-
238
- # TODO: update the $? global variable (if/when possible)
239
- exit_code = exit_code.unpack('L').first
240
-
241
- [pid, exit_code]
242
- end
389
+ ensure
390
+ CloseHandle(handle)
391
+ end
392
+
393
+ @child_pids.delete(pid)
394
+
395
+ # TODO: update the $? global variable (if/when possible)
396
+ exit_code = exit_code.unpack('L').first
397
+
398
+ [pid, exit_code]
399
+ end
243
400
 
244
- # Sends the given +signal+ to an array of process id's. The +signal+ may
245
- # be any value from 0 to 9, or the special strings 'SIGINT' (or 'INT'),
246
- # 'SIGBRK' (or 'BRK') and 'SIGKILL' (or 'KILL'). An array of successfully
247
- # killed pids is returned.
248
- #
249
- # Signal 0 merely tests if the process is running without killing it.
250
- # Signal 2 sends a CTRL_C_EVENT to the process.
251
- # Signal 3 sends a CTRL_BRK_EVENT to the process.
252
- # Signal 9 kills the process in a harsh manner.
253
- # Signals 1 and 4-8 kill the process in a nice manner.
254
- #
255
- # SIGINT/INT corresponds to signal 2
256
- # SIGBRK/BRK corresponds to signal 3
257
- # SIGKILL/KILL corresponds to signal 9
258
- #
259
- # Signals 2 and 3 only affect console processes, and then only if the
260
- # process was created with the CREATE_NEW_PROCESS_GROUP flag.
261
- #
262
- def kill(signal, *pids)
263
- case signal
264
- when 'SIGINT', 'INT'
265
- signal = 2
266
- when 'SIGBRK', 'BRK'
267
- signal = 3
268
- when 'SIGKILL', 'KILL'
269
- signal = 9
270
- when 0..9
271
- # Do nothing
272
- else
273
- raise Error, "Invalid signal '#{signal}'"
274
- end
401
+ # Sends the given +signal+ to an array of process id's. The +signal+ may
402
+ # be any value from 0 to 9, or the special strings 'SIGINT' (or 'INT'),
403
+ # 'SIGBRK' (or 'BRK') and 'SIGKILL' (or 'KILL'). An array of successfully
404
+ # killed pids is returned.
405
+ #
406
+ # Signal 0 merely tests if the process is running without killing it.
407
+ # Signal 2 sends a CTRL_C_EVENT to the process.
408
+ # Signal 3 sends a CTRL_BRK_EVENT to the process.
409
+ # Signal 9 kills the process in a harsh manner.
410
+ # Signals 1 and 4-8 kill the process in a nice manner.
411
+ #
412
+ # SIGINT/INT corresponds to signal 2
413
+ # SIGBRK/BRK corresponds to signal 3
414
+ # SIGKILL/KILL corresponds to signal 9
415
+ #
416
+ # Signals 2 and 3 only affect console processes, and then only if the
417
+ # process was created with the CREATE_NEW_PROCESS_GROUP flag.
418
+ #
419
+ def kill(signal, *pids)
420
+ case signal
421
+ when 'SIGINT', 'INT'
422
+ signal = 2
423
+ when 'SIGBRK', 'BRK'
424
+ signal = 3
425
+ when 'SIGKILL', 'KILL'
426
+ signal = 9
427
+ when 0..9
428
+ # Do nothing
429
+ else
430
+ raise Error, "Invalid signal '#{signal}'"
431
+ end
275
432
 
276
- killed_pids = []
433
+ killed_pids = []
277
434
 
278
- pids.each{ |pid|
279
- # Send the signal to the current process if the pid is zero
280
- if pid == 0
281
- pid = Process.pid
282
- end
435
+ pids.each{ |pid|
436
+ # Send the signal to the current process if the pid is zero
437
+ if pid == 0
438
+ pid = Process.pid
439
+ end
283
440
 
284
- # No need for full access if the signal is zero
285
- if signal == 0
286
- access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
287
- handle = OpenProcess(access, 0 , pid)
288
- else
289
- handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
290
- end
441
+ # No need for full access if the signal is zero
442
+ if signal == 0
443
+ access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
444
+ handle = OpenProcess(access, 0 , pid)
445
+ else
446
+ handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
447
+ end
291
448
 
292
- begin
293
- case signal
294
- when 0
295
- if handle != 0
296
- killed_pids.push(pid)
297
- else
298
- # If ERROR_ACCESS_DENIED is returned, we know it's running
299
- if GetLastError() == ERROR_ACCESS_DENIED
300
- killed_pids.push(pid)
301
- else
302
- raise Error, get_last_error
303
- end
304
- end
305
- when 2
306
- if GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)
307
- killed_pids.push(pid)
308
- end
309
- when 3
310
- if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)
311
- killed_pids.push(pid)
312
- end
313
- when 9
314
- if TerminateProcess(handle, pid)
315
- killed_pids.push(pid)
316
- @child_pids.delete(pid)
317
- else
318
- raise Error, get_last_error
319
- end
320
- else
321
- if handle != 0
322
- thread_id = [0].pack('L')
323
- dll = 'kernel32'
324
- eproc = 'ExitProcess'
325
-
326
- thread = CreateRemoteThread(
327
- handle,
328
- 0,
329
- 0,
330
- GetProcAddress(GetModuleHandle(dll), eproc),
331
- 0,
332
- 0,
333
- thread_id
334
- )
335
-
336
- if thread
337
- WaitForSingleObject(thread, 5)
338
- killed_pids.push(pid)
339
- @child_pids.delete(pid)
340
- else
341
- raise Error, get_last_error
342
- end
343
- else
344
- raise Error, get_last_error
345
- end
346
- @child_pids.delete(pid)
449
+ begin
450
+ case signal
451
+ when 0
452
+ if handle != 0
453
+ killed_pids.push(pid)
454
+ else
455
+ # If ERROR_ACCESS_DENIED is returned, we know it's running
456
+ if GetLastError() == ERROR_ACCESS_DENIED
457
+ killed_pids.push(pid)
458
+ else
459
+ raise Error, get_last_error
460
+ end
347
461
  end
348
- ensure
349
- CloseHandle(handle) unless handle == INVALID_HANDLE_VALUE
350
- end
351
- }
462
+ when 2
463
+ if GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)
464
+ killed_pids.push(pid)
465
+ end
466
+ when 3
467
+ if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)
468
+ killed_pids.push(pid)
469
+ end
470
+ when 9
471
+ if TerminateProcess(handle, pid)
472
+ killed_pids.push(pid)
473
+ @child_pids.delete(pid)
474
+ else
475
+ raise Error, get_last_error
476
+ end
477
+ else
478
+ if handle != 0
479
+ thread_id = [0].pack('L')
480
+ dll = 'kernel32'
481
+ eproc = 'ExitProcess'
482
+
483
+ thread = CreateRemoteThread(
484
+ handle,
485
+ 0,
486
+ 0,
487
+ GetProcAddress(GetModuleHandle(dll), eproc),
488
+ 0,
489
+ 0,
490
+ thread_id
491
+ )
492
+
493
+ if thread
494
+ WaitForSingleObject(thread, 5)
495
+ killed_pids.push(pid)
496
+ @child_pids.delete(pid)
497
+ else
498
+ raise Error, get_last_error
499
+ end
500
+ else
501
+ raise Error, get_last_error
502
+ end
503
+
504
+ @child_pids.delete(pid)
505
+ end
506
+ ensure
507
+ CloseHandle(handle) unless handle == INVALID_HANDLE_VALUE
508
+ end
509
+ }
352
510
 
353
- killed_pids
354
- end
511
+ killed_pids
512
+ end
355
513
 
356
- # Process.create(key => value, ...) => ProcessInfo
357
- #
358
- # This is a wrapper for the CreateProcess() function. It executes a process,
359
- # returning a ProcessInfo struct. It accepts a hash as an argument.
360
- # There are several primary keys:
514
+ # Process.create(key => value, ...) => ProcessInfo
515
+ #
516
+ # This is a wrapper for the CreateProcess() function. It executes a process,
517
+ # returning a ProcessInfo struct. It accepts a hash as an argument.
518
+ # There are several primary keys:
361
519
  #
362
- # * command_line (mandatory)
363
- # * app_name (default: nil)
364
- # * inherit (default: false)
365
- # * process_inherit (default: false)
366
- # * thread_inherit (default: false)
367
- # * creation_flags (default: 0)
368
- # * cwd (default: Dir.pwd)
369
- # * startup_info (default: nil)
370
- # * environment (default: nil)
371
- # * close_handles (default: true)
372
- # * with_logon (default: nil)
373
- # * domain (default: nil)
374
- # * password (default: nil)
520
+ # * command_line (mandatory)
521
+ # * app_name (default: nil)
522
+ # * inherit (default: false)
523
+ # * process_inherit (default: false)
524
+ # * thread_inherit (default: false)
525
+ # * creation_flags (default: 0)
526
+ # * cwd (default: Dir.pwd)
527
+ # * startup_info (default: nil)
528
+ # * environment (default: nil)
529
+ # * close_handles (default: true)
530
+ # * with_logon (default: nil)
531
+ # * domain (default: nil)
532
+ # * password (default: nil)
375
533
  #
376
- # Of these, the 'command_line' or 'app_name' must be specified or an
377
- # error is raised. Both may be set individually, but 'command_line' should
378
- # be preferred if only one of them is set because it does not (necessarily)
379
- # require an explicit path or extension to work.
380
- #
381
- # The 'domain' and 'password' options are only relevent in the context
382
- # of 'with_logon'.
534
+ # Of these, the 'command_line' or 'app_name' must be specified or an
535
+ # error is raised. Both may be set individually, but 'command_line' should
536
+ # be preferred if only one of them is set because it does not (necessarily)
537
+ # require an explicit path or extension to work.
538
+ #
539
+ # The 'domain' and 'password' options are only relevent in the context
540
+ # of 'with_logon'.
383
541
  #
384
- # The startup_info key takes a hash. Its keys are attributes that are
385
- # part of the StartupInfo struct, and are generally only meaningful for
386
- # GUI or console processes. See the documentation on CreateProcess()
387
- # and the StartupInfo struct on MSDN for more information.
542
+ # The startup_info key takes a hash. Its keys are attributes that are
543
+ # part of the StartupInfo struct, and are generally only meaningful for
544
+ # GUI or console processes. See the documentation on CreateProcess()
545
+ # and the StartupInfo struct on MSDN for more information.
388
546
  #
389
- # * desktop
390
- # * title
391
- # * x
392
- # * y
393
- # * x_size
394
- # * y_size
395
- # * x_count_chars
396
- # * y_count_chars
397
- # * fill_attribute
398
- # * sw_flags
399
- # * startf_flags
400
- # * stdin
401
- # * stdout
402
- # * stderr
403
- #
404
- # The relevant constants for 'creation_flags', 'sw_flags' and 'startf_flags'
405
- # are included in the Windows::Process, Windows::Console and Windows::Window
406
- # modules. These come with the windows-pr library, a prerequisite of this
407
- # library. Note that the 'stdin', 'stdout' and 'stderr' options can be
408
- # either Ruby IO objects or file descriptors (i.e. a fileno). However,
409
- # StringIO objects are not currently supported.
410
- #
411
- # If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
412
- # is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
413
- # automatically OR'd to the +startf_flags+ value.
414
- #
415
- # The ProcessInfo struct contains the following members:
416
- #
417
- # * process_handle - The handle to the newly created process.
418
- # * thread_handle - The handle to the primary thread of the process.
419
- # * process_id - Process ID.
420
- # * thread_id - Thread ID.
421
- #
422
- # If the 'close_handles' option is set to true (the default) then the
423
- # process_handle and the thread_handle are automatically closed for you
424
- # before the ProcessInfo struct is returned.
425
- #
426
- # If the 'with_logon' option is set, then the process runs the specified
427
- # executable file in the security context of the specified credentials.
428
- #
429
- def create(args)
430
- unless args.kind_of?(Hash)
431
- raise TypeError, 'Expecting hash-style keyword arguments'
547
+ # * desktop
548
+ # * title
549
+ # * x
550
+ # * y
551
+ # * x_size
552
+ # * y_size
553
+ # * x_count_chars
554
+ # * y_count_chars
555
+ # * fill_attribute
556
+ # * sw_flags
557
+ # * startf_flags
558
+ # * stdin
559
+ # * stdout
560
+ # * stderr
561
+ #
562
+ # The relevant constants for 'creation_flags', 'sw_flags' and 'startf_flags'
563
+ # are included in the Windows::Process, Windows::Console and Windows::Window
564
+ # modules. These come with the windows-pr library, a prerequisite of this
565
+ # library. Note that the 'stdin', 'stdout' and 'stderr' options can be
566
+ # either Ruby IO objects or file descriptors (i.e. a fileno). However,
567
+ # StringIO objects are not currently supported.
568
+ #
569
+ # If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
570
+ # is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
571
+ # automatically OR'd to the +startf_flags+ value.
572
+ #
573
+ # The ProcessInfo struct contains the following members:
574
+ #
575
+ # * process_handle - The handle to the newly created process.
576
+ # * thread_handle - The handle to the primary thread of the process.
577
+ # * process_id - Process ID.
578
+ # * thread_id - Thread ID.
579
+ #
580
+ # If the 'close_handles' option is set to true (the default) then the
581
+ # process_handle and the thread_handle are automatically closed for you
582
+ # before the ProcessInfo struct is returned.
583
+ #
584
+ # If the 'with_logon' option is set, then the process runs the specified
585
+ # executable file in the security context of the specified credentials.
586
+ #
587
+ def create(args)
588
+ unless args.kind_of?(Hash)
589
+ raise TypeError, 'Expecting hash-style keyword arguments'
590
+ end
591
+
592
+ valid_keys = %w/
593
+ app_name command_line inherit creation_flags cwd environment
594
+ startup_info thread_inherit process_inherit close_handles with_logon
595
+ domain password
596
+ /
597
+
598
+ valid_si_keys = %/
599
+ startf_flags desktop title x y x_size y_size x_count_chars
600
+ y_count_chars fill_attribute sw_flags stdin stdout stderr
601
+ /
602
+
603
+ # Set default values
604
+ hash = {
605
+ 'app_name' => nil,
606
+ 'creation_flags' => 0,
607
+ 'close_handles' => true
608
+ }
609
+
610
+ # Validate the keys, and convert symbols and case to lowercase strings.
611
+ args.each{ |key, val|
612
+ key = key.to_s.downcase
613
+ unless valid_keys.include?(key)
614
+ raise Error, "invalid key '#{key}'"
432
615
  end
433
-
434
- valid_keys = %w/
435
- app_name command_line inherit creation_flags cwd environment
436
- startup_info thread_inherit process_inherit close_handles with_logon
437
- domain password
438
- /
439
-
440
- valid_si_keys = %/
441
- startf_flags desktop title x y x_size y_size x_count_chars
442
- y_count_chars fill_attribute sw_flags stdin stdout stderr
443
- /
444
-
445
- # Set default values
446
- hash = {
447
- 'app_name' => nil,
448
- 'creation_flags' => 0,
449
- 'close_handles' => true
616
+ hash[key] = val
617
+ }
618
+
619
+ si_hash = {}
620
+
621
+ # If the startup_info key is present, validate its subkeys
622
+ if hash['startup_info']
623
+ hash['startup_info'].each{ |key, val|
624
+ key = key.to_s.downcase
625
+ unless valid_si_keys.include?(key)
626
+ raise Error, "invalid startup_info key '#{key}'"
627
+ end
628
+ si_hash[key] = val
450
629
  }
451
-
452
- # Validate the keys, and convert symbols and case to lowercase strings.
453
- args.each{ |key, val|
454
- key = key.to_s.downcase
455
- unless valid_keys.include?(key)
456
- raise Error, "invalid key '#{key}'"
457
- end
458
- hash[key] = val
459
- }
460
-
461
- si_hash = {}
462
-
463
- # If the startup_info key is present, validate its subkeys
464
- if hash['startup_info']
465
- hash['startup_info'].each{ |key, val|
466
- key = key.to_s.downcase
467
- unless valid_si_keys.include?(key)
468
- raise Error, "invalid startup_info key '#{key}'"
469
- end
470
- si_hash[key] = val
471
- }
472
- end
473
-
474
- # The +command_line+ key is mandatory unless the +app_name+ key
475
- # is specified.
476
- unless hash['command_line']
477
- if hash['app_name']
478
- hash['command_line'] = hash['app_name']
479
- hash['app_name'] = nil
480
- else
481
- raise Error, 'command_line or app_name must be specified'
482
- end
630
+ end
631
+
632
+ # The +command_line+ key is mandatory unless the +app_name+ key
633
+ # is specified.
634
+ unless hash['command_line']
635
+ if hash['app_name']
636
+ hash['command_line'] = hash['app_name']
637
+ hash['app_name'] = nil
638
+ else
639
+ raise Error, 'command_line or app_name must be specified'
483
640
  end
641
+ end
484
642
 
485
- # The environment string should be passed as a string of ';' separated
486
- # paths.
487
- if hash['environment']
488
- env = hash['environment'].split(File::PATH_SEPARATOR) << 0.chr
489
- if hash['with_logon']
490
- env = env.map{ |e| multi_to_wide(e) }
491
- env = [env.join("\0\0")].pack('p*').unpack('L').first
492
- else
493
- env = [env.join("\0")].pack('p*').unpack('L').first
494
- end
643
+ # The environment string should be passed as a string of ';' separated
644
+ # paths.
645
+ if hash['environment']
646
+ env = hash['environment'].split(File::PATH_SEPARATOR) << 0.chr
647
+ if hash['with_logon']
648
+ env = env.map{ |e| multi_to_wide(e) }
649
+ env = [env.join("\0\0")].pack('p*').unpack('L').first
495
650
  else
496
- env = nil
497
- end
498
-
499
- startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
500
- startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
501
- procinfo = [0,0,0,0].pack('LLLL')
502
-
503
- # Process SECURITY_ATTRIBUTE structure
504
- process_security = 0
505
- if hash['process_inherit']
506
- process_security = [0,0,0].pack('LLL')
507
- process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
508
- process_security[8,4] = [1].pack('L') # TRUE
651
+ env = [env.join("\0")].pack('p*').unpack('L').first
509
652
  end
510
-
511
- # Thread SECURITY_ATTRIBUTE structure
512
- thread_security = 0
513
- if hash['thread_inherit']
514
- thread_security = [0,0,0].pack('LLL')
515
- thread_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
516
- thread_security[8,4] = [1].pack('L') # TRUE
517
- end
518
-
519
- # Automatically handle stdin, stdout and stderr as either IO objects
520
- # or file descriptors. This won't work for StringIO, however.
521
- ['stdin', 'stdout', 'stderr'].each{ |io|
522
- if si_hash[io]
523
- if si_hash[io].respond_to?(:fileno)
524
- handle = get_osfhandle(si_hash[io].fileno)
525
- else
526
- handle = get_osfhandle(si_hash[io])
527
- end
653
+ else
654
+ env = nil
655
+ end
656
+
657
+ startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
658
+ startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
659
+ procinfo = [0,0,0,0].pack('LLLL')
660
+
661
+ # Process SECURITY_ATTRIBUTE structure
662
+ process_security = 0
663
+ if hash['process_inherit']
664
+ process_security = [0,0,0].pack('LLL')
665
+ process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
666
+ process_security[8,4] = [1].pack('L') # TRUE
667
+ end
668
+
669
+ # Thread SECURITY_ATTRIBUTE structure
670
+ thread_security = 0
671
+ if hash['thread_inherit']
672
+ thread_security = [0,0,0].pack('LLL')
673
+ thread_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
674
+ thread_security[8,4] = [1].pack('L') # TRUE
675
+ end
676
+
677
+ # Automatically handle stdin, stdout and stderr as either IO objects
678
+ # or file descriptors. This won't work for StringIO, however.
679
+ ['stdin', 'stdout', 'stderr'].each{ |io|
680
+ if si_hash[io]
681
+ if si_hash[io].respond_to?(:fileno)
682
+ handle = get_osfhandle(si_hash[io].fileno)
683
+ else
684
+ handle = get_osfhandle(si_hash[io])
685
+ end
528
686
 
529
- if handle == INVALID_HANDLE_VALUE
530
- raise Error, get_last_error
531
- end
532
-
533
- si_hash[io] = handle
534
- si_hash['startf_flags'] ||= 0
535
- si_hash['startf_flags'] |= STARTF_USESTDHANDLES
536
- hash['inherit'] = true
537
- end
538
- }
539
-
540
- # The bytes not covered here are reserved (null)
541
- unless si_hash.empty?
542
- startinfo[0,4] = [startinfo.size].pack('L')
543
- startinfo[8,4] = [si_hash['desktop']].pack('p*') if si_hash['desktop']
544
- startinfo[12,4] = [si_hash['title']].pack('p*') if si_hash['title']
545
- startinfo[16,4] = [si_hash['x']].pack('L') if si_hash['x']
546
- startinfo[20,4] = [si_hash['y']].pack('L') if si_hash['y']
547
- startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
548
- startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
549
- startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
550
- startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
551
- startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
552
- startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
553
- startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
554
- startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
555
- startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
556
- startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
687
+ if handle == INVALID_HANDLE_VALUE
688
+ raise Error, get_last_error
689
+ end
690
+
691
+ # Most implementations of Ruby on Windows create inheritable
692
+ # handles by default, but some do not. RF bug #26988.
693
+ bool = SetHandleInformation(
694
+ handle,
695
+ HANDLE_FLAG_INHERIT,
696
+ HANDLE_FLAG_INHERIT
697
+ )
698
+
699
+ raise Error, get_last_error unless bool
700
+
701
+ si_hash[io] = handle
702
+ si_hash['startf_flags'] ||= 0
703
+ si_hash['startf_flags'] |= STARTF_USESTDHANDLES
704
+ hash['inherit'] = true
557
705
  end
558
-
559
- if hash['with_logon']
560
- logon = multi_to_wide(hash['with_logon'])
561
- domain = multi_to_wide(hash['domain'])
562
- app = hash['app_name'].nil? ? nil : multi_to_wide(hash['app_name'])
563
- cmd = hash['command_line'].nil? ? nil : multi_to_wide(hash['command_line'])
564
- cwd = multi_to_wide(hash['cwd'])
565
- passwd = multi_to_wide(hash['password'])
706
+ }
707
+
708
+ # The bytes not covered here are reserved (null)
709
+ unless si_hash.empty?
710
+ startinfo[0,4] = [startinfo.size].pack('L')
711
+ startinfo[8,4] = [si_hash['desktop']].pack('p*') if si_hash['desktop']
712
+ startinfo[12,4] = [si_hash['title']].pack('p*') if si_hash['title']
713
+ startinfo[16,4] = [si_hash['x']].pack('L') if si_hash['x']
714
+ startinfo[20,4] = [si_hash['y']].pack('L') if si_hash['y']
715
+ startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
716
+ startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
717
+ startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
718
+ startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
719
+ startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
720
+ startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
721
+ startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
722
+ startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
723
+ startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
724
+ startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
725
+ end
726
+
727
+ if hash['with_logon']
728
+ logon = multi_to_wide(hash['with_logon'])
729
+ domain = multi_to_wide(hash['domain'])
730
+ app = hash['app_name'].nil? ? nil : multi_to_wide(hash['app_name'])
731
+ cmd = hash['command_line'].nil? ? nil : multi_to_wide(hash['command_line'])
732
+ cwd = multi_to_wide(hash['cwd'])
733
+ passwd = multi_to_wide(hash['password'])
566
734
 
567
- hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
568
-
569
- bool = CreateProcessWithLogonW(
570
- logon, # User
571
- domain, # Domain
572
- passwd, # Password
573
- LOGON_WITH_PROFILE, # Logon flags
574
- app, # App name
575
- cmd, # Command line
576
- hash['creation_flags'], # Creation flags
577
- env, # Environment
578
- cwd, # Working directory
579
- startinfo, # Startup Info
580
- procinfo # Process Info
581
- )
582
- else
583
- bool = CreateProcess(
584
- hash['app_name'], # App name
585
- hash['command_line'], # Command line
586
- process_security, # Process attributes
587
- thread_security, # Thread attributes
588
- hash['inherit'], # Inherit handles?
589
- hash['creation_flags'], # Creation flags
590
- env, # Environment
591
- hash['cwd'], # Working directory
592
- startinfo, # Startup Info
593
- procinfo # Process Info
594
- )
595
- end
596
-
597
- # TODO: Close stdin, stdout and stderr handles in the si_hash unless
598
- # they're pointing to one of the standard handles already. [Maybe]
599
- unless bool
600
- raise Error, "CreateProcess() failed: ", get_last_error
601
- end
602
-
603
- # Automatically close the process and thread handles in the
604
- # PROCESS_INFORMATION struct unless explicitly told not to.
605
- if hash['close_handles']
606
- CloseHandle(procinfo[0,4].unpack('L').first)
607
- CloseHandle(procinfo[4,4].unpack('L').first)
608
- end
609
-
610
- ProcessInfo.new(
611
- procinfo[0,4].unpack('L').first, # hProcess
612
- procinfo[4,4].unpack('L').first, # hThread
613
- procinfo[8,4].unpack('L').first, # hProcessId
614
- procinfo[12,4].unpack('L').first # hThreadId
735
+ hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
736
+
737
+ bool = CreateProcessWithLogonW(
738
+ logon, # User
739
+ domain, # Domain
740
+ passwd, # Password
741
+ LOGON_WITH_PROFILE, # Logon flags
742
+ app, # App name
743
+ cmd, # Command line
744
+ hash['creation_flags'], # Creation flags
745
+ env, # Environment
746
+ cwd, # Working directory
747
+ startinfo, # Startup Info
748
+ procinfo # Process Info
615
749
  )
616
- end
750
+ else
751
+ bool = CreateProcess(
752
+ hash['app_name'], # App name
753
+ hash['command_line'], # Command line
754
+ process_security, # Process attributes
755
+ thread_security, # Thread attributes
756
+ hash['inherit'], # Inherit handles?
757
+ hash['creation_flags'], # Creation flags
758
+ env, # Environment
759
+ hash['cwd'], # Working directory
760
+ startinfo, # Startup Info
761
+ procinfo # Process Info
762
+ )
763
+ end
764
+
765
+ # TODO: Close stdin, stdout and stderr handles in the si_hash unless
766
+ # they're pointing to one of the standard handles already. [Maybe]
767
+ unless bool
768
+ raise Error, "CreateProcess() failed: ", get_last_error
769
+ end
770
+
771
+ # Automatically close the process and thread handles in the
772
+ # PROCESS_INFORMATION struct unless explicitly told not to.
773
+ if hash['close_handles']
774
+ CloseHandle(procinfo[0,4].unpack('L').first)
775
+ CloseHandle(procinfo[4,4].unpack('L').first)
776
+ end
777
+
778
+ ProcessInfo.new(
779
+ procinfo[0,4].unpack('L').first, # hProcess
780
+ procinfo[4,4].unpack('L').first, # hThread
781
+ procinfo[8,4].unpack('L').first, # hProcessId
782
+ procinfo[12,4].unpack('L').first # hThreadId
783
+ )
784
+ end
617
785
 
618
- # Waits for any child process to exit and returns the process id of that
619
- # child.
620
- #
621
- # Note that the $? (Process::Status) global variable is NOT set. This
622
- # may be addressed in a future release.
623
- #--
624
- # The GetProcessId() function is not defined in Windows 2000 or earlier
625
- # so we have to do some extra work for those platforms.
626
- #
627
- def wait
628
- handles = []
629
-
630
- # Windows 2000 or earlier
631
- unless defined? GetProcessId
632
- pids = []
786
+ # Waits for any child process to exit and returns the process id of that
787
+ # child.
788
+ #
789
+ # Note that the $? (Process::Status) global variable is NOT set. This
790
+ # may be addressed in a future release.
791
+ #--
792
+ # The GetProcessId() function is not defined in Windows 2000 or earlier
793
+ # so we have to do some extra work for those platforms.
794
+ #
795
+ def wait
796
+ handles = []
797
+
798
+ # Windows 2000 or earlier
799
+ unless defined? GetProcessId
800
+ pids = []
801
+ end
802
+
803
+ @child_pids.each_with_index{ |pid, i|
804
+ handles[i] = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
805
+
806
+ if handles[i] == INVALID_HANDLE_VALUE
807
+ err = "unable to get HANDLE on process associated with pid #{pid}"
808
+ raise Error, err
633
809
  end
634
-
635
- @child_pids.each_with_index{ |pid, i|
636
- handles[i] = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
637
-
638
- if handles[i] == INVALID_HANDLE_VALUE
639
- err = "unable to get HANDLE on process associated with pid #{pid}"
640
- raise Error, err
641
- end
642
810
 
643
- unless defined? GetProcessId
644
- pids[i] = pid
645
- end
646
- }
811
+ unless defined? GetProcessId
812
+ pids[i] = pid
813
+ end
814
+ }
647
815
 
648
- wait = WaitForMultipleObjects(
649
- handles.size,
650
- handles.pack('L*'),
651
- 0,
652
- INFINITE
653
- )
654
-
655
- if wait >= WAIT_OBJECT_0 && wait <= WAIT_OBJECT_0 + @child_pids.size - 1
656
- index = wait - WAIT_OBJECT_0
657
- handle = handles[index]
816
+ wait = WaitForMultipleObjects(
817
+ handles.size,
818
+ handles.pack('L*'),
819
+ 0,
820
+ INFINITE
821
+ )
822
+
823
+ if wait >= WAIT_OBJECT_0 && wait <= WAIT_OBJECT_0 + @child_pids.size - 1
824
+ index = wait - WAIT_OBJECT_0
825
+ handle = handles[index]
658
826
 
659
- if defined? GetProcessId
660
- pid = GetProcessId(handle)
661
- else
662
- pid = pids[index]
663
- end
664
-
665
- @child_pids.delete(pid)
666
- handles.each{ |handle| CloseHandle(handle) }
667
- return pid
827
+ if defined? GetProcessId
828
+ pid = GetProcessId(handle)
829
+ else
830
+ pid = pids[index]
668
831
  end
832
+
833
+ @child_pids.delete(pid)
834
+ handles.each{ |handle| CloseHandle(handle) }
835
+ return pid
836
+ end
669
837
 
670
- nil
671
- end
838
+ nil
839
+ end
672
840
 
673
- # Waits for any child process to exit and returns an array containing the
674
- # process id and the exit status of that child.
675
- #
676
- # Note that the $? (Process::Status) global variable is NOT set. This
677
- # may be addressed in a future release.
678
- #--
679
- # The GetProcessId() function is not defined in Windows 2000 or earlier
680
- # so we have to do some extra work for those platforms.
681
- #
682
- def wait2
683
- handles = []
684
-
685
- # Windows 2000 or earlier
686
- unless defined? GetProcessId
687
- pids = []
841
+ # Waits for any child process to exit and returns an array containing the
842
+ # process id and the exit status of that child.
843
+ #
844
+ # Note that the $? (Process::Status) global variable is NOT set. This
845
+ # may be addressed in a future release.
846
+ #--
847
+ # The GetProcessId() function is not defined in Windows 2000 or earlier
848
+ # so we have to do some extra work for those platforms.
849
+ #
850
+ def wait2
851
+ handles = []
852
+
853
+ # Windows 2000 or earlier
854
+ unless defined? GetProcessId
855
+ pids = []
856
+ end
857
+
858
+ @child_pids.each_with_index{ |pid, i|
859
+ handles[i] = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
860
+
861
+ if handles[i] == INVALID_HANDLE_VALUE
862
+ err = "unable to get HANDLE on process associated with pid #{pid}"
863
+ raise Error, err
688
864
  end
689
-
690
- @child_pids.each_with_index{ |pid, i|
691
- handles[i] = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
692
-
693
- if handles[i] == INVALID_HANDLE_VALUE
694
- err = "unable to get HANDLE on process associated with pid #{pid}"
695
- raise Error, err
696
- end
697
-
698
- unless defined? GetProcessId
699
- pids[i] = pid
700
- end
701
- }
865
+
866
+ unless defined? GetProcessId
867
+ pids[i] = pid
868
+ end
869
+ }
702
870
 
703
- wait = WaitForMultipleObjects(
704
- handles.size,
705
- handles.pack('L*'),
706
- 0,
707
- INFINITE
708
- )
709
-
710
- if wait >= WAIT_OBJECT_0 && wait <= WAIT_OBJECT_0 + @child_pids.size - 1
711
- index = wait - WAIT_OBJECT_0
712
- handle = handles[index]
713
-
714
- if defined? GetProcessId
715
- pid = GetProcessId(handle)
716
- else
717
- pid = pids[index]
718
- end
871
+ wait = WaitForMultipleObjects(
872
+ handles.size,
873
+ handles.pack('L*'),
874
+ 0,
875
+ INFINITE
876
+ )
877
+
878
+ if wait >= WAIT_OBJECT_0 && wait <= WAIT_OBJECT_0 + @child_pids.size - 1
879
+ index = wait - WAIT_OBJECT_0
880
+ handle = handles[index]
881
+
882
+ if defined? GetProcessId
883
+ pid = GetProcessId(handle)
884
+ else
885
+ pid = pids[index]
886
+ end
719
887
 
720
- exit_code = [0].pack('l')
721
- unless GetExitCodeProcess(handle, exit_code)
722
- raise get_last_error
723
- end
888
+ exit_code = [0].pack('l')
889
+
890
+ unless GetExitCodeProcess(handle, exit_code)
891
+ raise get_last_error
892
+ end
724
893
 
725
- @child_pids.delete(pid)
894
+ @child_pids.delete(pid)
726
895
 
727
- handles.each{ |handle| CloseHandle(handle) }
728
- return [pid, exit_code.unpack('l').first]
729
- end
896
+ handles.each{ |handle| CloseHandle(handle) }
897
+ return [pid, exit_code.unpack('l').first]
898
+ end
730
899
 
731
- nil
732
- end
900
+ nil
901
+ end
733
902
 
734
- # Returns the process ID of the parent of this process.
735
- #--
736
- # In MRI this method always returns 0.
737
- #
738
- def ppid
739
- ppid = 0
903
+ # Returns the process ID of the parent of this process.
904
+ #--
905
+ # In MRI this method always returns 0.
906
+ #
907
+ def ppid
908
+ ppid = 0
740
909
 
741
- return ppid if Process.pid == 0 # Paranoia
910
+ return ppid if Process.pid == 0 # Paranoia
742
911
 
743
- handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
912
+ handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
744
913
 
745
- if handle == INVALID_HANDLE_VALUE
746
- raise Error, get_last_error
747
- end
914
+ if handle == INVALID_HANDLE_VALUE
915
+ raise Error, get_last_error
916
+ end
748
917
 
749
- proc_entry = 0.chr * 296 # 36 + 260
750
- proc_entry[0, 4] = [proc_entry.size].pack('L') # Set dwSize member
918
+ proc_entry = 0.chr * 296 # 36 + 260
919
+ proc_entry[0, 4] = [proc_entry.size].pack('L') # Set dwSize member
751
920
 
752
- begin
753
- unless Process32First(handle, proc_entry)
754
- error = get_last_error
755
- raise Error, error
756
- end
757
-
758
- while Process32Next(handle, proc_entry)
759
- if proc_entry[8, 4].unpack('L')[0] == Process.pid
760
- ppid = proc_entry[24, 4].unpack('L')[0] # th32ParentProcessID
761
- break
762
- end
763
- end
764
- ensure
765
- CloseHandle(handle)
921
+ begin
922
+ unless Process32First(handle, proc_entry)
923
+ error = get_last_error
924
+ raise Error, error
766
925
  end
767
926
 
768
- ppid
769
- end
770
-
771
- # Creates the equivalent of a subshell via the CreateProcess() function.
772
- # This behaves in a manner that is similar, but not identical to, the
773
- # Kernel.fork method for Unix. Unlike the Unix fork, this method starts
774
- # from the top of the script rather than the point of the call.
775
- #
776
- # WARNING: This implementation should be considered experimental. It is
777
- # not recommended for production use.
778
- #
779
- def fork
780
- last_arg = ARGV.last
781
-
782
- # Look for the 'child#xxx' tag
783
- if last_arg =~ /child#\d+/
784
- @i += 1
785
- num = last_arg.split('#').last.to_i
786
- if num == @i
787
- if block_given?
788
- status = 0
789
- begin
790
- yield
791
- rescue Exception
792
- status = -1 # Any non-zero result is failure
793
- ensure
794
- return status
795
- end
796
- end
797
- return nil
798
- else
799
- return false
800
- end
927
+ while Process32Next(handle, proc_entry)
928
+ if proc_entry[8, 4].unpack('L')[0] == Process.pid
929
+ ppid = proc_entry[24, 4].unpack('L')[0] # th32ParentProcessID
930
+ break
931
+ end
801
932
  end
933
+ ensure
934
+ CloseHandle(handle)
935
+ end
936
+
937
+ ppid
938
+ end
939
+
940
+ # Creates the equivalent of a subshell via the CreateProcess() function.
941
+ # This behaves in a manner that is similar, but not identical to, the
942
+ # Kernel.fork method for Unix. Unlike the Unix fork, this method starts
943
+ # from the top of the script rather than the point of the call.
944
+ #
945
+ # WARNING: This implementation should be considered experimental. It is
946
+ # not recommended for production use.
947
+ #
948
+ def fork
949
+ last_arg = ARGV.last
950
+
951
+ # Look for the 'child#xxx' tag
952
+ if last_arg =~ /child#\d+/
953
+ @i += 1
954
+ num = last_arg.split('#').last.to_i
955
+ if num == @i
956
+ if block_given?
957
+ status = 0
958
+ begin
959
+ yield
960
+ rescue Exception
961
+ status = -1 # Any non-zero result is failure
962
+ ensure
963
+ return status
964
+ end
965
+ end
966
+ return nil
967
+ else
968
+ return false
969
+ end
970
+ end
802
971
 
803
- # Tag the command with the word 'child#xxx' to distinguish it
804
- # from the calling process.
805
- cmd = 'ruby -I "' + $LOAD_PATH.join(File::PATH_SEPARATOR) << '" "'
806
- cmd << File.expand_path($PROGRAM_NAME) << '" ' << ARGV.join(' ')
807
- cmd << ' child#' << @child_pids.length.to_s
972
+ # Tag the command with the word 'child#xxx' to distinguish it
973
+ # from the calling process.
974
+ cmd = 'ruby -I "' + $LOAD_PATH.join(File::PATH_SEPARATOR) << '" "'
975
+ cmd << File.expand_path($PROGRAM_NAME) << '" ' << ARGV.join(' ')
976
+ cmd << ' child#' << @child_pids.length.to_s
808
977
 
809
- startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
810
- startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
811
- procinfo = [0,0,0,0].pack('LLLL')
978
+ startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
979
+ startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
980
+ procinfo = [0,0,0,0].pack('LLLL')
812
981
 
813
- rv = CreateProcess(0, cmd, 0, 0, 1, 0, 0, 0, startinfo, procinfo)
982
+ rv = CreateProcess(0, cmd, 0, 0, 1, 0, 0, 0, startinfo, procinfo)
814
983
 
815
- if rv == 0
816
- raise Error, get_last_error
817
- end
984
+ if rv == 0
985
+ raise Error, get_last_error
986
+ end
818
987
 
819
- pid = procinfo[8,4].unpack('L').first
820
- @child_pids.push(pid)
988
+ pid = procinfo[8,4].unpack('L').first
989
+ @child_pids.push(pid)
821
990
 
822
- pid
823
- end
991
+ pid
992
+ end
824
993
 
825
- module_function :create, :fork, :getpriority, :kill, :ppid, :setpriority
826
- module_function :wait, :wait2, :waitpid, :waitpid2, :uid
994
+ module_function :create, :fork, :get_affinity, :getrlimit, :getpriority, :kill, :ppid
995
+ module_function :setpriority, :wait, :wait2, :waitpid, :waitpid2, :uid
827
996
  end
828
997
 
829
998
  # Create a global fork method
830
999
  module Kernel
831
- undef_method :fork # Eliminate redefinition warning
832
- def fork(&block)
833
- Process.fork(&block)
834
- end
1000
+ undef_method :fork # Eliminate redefinition warning
1001
+ def fork(&block)
1002
+ Process.fork(&block)
1003
+ end
835
1004
  end