win32-process 0.6.6 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/win32/process.rb CHANGED
@@ -1,1143 +1,881 @@
1
- require 'windows/error'
2
- require 'windows/process'
3
- require 'windows/thread'
4
- require 'windows/synchronize'
5
- require 'windows/handle'
6
- require 'windows/library'
7
- require 'windows/console'
8
- require 'windows/window'
9
- require 'windows/unicode'
10
- require 'windows/tool_helper'
11
- require 'windows/security'
12
- require 'windows/msvcrt/string'
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'process', 'functions')
2
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'process', 'constants')
3
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'process', 'structs')
4
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'process', 'helper')
13
5
 
14
- module Process
15
- # The Process::Error class is typically raised if any of the custom
16
- # Process methods fail.
17
- class Error < RuntimeError; end
18
-
19
- # Eliminates redefinition warnings.
20
- undef_method :getpriority, :kill, :getrlimit, :ppid, :setrlimit
21
- undef_method :setpriority, :wait, :wait2, :waitpid, :waitpid2, :uid
22
-
23
- # The version of the win32-process library
24
- WIN32_PROCESS_VERSION = '0.6.6'
25
-
26
- # Defined for interface compatibility, but not used
27
-
28
- PRIO_PROCESS = 0
29
- PRIO_PGRP = 1
30
- PRIO_USER = 2
31
-
32
- include Windows::Process
33
- include Windows::Thread
34
- include Windows::Error
35
- include Windows::Library
36
- include Windows::Console
37
- include Windows::Handle
38
- include Windows::Security
39
- include Windows::Synchronize
40
- include Windows::Window
41
- include Windows::Unicode
42
- include Windows::ToolHelper
43
- include Windows::MSVCRT::String
44
-
45
- extend Windows::Error
46
- extend Windows::Process
47
- extend Windows::Thread
48
- extend Windows::Security
49
- extend Windows::Synchronize
50
- extend Windows::Handle
51
- extend Windows::Library
52
- extend Windows::Console
53
- extend Windows::Unicode
54
- extend Windows::ToolHelper
55
- extend Windows::MSVCRT::String
56
-
57
- # :stopdoc:
58
-
59
- # Used by Process.create
60
- ProcessInfo = Struct.new("ProcessInfo",
61
- :process_handle,
62
- :thread_handle,
63
- :process_id,
64
- :thread_id
65
- )
66
-
67
- @child_pids = [] # Static variable used for Process.fork
68
- @i = -1 # Static variable used for Process.fork
69
-
70
- # These are probably not defined on MS Windows by default
71
- unless defined? RLIMIT_CPU
72
- RLIMIT_CPU = 0 # PerProcessUserTimeLimit
73
- RLIMIT_FSIZE = 1 # Hard coded at 4TB - 64K (assumes NTFS)
74
- RLIMIT_AS = 5 # ProcessMemoryLimit
75
- RLIMIT_RSS = 5 # ProcessMemoryLimit
76
- RLIMIT_VMEM = 5 # ProcessMemoryLimit
77
- end
78
6
 
79
- # :startdoc:
80
-
81
- # Returns the process and system affinity mask for the given +pid+, or the
82
- # current process if no pid is provided. The return value is a two element
83
- # array, with the first containing the process affinity mask, and the second
84
- # containing the system affinity mask. Both are decimal values.
85
- #
86
- # A process affinity mask is a bit vector indicating the processors that a
87
- # process is allowed to run on. A system affinity mask is a bit vector in
88
- # which each bit represents the processors that are configured into a
89
- # system.
90
- #
91
- # Example:
92
- #
93
- # # System has 4 processors, current process is allowed to run on all
94
- # Process.get_affinity # => [[15], [15]]
95
- #
96
- # # System has 4 processors, current process only allowed on 1 and 4 only
97
- # Process.get_affinity # => [[9], [15]]
98
- #
99
- # If you want to convert a decimal bit vector into an array of 0's and 1's
100
- # indicating the flag value of each processor, you can use something like
101
- # this approach:
102
- #
103
- # mask = Process.get_affinity.first
104
- # (0..mask).to_a.map{ |n| mask[n] }
105
- #
106
- def get_affinity(int = Process.pid)
107
- pmask = 0.chr * 4
108
- smask = 0.chr * 4
109
-
110
- if int == Process.pid
111
- unless GetProcessAffinityMask(GetCurrentProcess(), pmask, smask)
112
- raise Error, get_last_error
113
- end
114
- else
115
- begin
116
- handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
117
-
118
- if handle == INVALID_HANDLE_VALUE
119
- raise Error, get_last_error
7
+ module Process
8
+ include Process::Constants
9
+ extend Process::Functions
10
+ extend Process::Structs
11
+ extend Process::Constants
12
+
13
+ WIN32_PROCESS_VERSION = '0.7.0'
14
+
15
+ # Disable popups. This mostly affects the Process.kill method.
16
+ SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX)
17
+
18
+ class << self
19
+ # Returns whether or not the current process is part of a Job (process group).
20
+ def job?
21
+ pbool = FFI::MemoryPointer.new(:int)
22
+ IsProcessInJob(GetCurrentProcess(), nil, pbool)
23
+ pbool.read_int == 1 ? true : false
24
+ end
25
+
26
+ # Returns the process and system affinity mask for the given +pid+, or the
27
+ # current process if no pid is provided. The return value is a two element
28
+ # array, with the first containing the process affinity mask, and the second
29
+ # containing the system affinity mask. Both are decimal values.
30
+ #
31
+ # A process affinity mask is a bit vector indicating the processors that a
32
+ # process is allowed to run on. A system affinity mask is a bit vector in
33
+ # which each bit represents the processors that are configured into a
34
+ # system.
35
+ #
36
+ # Example:
37
+ #
38
+ # # System has 4 processors, current process is allowed to run on all
39
+ # Process.get_affinity # => [[15], [15]], where '15' is 1 + 2 + 4 + 8
40
+ #
41
+ # # System has 4 processors, current process only allowed on 1 and 4 only
42
+ # Process.get_affinity # => [[9], [15]]
43
+ #
44
+ # If you want to convert a decimal bit vector into an array of 0's and 1's
45
+ # indicating the flag value of each processor, you can use something like
46
+ # this approach:
47
+ #
48
+ # mask = Process.get_affinity.first
49
+ # (0..mask).to_a.map{ |n| mask[n] }
50
+ #
51
+ def get_affinity(int = Process.pid)
52
+ pmask = FFI::MemoryPointer.new(:ulong)
53
+ smask = FFI::MemoryPointer.new(:ulong)
54
+
55
+ if int == Process.pid
56
+ unless GetProcessAffinityMask(GetCurrentProcess(), pmask, smask)
57
+ raise SystemCallError, FFI.errno, "GetProcessAffinityMask"
120
58
  end
59
+ else
60
+ begin
61
+ handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
121
62
 
122
- unless GetProcessAffinityMask(handle, pmask, smask)
123
- raise Error, get_last_error
63
+ if handle == INVALID_HANDLE_VALUE
64
+ raise SystemCallError, FFI.errno, "OpeProcess"
65
+ end
66
+
67
+ unless GetProcessAffinityMask(handle, pmask, smask)
68
+ raise SystemCallError, FFI.errno, "GetProcessAffinityMask"
69
+ end
70
+ ensure
71
+ CloseHandle(handle)
124
72
  end
125
- ensure
126
- CloseHandle(handle) if handle != INVALID_HANDLE_VALUE
127
73
  end
74
+
75
+ [pmask.read_ulong, smask.read_ulong]
128
76
  end
129
77
 
130
- pmask = pmask.unpack('L').first
131
- smask = smask.unpack('L').first
78
+ remove_method :getpriority
132
79
 
133
- [pmask, smask]
134
- end
80
+ # Retrieves the priority class for the specified process id +int+. Unlike
81
+ # the default implementation, lower return values do not necessarily
82
+ # correspond to higher priority classes.
83
+ #
84
+ # The +kind+ parameter is ignored but required for API compatibility.
85
+ # You can only retrieve process information, not process group or user
86
+ # information, so it is effectively always Process::PRIO_PROCESS.
87
+ #
88
+ # Possible return values are:
89
+ #
90
+ # 32 => Process::NORMAL_PRIORITY_CLASS
91
+ # 64 => Process::IDLE_PRIORITY_CLASS
92
+ # 128 => Process::HIGH_PRIORITY_CLASS
93
+ # 256 => Process::REALTIME_PRIORITY_CLASS
94
+ # 16384 => Process::BELOW_NORMAL_PRIORITY_CLASS
95
+ # 32768 => Process::ABOVE_NORMAL_PRIORITY_CLASS
96
+ #
97
+ def getpriority(kind, int)
98
+ raise TypeError, kind unless kind.is_a?(Fixnum) # Match spec
99
+ raise TypeError, int unless int.is_a?(Fixnum) # Match spec
100
+ int = Process.pid if int == 0 # Match spec
135
101
 
