win32-process 0.6.1 → 0.6.2

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/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