win32-process 0.7.2 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
data/MANIFEST CHANGED
@@ -1,9 +1,9 @@
1
- * CHANGES
2
- * README
3
- * MANIFEST
4
- * Rakefile
5
- * win32-process.gemspec
6
- * examples/example_create.rb
7
- * examples/example_kill.rb
8
- * lib/win32/process.rb
9
- * test/test_process.rb
1
+ * CHANGES
2
+ * README
3
+ * MANIFEST
4
+ * Rakefile
5
+ * win32-process.gemspec
6
+ * examples/example_create.rb
7
+ * examples/example_kill.rb
8
+ * lib/win32/process.rb
9
+ * test/test_process.rb
data/README CHANGED
@@ -1,76 +1,76 @@
1
- = Description
2
- This library provides analogues of the :getpriority, :setpriority, :getrlimit,
3
- :setrlimit and :uid methods for MS Windows. It also adds the new methods :job?,
4
- :get_affinity, and :create, and redefines the :kill method.
5
-
6
- = Prerequisites
7
- * ffi
8
- * sys-proctable (dev only)
9
- * test-unit 2 (dev only)
10
-
11
- = Supported Platforms
12
- This library is supported on Windows 2000 or later.
13
-
14
- = Installation
15
- gem install win32-process
16
-
17
- = Synopsis
18
- require 'win32/process'
19
-
20
- p Process.job? # => true or false
21
-
22
- info = Process.create(
23
- :app_name => "notepad.exe",
24
- :creation_flags => Process::DETACHED_PROCESS,
25
- :process_inherit => false,
26
- :thread_inherit => true,
27
- :cwd => "C:\\"
28
- )
29
-
30
- p info.process_id
31
-
32
- = Developer's Notes
33
- == Removal of Process.fork in release 0.7.0
34
- The Process.fork method was originally experimental but it has never
35
- been particularly useful in practice. On top of that, it required special
36
- implementations of the Process.waitXXX methods, so it was a maintenance
37
- issue as well.
38
-
39
- With Ruby 1.9 now becoming standard and its addition of Process.spawn
40
- and friends (and concomitant support for the Process.waitXXX methods) I
41
- felt it was time to remove it.
42
-
43
- You can still simulate Process.fork if you like using Process.create, which
44
- is how it was implemented internally anyway. A better solution might be
45
- to follow in the footsteps of ActiveState Perl, which uses native threads
46
- to simulate fork on Windows.
47
-
48
- == Changes in the custom Process.kill method for 0.7.0
49
- The Process.kill method in 0.7.0 more closely matches the spec now, but
50
- the internal method for killing processes is still nicer for most signals.
51
- With the release of 0.7.0 users can now specify options that provide finer
52
- control over how a process is killed. See the documentation for details.
53
-
54
- == The removal of the custom Process.ppid method
55
- This was added at some point in the Ruby 1.9 dev cycle so it was removed
56
- from this library.
57
-
58
- = Known Bugs
59
- None known. Any bugs should be reported on the project page at
60
- https://github.com/djberg96/win32-process.
61
-
62
- = License
63
- Artistic 2.0
64
-
65
- = Copyright
66
- (C) 2003-2013 Daniel J. Berger
67
- All Rights Reserved
68
-
69
- = Warranty
70
- This library is provided "as is" and without any express or
71
- implied warranties, including, without limitation, the implied
72
- warranties of merchantability and fitness for a particular purpose.
73
-
74
- = Author(s)
75
- Park Heesob
76
- Daniel J. Berger
1
+ = Description
2
+ This library provides analogues of the :getpriority, :setpriority, :getrlimit,
3
+ :setrlimit and :uid methods for MS Windows. It also adds the new methods :job?,
4
+ :get_affinity, and :create, and redefines the :kill method.
5
+
6
+ = Prerequisites
7
+ * ffi
8
+ * sys-proctable (dev only)
9
+ * test-unit 2 (dev only)
10
+
11
+ = Supported Platforms
12
+ This library is supported on Windows 2000 or later.
13
+
14
+ = Installation
15
+ gem install win32-process
16
+
17
+ = Synopsis
18
+ require 'win32/process'
19
+
20
+ p Process.job? # => true or false
21
+
22
+ info = Process.create(
23
+ :app_name => "notepad.exe",
24
+ :creation_flags => Process::DETACHED_PROCESS,
25
+ :process_inherit => false,
26
+ :thread_inherit => true,
27
+ :cwd => "C:\\"
28
+ )
29
+
30
+ p info.process_id
31
+
32
+ = Developer's Notes
33
+ == Removal of Process.fork in release 0.7.0
34
+ The Process.fork method was originally experimental but it has never
35
+ been particularly useful in practice. On top of that, it required special
36
+ implementations of the Process.waitXXX methods, so it was a maintenance
37
+ issue as well.
38
+
39
+ With Ruby 1.9 now becoming standard and its addition of Process.spawn
40
+ and friends (and concomitant support for the Process.waitXXX methods) I
41
+ felt it was time to remove it.
42
+
43
+ You can still simulate Process.fork if you like using Process.create, which
44
+ is how it was implemented internally anyway. A better solution might be
45
+ to follow in the footsteps of ActiveState Perl, which uses native threads
46
+ to simulate fork on Windows.
47
+
48
+ == Changes in the custom Process.kill method for 0.7.0
49
+ The Process.kill method in 0.7.0 more closely matches the spec now, but
50
+ the internal method for killing processes is still nicer for most signals.
51
+ With the release of 0.7.0 users can now specify options that provide finer
52
+ control over how a process is killed. See the documentation for details.
53
+
54
+ == The removal of the custom Process.ppid method
55
+ This was added at some point in the Ruby 1.9 dev cycle so it was removed
56
+ from this library.
57
+
58
+ = Known Bugs
59
+ None known. Any bugs should be reported on the project page at
60
+ https://github.com/djberg96/win32-process.
61
+
62
+ = License
63
+ Artistic 2.0
64
+
65
+ = Copyright
66
+ (C) 2003-2013 Daniel J. Berger
67
+ All Rights Reserved
68
+
69
+ = Warranty
70
+ This library is provided "as is" and without any express or
71
+ implied warranties, including, without limitation, the implied
72
+ warranties of merchantability and fitness for a particular purpose.
73
+
74
+ = Author(s)
75
+ Park Heesob
76
+ Daniel J. Berger
data/Rakefile CHANGED
@@ -1,58 +1,63 @@
1
- require 'rake'
2
- require 'rake/clean'
3
- require 'rake/testtask'
4
- require 'rbconfig'
5
- include RbConfig
6
-
7
- CLEAN.include('**/*.gem', '**/*.rbc', '**/*.log')
8
-
9
- namespace :gem do
10
- desc 'Create the win32-process gem'
11
- task :create => [:clean] do
12
- spec = eval(IO.read('win32-process.gemspec'))
13
- Gem::Builder.new(spec).build
14
- end
15
-
16
- desc 'Install the win32-process gem'
17
- task :install => [:create] do
18
- file = Dir["*.gem"].first
19
- sh "gem install #{file}"
20
- end
21
- end
22
-
23
- namespace :example do
24
- desc 'Run the fork + wait example'
25
- task :fork_wait do
26
- sh "ruby -Ilib examples/example_fork_wait.rb"
27
- end
28
-
29
- desc 'Run the fork + waitpid example'
30
- task :fork_waitpid do
31
- sh "ruby -Ilib examples/example_fork_waitpid.rb"
32
- end
33
-
34
- desc 'Run the kill example'
35
- task :kill do
36
- sh "ruby -Ilib examples/example_kill.rb"
37
- end
38
-
39
- desc 'Run the create example'
40
- task :create do
41
- sh "ruby -Ilib examples/example_create.rb"
42
- end
43
- end
44
-
45
- namespace :test do
46
- Rake::TestTask.new(:kill) do |t|
47
- t.verbose = true
48
- t.warning = true
49
- t.test_files = FileList['test/test_win32_process_kill.rb']
50
- end
51
- end
52
-
53
- Rake::TestTask.new do |t|
54
- t.verbose = true
55
- t.warning = true
56
- end
57
-
58
- task :default => :test
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+ require 'rbconfig'
5
+ include RbConfig
6
+
7
+ CLEAN.include('**/*.gem', '**/*.rbc', '**/*.log')
8
+
9
+ namespace :gem do
10
+ desc 'Create the win32-process gem'
11
+ task :create => [:clean] do
12
+ spec = eval(IO.read('win32-process.gemspec'))
13
+ if Gem::VERSION.to_f < 2.0
14
+ Gem::Builder.new(spec).build
15
+ else
16
+ require 'rubygems/package'
17
+ Gem::Package.build(spec)
18
+ end
19
+ end
20
+
21
+ desc 'Install the win32-process gem'
22
+ task :install => [:create] do
23
+ file = Dir["*.gem"].first
24
+ sh "gem install #{file}"
25
+ end
26
+ end
27
+
28
+ namespace :example do
29
+ desc 'Run the fork + wait example'
30
+ task :fork_wait do
31
+ sh "ruby -Ilib examples/example_fork_wait.rb"
32
+ end
33
+
34
+ desc 'Run the fork + waitpid example'
35
+ task :fork_waitpid do
36
+ sh "ruby -Ilib examples/example_fork_waitpid.rb"
37
+ end
38
+
39
+ desc 'Run the kill example'
40
+ task :kill do
41
+ sh "ruby -Ilib examples/example_kill.rb"
42
+ end
43
+
44
+ desc 'Run the create example'
45
+ task :create do
46
+ sh "ruby -Ilib examples/example_create.rb"
47
+ end
48
+ end
49
+
50
+ namespace :test do
51
+ Rake::TestTask.new(:kill) do |t|
52
+ t.verbose = true
53
+ t.warning = true
54
+ t.test_files = FileList['test/test_win32_process_kill.rb']
55
+ end
56
+ end
57
+
58
+ Rake::TestTask.new do |t|
59
+ t.verbose = true
60
+ t.warning = true
61
+ end
62
+
63
+ task :default => :test
data/lib/win32/process.rb CHANGED
@@ -1,894 +1,937 @@
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')
5
-
6
-
7
- module Process
8
- include Process::Constants
9
- extend Process::Functions
10
- extend Process::Structs
11
- extend Process::Constants
12
-
13
- # The version of the win32-process library.
14
- WIN32_PROCESS_VERSION = '0.7.2'
15
-
16
- # Disable popups. This mostly affects the Process.kill method.
17
- SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX)
18
-
19
- class << self
20
- # Returns whether or not the current process is part of a Job (process group).
21
- def job?
22
- pbool = FFI::MemoryPointer.new(:int)
23
- IsProcessInJob(GetCurrentProcess(), nil, pbool)
24
- pbool.read_int == 1 ? true : false
25
- end
26
-
27
- # Returns the process and system affinity mask for the given +pid+, or the
28
- # current process if no pid is provided. The return value is a two element
29
- # array, with the first containing the process affinity mask, and the second
30
- # containing the system affinity mask. Both are decimal values.
31
- #
32
- # A process affinity mask is a bit vector indicating the processors that a
33
- # process is allowed to run on. A system affinity mask is a bit vector in
34
- # which each bit represents the processors that are configured into a
35
- # system.
36
- #
37
- # Example:
38
- #
39
- # # System has 4 processors, current process is allowed to run on all
40
- # Process.get_affinity # => [[15], [15]], where '15' is 1 + 2 + 4 + 8
41
- #
42
- # # System has 4 processors, current process only allowed on 1 and 4 only
43
- # Process.get_affinity # => [[9], [15]]
44
- #
45
- # If you want to convert a decimal bit vector into an array of 0's and 1's
46
- # indicating the flag value of each processor, you can use something like
47
- # this approach:
48
- #
49
- # mask = Process.get_affinity.first
50
- # (0..mask).to_a.map{ |n| mask[n] }
51
- #
52
- def get_affinity(int = Process.pid)
53
- pmask = FFI::MemoryPointer.new(:ulong)
54
- smask = FFI::MemoryPointer.new(:ulong)
55
-
56
- if int == Process.pid
57
- unless GetProcessAffinityMask(GetCurrentProcess(), pmask, smask)
58
- raise SystemCallError, FFI.errno, "GetProcessAffinityMask"
59
- end
60
- else
61
- begin
62
- handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
63
-
64
- if handle == INVALID_HANDLE_VALUE
65
- raise SystemCallError, FFI.errno, "OpeProcess"
66
- end
67
-
68
- unless GetProcessAffinityMask(handle, pmask, smask)
69
- raise SystemCallError, FFI.errno, "GetProcessAffinityMask"
70
- end
71
- ensure
72
- CloseHandle(handle)
73
- end
74
- end
75
-
76
- [pmask.read_ulong, smask.read_ulong]
77
- end
78
-
79
- remove_method :getpriority
80
-
81
- # Retrieves the priority class for the specified process id +int+. Unlike
82
- # the default implementation, lower return values do not necessarily
83
- # correspond to higher priority classes.
84
- #
85
- # The +kind+ parameter is ignored but required for API compatibility.
86
- # You can only retrieve process information, not process group or user
87
- # information, so it is effectively always Process::PRIO_PROCESS.
88
- #
89
- # Possible return values are:
90
- #
91
- # 32 => Process::NORMAL_PRIORITY_CLASS
92
- # 64 => Process::IDLE_PRIORITY_CLASS
93
- # 128 => Process::HIGH_PRIORITY_CLASS
94
- # 256 => Process::REALTIME_PRIORITY_CLASS
95
- # 16384 => Process::BELOW_NORMAL_PRIORITY_CLASS
96
- # 32768 => Process::ABOVE_NORMAL_PRIORITY_CLASS
97
- #
98
- def getpriority(kind, int)
99
- raise TypeError, kind unless kind.is_a?(Fixnum) # Match spec
100
- raise TypeError, int unless int.is_a?(Fixnum) # Match spec
101
- int = Process.pid if int == 0 # Match spec
102
-
103
- handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, int)
104
-
105
- if handle == INVALID_HANDLE_VALUE
106
- raise SystemCallError, FFI.errno, "OpenProcess"
107
- end
108
-
109
- begin
110
- priority = GetPriorityClass(handle)
111
-
112
- if priority == 0
113
- raise SystemCallError, FFI.errno, "GetPriorityClass"
114
- end
115
- ensure
116
- CloseHandle(handle)
117
- end
118
-
119
- priority
120
- end
121
-
122
- remove_method :setpriority
123
-
124
- # Sets the priority class for the specified process id +int+.
125
- #
126
- # The +kind+ parameter is ignored but present for API compatibility.
127
- # You can only retrieve process information, not process group or user
128
- # information, so it is effectively always Process::PRIO_PROCESS.
129
- #
130
- # Possible +int_priority+ values are:
131
- #
132
- # * Process::NORMAL_PRIORITY_CLASS
133
- # * Process::IDLE_PRIORITY_CLASS
134
- # * Process::HIGH_PRIORITY_CLASS
135
- # * Process::REALTIME_PRIORITY_CLASS
136
- # * Process::BELOW_NORMAL_PRIORITY_CLASS
137
- # * Process::ABOVE_NORMAL_PRIORITY_CLASS
138
- #
139
- def setpriority(kind, int, int_priority)
140
- raise TypeError unless kind.is_a?(Integer) # Match spec
141
- raise TypeError unless int.is_a?(Integer) # Match spec
142
- raise TypeError unless int_priority.is_a?(Integer) # Match spec
143
- int = Process.pid if int == 0 # Match spec
144
-
145
- handle = OpenProcess(PROCESS_SET_INFORMATION, false , int)
146
-
147
- if handle == INVALID_HANDLE_VALUE
148
- raise SystemCallError, FFI.errno, "OpenProcess"
149
- end
150
-
151
- begin
152
- unless SetPriorityClass(handle, int_priority)
153
- raise SystemCallError, FFI.errno, "SetPriorityClass"
154
- end
155
- ensure
156
- CloseHandle(handle)
157
- end
158
-
159
- return 0 # Match the spec
160
- end
161
-
162
- remove_method :uid
163
-
164
- # Returns the uid of the current process. Specifically, it returns the
165
- # RID of the SID associated with the owner of the process.
166
- #
167
- # If +sid+ is set to true, then a binary sid is returned. Otherwise, a
168
- # numeric id is returned (the default).
169
- #--
170
- # The Process.uid method in core Ruby always returns 0 on MS Windows.
171
- #
172
- def uid(sid = false)
173
- token = FFI::MemoryPointer.new(:ulong)
174
-
175
- raise TypeError unless sid.is_a?(TrueClass) || sid.is_a?(FalseClass)
176
-
177
- unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
178
- raise SystemCallError, FFI.errno, "OpenProcessToken"
179
- end
180
-
181
- token = token.read_ulong
182
- rlength = FFI::MemoryPointer.new(:ulong)
183
- tuser = 0.chr * 512
184
-
185
- bool = GetTokenInformation(
186
- token,
187
- TokenUser,
188
- tuser,
189
- tuser.size,
190
- rlength
191
- )
192
-
193
- unless bool
194
- raise SystemCallError, FFI.errno, "GetTokenInformation"
195
- end
196
-
197
- string_sid = tuser[8, (rlength.read_ulong - 8)]
198
-
199
- if sid
200
- string_sid
201
- else
202
- psid = FFI::MemoryPointer.new(:ulong)
203
-
204
- unless ConvertSidToStringSidA(string_sid, psid)
205
- raise SystemCallError, FFI.errno, "ConvertSidToStringSid"
206
- end
207
-
208
- psid.read_pointer.read_string.split('-').last.to_i
209
- end
210
- end
211
-
212
- remove_method :getrlimit
213
-
214
- # Gets the resource limit of the current process. Only a limited number
215
- # of flags are supported.
216
- #
217
- # Process::RLIMIT_CPU
218
- # Process::RLIMIT_FSIZE
219
- # Process::RLIMIT_AS
220
- # Process::RLIMIT_RSS
221
- # Process::RLIMIT_VMEM
222
- #
223
- # The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants
224
- # all refer to the Process memory limit. The Process::RLIMIT_CPU constant
225
- # refers to the per process user time limit. The Process::RLIMIT_FSIZE
226
- # constant is hard coded to the maximum file size on an NTFS filesystem,
227
- # approximately 4TB (or 4GB if not NTFS).
228
- #
229
- # While a two element array is returned in order to comply with the spec,
230
- # there is no separate hard and soft limit. The values will always be the
231
- # same.
232
- #
233
- # If [0,0] is returned then it means no limit has been set.
234
- #
235
- # Example:
236
- #
237
- # Process.getrlimit(Process::RLIMIT_VMEM) # => [0, 0]
238
- #--
239
- # NOTE: Both the getrlimit and setrlimit method use an at_exit handler
240
- # to close a job handle. This is necessary because simply calling it
241
- # at the end of the block, while marking it for closure, would also make
242
- # it unavailable within the same process again since it would no longer
243
- # be associated with the job. In other words, trying to call it more than
244
- # once within the same program would fail.
245
- #
246
- def getrlimit(resource)
247
- if resource == RLIMIT_FSIZE
248
- if volume_type == 'NTFS'
249
- return ((1024**4) * 4) - (1024 * 64) # ~ 4TB
250
- else
251
- return (1024**3) * 4 # 4 GB
252
- end
253
- end
254
-
255
- handle = nil
256
- in_job = Process.job?
257
-
258
- # Put the current process in a job if it's not already in one
259
- if in_job && defined?(@win32_process_job_name)
260
- handle = OpenJobObjectA(JOB_OBJECT_QUERY, true, @win32_process_job_name)
261
- raise SystemCallError, FFI.errno, "OpenJobObject" if handle == 0
262
- else
263
- @win32_process_job_name = 'ruby_' + Process.pid.to_s
264
- handle = CreateJobObjectA(nil, @win32_process_job_name)
265
- raise SystemCallError, FFI.errno, "CreateJobObject" if handle == 0
266
- end
267
-
268
- begin
269
- unless in_job
270
- unless AssignProcessToJobObject(handle, GetCurrentProcess())
271
- raise Error, get_last_error
272
- end
273
- end
274
-
275
- ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
276
- val = nil
277
-
278
- # Set the LimitFlags member of the struct
279
- case resource
280
- when RLIMIT_CPU
281
- ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_TIME
282
- when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
283
- ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_MEMORY
284
- else
285
- raise ArgumentError, "unsupported resource type: '#{resource}'"
286
- end
287
-
288
- bool = QueryInformationJobObject(
289
- handle,
290
- JobObjectExtendedLimitInformation,
291
- ptr,
292
- ptr.size,
293
- nil
294
- )
295
-
296
- unless bool
297
- raise SystemCallError, FFI.errno, "QueryInformationJobObject"
298
- end
299
-
300
- case resource
301
- when Process::RLIMIT_CPU
302
- val = ptr[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart]
303
- when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
304
- val = ptr[:ProcessMemoryLimit]
305
- end
306
-
307
- ensure
308
- at_exit{ CloseHandle(handle) if handle }
309
- end
310
-
311
- [val, val]
312
- end
313
-
314
- remove_method :setrlimit
315
-
316
- # Sets the resource limit of the current process. Only a limited number
317
- # of flags are supported.
318
- #
319
- # Process::RLIMIT_CPU
320
- # Process::RLIMIT_AS
321
- # Process::RLIMIT_RSS
322
- # Process::RLIMIT_VMEM
323
- #
324
- # The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants
325
- # all refer to the Process memory limit. The Process::RLIMIT_CPU constant
326
- # refers to the per process user time limit.
327
- #
328
- # The +max_limit+ parameter is provided for interface compatibility only.
329
- # It is always set to the current_limit value.
330
- #
331
- # Example:
332
- #
333
- # Process.setrlimit(Process::RLIMIT_VMEM, 1024 * 4) # => nil
334
- # Process.getrlimit(Process::RLIMIT_VMEM) # => [4096, 4096]
335
- #
336
- def setrlimit(resource, current_limit, max_limit = nil)
337
- max_limit = current_limit
338
-
339
- handle = nil
340
- in_job = Process.job?
341
-
342
- unless [RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS, RLIMIT_CPU].include?(resource)
343
- raise ArgumentError, "unsupported resource type: '#{resource}'"
344
- end
345
-
346
- # Put the current process in a job if it's not already in one
347
- if in_job && defined? @win32_process_job_name
348
- handle = OpenJobObjectA(JOB_OBJECT_SET_ATTRIBUTES, true, @win32_process_job_name)
349
- raise SystemCallError, FFI.errno, "OpenJobObject" if handle == 0
350
- else
351
- @job_name = 'ruby_' + Process.pid.to_s
352
- handle = CreateJobObjectA(nil, job_name)
353
- raise SystemCallError, FFI.errno, "CreateJobObject" if handle == 0
354
- end
355
-
356
- begin
357
- unless in_job
358
- unless AssignProcessToJobObject(handle, GetCurrentProcess())
359
- raise SystemCallError, FFI.errno, "AssignProcessToJobObject"
360
- end
361
- end
362
-
363
- ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
364
-
365
- # Set the LimitFlags and relevant members of the struct
366
- if resource == RLIMIT_CPU
367
- ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_TIME
368
- ptr[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart] = max_limit
369
- else
370
- ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_MEMORY
371
- ptr[:ProcessMemoryLimit] = max_limit
372
- end
373
-
374
- bool = SetInformationJobObject(
375
- handle,
376
- JobObjectExtendedLimitInformation,
377
- ptr,
378
- ptr.size
379
- )
380
-
381
- unless bool
382
- raise SystemCallError, FFI.errno, "SetInformationJobObject"
383
- end
384
- ensure
385
- at_exit{ CloseHandle(handle) if handle }
386
- end
387
- end
388
-
389
- # Process.create(key => value, ...) => ProcessInfo
390
- #
391
- # This is a wrapper for the CreateProcess() function. It executes a process,
392
- # returning a ProcessInfo struct. It accepts a hash as an argument.
393
- # There are several primary keys:
394
- #
395
- # * command_line (this or app_name must be present)
396
- # * app_name (default: nil)
397
- # * inherit (default: false)
398
- # * process_inherit (default: false)
399
- # * thread_inherit (default: false)
400
- # * creation_flags (default: 0)
401
- # * cwd (default: Dir.pwd)
402
- # * startup_info (default: nil)
403
- # * environment (default: nil)
404
- # * close_handles (default: true)
405
- # * with_logon (default: nil)
406
- # * domain (default: nil)
407
- # * password (default: nil, mandatory if with_logon)
408
- #
409
- # Of these, the 'command_line' or 'app_name' must be specified or an
410
- # error is raised. Both may be set individually, but 'command_line' should
411
- # be preferred if only one of them is set because it does not (necessarily)
412
- # require an explicit path or extension to work.
413
- #
414
- # The 'domain' and 'password' options are only relevent in the context
415
- # of 'with_logon'. If 'with_logon' is set, then the 'password' option is
416
- # mandatory.
417
- #
418
- # The startup_info key takes a hash. Its keys are attributes that are
419
- # part of the StartupInfo struct, and are generally only meaningful for
420
- # GUI or console processes. See the documentation on CreateProcess()
421
- # and the StartupInfo struct on MSDN for more information.
422
- #
423
- # * desktop
424
- # * title
425
- # * x
426
- # * y
427
- # * x_size
428
- # * y_size
429
- # * x_count_chars
430
- # * y_count_chars
431
- # * fill_attribute
432
- # * sw_flags
433
- # * startf_flags
434
- # * stdin
435
- # * stdout
436
- # * stderr
437
- #
438
- # Note that the 'stdin', 'stdout' and 'stderr' options can be either Ruby
439
- # IO objects or file descriptors (i.e. a fileno). However, StringIO objects
440
- # are not currently supported. Unfortunately, setting these is not currently
441
- # an option for JRuby.
442
- #
443
- # If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
444
- # is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
445
- # automatically OR'd to the +startf_flags+ value.
446
- #
447
- # The ProcessInfo struct contains the following members:
448
- #
449
- # * process_handle - The handle to the newly created process.
450
- # * thread_handle - The handle to the primary thread of the process.
451
- # * process_id - Process ID.
452
- # * thread_id - Thread ID.
453
- #
454
- # If the 'close_handles' option is set to true (the default) then the
455
- # process_handle and the thread_handle are automatically closed for you
456
- # before the ProcessInfo struct is returned.
457
- #
458
- # If the 'with_logon' option is set, then the process runs the specified
459
- # executable file in the security context of the specified credentials.
460
- #
461
- def create(args)
462
- unless args.kind_of?(Hash)
463
- raise TypeError, 'hash keyword arguments expected'
464
- end
465
-
466
- valid_keys = %w[
467
- app_name command_line inherit creation_flags cwd environment
468
- startup_info thread_inherit process_inherit close_handles with_logon
469
- domain password
470
- ]
471
-
472
- valid_si_keys = %w[
473
- startf_flags desktop title x y x_size y_size x_count_chars
474
- y_count_chars fill_attribute sw_flags stdin stdout stderr
475
- ]
476
-
477
- # Set default values
478
- hash = {
479
- 'app_name' => nil,
480
- 'creation_flags' => 0,
481
- 'close_handles' => true
482
- }
483
-
484
- # Validate the keys, and convert symbols and case to lowercase strings.
485
- args.each{ |key, val|
486
- key = key.to_s.downcase
487
- unless valid_keys.include?(key)
488
- raise ArgumentError, "invalid key '#{key}'"
489
- end
490
- hash[key] = val
491
- }
492
-
493
- si_hash = {}
494
-
495
- # If the startup_info key is present, validate its subkeys
496
- if hash['startup_info']
497
- hash['startup_info'].each{ |key, val|
498
- key = key.to_s.downcase
499
- unless valid_si_keys.include?(key)
500
- raise ArgumentError, "invalid startup_info key '#{key}'"
501
- end
502
- si_hash[key] = val
503
- }
504
- end
505
-
506
- # The +command_line+ key is mandatory unless the +app_name+ key
507
- # is specified.
508
- unless hash['command_line']
509
- if hash['app_name']
510
- hash['command_line'] = hash['app_name']
511
- hash['app_name'] = nil
512
- else
513
- raise ArgumentError, 'command_line or app_name must be specified'
514
- end
515
- end
516
-
517
- env = nil
518
-
519
- # The env string should be passed as a string of ';' separated paths.
520
- if hash['environment']
521
- env = hash['environment']
522
-
523
- unless env.respond_to?(:join)
524
- env = hash['environment'].split(File::PATH_SEPARATOR)
525
- end
526
-
527
- env = env.map{ |e| e + 0.chr }.join('') + 0.chr
528
- env.to_wide_string! if hash['with_logon']
529
- end
530
-
531
- # Process SECURITY_ATTRIBUTE structure
532
- process_security = nil
533
-
534
- if hash['process_inherit']
535
- process_security = SECURITY_ATTRIBUTES.new
536
- process_security[:nLength] = 12
537
- process_security[:bInheritHandle] = true
538
- end
539
-
540
- # Thread SECURITY_ATTRIBUTE structure
541
- thread_security = nil
542
-
543
- if hash['thread_inherit']
544
- thread_security = SECURITY_ATTRIBUTES.new
545
- thread_security[:nLength] = 12
546
- thread_security[:bInheritHandle] = true
547
- end
548
-
549
- # Automatically handle stdin, stdout and stderr as either IO objects
550
- # or file descriptors. This won't work for StringIO, however. It also
551
- # will not work on JRuby because of the way it handles internal file
552
- # descriptors.
553
- #
554
- ['stdin', 'stdout', 'stderr'].each{ |io|
555
- if si_hash[io]
556
- if si_hash[io].respond_to?(:fileno)
557
- handle = get_osfhandle(si_hash[io].fileno)
558
- else
559
- handle = get_osfhandle(si_hash[io])
560
- end
561
-
562
- if handle == INVALID_HANDLE_VALUE
563
- ptr = FFI::MemoryPointer.new(:int)
564
-
565
- if windows_version >= 6 && get_errno(ptr) == 0
566
- errno = ptr.read_int
567
- else
568
- errno = FFI.errno
569
- end
570
-
571
- raise SystemCallError.new("get_osfhandle", errno)
572
- end
573
-
574
- # Most implementations of Ruby on Windows create inheritable
575
- # handles by default, but some do not. RF bug #26988.
576
- bool = SetHandleInformation(
577
- handle,
578
- HANDLE_FLAG_INHERIT,
579
- HANDLE_FLAG_INHERIT
580
- )
581
-
582
- raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool
583
-
584
- si_hash[io] = handle
585
- si_hash['startf_flags'] ||= 0
586
- si_hash['startf_flags'] |= STARTF_USESTDHANDLES
587
- hash['inherit'] = true
588
- end
589
- }
590
-
591
- procinfo = PROCESS_INFORMATION.new
592
- startinfo = STARTUPINFO.new
593
-
594
- unless si_hash.empty?
595
- startinfo[:cb] = startinfo.size
596
- startinfo[:lpDesktop] = si_hash['desktop'] if si_hash['desktop']
597
- startinfo[:lpTitle] = si_hash['title'] if si_hash['title']
598
- startinfo[:dwX] = si_hash['x'] if si_hash['x']
599
- startinfo[:dwY] = si_hash['y'] if si_hash['y']
600
- startinfo[:dwXSize] = si_hash['x_size'] if si_hash['x_size']
601
- startinfo[:dwYSize] = si_hash['y_size'] if si_hash['y_size']
602
- startinfo[:dwXCountChars] = si_hash['x_count_chars'] if si_hash['x_count_chars']
603
- startinfo[:dwYCountChars] = si_hash['y_count_chars'] if si_hash['y_count_chars']
604
- startinfo[:dwFillAttribute] = si_hash['fill_attribute'] if si_hash['fill_attribute']
605
- startinfo[:dwFlags] = si_hash['startf_flags'] if si_hash['startf_flags']
606
- startinfo[:wShowWindow] = si_hash['sw_flags'] if si_hash['sw_flags']
607
- startinfo[:cbReserved2] = 0
608
- startinfo[:hStdInput] = si_hash['stdin'] if si_hash['stdin']
609
- startinfo[:hStdOutput] = si_hash['stdout'] if si_hash['stdout']
610
- startinfo[:hStdError] = si_hash['stderr'] if si_hash['stderr']
611
- end
612
-
613
- app = nil
614
- cmd = nil
615
-
616
- # Convert strings to wide character strings if present
617
- if hash['app_name']
618
- app = hash['app_name'].to_wide_string
619
- end
620
-
621
- if hash['command_line']
622
- cmd = hash['command_line'].to_wide_string
623
- end
624
-
625
- if hash['cwd']
626
- cwd = hash['cwd'].to_wide_string
627
- end
628
-
629
- if hash['with_logon']
630
- logon = hash['with_logon'].to_wide_string
631
-
632
- if hash['password']
633
- passwd = hash['password'].to_wide_string
634
- else
635
- raise ArgumentError, 'password must be specified if with_logon is used'
636
- end
637
-
638
- if hash['domain']
639
- domain = hash['domain'].to_wide_string
640
- end
641
-
642
- hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
643
-
644
- bool = CreateProcessWithLogonW(
645
- logon, # User
646
- domain, # Domain
647
- passwd, # Password
648
- LOGON_WITH_PROFILE, # Logon flags
649
- app, # App name
650
- cmd, # Command line
651
- hash['creation_flags'], # Creation flags
652
- env, # Environment
653
- cwd, # Working directory
654
- startinfo, # Startup Info
655
- procinfo # Process Info
656
- )
657
-
658
- unless bool
659
- raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
660
- end
661
- else
662
- inherit = hash['inherit'] || false
663
-
664
- bool = CreateProcessW(
665
- app, # App name
666
- cmd, # Command line
667
- process_security, # Process attributes
668
- thread_security, # Thread attributes
669
- inherit, # Inherit handles?
670
- hash['creation_flags'], # Creation flags
671
- env, # Environment
672
- cwd, # Working directory
673
- startinfo, # Startup Info
674
- procinfo # Process Info
675
- )
676
-
677
- unless bool
678
- raise SystemCallError.new("CreateProcess", FFI.errno)
679
- end
680
- end
681
-
682
- # Automatically close the process and thread handles in the
683
- # PROCESS_INFORMATION struct unless explicitly told not to.
684
- if hash['close_handles']
685
- CloseHandle(procinfo[:hProcess])
686
- CloseHandle(procinfo[:hThread])
687
- end
688
-
689
- ProcessInfo.new(
690
- procinfo[:hProcess],
691
- procinfo[:hThread],
692
- procinfo[:dwProcessId],
693
- procinfo[:dwThreadId]
694
- )
695
- end
696
-
697
- remove_method :kill
698
-
699
- # Kill a given process with a specific signal. This overrides the default
700
- # implementation of Process.kill. The differences mainly reside in the way
701
- # it kills processes, but this version also gives you finer control over
702
- # behavior.
703
- #
704
- # Internally, signals 2 and 3 will generate a console control event, using
705
- # a ctrl-c or ctrl-break event, respectively. Signal 9 terminates the
706
- # process harshly, given that process no chance to do any internal cleanup.
707
- # Signals 1 and 4-8 kill the process more nicely, giving the process a
708
- # chance to do internal cleanup before being killed. Signal 0 behaves the
709
- # same as the default implementation.
710
- #
711
- # When using signals 1 or 4-8 you may specify additional options that
712
- # allow finer control over how that process is killed and how your program
713
- # behaves.
714
- #
715
- # Possible options for signals 1 and 4-8.
716
- #
717
- # :exit_proc => The name of the exit function called when signal 1 or 4-8
718
- # is used. The default is 'ExitProcess'.
719
- #
720
- # :dll_module => The name of the .dll (or .exe) that contains :exit_proc.
721
- # The default is 'kernel32'.
722
- #
723
- # :wait_time => The time, in milliseconds, to wait for the process to
724
- # actually die. The default is 5ms. If you specify 0 here
725
- # then the process does not wait if the process is not
726
- # signaled and instead returns immediately. Alternatively,
727
- # you may specify Process::INFINITE, and your code will
728
- # block until the process is actually signaled.
729
- #
730
- # Example:
731
- #
732
- # Process.kill(1, 12345, :exit_proc => 'ExitProcess', :module => 'kernel32')
733
- #
734
- def kill(signal, *pids)
735
- # Match the spec, require at least 2 arguments
736
- if pids.length == 0
737
- raise ArgumentError, "wrong number of arguments (1 for at least 2)"
738
- end
739
-
740
- # Match the spec, signal may not be less than zero if numeric
741
- if signal.is_a?(Numeric) && signal < 0 # EINVAL
742
- raise SystemCallError.new(22)
743
- end
744
-
745
- # Match the spec, signal must be a numeric, string or symbol
746
- unless signal.is_a?(String) || signal.is_a?(Numeric) || signal.is_a?(Symbol)
747
- raise ArgumentError, "bad signal type #{signal.class}"
748
- end
749
-
750
- # Match the spec, making an exception for BRK/SIGBRK, if the signal name is invalid
751
- if signal.is_a?(String) || signal.is_a?(Symbol)
752
- signal = signal.to_s.sub('SIG', '')
753
- unless Signal.list.keys.include?(signal) || ['BRK', 'BRK'].include?(signal)
754
- raise ArgumentError, "unsupported name '#{signal}'"
755
- end
756
- end
757
-
758
- # If the last argument is a hash, pop it and assume it's a hash of options
759
- if pids.last.is_a?(Hash)
760
- hash = pids.pop
761
- opts = {}
762
-
763
- valid = %w[exit_proc dll_module wait_time]
764
-
765
- hash.each{ |k,v|
766
- k = k.to_s.downcase
767
- unless valid.include?(k)
768
- raise ArgumentError, "invalid option '#{k}'"
769
- end
770
- opts[k] = v
771
- }
772
-
773
- exit_proc = opts['exit_proc'] || 'ExitProcess'
774
- dll_module = opts['dll_module'] || 'kernel32'
775
- wait_time = opts['wait_time'] || 5
776
- else
777
- wait_time = 5
778
- exit_proc = 'ExitProcess'
779
- dll_module = 'kernel32'
780
- end
781
-
782
- count = 0
783
-
784
- pids.each{ |pid|
785
- raise TypeError unless pid.is_a?(Numeric) # Match spec, pid must be a number
786
- raise SystemCallError.new(22) if pid < 0 # Match spec, EINVAL if pid less than zero
787
-
788
- sigint = [Signal.list['INT'], 'INT', 'SIGINT', :INT, :SIGINT, 2]
789
-
790
- # Match the spec
791
- if pid == 0 && !sigint.include?(signal)
792
- raise SystemCallError.new(22)
793
- end
794
-
795
- if signal == 0
796
- access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
797
- elsif signal == 9
798
- access = PROCESS_TERMINATE
799
- else
800
- access = PROCESS_ALL_ACCESS
801
- end
802
-
803
- begin
804
- handle = OpenProcess(access, false, pid)
805
-
806
- if signal != 0 && handle == 0
807
- raise SystemCallError, FFI.errno, "OpenProcess"
808
- end
809
-
810
- case signal
811
- when 0
812
- if handle != 0
813
- count += 1
814
- else
815
- if FFI.errno == ERROR_ACCESS_DENIED
816
- count += 1
817
- else
818
- raise SystemCallError.new(3) # ESRCH
819
- end
820
- end
821
- when Signal.list['INT'], 'INT', 'SIGINT', :INT, :SIGINT, 2
822
- if GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)
823
- count += 1
824
- else
825
- raise SystemCallError.new("GenerateConsoleCtrlEvent", FFI.errno)
826
- end
827
- when Signal.list['BRK'], 'BRK', 'SIGBRK', :BRK, :SIGBRK, 3
828
- if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)
829
- count += 1
830
- else
831
- raise SystemCallError.new("GenerateConsoleCtrlEvent", FFI.errno)
832
- end
833
- when Signal.list['KILL'], 'KILL', 'SIGKILL', :KILL, :SIGKILL, 9
834
- if TerminateProcess(handle, pid)
835
- count += 1
836
- else
837
- raise SystemCallError.new("TerminateProcess", FFI.errno)
838
- end
839
- else
840
- thread_id = FFI::MemoryPointer.new(:ulong)
841
-
842
- mod = GetModuleHandle(dll_module)
843
-
844
- if mod == 0
845
- raise SystemCallError.new("GetModuleHandle: '#{dll_module}'", FFI.errno)
846
- end
847
-
848
- proc_addr = GetProcAddress(mod, exit_proc)
849
-
850
- if proc_addr == 0
851
- raise SystemCallError.new("GetProcAddress: '#{exit_proc}'", FFI.errno)
852
- end
853
-
854
- thread = CreateRemoteThread(handle, nil, 0, proc_addr, nil, 0, thread_id)
855
-
856
- if thread > 0
857
- WaitForSingleObject(thread, wait_time)
858
- count += 1
859
- else
860
- raise SystemCallError.new("CreateRemoteThread", FFI.errno)
861
- end
862
- end
863
- ensure
864
- CloseHandle(handle) if handle
865
- end
866
- }
867
-
868
- count
869
- end
870
- end
871
-
872
- class << self
873
- private
874
-
875
- # Private method that returns the volume type, e.g. "NTFS", etc.
876
- def volume_type
877
- buf = FFI::MemoryPointer.new(:char, 32)
878
- bool = GetVolumeInformationA(nil, nil, 0, nil, nil, nil, buf, buf.size)
879
- bool ? buf.read_string : nil
880
- end
881
-
882
- # Private method that returns the Windows major version number.
883
- def windows_version
884
- ver = OSVERSIONINFO.new
885
- ver[:dwOSVersionInfoSize] = ver.size
886
-
887
- unless GetVersionExA(ver)
888
- raise SystemCallError.new("GetVersionEx", FFI.errno)
889
- end
890
-
891
- ver[:dwMajorVersion]
892
- end
893
- end
894
- end
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')
5
+
6
+
7
+ module Process
8
+ include Process::Constants
9
+ extend Process::Functions
10
+ extend Process::Structs
11
+ extend Process::Constants
12
+
13
+ # The version of the win32-process library.
14
+ WIN32_PROCESS_VERSION = '0.7.3'
15
+
16
+ # Disable popups. This mostly affects the Process.kill method.
17
+ SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX)
18
+
19
+ class << self
20
+ # Returns whether or not the current process is part of a Job (process group).
21
+ def job?
22
+ pbool = FFI::MemoryPointer.new(:int)
23
+ IsProcessInJob(GetCurrentProcess(), nil, pbool)
24
+ pbool.read_int == 1 ? true : false
25
+ end
26
+
27
+ # Returns the process and system affinity mask for the given +pid+, or the
28
+ # current process if no pid is provided. The return value is a two element
29
+ # array, with the first containing the process affinity mask, and the second
30
+ # containing the system affinity mask. Both are decimal values.
31
+ #
32
+ # A process affinity mask is a bit vector indicating the processors that a
33
+ # process is allowed to run on. A system affinity mask is a bit vector in
34
+ # which each bit represents the processors that are configured into a
35
+ # system.
36
+ #
37
+ # Example:
38
+ #
39
+ # # System has 4 processors, current process is allowed to run on all.
40
+ # Process.get_affinity # => [[15], [15]], where '15' is 1 + 2 + 4 + 8
41
+ #
42
+ # # System has 4 processors, current process only allowed on 1 and 4.
43
+ # Process.get_affinity # => [[9], [15]]
44
+ #
45
+ # If you want to convert a decimal bit vector into an array of 0's and 1's
46
+ # indicating the flag value of each processor, you can use something like
47
+ # this approach:
48
+ #
49
+ # mask = Process.get_affinity.first
50
+ # (0..mask).to_a.map{ |n| mask[n] }
51
+ #
52
+ def get_affinity(int = Process.pid)
53
+ pmask = FFI::MemoryPointer.new(:ulong)
54
+ smask = FFI::MemoryPointer.new(:ulong)
55
+
56
+ if int == Process.pid
57
+ unless GetProcessAffinityMask(GetCurrentProcess(), pmask, smask)
58
+ raise SystemCallError, FFI.errno, "GetProcessAffinityMask"
59
+ end
60
+ else
61
+ begin
62
+ handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
63
+
64
+ if handle == 0
65
+ raise SystemCallError, FFI.errno, "OpenProcess"
66
+ end
67
+
68
+ unless GetProcessAffinityMask(handle, pmask, smask)
69
+ raise SystemCallError, FFI.errno, "GetProcessAffinityMask"
70
+ end
71
+ ensure
72
+ CloseHandle(handle)
73
+ end
74
+ end
75
+
76
+ [pmask.read_ulong, smask.read_ulong]
77
+ end
78
+
79
+ remove_method :getpriority
80
+
81
+ # Retrieves the priority class for the specified process id +int+. Unlike
82
+ # the default implementation, lower return values do not necessarily
83
+ # correspond to higher priority classes.
84
+ #
85
+ # The +kind+ parameter is ignored but required for API compatibility.
86
+ # You can only retrieve process information, not process group or user
87
+ # information, so it is effectively always Process::PRIO_PROCESS.
88
+ #
89
+ # Possible return values are:
90
+ #
91
+ # 32 => Process::NORMAL_PRIORITY_CLASS
92
+ # 64 => Process::IDLE_PRIORITY_CLASS
93
+ # 128 => Process::HIGH_PRIORITY_CLASS
94
+ # 256 => Process::REALTIME_PRIORITY_CLASS
95
+ # 16384 => Process::BELOW_NORMAL_PRIORITY_CLASS
96
+ # 32768 => Process::ABOVE_NORMAL_PRIORITY_CLASS
97
+ #
98
+ def getpriority(kind, int)
99
+ raise TypeError, kind unless kind.is_a?(Fixnum) # Match spec
100
+ raise TypeError, int unless int.is_a?(Fixnum) # Match spec
101
+ int = Process.pid if int == 0 # Match spec
102
+
103
+ handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, int)
104
+
105
+ if handle == 0
106
+ raise SystemCallError, FFI.errno, "OpenProcess"
107
+ end
108
+
109
+ begin
110
+ priority = GetPriorityClass(handle)
111
+
112
+ if priority == 0
113
+ raise SystemCallError, FFI.errno, "GetPriorityClass"
114
+ end
115
+ ensure
116
+ CloseHandle(handle)
117
+ end
118
+
119
+ priority
120
+ end
121
+
122
+ remove_method :setpriority
123
+
124
+ # Sets the priority class for the specified process id +int+.
125
+ #
126
+ # The +kind+ parameter is ignored but present for API compatibility.
127
+ # You can only retrieve process information, not process group or user
128
+ # information, so it is effectively always Process::PRIO_PROCESS.
129
+ #
130
+ # Possible +int_priority+ values are:
131
+ #
132
+ # * Process::NORMAL_PRIORITY_CLASS
133
+ # * Process::IDLE_PRIORITY_CLASS
134
+ # * Process::HIGH_PRIORITY_CLASS
135
+ # * Process::REALTIME_PRIORITY_CLASS
136
+ # * Process::BELOW_NORMAL_PRIORITY_CLASS
137
+ # * Process::ABOVE_NORMAL_PRIORITY_CLASS
138
+ #
139
+ def setpriority(kind, int, int_priority)
140
+ raise TypeError unless kind.is_a?(Integer) # Match spec
141
+ raise TypeError unless int.is_a?(Integer) # Match spec
142
+ raise TypeError unless int_priority.is_a?(Integer) # Match spec
143
+ int = Process.pid if int == 0 # Match spec
144
+
145
+ handle = OpenProcess(PROCESS_SET_INFORMATION, false , int)
146
+
147
+ if handle == 0
148
+ raise SystemCallError, FFI.errno, "OpenProcess"
149
+ end
150
+
151
+ begin
152
+ unless SetPriorityClass(handle, int_priority)
153
+ raise SystemCallError, FFI.errno, "SetPriorityClass"
154
+ end
155
+ ensure
156
+ CloseHandle(handle)
157
+ end
158
+
159
+ return 0 # Match the spec
160
+ end
161
+
162
+ remove_method :uid
163
+
164
+ # Returns the uid of the current process. Specifically, it returns the
165
+ # RID of the SID associated with the owner of the process.
166
+ #
167
+ # If +sid+ is set to true, then a binary sid is returned. Otherwise, a
168
+ # numeric id is returned (the default).
169
+ #--
170
+ # The Process.uid method in core Ruby always returns 0 on MS Windows.
171
+ #
172
+ def uid(sid = false)
173
+ token = FFI::MemoryPointer.new(:ulong)
174
+
175
+ raise TypeError unless sid.is_a?(TrueClass) || sid.is_a?(FalseClass)
176
+
177
+ unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
178
+ raise SystemCallError, FFI.errno, "OpenProcessToken"
179
+ end
180
+
181
+ token = token.read_ulong
182
+ rlength = FFI::MemoryPointer.new(:ulong)
183
+ tuser = 0.chr * 512
184
+
185
+ bool = GetTokenInformation(
186
+ token,
187
+ TokenUser,
188
+ tuser,
189
+ tuser.size,
190
+ rlength
191
+ )
192
+
193
+ unless bool
194
+ raise SystemCallError, FFI.errno, "GetTokenInformation"
195
+ end
196
+
197
+ string_sid = tuser[FFI.type_size(:pointer)*2, (rlength.read_ulong - FFI.type_size(:pointer)*2)]
198
+
199
+ if sid
200
+ string_sid
201
+ else
202
+ psid = FFI::MemoryPointer.new(:uintptr_t)
203
+
204
+ unless ConvertSidToStringSidA(string_sid, psid)
205
+ raise SystemCallError, FFI.errno, "ConvertSidToStringSid"
206
+ end
207
+
208
+ psid.read_pointer.read_string.split('-').last.to_i
209
+ end
210
+ end
211
+
212
+ remove_method :getrlimit
213
+
214
+ # Gets the resource limit of the current process. Only a limited number
215
+ # of flags are supported.
216
+ #
217
+ # Process::RLIMIT_CPU
218
+ # Process::RLIMIT_FSIZE
219
+ # Process::RLIMIT_AS
220
+ # Process::RLIMIT_RSS
221
+ # Process::RLIMIT_VMEM
222
+ #
223
+ # The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants
224
+ # all refer to the Process memory limit. The Process::RLIMIT_CPU constant
225
+ # refers to the per process user time limit. The Process::RLIMIT_FSIZE
226
+ # constant is hard coded to the maximum file size on an NTFS filesystem,
227
+ # approximately 4TB (or 4GB if not NTFS).
228
+ #
229
+ # While a two element array is returned in order to comply with the spec,
230
+ # there is no separate hard and soft limit. The values will always be the
231
+ # same.
232
+ #
233
+ # If [0,0] is returned then it means no limit has been set.
234
+ #
235
+ # Example:
236
+ #
237
+ # Process.getrlimit(Process::RLIMIT_VMEM) # => [0, 0]
238
+ #--
239
+ # NOTE: Both the getrlimit and setrlimit method use an at_exit handler
240
+ # to close a job handle. This is necessary because simply calling it
241
+ # at the end of the block, while marking it for closure, would also make
242
+ # it unavailable within the same process again since it would no longer
243
+ # be associated with the job. In other words, trying to call it more than
244
+ # once within the same program would fail.
245
+ #
246
+ def getrlimit(resource)
247
+ if resource == RLIMIT_FSIZE
248
+ if volume_type == 'NTFS'
249
+ return ((1024**4) * 4) - (1024 * 64) # ~ 4TB
250
+ else
251
+ return (1024**3) * 4 # 4 GB
252
+ end
253
+ end
254
+
255
+ handle = nil
256
+ in_job = Process.job?
257
+
258
+ # Put the current process in a job if it's not already in one
259
+ if in_job && defined?(@win32_process_job_name)
260
+ handle = OpenJobObjectA(JOB_OBJECT_QUERY, true, @win32_process_job_name)
261
+ raise SystemCallError, FFI.errno, "OpenJobObject" if handle == 0
262
+ else
263
+ @win32_process_job_name = 'ruby_' + Process.pid.to_s
264
+ handle = CreateJobObjectA(nil, @win32_process_job_name)
265
+ raise SystemCallError, FFI.errno, "CreateJobObject" if handle == 0
266
+ end
267
+
268
+ begin
269
+ unless in_job
270
+ unless AssignProcessToJobObject(handle, GetCurrentProcess())
271
+ raise Error, get_last_error
272
+ end
273
+ end
274
+
275
+ ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
276
+ val = nil
277
+
278
+ # Set the LimitFlags member of the struct
279
+ case resource
280
+ when RLIMIT_CPU
281
+ ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_TIME
282
+ when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
283
+ ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_MEMORY
284
+ else
285
+ raise ArgumentError, "unsupported resource type: '#{resource}'"
286
+ end
287
+
288
+ bool = QueryInformationJobObject(
289
+ handle,
290
+ JobObjectExtendedLimitInformation,
291
+ ptr,
292
+ ptr.size,
293
+ nil
294
+ )
295
+
296
+ unless bool
297
+ raise SystemCallError, FFI.errno, "QueryInformationJobObject"
298
+ end
299
+
300
+ case resource
301
+ when Process::RLIMIT_CPU
302
+ val = ptr[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart]
303
+ when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
304
+ val = ptr[:ProcessMemoryLimit]
305
+ end
306
+
307
+ ensure
308
+ at_exit{ CloseHandle(handle) if handle }
309
+ end
310
+
311
+ [val, val]
312
+ end
313
+
314
+ remove_method :setrlimit
315
+
316
+ # Sets the resource limit of the current process. Only a limited number
317
+ # of flags are supported.
318
+ #
319
+ # Process::RLIMIT_CPU
320
+ # Process::RLIMIT_AS
321
+ # Process::RLIMIT_RSS
322
+ # Process::RLIMIT_VMEM
323
+ #
324
+ # The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants
325
+ # all refer to the Process memory limit. The Process::RLIMIT_CPU constant
326
+ # refers to the per process user time limit.
327
+ #
328
+ # The +max_limit+ parameter is provided for interface compatibility only.
329
+ # It is always set to the current_limit value.
330
+ #
331
+ # Example:
332
+ #
333
+ # Process.setrlimit(Process::RLIMIT_VMEM, 1024 * 4) # => nil
334
+ # Process.getrlimit(Process::RLIMIT_VMEM) # => [4096, 4096]
335
+ #
336
+ def setrlimit(resource, current_limit, max_limit = nil)
337
+ max_limit = current_limit
338
+
339
+ handle = nil
340
+ in_job = Process.job?
341
+
342
+ unless [RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS, RLIMIT_CPU].include?(resource)
343
+ raise ArgumentError, "unsupported resource type: '#{resource}'"
344
+ end
345
+
346
+ # Put the current process in a job if it's not already in one
347
+ if in_job && defined? @win32_process_job_name
348
+ handle = OpenJobObjectA(JOB_OBJECT_SET_ATTRIBUTES, true, @win32_process_job_name)
349
+ raise SystemCallError, FFI.errno, "OpenJobObject" if handle == 0
350
+ else
351
+ @job_name = 'ruby_' + Process.pid.to_s
352
+ handle = CreateJobObjectA(nil, job_name)
353
+ raise SystemCallError, FFI.errno, "CreateJobObject" if handle == 0
354
+ end
355
+
356
+ begin
357
+ unless in_job
358
+ unless AssignProcessToJobObject(handle, GetCurrentProcess())
359
+ raise SystemCallError, FFI.errno, "AssignProcessToJobObject"
360
+ end
361
+ end
362
+
363
+ ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
364
+
365
+ # Set the LimitFlags and relevant members of the struct
366
+ if resource == RLIMIT_CPU
367
+ ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_TIME
368
+ ptr[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart] = max_limit
369
+ else
370
+ ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_MEMORY
371
+ ptr[:ProcessMemoryLimit] = max_limit
372
+ end
373
+
374
+ bool = SetInformationJobObject(
375
+ handle,
376
+ JobObjectExtendedLimitInformation,
377
+ ptr,
378
+ ptr.size
379
+ )
380
+
381
+ unless bool
382
+ raise SystemCallError, FFI.errno, "SetInformationJobObject"
383
+ end
384
+ ensure
385
+ at_exit{ CloseHandle(handle) if handle }
386
+ end
387
+ end
388
+
389
+ # Process.create(key => value, ...) => ProcessInfo
390
+ #
391
+ # This is a wrapper for the CreateProcess() function. It executes a process,
392
+ # returning a ProcessInfo struct. It accepts a hash as an argument.
393
+ # There are several primary keys:
394
+ #
395
+ # * command_line (this or app_name must be present)
396
+ # * app_name (default: nil)
397
+ # * inherit (default: false)
398
+ # * process_inherit (default: false)
399
+ # * thread_inherit (default: false)
400
+ # * creation_flags (default: 0)
401
+ # * cwd (default: Dir.pwd)
402
+ # * startup_info (default: nil)
403
+ # * environment (default: nil)
404
+ # * close_handles (default: true)
405
+ # * with_logon (default: nil)
406
+ # * domain (default: nil)
407
+ # * password (default: nil, mandatory if with_logon)
408
+ #
409
+ # Of these, the 'command_line' or 'app_name' must be specified or an
410
+ # error is raised. Both may be set individually, but 'command_line' should
411
+ # be preferred if only one of them is set because it does not (necessarily)
412
+ # require an explicit path or extension to work.
413
+ #
414
+ # The 'domain' and 'password' options are only relevent in the context
415
+ # of 'with_logon'. If 'with_logon' is set, then the 'password' option is
416
+ # mandatory.
417
+ #
418
+ # The startup_info key takes a hash. Its keys are attributes that are
419
+ # part of the StartupInfo struct, and are generally only meaningful for
420
+ # GUI or console processes. See the documentation on CreateProcess()
421
+ # and the StartupInfo struct on MSDN for more information.
422
+ #
423
+ # * desktop
424
+ # * title
425
+ # * x
426
+ # * y
427
+ # * x_size
428
+ # * y_size
429
+ # * x_count_chars
430
+ # * y_count_chars
431
+ # * fill_attribute
432
+ # * sw_flags
433
+ # * startf_flags
434
+ # * stdin
435
+ # * stdout
436
+ # * stderr
437
+ #
438
+ # Note that the 'stdin', 'stdout' and 'stderr' options can be either Ruby
439
+ # IO objects or file descriptors (i.e. a fileno). However, StringIO objects
440
+ # are not currently supported. Unfortunately, setting these is not currently
441
+ # an option for JRuby.
442
+ #
443
+ # If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
444
+ # is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
445
+ # automatically OR'd to the +startf_flags+ value.
446
+ #
447
+ # The ProcessInfo struct contains the following members:
448
+ #
449
+ # * process_handle - The handle to the newly created process.
450
+ # * thread_handle - The handle to the primary thread of the process.
451
+ # * process_id - Process ID.
452
+ # * thread_id - Thread ID.
453
+ #
454
+ # If the 'close_handles' option is set to true (the default) then the
455
+ # process_handle and the thread_handle are automatically closed for you
456
+ # before the ProcessInfo struct is returned.
457
+ #
458
+ # If the 'with_logon' option is set, then the process runs the specified
459
+ # executable file in the security context of the specified credentials.
460
+ #
461
+ # To simulate Process.wait you can use this approach:
462
+ #
463
+ # sleep 0.1 while !Process.get_exitcode(info.process_id)
464
+ #
465
+ # If you really to use Process.wait, then you should use the
466
+ # Process.spawn method instead of Process.create where possible.
467
+ #
468
+ def create(args)
469
+ unless args.kind_of?(Hash)
470
+ raise TypeError, 'hash keyword arguments expected'
471
+ end
472
+
473
+ valid_keys = %w[
474
+ app_name command_line inherit creation_flags cwd environment
475
+ startup_info thread_inherit process_inherit close_handles with_logon
476
+ domain password
477
+ ]
478
+
479
+ valid_si_keys = %w[
480
+ startf_flags desktop title x y x_size y_size x_count_chars
481
+ y_count_chars fill_attribute sw_flags stdin stdout stderr
482
+ ]
483
+
484
+ # Set default values
485
+ hash = {
486
+ 'app_name' => nil,
487
+ 'creation_flags' => 0,
488
+ 'close_handles' => true
489
+ }
490
+
491
+ # Validate the keys, and convert symbols and case to lowercase strings.
492
+ args.each{ |key, val|
493
+ key = key.to_s.downcase
494
+ unless valid_keys.include?(key)
495
+ raise ArgumentError, "invalid key '#{key}'"
496
+ end
497
+ hash[key] = val
498
+ }
499
+
500
+ si_hash = {}
501
+
502
+ # If the startup_info key is present, validate its subkeys
503
+ if hash['startup_info']
504
+ hash['startup_info'].each{ |key, val|
505
+ key = key.to_s.downcase
506
+ unless valid_si_keys.include?(key)
507
+ raise ArgumentError, "invalid startup_info key '#{key}'"
508
+ end
509
+ si_hash[key] = val
510
+ }
511
+ end
512
+
513
+ # The +command_line+ key is mandatory unless the +app_name+ key
514
+ # is specified.
515
+ unless hash['command_line']
516
+ if hash['app_name']
517
+ hash['command_line'] = hash['app_name']
518
+ hash['app_name'] = nil
519
+ else
520
+ raise ArgumentError, 'command_line or app_name must be specified'
521
+ end
522
+ end
523
+
524
+ env = nil
525
+
526
+ # The env string should be passed as a string of ';' separated paths.
527
+ if hash['environment']
528
+ env = hash['environment']
529
+
530
+ unless env.respond_to?(:join)
531
+ env = hash['environment'].split(File::PATH_SEPARATOR)
532
+ end
533
+
534
+ env = env.map{ |e| e + 0.chr }.join('') + 0.chr
535
+ env.to_wide_string! if hash['with_logon']
536
+ end
537
+
538
+ # Process SECURITY_ATTRIBUTE structure
539
+ process_security = nil
540
+
541
+ if hash['process_inherit']
542
+ process_security = SECURITY_ATTRIBUTES.new
543
+ process_security[:nLength] = 12
544
+ process_security[:bInheritHandle] = true
545
+ end
546
+
547
+ # Thread SECURITY_ATTRIBUTE structure
548
+ thread_security = nil
549
+
550
+ if hash['thread_inherit']
551
+ thread_security = SECURITY_ATTRIBUTES.new
552
+ thread_security[:nLength] = 12
553
+ thread_security[:bInheritHandle] = true
554
+ end
555
+
556
+ # Automatically handle stdin, stdout and stderr as either IO objects
557
+ # or file descriptors. This won't work for StringIO, however. It also
558
+ # will not work on JRuby because of the way it handles internal file
559
+ # descriptors.
560
+ #
561
+ ['stdin', 'stdout', 'stderr'].each{ |io|
562
+ if si_hash[io]
563
+ if si_hash[io].respond_to?(:fileno)
564
+ handle = get_osfhandle(si_hash[io].fileno)
565
+ else
566
+ handle = get_osfhandle(si_hash[io])
567
+ end
568
+
569
+ if handle == INVALID_HANDLE_VALUE
570
+ ptr = FFI::MemoryPointer.new(:int)
571
+
572
+ if windows_version >= 6 && get_errno(ptr) == 0
573
+ errno = ptr.read_int
574
+ else
575
+ errno = FFI.errno
576
+ end
577
+
578
+ raise SystemCallError.new("get_osfhandle", errno)
579
+ end
580
+
581
+ # Most implementations of Ruby on Windows create inheritable
582
+ # handles by default, but some do not. RF bug #26988.
583
+ bool = SetHandleInformation(
584
+ handle,
585
+ HANDLE_FLAG_INHERIT,
586
+ HANDLE_FLAG_INHERIT
587
+ )
588
+
589
+ raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool
590
+
591
+ si_hash[io] = handle
592
+ si_hash['startf_flags'] ||= 0
593
+ si_hash['startf_flags'] |= STARTF_USESTDHANDLES
594
+ hash['inherit'] = true
595
+ end
596
+ }
597
+
598
+ procinfo = PROCESS_INFORMATION.new
599
+ startinfo = STARTUPINFO.new
600
+
601
+ unless si_hash.empty?
602
+ startinfo[:cb] = startinfo.size
603
+ startinfo[:lpDesktop] = si_hash['desktop'] if si_hash['desktop']
604
+ startinfo[:lpTitle] = si_hash['title'] if si_hash['title']
605
+ startinfo[:dwX] = si_hash['x'] if si_hash['x']
606
+ startinfo[:dwY] = si_hash['y'] if si_hash['y']
607
+ startinfo[:dwXSize] = si_hash['x_size'] if si_hash['x_size']
608
+ startinfo[:dwYSize] = si_hash['y_size'] if si_hash['y_size']
609
+ startinfo[:dwXCountChars] = si_hash['x_count_chars'] if si_hash['x_count_chars']
610
+ startinfo[:dwYCountChars] = si_hash['y_count_chars'] if si_hash['y_count_chars']
611
+ startinfo[:dwFillAttribute] = si_hash['fill_attribute'] if si_hash['fill_attribute']
612
+ startinfo[:dwFlags] = si_hash['startf_flags'] if si_hash['startf_flags']
613
+ startinfo[:wShowWindow] = si_hash['sw_flags'] if si_hash['sw_flags']
614
+ startinfo[:cbReserved2] = 0
615
+ startinfo[:hStdInput] = si_hash['stdin'] if si_hash['stdin']
616
+ startinfo[:hStdOutput] = si_hash['stdout'] if si_hash['stdout']
617
+ startinfo[:hStdError] = si_hash['stderr'] if si_hash['stderr']
618
+ end
619
+
620
+ app = nil
621
+ cmd = nil
622
+
623
+ # Convert strings to wide character strings if present
624
+ if hash['app_name']
625
+ app = hash['app_name'].to_wide_string
626
+ end
627
+
628
+ if hash['command_line']
629
+ cmd = hash['command_line'].to_wide_string
630
+ end
631
+
632
+ if hash['cwd']
633
+ cwd = hash['cwd'].to_wide_string
634
+ end
635
+
636
+ if hash['with_logon']
637
+ logon = hash['with_logon'].to_wide_string
638
+
639
+ if hash['password']
640
+ passwd = hash['password'].to_wide_string
641
+ else
642
+ raise ArgumentError, 'password must be specified if with_logon is used'
643
+ end
644
+
645
+ if hash['domain']
646
+ domain = hash['domain'].to_wide_string
647
+ end
648
+
649
+ hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
650
+
651
+ bool = CreateProcessWithLogonW(
652
+ logon, # User
653
+ domain, # Domain
654
+ passwd, # Password
655
+ LOGON_WITH_PROFILE, # Logon flags
656
+ app, # App name
657
+ cmd, # Command line
658
+ hash['creation_flags'], # Creation flags
659
+ env, # Environment
660
+ cwd, # Working directory
661
+ startinfo, # Startup Info
662
+ procinfo # Process Info
663
+ )
664
+
665
+ unless bool
666
+ raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
667
+ end
668
+ else
669
+ inherit = hash['inherit'] || false
670
+
671
+ bool = CreateProcessW(
672
+ app, # App name
673
+ cmd, # Command line
674
+ process_security, # Process attributes
675
+ thread_security, # Thread attributes
676
+ inherit, # Inherit handles?
677
+ hash['creation_flags'], # Creation flags
678
+ env, # Environment
679
+ cwd, # Working directory
680
+ startinfo, # Startup Info
681
+ procinfo # Process Info
682
+ )
683
+
684
+ unless bool
685
+ raise SystemCallError.new("CreateProcess", FFI.errno)
686
+ end
687
+ end
688
+
689
+ # Automatically close the process and thread handles in the
690
+ # PROCESS_INFORMATION struct unless explicitly told not to.
691
+ if hash['close_handles']
692
+ CloseHandle(procinfo[:hProcess])
693
+ CloseHandle(procinfo[:hThread])
694
+ end
695
+
696
+ ProcessInfo.new(
697
+ procinfo[:hProcess],
698
+ procinfo[:hThread],
699
+ procinfo[:dwProcessId],
700
+ procinfo[:dwThreadId]
701
+ )
702
+ end
703
+
704
+ remove_method :kill
705
+
706
+ # Kill a given process with a specific signal. This overrides the default
707
+ # implementation of Process.kill. The differences mainly reside in the way
708
+ # it kills processes, but this version also gives you finer control over
709
+ # behavior.
710
+ #
711
+ # Internally, signals 2 and 3 will generate a console control event, using
712
+ # a ctrl-c or ctrl-break event, respectively. Signal 9 terminates the
713
+ # process harshly, given that process no chance to do any internal cleanup.
714
+ # Signals 1 and 4-8 kill the process more nicely, giving the process a
715
+ # chance to do internal cleanup before being killed. Signal 0 behaves the
716
+ # same as the default implementation.
717
+ #
718
+ # When using signals 1 or 4-8 you may specify additional options that
719
+ # allow finer control over how that process is killed and how your program
720
+ # behaves.
721
+ #
722
+ # Possible options for signals 1 and 4-8.
723
+ #
724
+ # :exit_proc => The name of the exit function called when signal 1 or 4-8
725
+ # is used. The default is 'ExitProcess'.
726
+ #
727
+ # :dll_module => The name of the .dll (or .exe) that contains :exit_proc.
728
+ # The default is 'kernel32'.
729
+ #
730
+ # :wait_time => The time, in milliseconds, to wait for the process to
731
+ # actually die. The default is 5ms. If you specify 0 here
732
+ # then the process does not wait if the process is not
733
+ # signaled and instead returns immediately. Alternatively,
734
+ # you may specify Process::INFINITE, and your code will
735
+ # block until the process is actually signaled.
736
+ #
737
+ # Example:
738
+ #
739
+ # Process.kill(1, 12345, :exit_proc => 'ExitProcess', :module => 'kernel32')
740
+ #
741
+ def kill(signal, *pids)
742
+ raise SecurityError if $SAFE && $SAFE >= 2 # Match the spec
743
+
744
+ # Match the spec, require at least 2 arguments
745
+ if pids.length == 0
746
+ raise ArgumentError, "wrong number of arguments (1 for at least 2)"
747
+ end
748
+
749
+ # Match the spec, signal may not be less than zero if numeric
750
+ if signal.is_a?(Numeric) && signal < 0 # EINVAL
751
+ raise SystemCallError.new(22)
752
+ end
753
+
754
+ # Match the spec, signal must be a numeric, string or symbol
755
+ unless signal.is_a?(String) || signal.is_a?(Numeric) || signal.is_a?(Symbol)
756
+ raise ArgumentError, "bad signal type #{signal.class}"
757
+ end
758
+
759
+ # Match the spec, making an exception for BRK/SIGBRK, if the signal name is invalid
760
+ if signal.is_a?(String) || signal.is_a?(Symbol)
761
+ signal = signal.to_s.sub('SIG', '')
762
+ unless Signal.list.keys.include?(signal) || ['BRK', 'BRK'].include?(signal)
763
+ raise ArgumentError, "unsupported name '#{signal}'"
764
+ end
765
+ end
766
+
767
+ # If the last argument is a hash, pop it and assume it's a hash of options
768
+ if pids.last.is_a?(Hash)
769
+ hash = pids.pop
770
+ opts = {}
771
+
772
+ valid = %w[exit_proc dll_module wait_time]
773
+
774
+ hash.each{ |k,v|
775
+ k = k.to_s.downcase
776
+ unless valid.include?(k)
777
+ raise ArgumentError, "invalid option '#{k}'"
778
+ end
779
+ opts[k] = v
780
+ }
781
+
782
+ exit_proc = opts['exit_proc'] || 'ExitProcess'
783
+ dll_module = opts['dll_module'] || 'kernel32'
784
+ wait_time = opts['wait_time'] || 5
785
+ else
786
+ wait_time = 5
787
+ exit_proc = 'ExitProcess'
788
+ dll_module = 'kernel32'
789
+ end
790
+
791
+ count = 0
792
+
793
+ pids.each{ |pid|
794
+ raise TypeError unless pid.is_a?(Numeric) # Match spec, pid must be a number
795
+ raise SystemCallError.new(22) if pid < 0 # Match spec, EINVAL if pid less than zero
796
+
797
+ sigint = [Signal.list['INT'], 'INT', 'SIGINT', :INT, :SIGINT, 2]
798
+
799
+ # Match the spec
800
+ if pid == 0 && !sigint.include?(signal)
801
+ raise SystemCallError.new(22)
802
+ end
803
+
804
+ if signal == 0
805
+ access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
806
+ elsif signal == 9
807
+ access = PROCESS_TERMINATE
808
+ else
809
+ access = PROCESS_ALL_ACCESS
810
+ end
811
+
812
+ begin
813
+ handle = OpenProcess(access, false, pid)
814
+
815
+ if signal != 0 && handle == 0
816
+ raise SystemCallError, FFI.errno, "OpenProcess"
817
+ end
818
+
819
+ case signal
820
+ when 0
821
+ if handle != 0
822
+ count += 1
823
+ else
824
+ if FFI.errno == ERROR_ACCESS_DENIED
825
+ count += 1
826
+ else
827
+ raise SystemCallError.new(3) # ESRCH
828
+ end
829
+ end
830
+ when Signal.list['INT'], 'INT', 'SIGINT', :INT, :SIGINT, 2
831
+ if GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)
832
+ count += 1
833
+ else
834
+ raise SystemCallError.new("GenerateConsoleCtrlEvent", FFI.errno)
835
+ end
836
+ when Signal.list['BRK'], 'BRK', 'SIGBRK', :BRK, :SIGBRK, 3
837
+ if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)
838
+ count += 1
839
+ else
840
+ raise SystemCallError.new("GenerateConsoleCtrlEvent", FFI.errno)
841
+ end
842
+ when Signal.list['KILL'], 'KILL', 'SIGKILL', :KILL, :SIGKILL, 9
843
+ if TerminateProcess(handle, pid)
844
+ count += 1
845
+ else
846
+ raise SystemCallError.new("TerminateProcess", FFI.errno)
847
+ end
848
+ else
849
+ thread_id = FFI::MemoryPointer.new(:ulong)
850
+
851
+ mod = GetModuleHandle(dll_module)
852
+
853
+ if mod == 0
854
+ raise SystemCallError.new("GetModuleHandle: '#{dll_module}'", FFI.errno)
855
+ end
856
+
857
+ proc_addr = GetProcAddress(mod, exit_proc)
858
+
859
+ if proc_addr == 0
860
+ raise SystemCallError.new("GetProcAddress: '#{exit_proc}'", FFI.errno)
861
+ end
862
+
863
+ thread = CreateRemoteThread(handle, nil, 0, proc_addr, nil, 0, thread_id)
864
+
865
+ if thread > 0
866
+ WaitForSingleObject(thread, wait_time)
867
+ count += 1
868
+ else
869
+ raise SystemCallError.new("CreateRemoteThread", FFI.errno)
870
+ end
871
+ end
872
+ ensure
873
+ CloseHandle(handle) if handle
874
+ end
875
+ }
876
+
877
+ count
878
+ end
879
+
880
+ # Returns the exitcode of the process with given +pid+ or nil if the process
881
+ # is still running. Note that the process doesn't have to be a child process.
882
+ #
883
+ # This method is very handy for finding out if a process started with Process.create
884
+ # is still running. The usual way of calling Process.wait doesn't work when
885
+ # the process isn't recognized as a child process (ECHILD). This happens for example
886
+ # when stdin, stdout or stderr are set to custom values.
887
+ #
888
+ def get_exitcode(pid)
889
+ handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid)
890
+
891
+ if handle == INVALID_HANDLE_VALUE
892
+ raise SystemCallError.new("OpenProcess", FFI.errno)
893
+ end
894
+
895
+ begin
896
+ buf = FFI::MemoryPointer.new(:ulong, 1)
897
+
898
+ unless GetExitCodeProcess(handle, buf)
899
+ raise SystemCallError.new("GetExitCodeProcess", FFI.errno)
900
+ end
901
+ ensure
902
+ CloseHandle(handle)
903
+ end
904
+
905
+ exitcode = buf.read_int
906
+
907
+ if exitcode == STILL_ACTIVE
908
+ nil
909
+ else
910
+ exitcode
911
+ end
912
+ end
913
+ end
914
+
915
+ class << self
916
+ private
917
+
918
+ # Private method that returns the volume type, e.g. "NTFS", etc.
919
+ def volume_type
920
+ buf = FFI::MemoryPointer.new(:char, 32)
921
+ bool = GetVolumeInformationA(nil, nil, 0, nil, nil, nil, buf, buf.size)
922
+ bool ? buf.read_string : nil
923
+ end
924
+
925
+ # Private method that returns the Windows major version number.
926
+ def windows_version
927
+ ver = OSVERSIONINFO.new
928
+ ver[:dwOSVersionInfoSize] = ver.size
929
+
930
+ unless GetVersionExA(ver)
931
+ raise SystemCallError.new("GetVersionEx", FFI.errno)
932
+ end
933
+
934
+ ver[:dwMajorVersion]
935
+ end
936
+ end
937
+ end