136
- # Returns whether or not the current process is part of a Job.
137
- #
138
- def job?
139
- pbool = 0.chr * 4
140
- IsProcessInJob(GetCurrentProcess(), nil, pbool)
141
- pbool.unpack('L').first == 0 ? false : true
142
- end
102
+ handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, int)
143
103
 
144
- # Gets the resource limit of the current process. Only a limited number
145
- # of flags are supported.
146
- #
147
- # Process::RLIMIT_CPU
148
- # Process::RLIMIT_FSIZE
149
- # Process::RLIMIT_AS
150
- # Process::RLIMIT_RSS
151
- # Process::RLIMIT_VMEM
152
- #
153
- # The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants
154
- # all refer to the Process memory limit. The Process::RLIMIT_CPU constant
155
- # refers to the per process user time limit. The Process::RLIMIT_FSIZE
156
- # constant is hard coded to the maximum file size on an NTFS filesystem,
157
- # approximately 4TB (or 4GB if not NTFS).
158
- #
159
- # While a two element array is returned in order to comply with the spec,
160
- # there is no separate hard and soft limit. The values will always be the
161
- # same.
162
- #
163
- # If [0,0] is returned then it means no limit has been set.
164
- #
165
- # Example:
166
- #
167
- # Process.getrlimit(Process::RLIMIT_VMEM) # => [0, 0]
168
- #--
169
- # NOTE: Both the getrlimit and setrlimit method use an at_exit handler
170
- # to close a job handle. This is necessary because simply calling it
171
- # at the end of the block, while marking it for closure, would also make
172
- # it unavailable even within the same process since it would no longer
173
- # be associated with the job.
174
- #
175
- def getrlimit(resource)
176
- if resource == RLIMIT_FSIZE
177
- if get_volume_type == 'NTFS'
178
- return ((1024**4) * 4) - (1024 * 64) # ~4 TB
179
- else
180
- return (1024**3) * 4 # 4 GB
104
+ if handle == INVALID_HANDLE_VALUE
105
+ raise SystemCallError, FFI.errno, "OpenProcess"
181
106
  end
182
- end
183
107
 
184
- handle = nil
185
- in_job = Process.job?
186
-
187
- # Put the current process in a job if it's not already in one
188
- if in_job && defined?(@job_name)
189
- handle = OpenJobObject(JOB_OBJECT_QUERY, true, @job_name)
190
- raise Error, get_last_error if handle == 0
191
- else
192
- @job_name = 'ruby_' + Process.pid.to_s
193
- handle = CreateJobObject(nil, @job_name)
194
- raise Error, get_last_error if handle == 0
195
- end
108
+ begin
109
+ priority = GetPriorityClass(handle)
196
110
 
197
- begin
198
- unless in_job
199
- unless AssignProcessToJobObject(handle, GetCurrentProcess())
200
- raise Error, get_last_error
111
+ if priority == 0
112
+ raise SystemCallError, FFI.errno, "GetPriorityClass"
201
113
  end
114
+ ensure
115
+ CloseHandle(handle)
202
116
  end
203
117
 
204
- buf = 0.chr * 112 # sizeof(struct JOBJECT_EXTENDED_LIMIT_INFORMATION)
205
- val = nil # value returned at end of method
118
+ priority
119
+ end
206
120
 
207
- # Set the LimitFlags member of the struct
208
- case resource
209
- when RLIMIT_CPU
210
- buf[16,4] = [JOB_OBJECT_LIMIT_PROCESS_TIME].pack('L')
211
- when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
212
- buf[16,4] = [JOB_OBJECT_LIMIT_PROCESS_MEMORY].pack('L')
213
- else
214
- raise Error, "unsupported resource type"
215
- end
121
+ remove_method :setpriority
216
122
 
217
- bool = QueryInformationJobObject(
218
- handle,
219
- JobObjectExtendedLimitInformation,
220
- buf,
221
- buf.size,
222
- nil
223
- )
123
+ # Sets the priority class for the specified process id +int+.
124
+ #
125
+ # The +kind+ parameter is ignored but present for API compatibility.
126
+ # You can only retrieve process information, not process group or user
127
+ # information, so it is effectively always Process::PRIO_PROCESS.
128
+ #
129
+ # Possible +int_priority+ values are:
130
+ #
131
+ # * Process::NORMAL_PRIORITY_CLASS
132
+ # * Process::IDLE_PRIORITY_CLASS
133
+ # * Process::HIGH_PRIORITY_CLASS
134
+ # * Process::REALTIME_PRIORITY_CLASS
135
+ # * Process::BELOW_NORMAL_PRIORITY_CLASS
136
+ # * Process::ABOVE_NORMAL_PRIORITY_CLASS
137
+ #
138
+ def setpriority(kind, int, int_priority)
139
+ raise TypeError unless kind.is_a?(Integer) # Match spec
140
+ raise TypeError unless int.is_a?(Integer) # Match spec
141
+ raise TypeError unless int_priority.is_a?(Integer) # Match spec
142
+ int = Process.pid if int == 0 # Match spec
224
143
 
225
- unless bool
226
- raise Error, get_last_error
144
+ handle = OpenProcess(PROCESS_SET_INFORMATION, false , int)
145
+
146
+ if handle == INVALID_HANDLE_VALUE
147
+ raise SystemCallError, FFI.errno, "OpenProcess"
227
148
  end
228
149
 
229
- case resource
230
- when Process::RLIMIT_CPU
231
- val = buf[0,8].unpack('Q').first
232
- when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
233
- val = buf[96,4].unpack('L').first
150
+ begin
151
+ unless SetPriorityClass(handle, int_priority)
152
+ raise SystemCallError, FFI.errno, "SetPriorityClass"
153
+ end
154
+ ensure
155
+ CloseHandle(handle)
234
156
  end
235
- ensure
236
- at_exit{ CloseHandle(handle) if handle }
157
+
158
+ return 0 # Match the spec
237
159
  end
238
160
 
239
- [val, val] # Return an array of two values to comply with spec
240
- end
161
+ remove_method :uid
241
162
 
242
- # Sets the resource limit of the current process. Only a limited number
243
- # of flags are supported.
244
- #
245
- # Process::RLIMIT_CPU
246
- # Process::RLIMIT_AS
247
- # Process::RLIMIT_RSS
248
- # Process::RLIMIT_VMEM
249
- #
250
- # The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants
251
- # all refer to the Process memory limit. The Process::RLIMIT_CPU constant
252
- # refers to the per process user time limit.
253
- #
254
- # The +max_limit+ parameter is provided for interface compatibility only.
255
- # It is always set to the current_limit value.
256
- #
257
- # Example:
258
- #
259
- # Process.setrlimit(Process::RLIMIT_VMEM, 1024 * 4) # => nil
260
- # Process.getrlimit(Process::RLIMIT_VMEM) # => [4096, 4096]
261
- #
262
- def setrlimit(resource, current_limit, max_limit = nil)
263
- max_limit = current_limit
264
-
265
- handle = nil
266
- in_job = Process.job?
267
-
268
- # Put the current process in a job if it's not already in one
269
- if in_job && defined? @job_name
270
- handle = OpenJobObject(JOB_OBJECT_SET_ATTRIBUTES, true, @job_name)
271
- raise Error, get_last_error if handle == 0
272
- else
273
- @job_name = 'ruby_' + Process.pid.to_s
274
- handle = CreateJobObject(nil, job_name)
275
- raise Error, get_last_error if handle == 0
276
- end
163
+ # Returns the uid of the current process. Specifically, it returns the
164
+ # RID of the SID associated with the owner of the process.
165
+ #
166
+ # If +sid+ is set to true, then a binary sid is returned. Otherwise, a
167
+ # numeric id is returned (the default).
168
+ #--
169
+ # The Process.uid method in core Ruby always returns 0 on MS Windows.
170
+ #
171
+ def uid(sid = false)
172
+ token = FFI::MemoryPointer.new(:ulong)
277
173
 
278
- begin
279
- unless in_job
280
- unless AssignProcessToJobObject(handle, GetCurrentProcess())
281
- raise Error, get_last_error
282
- end
283
- end
174
+ raise TypeError unless sid.is_a?(TrueClass) || sid.is_a?(FalseClass)
284
175
 
285
- # sizeof(struct JOBJECT_EXTENDED_LIMIT_INFORMATION)
286
- buf = 0.chr * 112
287
-
288
- # Set the LimitFlags and relevant members of the struct
289
- case resource
290
- when RLIMIT_CPU
291
- buf[16,4] = [JOB_OBJECT_LIMIT_PROCESS_TIME].pack('L')
292
- buf[0,8] = [max_limit].pack('Q') # PerProcessUserTimeLimit
293
- when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
294
- buf[16,4] = [JOB_OBJECT_LIMIT_PROCESS_MEMORY].pack('L')
295
- buf[96,4] = [max_limit].pack('L') # ProcessMemoryLimit
296
- else
297
- raise Error, "unsupported resource type"
176
+ unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
177
+ raise SystemCallError, FFI.errno, "OpenProcessToken"
298
178
  end
299
179
 
