win32-process 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
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-2012 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-2012 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,58 @@
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
+ 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,35 +1,35 @@
1
- ##########################################################################
2
- # example_create.rb
3
- #
4
- # Simple test program for the Process.create() method. You can run this
5
- # code via the 'example:create' task.
6
- ##########################################################################
7
- require "win32/process"
8
-
9
- p Process::WIN32_PROCESS_VERSION
10
-
11
- struct = Process.create(
12
- :app_name => "notepad.exe",
13
- :creation_flags => Process::DETACHED_PROCESS,
14
- :process_inherit => false,
15
- :thread_inherit => true,
16
- :cwd => "C:\\",
17
- :inherit => true,
18
- :environment => "SYSTEMROOT=#{ENV['SYSTEMROOT']};PATH=C:\\"
19
- )
20
-
21
- p struct
22
-
23
- =begin
24
- # Don't run this from an existing terminal
25
- pid = Process.create(
26
- :app_name => "cmd.exe",
27
- :creation_flags => Process::DETACHED_PROCESS,
28
- :startf_flags => Process::USEPOSITION,
29
- :x => 0,
30
- :y => 0,
31
- :title => "Hi Dan"
32
- )
33
-
34
- puts "Pid of new process: #{pid}"
35
- =end
1
+ ##########################################################################
2
+ # example_create.rb
3
+ #
4
+ # Simple test program for the Process.create() method. You can run this
5
+ # code via the 'example:create' task.
6
+ ##########################################################################
7
+ require "win32/process"
8
+
9
+ p Process::WIN32_PROCESS_VERSION
10
+
11
+ struct = Process.create(
12
+ :app_name => "notepad.exe",
13
+ :creation_flags => Process::DETACHED_PROCESS,
14
+ :process_inherit => false,
15
+ :thread_inherit => true,
16
+ :cwd => "C:\\",
17
+ :inherit => true,
18
+ :environment => "SYSTEMROOT=#{ENV['SYSTEMROOT']};PATH=C:\\"
19
+ )
20
+
21
+ p struct
22
+
23
+ =begin
24
+ # Don't run this from an existing terminal
25
+ pid = Process.create(
26
+ :app_name => "cmd.exe",
27
+ :creation_flags => Process::DETACHED_PROCESS,
28
+ :startf_flags => Process::USEPOSITION,
29
+ :x => 0,
30
+ :y => 0,
31
+ :title => "Hi Dan"
32
+ )
33
+
34
+ puts "Pid of new process: #{pid}"
35
+ =end
@@ -1,34 +1,34 @@
1
- ##########################################################################
2
- # example_kill.rb
3
- #
4
- # Generic test script for futzing around Process.kill. This script
5
- # requires the sys-proctable library.
6
- #
7
- # You can run this example via the 'example:kill' task.
8
- ##########################################################################
9
- require "win32/process"
10
-
11
- begin
12
- require "sys/proctable"
13
- rescue LoadError
14
- STDERR.puts "Whoa there!"
15
- STDERR.puts "This script requires the sys-proctable package to work."
16
- STDERR.puts "You can find it at http://ruby-sysutils.sf.net"
17
- STDERR.puts "Exiting..."
18
- exit
19
- end
20
-
21
- include Sys
22
-
23
- puts "VERSION: " + Process::WIN32_PROCESS_VERSION
24
-
25
- IO.popen("notepad")
26
- sleep 1 # Give it a chance to start before checking for its pid
27
-
28
- pids = []
29
-
30
- ProcTable.ps{ |s|
31
- pids.push(s.pid) if s.cmdline =~ /notepad/i
32
- }
33
-
34
- p Process.kill(9,pids.last)
1
+ ##########################################################################
2
+ # example_kill.rb
3
+ #
4
+ # Generic test script for futzing around Process.kill. This script
5
+ # requires the sys-proctable library.
6
+ #
7
+ # You can run this example via the 'example:kill' task.
8
+ ##########################################################################
9
+ require "win32/process"
10
+
11
+ begin
12
+ require "sys/proctable"
13
+ rescue LoadError
14
+ STDERR.puts "Whoa there!"
15
+ STDERR.puts "This script requires the sys-proctable package to work."
16
+ STDERR.puts "You can find it at http://ruby-sysutils.sf.net"
17
+ STDERR.puts "Exiting..."
18
+ exit
19
+ end
20
+
21
+ include Sys
22
+
23
+ puts "VERSION: " + Process::WIN32_PROCESS_VERSION
24
+
25
+ IO.popen("notepad")
26
+ sleep 1 # Give it a chance to start before checking for its pid
27
+
28
+ pids = []
29
+
30
+ ProcTable.ps{ |s|
31
+ pids.push(s.pid) if s.cmdline =~ /notepad/i
32
+ }
33
+
34
+ p Process.kill(9,pids.last)
data/lib/win32/process.rb CHANGED
@@ -1,881 +1,894 @@
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
- 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"
58
- end
59
- else
60
- begin
61
- handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
62
-
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)
72
- end
73
- end
74
-
75
- [pmask.read_ulong, smask.read_ulong]
76
- end
77
-
78
- remove_method :getpriority
79
-
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
101
-
102
- handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, int)
103
-
104
- if handle == INVALID_HANDLE_VALUE
105
- raise SystemCallError, FFI.errno, "OpenProcess"
106
- end
107
-
108
- begin
109
- priority = GetPriorityClass(handle)
110
-
111
- if priority == 0
112
- raise SystemCallError, FFI.errno, "GetPriorityClass"
113
- end
114
- ensure
115
- CloseHandle(handle)
116
- end
117
-
118
- priority
119
- end
120
-
121
- remove_method :setpriority
122
-
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
143
-
144
- handle = OpenProcess(PROCESS_SET_INFORMATION, false , int)
145
-
146
- if handle == INVALID_HANDLE_VALUE
147
- raise SystemCallError, FFI.errno, "OpenProcess"
148
- end
149
-
150
- begin
151
- unless SetPriorityClass(handle, int_priority)
152
- raise SystemCallError, FFI.errno, "SetPriorityClass"
153
- end
154
- ensure
155
- CloseHandle(handle)
156
- end
157
-
158
- return 0 # Match the spec
159
- end
160
-
161
- remove_method :uid
162
-
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)
173
-
174
- raise TypeError unless sid.is_a?(TrueClass) || sid.is_a?(FalseClass)
175
-
176
- unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
177
- raise SystemCallError, FFI.errno, "OpenProcessToken"
178
- end
179
-
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
190
- )
191
-
192
- unless bool
193
- raise SystemCallError, FFI.errno, "GetTokenInformation"
194
- end
195
-
196
- string_sid = tuser[8, (rlength.read_ulong - 8)]
197
-
198
- if sid
199
- string_sid
200
- else
201
- psid = FFI::MemoryPointer.new(:ulong)
202
-
203
- unless ConvertSidToStringSidA(string_sid, psid)
204
- raise SystemCallError, FFI.errno, "ConvertSidToStringSid"
205
- end
206
-
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
253
-
254
- handle = nil
255
- in_job = Process.job?
256
-
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
265
- end
266
-
267
- begin
268
- unless in_job
269
- unless AssignProcessToJobObject(handle, GetCurrentProcess())
270
- raise Error, get_last_error
271
- end
272
- end
273
-
274
- ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
275
- val = nil
276
-
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
286
-
287
- bool = QueryInformationJobObject(
288
- handle,
289
- JobObjectExtendedLimitInformation,
290
- ptr,
291
- ptr.size,
292
- nil
293
- )
294
-
295
- unless bool
296
- raise SystemCallError, FFI.errno, "QueryInformationJobObject"
297
- end
298
-
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
305
-
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
353
- end
354
-
355
- begin
356
- unless in_job
357
- unless AssignProcessToJobObject(handle, GetCurrentProcess())
358
- raise SystemCallError, FFI.errno, "AssignProcessToJobObject"
359
- end
360
- end
361
-
362
- ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
363
-
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
372
-
373
- bool = SetInformationJobObject(
374
- handle,
375
- JobObjectExtendedLimitInformation,
376
- ptr,
377
- ptr.size
378
- )
379
-
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
- }
482
-
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
- }
491
-
492
- si_hash = {}
493
-
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
-
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
514
- end
515
-
516
- env = nil
517
-
518
- # The env string should be passed as a string of ';' separated paths.
519
- if hash['environment']
520
- env = hash['environment']
521
-
522
- unless env.respond_to?(:join)
523
- env = hash['environment'].split(File::PATH_SEPARATOR)
524
- end
525
-
526
- env = env.map{ |e| e + 0.chr }.join('') + 0.chr
527
- env.to_wide_string! if hash['with_logon']
528
- end
529
-
530
- # Process SECURITY_ATTRIBUTE structure
531
- process_security = nil
532
-
533
- if hash['process_inherit']
534
- process_security = SECURITY_ATTRIBUTES.new
535
- process_security[:nLength] = 12
536
- process_security[:bInheritHandle] = true
537
- end
538
-
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
546
- end
547
-
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)
557
- else
558
- handle = get_osfhandle(si_hash[io])
559
- end
560
-
561
- if handle == INVALID_HANDLE_VALUE
562
- ptr = FFI::MemoryPointer.new(:int)
563
-
564
- if get_errno(ptr) == 0
565
- errno = ptr.read_int
566
- else
567
- errno = FFI.errno
568
- end
569
-
570
- raise SystemCallError.new("get_osfhandle", errno)
571
- end
572
-
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
- )
580
-
581
- raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool
582
-
583
- si_hash[io] = handle
584
- si_hash['startf_flags'] ||= 0
585
- si_hash['startf_flags'] |= STARTF_USESTDHANDLES
586
- hash['inherit'] = true
587
- end
588
- }
589
-
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
616
- if hash['app_name']
617
- app = hash['app_name'].to_wide_string
618
- end
619
-
620
- if hash['command_line']
621
- cmd = hash['command_line'].to_wide_string
622
- end
623
-
624
- if hash['cwd']
625
- cwd = hash['cwd'].to_wide_string
626
- end
627
-
628
- if hash['with_logon']
629
- logon = hash['with_logon'].to_wide_string
630
-
631
- if hash['password']
632
- passwd = hash['password'].to_wide_string
633
- else
634
- raise ArgumentError, 'password must be specified if with_logon is used'
635
- end
636
-
637
- if hash['domain']
638
- domain = hash['domain'].to_wide_string
639
- end
640
-
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
655
- )
656
-
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
- )
675
-
676
- unless bool
677
- raise SystemCallError.new("CreateProcess", FFI.errno)
678
- end
679
- end
680
-
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])
686
- end
687
-
688
- ProcessInfo.new(
689
- procinfo[:hProcess],
690
- procinfo[:hThread],
691
- procinfo[:dwProcessId],
692
- procinfo[:dwThreadId]
693
- )
694
- end
695
-
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
755
- end
756
-
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 = {}
761
-
762
- valid = %w[exit_proc dll_module wait_time]
763
-
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
- }
771
-
772
- exit_proc = opts['exit_proc'] || 'ExitProcess'
773
- dll_module = opts['dll_module'] || 'kernel32'
774
- wait_time = opts['wait_time'] || 5
775
- else
776
- wait_time = 5
777
- exit_proc = 'ExitProcess'
778
- dll_module = 'kernel32'
779
- end
780
-
781
- count = 0
782
-
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
786
-
787
- sigint = [Signal.list['INT'], 'INT', 'SIGINT', :INT, :SIGINT, 2]
788
-
789
- # Match the spec
790
- if pid == 0 && !sigint.include?(signal)
791
- raise SystemCallError.new(22)
792
- end
793
-
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
801
-
802
- begin
803
- handle = OpenProcess(access, false, pid)
804
-
805
- if signal != 0 && handle == 0
806
- raise SystemCallError, FFI.errno, "OpenProcess"
807
- end
808
-
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)
840
-
841
- mod = GetModuleHandle(dll_module)
842
-
843
- if mod == 0
844
- raise SystemCallError.new("GetModuleHandle: '#{dll_module}'", FFI.errno)
845
- end
846
-
847
- proc_addr = GetProcAddress(mod, exit_proc)
848
-
849
- if proc_addr == 0
850
- raise SystemCallError.new("GetProcAddress: '#{exit_proc}'", FFI.errno)
851
- end
852
-
853
- thread = CreateRemoteThread(handle, nil, 0, proc_addr, nil, 0, thread_id)
854
-
855
- if thread > 0
856
- WaitForSingleObject(thread, wait_time)
857
- count += 1
858
- else
859
- raise SystemCallError.new("CreateRemoteThread", FFI.errno)
860
- end
861
- end
862
- ensure
863
- CloseHandle(handle) if handle
864
- end
865
- }
866
-
867
- count
868
- end
869
- end
870
-
871
- class << self
872
- private
873
-
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
880
- end
881
- 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.1'
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