win32-process 0.6.6 → 0.7.0

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