300
- bool = SetInformationJobObject(
301
- handle,
302
- JobObjectExtendedLimitInformation,
303
- buf,
304
- buf.size
180
+ token = token.read_ulong
181
+ rlength = FFI::MemoryPointer.new(:ulong)
182
+ tuser = 0.chr * 512
183
+
184
+ bool = GetTokenInformation(
185
+ token,
186
+ TokenUser,
187
+ tuser,
188
+ tuser.size,
189
+ rlength
305
190
  )
306
191
 
307
192
  unless bool
308
- raise Error, get_last_error
193
+ raise SystemCallError, FFI.errno, "GetTokenInformation"
309
194
  end
310
- ensure
311
- at_exit{ CloseHandle(handle) if handle }
312
- end
313
- end
314
195
 
315
- # Retrieves the priority class for the specified process id +int+. Unlike
316
- # the default implementation, lower return values do not necessarily
317
- # correspond to higher priority classes.
318
- #
319
- # The +kind+ parameter is ignored but required for API compatibility.
320
- # You can only retrieve process information, not process group or user
321
- # information, so it is effectively always Process::PRIO_PROCESS.
322
- #
323
- # Possible return values are:
324
- #
325
- # 32 - Process::NORMAL_PRIORITY_CLASS
326
- # 64 - Process::IDLE_PRIORITY_CLASS
327
- # 128 - Process::HIGH_PRIORITY_CLASS
328
- # 256 - Process::REALTIME_PRIORITY_CLASS
329
- # 16384 - Process::BELOW_NORMAL_PRIORITY_CLASS
330
- # 32768 - Process::ABOVE_NORMAL_PRIORITY_CLASS
331
- #
332
- def getpriority(kind, int)
333
- raise TypeError unless kind.is_a?(Integer)
334
- raise TypeError unless int.is_a?(Integer)
335
-
336
- int = Process.pid if int == 0 # Match spec
337
-
338
- handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
339
-
340
- if handle == INVALID_HANDLE_VALUE
341
- raise Error, get_last_error
342
- end
196
+ string_sid = tuser[8, (rlength.read_ulong - 8)]
343
197
 
344
- begin
345
- priority_class = GetPriorityClass(handle)
198
+ if sid
199
+ string_sid
200
+ else
201
+ psid = FFI::MemoryPointer.new(:ulong)
346
202
 
347
- if priority_class == 0
348
- raise Error, get_last_error
349
- end
350
- ensure
351
- CloseHandle(handle)
352
- end
203
+ unless ConvertSidToStringSidA(string_sid, psid)
204
+ raise SystemCallError, FFI.errno, "ConvertSidToStringSid"
205
+ end
353
206
 
354
- priority_class
355
- end
207
+ psid.read_pointer.read_string.split('-').last.to_i
208
+ end
209
+ end
210
+
211
+ remove_method :getrlimit
212
+
213
+ # Gets the resource limit of the current process. Only a limited number
214
+ # of flags are supported.
215
+ #
216
+ # Process::RLIMIT_CPU
217
+ # Process::RLIMIT_FSIZE
218
+ # Process::RLIMIT_AS
219
+ # Process::RLIMIT_RSS
220
+ # Process::RLIMIT_VMEM
221
+ #
222
+ # The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants
223
+ # all refer to the Process memory limit. The Process::RLIMIT_CPU constant
224
+ # refers to the per process user time limit. The Process::RLIMIT_FSIZE
225
+ # constant is hard coded to the maximum file size on an NTFS filesystem,
226
+ # approximately 4TB (or 4GB if not NTFS).
227
+ #
228
+ # While a two element array is returned in order to comply with the spec,
229
+ # there is no separate hard and soft limit. The values will always be the
230
+ # same.
231
+ #
232
+ # If [0,0] is returned then it means no limit has been set.
233
+ #
234
+ # Example:
235
+ #
236
+ # Process.getrlimit(Process::RLIMIT_VMEM) # => [0, 0]
237
+ #--
238
+ # NOTE: Both the getrlimit and setrlimit method use an at_exit handler
239
+ # to close a job handle. This is necessary because simply calling it
240
+ # at the end of the block, while marking it for closure, would also make
241
+ # it unavailable within the same process again since it would no longer
242
+ # be associated with the job. In other words, trying to call it more than
243
+ # once within the same program would fail.
244
+ #
245
+ def getrlimit(resource)
246
+ if resource == RLIMIT_FSIZE
247
+ if volume_type == 'NTFS'
248
+ return ((1024**4) * 4) - (1024 * 64) # ~ 4TB
249
+ else
250
+ return (1024**3) * 4 # 4 GB
251
+ end
252
+ end
356
253
 
357
- # Sets the priority class for the specified process id +int+.
358
- #
359
- # The +kind+ parameter is ignored but present for API compatibility.
360
- # You can only retrieve process information, not process group or user
361
- # information, so it is effectively always Process::PRIO_PROCESS.
362
- #
363
- # Possible +int_priority+ values are:
364
- #
365
- # * Process::NORMAL_PRIORITY_CLASS
366
- # * Process::IDLE_PRIORITY_CLASS
367
- # * Process::HIGH_PRIORITY_CLASS
368
- # * Process::REALTIME_PRIORITY_CLASS
369
- # * Process::BELOW_NORMAL_PRIORITY_CLASS
370
- # * Process::ABOVE_NORMAL_PRIORITY_CLASS
371
- #
372
- def setpriority(kind, int, int_priority)
373
- raise TypeError unless kind.is_a?(Integer)
374
- raise TypeError unless int.is_a?(Integer)
375
- raise TypeError unless int_priority.is_a?(Integer)
376
-
377
- int = Process.pid if int == 0 # Match spec
378
-
379
- handle = OpenProcess(PROCESS_SET_INFORMATION, 0 , int)
380
-
381
- if handle == INVALID_HANDLE_VALUE
382
- raise Error, get_last_error
383
- end
254
+ handle = nil
255
+ in_job = Process.job?
384
256
 
385
- begin
386
- unless SetPriorityClass(handle, int_priority)
387
- raise Error, get_last_error
257
+ # Put the current process in a job if it's not already in one
258
+ if in_job && defined?(@win32_process_job_name)
259
+ handle = OpenJobObjectA(JOB_OBJECT_QUERY, true, @win32_process_job_name)
260
+ raise SystemCallError, FFI.errno, "OpenJobObject" if handle == 0
261
+ else
262
+ @win32_process_job_name = 'ruby_' + Process.pid.to_s
263
+ handle = CreateJobObjectA(nil, @win32_process_job_name)
264
+ raise SystemCallError, FFI.errno, "CreateJobObject" if handle == 0
388
265
  end
389
- ensure
390
- CloseHandle(handle)
391
- end
392
-
393
- return 0 # Match the spec
394
- end
395
266
 
396
- # Returns the uid of the current process. Specifically, it returns the
397
- # RID of the SID associated with the owner of the process.
398
- #
399
- # If +sid+ is set to true, then a binary sid is returned. Otherwise, a
400
- # numeric id is returned (the default).
401
- #--
402
- # The Process.uid method in core Ruby always returns 0 on MS Windows.
403
- #
404
- def uid(sid = false)
405
- token = 0.chr * 4
406
-
407
- raise TypeError unless sid.is_a?(TrueClass) || sid.is_a?(FalseClass)
408
-
409
- unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
410
- raise Error, get_last_error
411
- end
267
+ begin
268
+ unless in_job
269
+ unless AssignProcessToJobObject(handle, GetCurrentProcess())
270
+ raise Error, get_last_error
271
+ end
272
+ end
412
273
 
413
- token = token.unpack('V')[0]
414
- rlength = 0.chr * 4
415
- tuser = 0.chr * 512
274
+ ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
275
+ val = nil
416
276
 
417
- bool = GetTokenInformation(
418
- token,
419
- TokenUser,
420
- tuser,
421
- tuser.size,
422
- rlength
423
- )
277
+ # Set the LimitFlags member of the struct
278
+ case resource
279
+ when RLIMIT_CPU
280
+ ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_TIME
281
+ when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
282
+ ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_MEMORY
283
+ else
284
+ raise ArgumentError, "unsupported resource type: '#{resource}'"
285
+ end
424
286
 
425
- unless bool
426
- raise Error, get_last_error
427
- end
287
+ bool = QueryInformationJobObject(
288
+ handle,
289
+ JobObjectExtendedLimitInformation,
290
+ ptr,
291
+ ptr.size,
292
+ nil
293
+ )
428
294
 
429
- lsid = tuser[8, (rlength.unpack('L').first - 8)]
295
+ unless bool
296
+ raise SystemCallError, FFI.errno, "QueryInformationJobObject"
297
+ end
430
298
 
431
- if sid
432
- lsid
433
- else
434
- sid_addr = [lsid].pack('p*').unpack('L')[0]
435
- sid_buf = 0.chr * 80
436
- sid_ptr = 0.chr * 4
299
+ case resource
300
+ when Process::RLIMIT_CPU
301
+ val = ptr[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart]
302
+ when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
303
+ val = ptr[:ProcessMemoryLimit]
304
+ end
437
305
 
438
- unless ConvertSidToStringSid(sid_addr, sid_ptr)
439
- raise Error, get_last_error
306
+ ensure
307
+ at_exit{ CloseHandle(handle) if handle }
308
+ end
309
+
310
+ [val, val]
311
+ end
312
+
313
+ remove_method :setrlimit
314
+
315
+ # Sets the resource limit of the current process. Only a limited number
316
+ # of flags are supported.
317
+ #
318
+ # Process::RLIMIT_CPU
319
+ # Process::RLIMIT_AS
320
+ # Process::RLIMIT_RSS
321
+ # Process::RLIMIT_VMEM
322
+ #
323
+ # The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants
324
+ # all refer to the Process memory limit. The Process::RLIMIT_CPU constant
325
+ # refers to the per process user time limit.
326
+ #
327
+ # The +max_limit+ parameter is provided for interface compatibility only.
328
+ # It is always set to the current_limit value.
329
+ #
330
+ # Example:
331
+ #
332
+ # Process.setrlimit(Process::RLIMIT_VMEM, 1024 * 4) # => nil
333
+ # Process.getrlimit(Process::RLIMIT_VMEM) # => [4096, 4096]
334
+ #
335
+ def setrlimit(resource, current_limit, max_limit = nil)
336
+ max_limit = current_limit
337
+
338
+ handle = nil
339
+ in_job = Process.job?
340
+
341
+ unless [RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS, RLIMIT_CPU].include?(resource)
342
+ raise ArgumentError, "unsupported resource type: '#{resource}'"
343
+ end
344
+
345
+ # Put the current process in a job if it's not already in one
346
+ if in_job && defined? @win32_process_job_name
347
+ handle = OpenJobObjectA(JOB_OBJECT_SET_ATTRIBUTES, true, @win32_process_job_name)
348
+ raise SystemCallError, FFI.errno, "OpenJobObject" if handle == 0
349
+ else
350
+ @job_name = 'ruby_' + Process.pid.to_s
351
+ handle = CreateJobObjectA(nil, job_name)
352
+ raise SystemCallError, FFI.errno, "CreateJobObject" if handle == 0
440
353
  end
441
354
 
442
- strcpy(sid_buf, sid_ptr.unpack('L')[0])
443
- sid_buf.strip.split('-').last.to_i
444
- end
445
- end
446
-
447
- # Waits for the given child process to exit and returns that pid.
448
- #
449
- # The +flags+ argument is ignored at the moment. It is provided strictly
450
- # for interface compatibility.
451
- #
452
- # Note that the $? (Process::Status) global variable is NOT set. This
453
- # may be addressed in a future release.
454
- #
455
- def waitpid(pid, flags = nil)
456
- exit_code = [0].pack('L')
457
- handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
458
-
459
- if handle == INVALID_HANDLE_VALUE
460
- raise Error, get_last_error
461
- end
355
+ begin
356
+ unless in_job
357
+ unless AssignProcessToJobObject(handle, GetCurrentProcess())
358
+ raise SystemCallError, FFI.errno, "AssignProcessToJobObject"
359
+ end
360
+ end
462
361
 
463
- # TODO: update the $? global variable (if/when possible)
464
- status = WaitForSingleObject(handle, INFINITE)
362
+ ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
465
363
 
466
- begin
467
- unless GetExitCodeProcess(handle, exit_code)
468
- raise Error, get_last_error
469
- end
470
- ensure
471
- CloseHandle(handle)
472
- end
364
+ # Set the LimitFlags and relevant members of the struct
365
+ if resource == RLIMIT_CPU
366
+ ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_TIME
367
+ ptr[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart] = max_limit
368
+ else
369
+ ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_MEMORY
370
+ ptr[:ProcessMemoryLimit] = max_limit
371
+ end
473
372
 
474
- @child_pids.delete(pid)
373
+ bool = SetInformationJobObject(
374
+ handle,
375
+ JobObjectExtendedLimitInformation,
376
+ ptr,
377
+ ptr.size
378
+ )
475
379
 
476
- # TODO: update the $? global variable (if/when possible)
477
- exit_code = exit_code.unpack('L').first
380
+ unless bool
381
+ raise SystemCallError, FFI.errno, "SetInformationJobObject"
382
+ end
383
+ ensure
384
+ at_exit{ CloseHandle(handle) if handle }
385
+ end
386
+ end
387
+
388
+ # Process.create(key => value, ...) => ProcessInfo
389
+ #
390
+ # This is a wrapper for the CreateProcess() function. It executes a process,
391
+ # returning a ProcessInfo struct. It accepts a hash as an argument.
392
+ # There are several primary keys:
393
+ #
394
+ # * command_line (this or app_name must be present)
395
+ # * app_name (default: nil)
396
+ # * inherit (default: false)
397
+ # * process_inherit (default: false)
398
+ # * thread_inherit (default: false)
399
+ # * creation_flags (default: 0)
400
+ # * cwd (default: Dir.pwd)
401
+ # * startup_info (default: nil)
402
+ # * environment (default: nil)
403
+ # * close_handles (default: true)
404
+ # * with_logon (default: nil)
405
+ # * domain (default: nil)
406
+ # * password (default: nil, mandatory if with_logon)
407
+ #
408
+ # Of these, the 'command_line' or 'app_name' must be specified or an
409
+ # error is raised. Both may be set individually, but 'command_line' should
410
+ # be preferred if only one of them is set because it does not (necessarily)
411
+ # require an explicit path or extension to work.
412
+ #
413
+ # The 'domain' and 'password' options are only relevent in the context
414
+ # of 'with_logon'. If 'with_logon' is set, then the 'password' option is
415
+ # mandatory.
416
+ #
417
+ # The startup_info key takes a hash. Its keys are attributes that are
418
+ # part of the StartupInfo struct, and are generally only meaningful for
419
+ # GUI or console processes. See the documentation on CreateProcess()
420
+ # and the StartupInfo struct on MSDN for more information.
421
+ #
422
+ # * desktop
423
+ # * title
424
+ # * x
425
+ # * y
426
+ # * x_size
427
+ # * y_size
428
+ # * x_count_chars
429
+ # * y_count_chars
430
+ # * fill_attribute
431
+ # * sw_flags
432
+ # * startf_flags
433
+ # * stdin
434
+ # * stdout
435
+ # * stderr
436
+ #
437
+ # Note that the 'stdin', 'stdout' and 'stderr' options can be either Ruby
438
+ # IO objects or file descriptors (i.e. a fileno). However, StringIO objects
439
+ # are not currently supported. Unfortunately, setting these is not currently
440
+ # an option for JRuby.
441
+ #
442
+ # If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
443
+ # is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
444
+ # automatically OR'd to the +startf_flags+ value.
445
+ #
446
+ # The ProcessInfo struct contains the following members:
447
+ #
448
+ # * process_handle - The handle to the newly created process.
449
+ # * thread_handle - The handle to the primary thread of the process.
450
+ # * process_id - Process ID.
451
+ # * thread_id - Thread ID.
452
+ #
453
+ # If the 'close_handles' option is set to true (the default) then the
454
+ # process_handle and the thread_handle are automatically closed for you
455
+ # before the ProcessInfo struct is returned.
456
+ #
457
+ # If the 'with_logon' option is set, then the process runs the specified
458
+ # executable file in the security context of the specified credentials.
459
+ #
460
+ def create(args)
461
+ unless args.kind_of?(Hash)
462
+ raise TypeError, 'hash keyword arguments expected'
463
+ end
464
+
465
+ valid_keys = %w[
466
+ app_name command_line inherit creation_flags cwd environment
467
+ startup_info thread_inherit process_inherit close_handles with_logon
468
+ domain password
469
+ ]
470
+
471
+ valid_si_keys = %w[
472
+ startf_flags desktop title x y x_size y_size x_count_chars
473
+ y_count_chars fill_attribute sw_flags stdin stdout stderr
474
+ ]
475
+
476
+ # Set default values
477
+ hash = {
478
+ 'app_name' => nil,
479
+ 'creation_flags' => 0,
480
+ 'close_handles' => true
481
+ }
478
482
 
479
- pid
480
- end
483
+ # Validate the keys, and convert symbols and case to lowercase strings.
484
+ args.each{ |key, val|
485
+ key = key.to_s.downcase
486
+ unless valid_keys.include?(key)
487
+ raise ArgumentError, "invalid key '#{key}'"
488
+ end
489
+ hash[key] = val
490
+ }
481
491
 
482
- # Waits for the given child process to exit and returns an array containing
483
- # the process id and the exit status.
484
- #
485
- # The +flags+ argument is ignored at the moment. It is provided strictly
486
- # for interface compatibility.
487
- #
488
- # Note that the $? (Process::Status) global variable is NOT set. This
489
- # may be addressed in a future release if/when possible.
490
- #--
491
- # Ruby does not provide a way to hook into $? so there's no way for us
492
- # to set it.
493
- #
494
- def waitpid2(pid, flags = nil)
495
- exit_code = [0].pack('L')
496
- handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
497
-
498
- if handle == INVALID_HANDLE_VALUE
499
- raise Error, get_last_error
500
- end
492
+ si_hash = {}
501
493
 
502
- # TODO: update the $? global variable (if/when possible)
503
- status = WaitForSingleObject(handle, INFINITE)
494
+ # If the startup_info key is present, validate its subkeys
495
+ if hash['startup_info']
496
+ hash['startup_info'].each{ |key, val|
497
+ key = key.to_s.downcase
498
+ unless valid_si_keys.include?(key)
499
+ raise ArgumentError, "invalid startup_info key '#{key}'"
500
+ end
501
+ si_hash[key] = val
502
+ }
503
+ end
504
504
 
505
- begin
506
- unless GetExitCodeProcess(handle, exit_code)
507
- raise Error, get_last_error
505
+ # The +command_line+ key is mandatory unless the +app_name+ key
506
+ # is specified.
507
+ unless hash['command_line']
508
+ if hash['app_name']
509
+ hash['command_line'] = hash['app_name']
510
+ hash['app_name'] = nil
511
+ else
512
+ raise ArgumentError, 'command_line or app_name must be specified'
513
+ end
508
514
  end
509
- ensure
510
- CloseHandle(handle)
511
- end
512
515
 
513
- @child_pids.delete(pid)
516
+ env = nil
514
517
 
515
- # TODO: update the $? global variable (if/when possible)
516
- exit_code = exit_code.unpack('L').first
518
+ # The env string should be passed as a string of ';' separated paths.
519
+ if hash['environment']
520
+ env = hash['environment']
517
521
 
518
- [pid, exit_code]
519
- end
522
+ unless env.respond_to?(:join)
523
+ env = hash['environment'].split(File::PATH_SEPARATOR)
524
+ end
520
525
 
521
- # Sends the given +signal+ to an array of process id's. The +signal+ may
522
- # be any value from 0 to 9, or the special strings 'SIGINT' (or 'INT'),
523
- # 'SIGBRK' (or 'BRK') and 'SIGKILL' (or 'KILL'). An array of successfully
524
- # killed pids is returned.
525
- #
526
- # Signal 0 merely tests if the process is running without killing it.
527
- # Signal 2 sends a CTRL_C_EVENT to the process.
528
- # Signal 3 sends a CTRL_BRK_EVENT to the process.
529
- # Signal 9 kills the process in a harsh manner.
530
- # Signals 1 and 4-8 kill the process in a nice manner.
531
- #
532
- # SIGINT/INT corresponds to signal 2
533
- # SIGBRK/BRK corresponds to signal 3
534
- # SIGKILL/KILL corresponds to signal 9
535
- #
536
- # Signals 2 and 3 only affect console processes, and then only if the
537
- # process was created with the CREATE_NEW_PROCESS_GROUP flag.
538
- #
539
- def kill(signal, *pids)
540
- case signal
541
- when 'SIGINT', 'INT'
542
- signal = 2
543
- when 'SIGBRK', 'BRK'
544
- signal = 3
545
- when 'SIGKILL', 'KILL'
546
- signal = 9
547
- when 0..9
548
- # Do nothing
549
- else
550
- raise Error, "Invalid signal '#{signal}'"
551
- end
526
+ env = env.map{ |e| e + 0.chr }.join('') + 0.chr
527
+ env.to_wide_string! if hash['with_logon']
528
+ end
552
529
 
553
- killed_pids = []
530
+ # Process SECURITY_ATTRIBUTE structure
531
+ process_security = nil
554
532
 
555
- pids.each{ |pid|
556
- # Send the signal to the current process if the pid is zero
557
- if pid == 0
558
- pid = Process.pid
533
+ if hash['process_inherit']
534
+ process_security = SECURITY_ATTRIBUTES.new
535
+ process_security[:nLength] = 12
536
+ process_security[:bInheritHandle] = true
559
537
  end
560
538
 
561
- # No need for full access if the signal is zero
562
- if signal == 0
563
- access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
564
- handle = OpenProcess(access, 0 , pid)
565
- else
566
- handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
539
+ # Thread SECURITY_ATTRIBUTE structure
540
+ thread_security = nil
541
+
542
+ if hash['thread_inherit']
543
+ thread_security = SECURITY_ATTRIBUTES.new
544
+ thread_security[:nLength] = 12
545
+ thread_security[:bInheritHandle] = true
567
546
  end
568
547
 
569
- begin
570
- case signal
571
- when 0
572
- if handle != 0
573
- killed_pids.push(pid)
574
- else
575
- # If ERROR_ACCESS_DENIED is returned, we know it's running
576
- if GetLastError() == ERROR_ACCESS_DENIED
577
- killed_pids.push(pid)
578
- else
579
- raise Error, get_last_error
580
- end
581
- end
582
- when 2
583
- if GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)
584
- killed_pids.push(pid)
585
- end
586
- when 3
587
- if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)
588
- killed_pids.push(pid)
589
- end
590
- when 9
591
- if TerminateProcess(handle, pid)
592
- killed_pids.push(pid)
593
- @child_pids.delete(pid)
594
- else
595
- raise Error, get_last_error
596
- end
548
+ # Automatically handle stdin, stdout and stderr as either IO objects
549
+ # or file descriptors. This won't work for StringIO, however. It also
550
+ # will not work on JRuby because of the way it handles internal file
551
+ # descriptors.
552
+ #
553
+ ['stdin', 'stdout', 'stderr'].each{ |io|
554
+ if si_hash[io]
555
+ if si_hash[io].respond_to?(:fileno)
556
+ handle = get_osfhandle(si_hash[io].fileno)
597
557
  else
598
- if handle != 0
599
- thread_id = [0].pack('L')
600
- begin
601
- thread = CreateRemoteThread(
602
- handle,
603
- 0,
604
- 0,
605
- GetProcAddress(GetModuleHandle('kernel32'), 'ExitProcess'),
606
- 0,
607
- 0,
608
- thread_id
609
- )
610
-
611
- if thread
612
- WaitForSingleObject(thread, 5)
613
- killed_pids.push(pid)
614
- @child_pids.delete(pid)
615
- else
616
- raise Error, get_last_error
617
- end
618
- ensure
619
- CloseHandle(thread) if thread
620
- end
621
- else
622
- raise Error, get_last_error
623
- end # case
558
+ handle = get_osfhandle(si_hash[io])
559
+ end
624
560
 
625
- @child_pids.delete(pid)
626
- end
627
- ensure
628
- CloseHandle(handle) unless handle == INVALID_HANDLE_VALUE
629
- end
630
- }
561
+ if handle == INVALID_HANDLE_VALUE
562
+ ptr = FFI::MemoryPointer.new(:int)
631
563
 
632
- killed_pids
633
- end
564
+ if get_errno(ptr) == 0
565
+ errno = ptr.read_int
566
+ else
567
+ errno = FFI.errno
568
+ end
634
569
 
635
- # Process.create(key => value, ...) => ProcessInfo
636
- #
637
- # This is a wrapper for the CreateProcess() function. It executes a process,
638
- # returning a ProcessInfo struct. It accepts a hash as an argument.
639
- # There are several primary keys:
640
- #
641
- # * command_line (mandatory)
642
- # * app_name (default: nil)
643
- # * inherit (default: false)
644
- # * process_inherit (default: false)
645
- # * thread_inherit (default: false)
646
- # * creation_flags (default: 0)
647
- # * cwd (default: Dir.pwd)
648
- # * startup_info (default: nil)
649
- # * environment (default: nil)
650
- # * close_handles (default: true)
651
- # * with_logon (default: nil)
652
- # * domain (default: nil)
653
- # * password (default: nil)
654
- #
655
- # Of these, the 'command_line' or 'app_name' must be specified or an
656
- # error is raised. Both may be set individually, but 'command_line' should
657
- # be preferred if only one of them is set because it does not (necessarily)
658
- # require an explicit path or extension to work.
659
- #
660
- # The 'domain' and 'password' options are only relevent in the context
661
- # of 'with_logon'.
662
- #
663
- # The startup_info key takes a hash. Its keys are attributes that are
664
- # part of the StartupInfo struct, and are generally only meaningful for
665
- # GUI or console processes. See the documentation on CreateProcess()
666
- # and the StartupInfo struct on MSDN for more information.
667
- #
668
- # * desktop
669
- # * title
670
- # * x
671
- # * y
672
- # * x_size
673
- # * y_size
674
- # * x_count_chars
675
- # * y_count_chars
676
- # * fill_attribute
677
- # * sw_flags
678
- # * startf_flags
679
- # * stdin
680
- # * stdout
681
- # * stderr
682
- #
683
- # The relevant constants for 'creation_flags', 'sw_flags' and 'startf_flags'
684
- # are included in the Windows::Process, Windows::Console and Windows::Window
685
- # modules. These come with the windows-pr library, a prerequisite of this
686
- # library. Note that the 'stdin', 'stdout' and 'stderr' options can be
687
- # either Ruby IO objects or file descriptors (i.e. a fileno). However,
688
- # StringIO objects are not currently supported.
689
- #
690
- # If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
691
- # is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
692
- # automatically OR'd to the +startf_flags+ value.
693
- #
694
- # The ProcessInfo struct contains the following members:
695
- #
696
- # * process_handle - The handle to the newly created process.
697
- # * thread_handle - The handle to the primary thread of the process.
698
- # * process_id - Process ID.
699
- # * thread_id - Thread ID.
700
- #
701
- # If the 'close_handles' option is set to true (the default) then the
702
- # process_handle and the thread_handle are automatically closed for you
703
- # before the ProcessInfo struct is returned.
704
- #
705
- # If the 'with_logon' option is set, then the process runs the specified
706
- # executable file in the security context of the specified credentials.
707
- #
708
- def create(args)
709
- unless args.kind_of?(Hash)
710
- raise TypeError, 'Expecting hash-style keyword arguments'
711
- end
570
+ raise SystemCallError.new("get_osfhandle", errno)
571
+ end
712
572
 
713
- valid_keys = %w/
714
- app_name command_line inherit creation_flags cwd environment
715
- startup_info thread_inherit process_inherit close_handles with_logon
716
- domain password
717
- /
718
-
719
- valid_si_keys = %/
720
- startf_flags desktop title x y x_size y_size x_count_chars
721
- y_count_chars fill_attribute sw_flags stdin stdout stderr
722
- /
723
-
724
- # Set default values
725
- hash = {
726
- 'app_name' => nil,
727
- 'creation_flags' => 0,
728
- 'close_handles' => true
729
- }
730
-
731
- # Validate the keys, and convert symbols and case to lowercase strings.
732
- args.each{ |key, val|
733
- key = key.to_s.downcase
734
- unless valid_keys.include?(key)
735
- raise ArgumentError, "invalid key '#{key}'"
736
- end
737
- hash[key] = val
738
- }
573
+ # Most implementations of Ruby on Windows create inheritable
574
+ # handles by default, but some do not. RF bug #26988.
575
+ bool = SetHandleInformation(
576
+ handle,
577
+ HANDLE_FLAG_INHERIT,
578
+ HANDLE_FLAG_INHERIT
579
+ )
739
580
 
740
- si_hash = {}
581
+ raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool
741
582
 
742
- # If the startup_info key is present, validate its subkeys
743
- if hash['startup_info']
744
- hash['startup_info'].each{ |key, val|
745
- key = key.to_s.downcase
746
- unless valid_si_keys.include?(key)
747
- raise ArgumentError, "invalid startup_info key '#{key}'"
583
+ si_hash[io] = handle
584
+ si_hash['startf_flags'] ||= 0
585
+ si_hash['startf_flags'] |= STARTF_USESTDHANDLES
586
+ hash['inherit'] = true
748
587
  end
749
- si_hash[key] = val
750
588
  }
751
- end
752
589
 
753
- # The +command_line+ key is mandatory unless the +app_name+ key
754
- # is specified.
755
- unless hash['command_line']
590
+ procinfo = PROCESS_INFORMATION.new
591
+ startinfo = STARTUPINFO.new
592
+
593
+ unless si_hash.empty?
594
+ startinfo[:cb] = startinfo.size
595
+ startinfo[:lpDesktop] = si_hash['desktop'] if si_hash['desktop']
596
+ startinfo[:lpTitle] = si_hash['title'] if si_hash['title']
597
+ startinfo[:dwX] = si_hash['x'] if si_hash['x']
598
+ startinfo[:dwY] = si_hash['y'] if si_hash['y']
599
+ startinfo[:dwXSize] = si_hash['x_size'] if si_hash['x_size']
600
+ startinfo[:dwYSize] = si_hash['y_size'] if si_hash['y_size']
601
+ startinfo[:dwXCountChars] = si_hash['x_count_chars'] if si_hash['x_count_chars']
602
+ startinfo[:dwYCountChars] = si_hash['y_count_chars'] if si_hash['y_count_chars']
603
+ startinfo[:dwFillAttribute] = si_hash['fill_attribute'] if si_hash['fill_attribute']
604
+ startinfo[:dwFlags] = si_hash['startf_flags'] if si_hash['startf_flags']
605
+ startinfo[:wShowWindow] = si_hash['sw_flags'] if si_hash['sw_flags']
606
+ startinfo[:cbReserved2] = 0
607
+ startinfo[:hStdInput] = si_hash['stdin'] if si_hash['stdin']
608
+ startinfo[:hStdOutput] = si_hash['stdout'] if si_hash['stdout']
609
+ startinfo[:hStdError] = si_hash['stderr'] if si_hash['stderr']
610
+ end
611
+
612
+ app = nil
613
+ cmd = nil
614
+
615
+ # Convert strings to wide character strings if present
756
616
  if hash['app_name']
757
- hash['command_line'] = hash['app_name']
758
- hash['app_name'] = nil
759
- else
760
- raise ArgumentError, 'command_line or app_name must be specified'
617
+ app = hash['app_name'].to_wide_string
761
618
  end
762
- end
763
619
 
764
- # The environment string should be passed as a string of ';' separated
765
- # paths.
766
- if hash['environment']
767
- env = hash['environment'].split(File::PATH_SEPARATOR) << 0.chr
768
- if hash['with_logon']
769
- env = env.map{ |e| multi_to_wide(e) }
770
- env = [env.join("\0\0")].pack('p*').unpack('L').first
771
- else
772
- env = [env.join("\0")].pack('p*').unpack('L').first
620
+ if hash['command_line']
621
+ cmd = hash['command_line'].to_wide_string
773
622
  end
774
- else
775
- env = nil
776
- end
777
-
778
- startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
779
- startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
780
- procinfo = [0,0,0,0].pack('LLLL')
781
623
 
782
- # Process SECURITY_ATTRIBUTE structure
783
- process_security = 0
784
- if hash['process_inherit']
785
- process_security = [0,0,0].pack('LLL')
786
- process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
787
- process_security[8,4] = [1].pack('L') # TRUE
788
- end
624
+ if hash['cwd']
625
+ cwd = hash['cwd'].to_wide_string
626
+ end
789
627
 
790
- # Thread SECURITY_ATTRIBUTE structure
791
- thread_security = 0
792
- if hash['thread_inherit']
793
- thread_security = [0,0,0].pack('LLL')
794
- thread_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
795
- thread_security[8,4] = [1].pack('L') # TRUE
796
- end
628
+ if hash['with_logon']
629
+ logon = hash['with_logon'].to_wide_string
797
630
 
798
- # Automatically handle stdin, stdout and stderr as either IO objects
799
- # or file descriptors. This won't work for StringIO, however.
800
- ['stdin', 'stdout', 'stderr'].each{ |io|
801
- if si_hash[io]
802
- if si_hash[io].respond_to?(:fileno)
803
- handle = get_osfhandle(si_hash[io].fileno)
631
+ if hash['password']
632
+ passwd = hash['password'].to_wide_string
804
633
  else
805
- handle = get_osfhandle(si_hash[io])
634
+ raise ArgumentError, 'password must be specified if with_logon is used'
806
635
  end
807
636
 
808
- if handle == INVALID_HANDLE_VALUE
809
- raise Error, get_last_error
637
+ if hash['domain']
638
+ domain = hash['domain'].to_wide_string
810
639
  end
811
640
 
812
- # Most implementations of Ruby on Windows create inheritable
813
- # handles by default, but some do not. RF bug #26988.
814
- bool = SetHandleInformation(
815
- handle,
816
- HANDLE_FLAG_INHERIT,
817
- HANDLE_FLAG_INHERIT
641
+ hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
642
+
643
+ bool = CreateProcessWithLogonW(
644
+ logon, # User
645
+ domain, # Domain
646
+ passwd, # Password
647
+ LOGON_WITH_PROFILE, # Logon flags
648
+ app, # App name
649
+ cmd, # Command line
650
+ hash['creation_flags'], # Creation flags
651
+ env, # Environment
652
+ cwd, # Working directory
653
+ startinfo, # Startup Info
654
+ procinfo # Process Info
818
655
  )
819
656
 
820
- raise Error, get_last_error unless bool
821
-
822
- si_hash[io] = handle
823
- si_hash['startf_flags'] ||= 0
824
- si_hash['startf_flags'] |= STARTF_USESTDHANDLES
825
- hash['inherit'] = true
826
- end
827
- }
828
-
829
- # The bytes not covered here are reserved (null)
830
- unless si_hash.empty?
831
- startinfo[0,4] = [startinfo.size].pack('L')
832
- startinfo[8,4] = [si_hash['desktop']].pack('p*') if si_hash['desktop']
833
- startinfo[12,4] = [si_hash['title']].pack('p*') if si_hash['title']
834
- startinfo[16,4] = [si_hash['x']].pack('L') if si_hash['x']
835
- startinfo[20,4] = [si_hash['y']].pack('L') if si_hash['y']
836
- startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
837
- startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
838
- startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
839
- startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
840
- startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
841
- startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
842
- startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
843
- startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
844
- startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
845
- startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
846
- end
847
-
848
- if hash['with_logon']
849
- logon = multi_to_wide(hash['with_logon'])
850
- domain = multi_to_wide(hash['domain'])
851
- app = hash['app_name'].nil? ? nil : multi_to_wide(hash['app_name'])
852
- cmd = hash['command_line'].nil? ? nil : multi_to_wide(hash['command_line'])
853
- cwd = multi_to_wide(hash['cwd'])
854
- passwd = multi_to_wide(hash['password'])
855
-
856
- hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
857
-
858
- bool = CreateProcessWithLogonW(
859
- logon, # User
860
- domain, # Domain
861
- passwd, # Password
862
- LOGON_WITH_PROFILE, # Logon flags
863
- app, # App name
864
- cmd, # Command line
865
- hash['creation_flags'], # Creation flags
866
- env, # Environment
867
- cwd, # Working directory
868
- startinfo, # Startup Info
869
- procinfo # Process Info
870
- )
871
- else
872
- bool = CreateProcess(
873
- hash['app_name'], # App name
874
- hash['command_line'], # Command line
875
- process_security, # Process attributes
876
- thread_security, # Thread attributes
877
- hash['inherit'], # Inherit handles?
878
- hash['creation_flags'], # Creation flags
879
- env, # Environment
880
- hash['cwd'], # Working directory
881
- startinfo, # Startup Info
882
- procinfo # Process Info
883
- )
884
- end
885
-
886
- # TODO: Close stdin, stdout and stderr handles in the si_hash unless
887
- # they're pointing to one of the standard handles already. [Maybe]
888
- unless bool
889
- raise Error, "CreateProcess() failed: " + get_last_error
890
- end
891
-
892
- # Automatically close the process and thread handles in the
893
- # PROCESS_INFORMATION struct unless explicitly told not to.
894
- if hash['close_handles']
895
- CloseHandle(procinfo[0,4].unpack('L').first)
896
- CloseHandle(procinfo[4,4].unpack('L').first)
897
- end
898
-
899
- ProcessInfo.new(
900
- procinfo[0,4].unpack('L').first, # hProcess
901
- procinfo[4,4].unpack('L').first, # hThread
902
- procinfo[8,4].unpack('L').first, # hProcessId
903
- procinfo[12,4].unpack('L').first # hThreadId
904
- )
905
- end
906
-
907
- # Waits for any child process to exit and returns the process id of that
908
- # child. If a pid is provided that is greater than or equal to 0, then
909
- # it is the equivalent of calling Process.waitpid.
910
- #
911
- # The +flags+ argument is ignored at the moment. It is provided strictly
912
- # for interface compatibility.
913
- #
914
- # Note that the $? (Process::Status) global variable is NOT set. This
915
- # may be addressed in a future release.
916
- #--
917
- # The GetProcessId() function is not defined in Windows 2000 or earlier
918
- # so we have to do some extra work for those platforms.
919
- #
920
- def wait(pid = -1, flags = nil)
921
- if pid && pid >= 0
922
- pid, status = waitpid(pid, flags)
923
- return pid
924
- end
925
-
926
- handles = []
927
-
928
- # Windows 2000 or earlier
929
- unless defined? GetProcessId
930
- pids = []
931
- end
932
-
933
- @child_pids.each_with_index{ |lpid, i|
934
- handles[i] = OpenProcess(PROCESS_ALL_ACCESS, 0, lpid)
657
+ unless bool
658
+ raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
659
+ end
660
+ else
661
+ inherit = hash['inherit'] || false
662
+
663
+ bool = CreateProcessW(
664
+ app, # App name
665
+ cmd, # Command line
666
+ process_security, # Process attributes
667
+ thread_security, # Thread attributes
668
+ inherit, # Inherit handles?
669
+ hash['creation_flags'], # Creation flags
670
+ env, # Environment
671
+ cwd, # Working directory
672
+ startinfo, # Startup Info
673
+ procinfo # Process Info
674
+ )
935
675
 
936
- if handles[i] == INVALID_HANDLE_VALUE
937
- err = "unable to get HANDLE on process associated with pid #{lpid}"
938
- raise Error, err
676
+ unless bool
677
+ raise SystemCallError.new("CreateProcess", FFI.errno)
678
+ end
939
679
  end
940
680
 
941
- unless defined? GetProcessId
942
- pids[i] = lpid
681
+ # Automatically close the process and thread handles in the
682
+ # PROCESS_INFORMATION struct unless explicitly told not to.
683
+ if hash['close_handles']
684
+ CloseHandle(procinfo[:hProcess])
685
+ CloseHandle(procinfo[:hThread])
943
686
  end
944
- }
945
687
 
946
- wait = WaitForMultipleObjects(
947
- handles.size,
948
- handles.pack('L*'),
949
- 0,
950
- INFINITE
951
- )
952
-
953
- if wait >= WAIT_OBJECT_0 && wait <= WAIT_OBJECT_0 + @child_pids.size - 1
954
- index = wait - WAIT_OBJECT_0
955
- handle = handles[index]
956
-
957
- if defined? GetProcessId
958
- pid = GetProcessId(handle)
959
- else
960
- pid = pids[index]
961
- end
962
-
963
- @child_pids.delete(pid)
964
- handles.each{ |h| CloseHandle(h) }
965
- return pid
966
- end
967
-
968
- nil
969
- end
970
-
971
- # Waits for any child process to exit and returns an array containing the
972
- # process id and the exit status of that child.
973
- #
974
- # The +flags+ argument is ignored at the moment. It is provided strictly
975
- # for interface compatibility.
976
- #
977
- # Note that the $? (Process::Status) global variable is NOT set. This
978
- # may be addressed in a future release.
979
- #--
980
- # The GetProcessId() function is not defined in Windows 2000 or earlier
981
- # so we have to do some extra work for those platforms.
982
- #
983
- def wait2(pid = -1, flags = nil)
984
- if pid && pid >= 0
985
- pid, status = waitpid2(pid, flags)
986
- return pid
987
- end
988
-
989
- handles = []
990
-
991
- # Windows 2000 or earlier
992
- unless defined? GetProcessId
993
- pids = []
688
+ ProcessInfo.new(
689
+ procinfo[:hProcess],
690
+ procinfo[:hThread],
691
+ procinfo[:dwProcessId],
692
+ procinfo[:dwThreadId]
693
+ )
994
694
  end
995
695
 
996
- @child_pids.each_with_index{ |lpid, i|
997
- handles[i] = OpenProcess(PROCESS_ALL_ACCESS, 0, lpid)
998
-
999
- if handles[i] == INVALID_HANDLE_VALUE
1000
- err = "unable to get HANDLE on process associated with pid #{lpid}"
1001
- raise Error, err
696
+ remove_method :kill
697
+
698
+ # Kill a given process with a specific signal. This overrides the default
699
+ # implementation of Process.kill. The differences mainly reside in the way
700
+ # it kills processes, but this version also gives you finer control over
701
+ # behavior.
702
+ #
703
+ # Internally, signals 2 and 3 will generate a console control event, using
704
+ # a ctrl-c or ctrl-break event, respectively. Signal 9 terminates the
705
+ # process harshly, given that process no chance to do any internal cleanup.
706
+ # Signals 1 and 4-8 kill the process more nicely, giving the process a
707
+ # chance to do internal cleanup before being killed. Signal 0 behaves the
708
+ # same as the default implementation.
709
+ #
710
+ # When using signals 1 or 4-8 you may specify additional options that
711
+ # allow finer control over how that process is killed and how your program
712
+ # behaves.
713
+ #
714
+ # Possible options for signals 1 and 4-8.
715
+ #
716
+ # :exit_proc => The name of the exit function called when signal 1 or 4-8
717
+ # is used. The default is 'ExitProcess'.
718
+ #
719
+ # :dll_module => The name of the .dll (or .exe) that contains :exit_proc.
720
+ # The default is 'kernel32'.
721
+ #
722
+ # :wait_time => The time, in milliseconds, to wait for the process to
723
+ # actually die. The default is 5ms. If you specify 0 here
724
+ # then the process does not wait if the process is not
725
+ # signaled and instead returns immediately. Alternatively,
726
+ # you may specify Process::INFINITE, and your code will
727
+ # block until the process is actually signaled.
728
+ #
729
+ # Example:
730
+ #
731
+ # Process.kill(1, 12345, :exit_proc => 'ExitProcess', :module => 'kernel32')
732
+ #
733
+ def kill(signal, *pids)
734
+ # Match the spec, require at least 2 arguments
735
+ if pids.length == 0
736
+ raise ArgumentError, "wrong number of arguments (1 for at least 2)"
737
+ end
738
+
739
+ # Match the spec, signal may not be less than zero if numeric
740
+ if signal.is_a?(Numeric) && signal < 0 # EINVAL
741
+ raise SystemCallError.new(22)
742
+ end
743
+
744
+ # Match the spec, signal must be a numeric, string or symbol
745
+ unless signal.is_a?(String) || signal.is_a?(Numeric) || signal.is_a?(Symbol)
746
+ raise ArgumentError, "bad signal type #{signal.class}"
747
+ end
748
+
749
+ # Match the spec, making an exception for BRK/SIGBRK, if the signal name is invalid
750
+ if signal.is_a?(String) || signal.is_a?(Symbol)
751
+ signal = signal.to_s.sub('SIG', '')
752
+ unless Signal.list.keys.include?(signal) || ['BRK', 'BRK'].include?(signal)
753
+ raise ArgumentError, "unsupported name '#{signal}'"
754
+ end
1002
755
  end
1003
756
 
1004
- unless defined? GetProcessId
1005
- pids[i] = lpid
1006
- end
1007
- }
757
+ # If the last argument is a hash, pop it and assume it's a hash of options
758
+ if pids.last.is_a?(Hash)
759
+ hash = pids.pop
760
+ opts = {}
1008
761
 
1009
- wait = WaitForMultipleObjects(
1010
- handles.size,
1011
- handles.pack('L*'),
1012
- 0,
1013
- INFINITE
1014
- )
762
+ valid = %w[exit_proc dll_module wait_time]
1015
763
 
1016
- if wait >= WAIT_OBJECT_0 && wait <= WAIT_OBJECT_0 + @child_pids.size - 1
1017
- index = wait - WAIT_OBJECT_0
1018
- handle = handles[index]
764
+ hash.each{ |k,v|
765
+ k = k.to_s.downcase
766
+ unless valid.include?(k)
767
+ raise ArgumentError, "invalid option '#{k}'"
768
+ end
769
+ opts[k] = v
770
+ }
1019
771
 
1020
- if defined? GetProcessId
1021
- pid = GetProcessId(handle)
772
+ exit_proc = opts['exit_proc'] || 'ExitProcess'
773
+ dll_module = opts['dll_module'] || 'kernel32'
774
+ wait_time = opts['wait_time'] || 5
1022
775
  else
1023
- pid = pids[index]
776
+ wait_time = 5
777
+ exit_proc = 'ExitProcess'
778
+ dll_module = 'kernel32'
1024
779
  end
1025
780
 
1026
- exit_code = [0].pack('l')
781
+ count = 0
1027
782
 
1028
- unless GetExitCodeProcess(handle, exit_code)
1029
- raise get_last_error
1030
- end
783
+ pids.each{ |pid|
784
+ raise TypeError unless pid.is_a?(Numeric) # Match spec, pid must be a number
785
+ raise SystemCallError.new(22) if pid < 0 # Match spec, EINVAL if pid less than zero
1031
786
 
1032
- @child_pids.delete(pid)
787
+ sigint = [Signal.list['INT'], 'INT', 'SIGINT', :INT, :SIGINT, 2]
1033
788
 
1034
- handles.each{ |h| CloseHandle(h) }
1035
- return [pid, exit_code.unpack('l').first]
1036
- end
789
+ # Match the spec
790
+ if pid == 0 && !sigint.include?(signal)
791
+ raise SystemCallError.new(22)
792
+ end
1037
793
 
1038
- nil
1039
- end
794
+ if signal == 0
795
+ access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
796
+ elsif signal == 9
797
+ access = PROCESS_TERMINATE
798
+ else
799
+ access = PROCESS_ALL_ACCESS
800
+ end
1040
801
 
1041
- # Returns the process ID of the parent of this process.
1042
- #--
1043
- # In MRI this method always returns 0.
1044
- #
1045
- def ppid
1046
- ppid = 0
802
+ begin
803
+ handle = OpenProcess(access, false, pid)
1047
804
 
1048
- return ppid if Process.pid == 0 # Paranoia
805
+ if signal != 0 && handle == 0
806
+ raise SystemCallError, FFI.errno, "OpenProcess"
807
+ end
1049
808
 
1050
- handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
809
+ case signal
810
+ when 0
811
+ if handle != 0
812
+ count += 1
813
+ else
814
+ if FFI.errno == ERROR_ACCESS_DENIED
815
+ count += 1
816
+ else
817
+ raise SystemCallError.new(3) # ESRCH
818
+ end
819
+ end
820
+ when Signal.list['INT'], 'INT', 'SIGINT', :INT, :SIGINT, 2
821
+ if GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)
822
+ count += 1
823
+ else
824
+ raise SystemCallError.new("GenerateConsoleCtrlEvent", FFI.errno)
825
+ end
826
+ when Signal.list['BRK'], 'BRK', 'SIGBRK', :BRK, :SIGBRK, 3
827
+ if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)
828
+ count += 1
829
+ else
830
+ raise SystemCallError.new("GenerateConsoleCtrlEvent", FFI.errno)
831
+ end
832
+ when Signal.list['KILL'], 'KILL', 'SIGKILL', :KILL, :SIGKILL, 9
833
+ if TerminateProcess(handle, pid)
834
+ count += 1
835
+ else
836
+ raise SystemCallError.new("TerminateProcess", FFI.errno)
837
+ end
838
+ else
839
+ thread_id = FFI::MemoryPointer.new(:ulong)
1051
840
 
1052
- if handle == INVALID_HANDLE_VALUE
1053
- raise Error, get_last_error
1054
- end
841
+ mod = GetModuleHandle(dll_module)
1055
842
 
1056
- proc_entry = 0.chr * 296 # 36 + 260
1057
- proc_entry[0, 4] = [proc_entry.size].pack('L') # Set dwSize member
843
+ if mod == 0
844
+ raise SystemCallError.new("GetModuleHandle: '#{dll_module}'", FFI.errno)
845
+ end
1058
846
 
1059
- begin
1060
- unless Process32First(handle, proc_entry)
1061
- error = get_last_error
1062
- raise Error, error
1063
- end
847
+ proc_addr = GetProcAddress(mod, exit_proc)
1064
848
 
1065
- while Process32Next(handle, proc_entry)
1066
- if proc_entry[8, 4].unpack('L')[0] == Process.pid
1067
- ppid = proc_entry[24, 4].unpack('L')[0] # th32ParentProcessID
1068
- break
1069
- end
1070
- end
1071
- ensure
1072
- CloseHandle(handle)
1073
- end
849
+ if proc_addr == 0
850
+ raise SystemCallError.new("GetProcAddress: '#{exit_proc}'", FFI.errno)
851
+ end
1074
852
 
1075
- ppid
1076
- end
853
+ thread = CreateRemoteThread(handle, nil, 0, proc_addr, nil, 0, thread_id)
1077
854
 
1078
- # Creates the equivalent of a subshell via the CreateProcess() function.
1079
- # This behaves in a manner that is similar, but not identical to, the
1080
- # Kernel.fork method for Unix. Unlike the Unix fork, this method starts
1081
- # from the top of the script rather than the point of the call.
1082
- #
1083
- # WARNING: This implementation should be considered experimental. It is
1084
- # not recommended for production use.
1085
- #
1086
- def fork
1087
- last_arg = ARGV.last
1088
-
1089
- # Look for the 'child#xxx' tag
1090
- if last_arg =~ /child#\d+/
1091
- @i += 1
1092
- num = last_arg.split('#').last.to_i
1093
- if num == @i
1094
- if block_given?
1095
- status = 0
1096
- begin
1097
- yield
1098
- rescue Exception
1099
- status = -1 # Any non-zero result is failure
1100
- ensure
1101
- return status
855
+ if thread > 0
856
+ WaitForSingleObject(thread, wait_time)
857
+ count += 1
858
+ else
859
+ raise SystemCallError.new("CreateRemoteThread", FFI.errno)
860
+ end
1102
861
  end
862
+ ensure
863
+ CloseHandle(handle) if handle
1103
864
  end
1104
- return nil
1105
- else
1106
- return false
1107
- end
1108
- end
1109
-
1110
- # Tag the command with the word 'child#xxx' to distinguish it
1111
- # from the calling process.
1112
- cmd = 'ruby -I "' + $LOAD_PATH.join(File::PATH_SEPARATOR) << '" "'
1113
- cmd << File.expand_path($PROGRAM_NAME) << '" ' << ARGV.join(' ')
1114
- cmd << ' child#' << @child_pids.length.to_s
1115
-
1116
- startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
1117
- startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
1118
- procinfo = [0,0,0,0].pack('LLLL')
1119
-
1120
- rv = CreateProcess(nil, cmd, 0, 0, 1, 0, 0, 0, startinfo, procinfo)
865
+ }
1121
866
 
1122
- if rv == 0
1123
- raise Error, get_last_error
867
+ count
1124
868
  end
1125
-
1126
- pid = procinfo[8,4].unpack('L').first
1127
- @child_pids.push(pid)
1128
-
1129
- pid
1130
869
  end
1131
870
 
1132
- module_function :create, :fork, :get_affinity, :getrlimit, :getpriority
1133
- module_function :job?, :kill, :ppid, :setpriority, :setrlimit
1134
- module_function :wait, :wait2, :waitpid, :waitpid2, :uid
1135
- end
871
+ class << self
872
+ private
1136
873
 
1137
- # Create a global fork method
1138
- module Kernel
1139
- undef_method :fork # Eliminate redefinition warning
1140
- def fork(&block)
1141
- Process.fork(&block)
874
+ # Private method that returns the volume type, e.g. "NTFS", etc.
875
+ def volume_type
876
+ buf = FFI::MemoryPointer.new(:char, 32)
877
+ bool = GetVolumeInformationA(nil, nil, 0, nil, nil, nil, buf, buf.size)
878
+ bool ? buf.read_string : nil
879
+ end
1142
880
  end
1143
881
  end