win32-process 0.8.3 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +74 -0
- data/lib/win32-process.rb +1 -1
- data/lib/win32/process.rb +1139 -1141
- data/lib/win32/process/constants.rb +121 -121
- data/lib/win32/process/functions.rb +91 -91
- data/lib/win32/process/helper.rb +12 -12
- data/lib/win32/process/structs.rb +218 -218
- data/test/test_win32_process.rb +370 -370
- data/test/test_win32_process_kill.rb +143 -165
- data/win32-process.gemspec +23 -29
- metadata +11 -76
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGES +0 -270
- data/MANIFEST +0 -15
- data/README +0 -78
- data/Rakefile +0 -60
- data/certs/djberg96_pub.pem +0 -21
- data/examples/example_create.rb +0 -35
- data/examples/example_kill.rb +0 -34
- metadata.gz.sig +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5f5cdcabac1da0aebf77bd252b0647899dfee2632e34e626f18a9951a9d17e62
|
4
|
+
data.tar.gz: 39382a91f43eba2e0c00f1b92e3f53de3a49b87e689b22683cf5c08f395077b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe1ba6453b70b3712322a588b83708c2eb5f657d97e443c974162be290f009488c28ddb57de1212bcb141a57006f6d0db2e59244b19583da21265df2cab35d83
|
7
|
+
data.tar.gz: b38df396da190521cbf3fa0975e8087d6f7435956996716b3fe3416ae0d5565e32787c3794a943a7f74c98cb4d31b8c704882b53f6a88d265c7e334a1da1923b
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# win32-process
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/win32-process.svg)](https://badge.fury.io/rb/win32-process)
|
4
|
+
|
5
|
+
This library provides analogues of the :getpriority, :setpriority, :getrlimit, :setrlimit and :uid methods for MS Windows. It also adds the new methods :job?, :get_affinity, and :create, and redefines the :kill method.
|
6
|
+
|
7
|
+
## Prerequisites
|
8
|
+
|
9
|
+
- ffi
|
10
|
+
- sys-proctable (dev only)
|
11
|
+
- test-unit 2 (dev only)
|
12
|
+
|
13
|
+
## Supported Platforms
|
14
|
+
|
15
|
+
This library is supported on Windows 2000 or later.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
```
|
20
|
+
gem install win32-process
|
21
|
+
```
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require 'win32/process'
|
27
|
+
|
28
|
+
p Process.job? # => true or false
|
29
|
+
|
30
|
+
info = Process.create( :app_name => "notepad.exe", :creation_flags => Process::DETACHED_PROCESS, :process_inherit => false, :thread_inherit => true, :cwd => "C:\" )
|
31
|
+
|
32
|
+
p info.process_id
|
33
|
+
```
|
34
|
+
|
35
|
+
## Developer's Notes
|
36
|
+
|
37
|
+
### Removal of Process.fork in release 0.7.0
|
38
|
+
|
39
|
+
The Process.fork method was originally experimental but it has never been particularly useful in practice. On top of that, it required special implementations of the Process.waitXXX methods, so it was a maintenance issue as well.
|
40
|
+
|
41
|
+
With Ruby 1.9 now becoming standard and its addition of Process.spawn and friends (and concomitant support for the Process.waitXXX methods) I felt it was time to remove it.
|
42
|
+
|
43
|
+
You can still simulate Process.fork if you like using Process.create, which is how it was implemented internally anyway. A better solution might be to follow in the footsteps of ActiveState Perl, which uses native threads to simulate fork on Windows.
|
44
|
+
|
45
|
+
### Changes in the custom Process.kill method for 0.7.0
|
46
|
+
|
47
|
+
The Process.kill method in 0.7.0 more closely matches the spec now, but the internal method for killing processes is still nicer for most signals. With the release of 0.7.0 users can now specify options that provide finer control over how a process is killed. See the documentation for details.
|
48
|
+
|
49
|
+
## The removal of the custom Process.ppid method
|
50
|
+
|
51
|
+
This was added at some point in the Ruby 1.9 dev cycle so it was removed from this library.
|
52
|
+
|
53
|
+
## Known Issues
|
54
|
+
|
55
|
+
JRuby doesn't seem to like SIGBRK for Process.kill.
|
56
|
+
|
57
|
+
Any issues or bugs should be reported on the project page at <https://github.com/djberg96/win32-process>.
|
58
|
+
|
59
|
+
## License
|
60
|
+
|
61
|
+
Artistic 2.0
|
62
|
+
|
63
|
+
## Copyright
|
64
|
+
|
65
|
+
(C) 2003-2015 Daniel J. Berger All Rights Reserved
|
66
|
+
|
67
|
+
## Warranty
|
68
|
+
|
69
|
+
This library is provided "as is" and without any express or implied warranties, including, without limitation, the implied warranties of merchantability and fitness for a particular purpose.
|
70
|
+
|
71
|
+
## Author(s)
|
72
|
+
|
73
|
+
- Park Heesob
|
74
|
+
- Daniel J. Berger
|
data/lib/win32-process.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require_relative 'win32/process'
|
1
|
+
require_relative 'win32/process'
|
data/lib/win32/process.rb
CHANGED
@@ -1,1141 +1,1139 @@
|
|
1
|
-
require_relative 'process/functions'
|
2
|
-
require_relative 'process/constants'
|
3
|
-
require_relative 'process/structs'
|
4
|
-
require_relative 'process/helper'
|
5
|
-
|
6
|
-
module Process
|
7
|
-
include Process::Constants
|
8
|
-
extend Process::Functions
|
9
|
-
extend Process::Structs
|
10
|
-
extend Process::Constants
|
11
|
-
|
12
|
-
# The version of the win32-process library.
|
13
|
-
WIN32_PROCESS_VERSION = '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.
|
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 == 0
|
64
|
-
raise SystemCallError, FFI.errno, "OpenProcess"
|
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, 0, int)
|
103
|
-
|
104
|
-
if handle == 0
|
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, 0 , int)
|
145
|
-
|
146
|
-
if handle == 0
|
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[FFI.type_size(:pointer)*2, (rlength.read_ulong - FFI.type_size(:pointer)*2)]
|
197
|
-
|
198
|
-
if sid
|
199
|
-
string_sid
|
200
|
-
else
|
201
|
-
psid = FFI::MemoryPointer.new(:uintptr_t)
|
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, 1, @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
|
-
# WARNING: Exceeding the limit you set with this method could segfault
|
336
|
-
# the interpreter. Consider this method experimental.
|
337
|
-
#
|
338
|
-
def setrlimit(resource, current_limit, max_limit = nil)
|
339
|
-
max_limit = current_limit
|
340
|
-
|
341
|
-
handle = nil
|
342
|
-
in_job = Process.job?
|
343
|
-
|
344
|
-
unless [RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS, RLIMIT_CPU].include?(resource)
|
345
|
-
raise ArgumentError, "unsupported resource type: '#{resource}'"
|
346
|
-
end
|
347
|
-
|
348
|
-
# Put the current process in a job if it's not already in one
|
349
|
-
if in_job && defined? @win32_process_job_name
|
350
|
-
handle = OpenJobObjectA(JOB_OBJECT_SET_ATTRIBUTES, 1, @win32_process_job_name)
|
351
|
-
raise SystemCallError, FFI.errno, "OpenJobObject" if handle == 0
|
352
|
-
else
|
353
|
-
@win32_process_job_name = 'ruby_' + Process.pid.to_s
|
354
|
-
handle = CreateJobObjectA(nil, @win32_process_job_name)
|
355
|
-
raise SystemCallError, FFI.errno, "CreateJobObject" if handle == 0
|
356
|
-
end
|
357
|
-
|
358
|
-
begin
|
359
|
-
unless in_job
|
360
|
-
unless AssignProcessToJobObject(handle, GetCurrentProcess())
|
361
|
-
raise SystemCallError, FFI.errno, "AssignProcessToJobObject"
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
|
366
|
-
|
367
|
-
# Set the LimitFlags and relevant members of the struct
|
368
|
-
if resource == RLIMIT_CPU
|
369
|
-
ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_TIME
|
370
|
-
ptr[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart] = max_limit
|
371
|
-
else
|
372
|
-
ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_MEMORY
|
373
|
-
ptr[:ProcessMemoryLimit] = max_limit
|
374
|
-
end
|
375
|
-
|
376
|
-
bool = SetInformationJobObject(
|
377
|
-
handle,
|
378
|
-
JobObjectExtendedLimitInformation,
|
379
|
-
ptr,
|
380
|
-
ptr.size
|
381
|
-
)
|
382
|
-
|
383
|
-
unless bool
|
384
|
-
raise SystemCallError, FFI.errno, "SetInformationJobObject"
|
385
|
-
end
|
386
|
-
ensure
|
387
|
-
at_exit{ CloseHandle(handle) if handle }
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
# Process.create(key => value, ...) => ProcessInfo
|
392
|
-
#
|
393
|
-
# This is a wrapper for the CreateProcess() function. It executes a process,
|
394
|
-
# returning a ProcessInfo struct. It accepts a hash as an argument.
|
395
|
-
# There are several primary keys:
|
396
|
-
#
|
397
|
-
# * command_line (this or app_name must be present)
|
398
|
-
# * app_name (default: nil)
|
399
|
-
# * inherit (default: false)
|
400
|
-
# * process_inherit (default: false)
|
401
|
-
# * thread_inherit (default: false)
|
402
|
-
# * creation_flags (default: 0)
|
403
|
-
# * cwd (default: Dir.pwd)
|
404
|
-
# * startup_info (default: nil)
|
405
|
-
# * environment (default: nil)
|
406
|
-
# * close_handles (default: true)
|
407
|
-
# * with_logon (default: nil)
|
408
|
-
# * domain (default: nil)
|
409
|
-
# * password (default: nil, mandatory if with_logon)
|
410
|
-
#
|
411
|
-
# Of these, the 'command_line' or 'app_name' must be specified or an
|
412
|
-
# error is raised. Both may be set individually, but 'command_line' should
|
413
|
-
# be preferred if only one of them is set because it does not (necessarily)
|
414
|
-
# require an explicit path or extension to work.
|
415
|
-
#
|
416
|
-
# The 'domain' and 'password' options are only relevent in the context
|
417
|
-
# of 'with_logon'. If 'with_logon' is set, then the 'password' option is
|
418
|
-
# mandatory.
|
419
|
-
#
|
420
|
-
# The startup_info key takes a hash. Its keys are attributes that are
|
421
|
-
# part of the StartupInfo struct, and are generally only meaningful for
|
422
|
-
# GUI or console processes. See the documentation on CreateProcess()
|
423
|
-
# and the StartupInfo struct on MSDN for more information.
|
424
|
-
#
|
425
|
-
# * desktop
|
426
|
-
# * title
|
427
|
-
# * x
|
428
|
-
# * y
|
429
|
-
# * x_size
|
430
|
-
# * y_size
|
431
|
-
# * x_count_chars
|
432
|
-
# * y_count_chars
|
433
|
-
# * fill_attribute
|
434
|
-
# * sw_flags
|
435
|
-
# * startf_flags
|
436
|
-
# * stdin
|
437
|
-
# * stdout
|
438
|
-
# * stderr
|
439
|
-
#
|
440
|
-
# Note that the 'stdin', 'stdout' and 'stderr' options can be either Ruby
|
441
|
-
# IO objects or file descriptors (i.e. a fileno). However, StringIO objects
|
442
|
-
# are not currently supported. Unfortunately, setting these is not currently
|
443
|
-
# an option for JRuby.
|
444
|
-
#
|
445
|
-
# If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
|
446
|
-
# is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
|
447
|
-
# automatically OR'd to the +startf_flags+ value.
|
448
|
-
#
|
449
|
-
# The ProcessInfo struct contains the following members:
|
450
|
-
#
|
451
|
-
# * process_handle - The handle to the newly created process.
|
452
|
-
# * thread_handle - The handle to the primary thread of the process.
|
453
|
-
# * process_id - Process ID.
|
454
|
-
# * thread_id - Thread ID.
|
455
|
-
#
|
456
|
-
# If the 'close_handles' option is set to true (the default) then the
|
457
|
-
# process_handle and the thread_handle are automatically closed for you
|
458
|
-
# before the ProcessInfo struct is returned.
|
459
|
-
#
|
460
|
-
# If the 'with_logon' option is set, then the process runs the specified
|
461
|
-
# executable file in the security context of the specified credentials.
|
462
|
-
#
|
463
|
-
# To simulate Process.wait you can use this approach:
|
464
|
-
#
|
465
|
-
# sleep 0.1 while !Process.get_exitcode(info.process_id)
|
466
|
-
#
|
467
|
-
# If you really to use Process.wait, then you should use the
|
468
|
-
# Process.spawn method instead of Process.create where possible.
|
469
|
-
#
|
470
|
-
def create(args)
|
471
|
-
unless args.kind_of?(Hash)
|
472
|
-
raise TypeError, 'hash keyword arguments expected'
|
473
|
-
end
|
474
|
-
|
475
|
-
valid_keys = %w[
|
476
|
-
app_name command_line inherit creation_flags cwd environment
|
477
|
-
startup_info thread_inherit process_inherit close_handles with_logon
|
478
|
-
domain password
|
479
|
-
]
|
480
|
-
|
481
|
-
valid_si_keys = %w[
|
482
|
-
startf_flags desktop title x y x_size y_size x_count_chars
|
483
|
-
y_count_chars fill_attribute sw_flags stdin stdout stderr
|
484
|
-
]
|
485
|
-
|
486
|
-
# Set default values
|
487
|
-
hash = {
|
488
|
-
'app_name' => nil,
|
489
|
-
'creation_flags' => 0,
|
490
|
-
'close_handles' => true
|
491
|
-
}
|
492
|
-
|
493
|
-
# Validate the keys, and convert symbols and case to lowercase strings.
|
494
|
-
args.each{ |key, val|
|
495
|
-
key = key.to_s.downcase
|
496
|
-
unless valid_keys.include?(key)
|
497
|
-
raise ArgumentError, "invalid key '#{key}'"
|
498
|
-
end
|
499
|
-
hash[key] = val
|
500
|
-
}
|
501
|
-
|
502
|
-
si_hash = {}
|
503
|
-
|
504
|
-
# If the startup_info key is present, validate its subkeys
|
505
|
-
if hash['startup_info']
|
506
|
-
hash['startup_info'].each{ |key, val|
|
507
|
-
key = key.to_s.downcase
|
508
|
-
unless valid_si_keys.include?(key)
|
509
|
-
raise ArgumentError, "invalid startup_info key '#{key}'"
|
510
|
-
end
|
511
|
-
si_hash[key] = val
|
512
|
-
}
|
513
|
-
end
|
514
|
-
|
515
|
-
# The +command_line+ key is mandatory unless the +app_name+ key
|
516
|
-
# is specified.
|
517
|
-
unless hash['command_line']
|
518
|
-
if hash['app_name']
|
519
|
-
hash['command_line'] = hash['app_name']
|
520
|
-
hash['app_name'] = nil
|
521
|
-
else
|
522
|
-
raise ArgumentError, 'command_line or app_name must be specified'
|
523
|
-
end
|
524
|
-
end
|
525
|
-
|
526
|
-
env = nil
|
527
|
-
|
528
|
-
# The env string should be passed as a string of ';' separated paths.
|
529
|
-
if hash['environment']
|
530
|
-
env = hash['environment']
|
531
|
-
|
532
|
-
unless env.respond_to?(:join)
|
533
|
-
env = hash['environment'].split(File::PATH_SEPARATOR)
|
534
|
-
end
|
535
|
-
|
536
|
-
env = env.map{ |e| e + 0.chr }.join('') + 0.chr
|
537
|
-
env.to_wide_string! if hash['with_logon']
|
538
|
-
end
|
539
|
-
|
540
|
-
# Process SECURITY_ATTRIBUTE structure
|
541
|
-
process_security = nil
|
542
|
-
|
543
|
-
if hash['process_inherit']
|
544
|
-
process_security = SECURITY_ATTRIBUTES.new
|
545
|
-
process_security[:nLength] = 12
|
546
|
-
process_security[:bInheritHandle] = 1
|
547
|
-
end
|
548
|
-
|
549
|
-
# Thread SECURITY_ATTRIBUTE structure
|
550
|
-
thread_security = nil
|
551
|
-
|
552
|
-
if hash['thread_inherit']
|
553
|
-
thread_security = SECURITY_ATTRIBUTES.new
|
554
|
-
thread_security[:nLength] = 12
|
555
|
-
thread_security[:bInheritHandle] = 1
|
556
|
-
end
|
557
|
-
|
558
|
-
# Automatically handle stdin, stdout and stderr as either IO objects
|
559
|
-
# or file descriptors. This won't work for StringIO, however. It also
|
560
|
-
# will not work on JRuby because of the way it handles internal file
|
561
|
-
# descriptors.
|
562
|
-
#
|
563
|
-
['stdin', 'stdout', 'stderr'].each{ |io|
|
564
|
-
if si_hash[io]
|
565
|
-
if si_hash[io].respond_to?(:fileno)
|
566
|
-
handle = get_osfhandle(si_hash[io].fileno)
|
567
|
-
else
|
568
|
-
handle = get_osfhandle(si_hash[io])
|
569
|
-
end
|
570
|
-
|
571
|
-
if handle == INVALID_HANDLE_VALUE
|
572
|
-
ptr = FFI::MemoryPointer.new(:int)
|
573
|
-
|
574
|
-
if windows_version >= 6 && get_errno(ptr) == 0
|
575
|
-
errno = ptr.read_int
|
576
|
-
else
|
577
|
-
errno = FFI.errno
|
578
|
-
end
|
579
|
-
|
580
|
-
raise SystemCallError.new("get_osfhandle", errno)
|
581
|
-
end
|
582
|
-
|
583
|
-
# Most implementations of Ruby on Windows create inheritable
|
584
|
-
# handles by default, but some do not. RF bug #26988.
|
585
|
-
bool = SetHandleInformation(
|
586
|
-
handle,
|
587
|
-
HANDLE_FLAG_INHERIT,
|
588
|
-
HANDLE_FLAG_INHERIT
|
589
|
-
)
|
590
|
-
|
591
|
-
raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool
|
592
|
-
|
593
|
-
si_hash[io] = handle
|
594
|
-
si_hash['startf_flags'] ||= 0
|
595
|
-
si_hash['startf_flags'] |= STARTF_USESTDHANDLES
|
596
|
-
hash['inherit'] = true
|
597
|
-
end
|
598
|
-
}
|
599
|
-
|
600
|
-
procinfo = PROCESS_INFORMATION.new
|
601
|
-
startinfo = STARTUPINFO.new
|
602
|
-
|
603
|
-
unless si_hash.empty?
|
604
|
-
startinfo[:cb] = startinfo.size
|
605
|
-
startinfo[:lpDesktop] = si_hash['desktop'] if si_hash['desktop']
|
606
|
-
startinfo[:lpTitle] = si_hash['title'] if si_hash['title']
|
607
|
-
startinfo[:dwX] = si_hash['x'] if si_hash['x']
|
608
|
-
startinfo[:dwY] = si_hash['y'] if si_hash['y']
|
609
|
-
startinfo[:dwXSize] = si_hash['x_size'] if si_hash['x_size']
|
610
|
-
startinfo[:dwYSize] = si_hash['y_size'] if si_hash['y_size']
|
611
|
-
startinfo[:dwXCountChars] = si_hash['x_count_chars'] if si_hash['x_count_chars']
|
612
|
-
startinfo[:dwYCountChars] = si_hash['y_count_chars'] if si_hash['y_count_chars']
|
613
|
-
startinfo[:dwFillAttribute] = si_hash['fill_attribute'] if si_hash['fill_attribute']
|
614
|
-
startinfo[:dwFlags] = si_hash['startf_flags'] if si_hash['startf_flags']
|
615
|
-
startinfo[:wShowWindow] = si_hash['sw_flags'] if si_hash['sw_flags']
|
616
|
-
startinfo[:cbReserved2] = 0
|
617
|
-
startinfo[:hStdInput] = si_hash['stdin'] if si_hash['stdin']
|
618
|
-
startinfo[:hStdOutput] = si_hash['stdout'] if si_hash['stdout']
|
619
|
-
startinfo[:hStdError] = si_hash['stderr'] if si_hash['stderr']
|
620
|
-
end
|
621
|
-
|
622
|
-
app = nil
|
623
|
-
cmd = nil
|
624
|
-
|
625
|
-
# Convert strings to wide character strings if present
|
626
|
-
if hash['app_name']
|
627
|
-
app = hash['app_name'].to_wide_string
|
628
|
-
end
|
629
|
-
|
630
|
-
if hash['command_line']
|
631
|
-
cmd = hash['command_line'].to_wide_string
|
632
|
-
end
|
633
|
-
|
634
|
-
if hash['cwd']
|
635
|
-
cwd = hash['cwd'].to_wide_string
|
636
|
-
end
|
637
|
-
|
638
|
-
if hash['with_logon']
|
639
|
-
logon = hash['with_logon'].to_wide_string
|
640
|
-
|
641
|
-
if hash['password']
|
642
|
-
passwd = hash['password'].to_wide_string
|
643
|
-
else
|
644
|
-
raise ArgumentError, 'password must be specified if with_logon is used'
|
645
|
-
end
|
646
|
-
|
647
|
-
if hash['domain']
|
648
|
-
domain = hash['domain'].to_wide_string
|
649
|
-
end
|
650
|
-
|
651
|
-
hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
|
652
|
-
|
653
|
-
bool = CreateProcessWithLogonW(
|
654
|
-
logon, # User
|
655
|
-
domain, # Domain
|
656
|
-
passwd, # Password
|
657
|
-
LOGON_WITH_PROFILE, # Logon flags
|
658
|
-
app, # App name
|
659
|
-
cmd, # Command line
|
660
|
-
hash['creation_flags'], # Creation flags
|
661
|
-
env, # Environment
|
662
|
-
cwd, # Working directory
|
663
|
-
startinfo, # Startup Info
|
664
|
-
procinfo # Process Info
|
665
|
-
)
|
666
|
-
|
667
|
-
unless bool
|
668
|
-
raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
|
669
|
-
end
|
670
|
-
else
|
671
|
-
inherit = hash['inherit'] ? 1 : 0
|
672
|
-
|
673
|
-
bool = CreateProcessW(
|
674
|
-
app, # App name
|
675
|
-
cmd, # Command line
|
676
|
-
process_security, # Process attributes
|
677
|
-
thread_security, # Thread attributes
|
678
|
-
inherit, # Inherit handles?
|
679
|
-
hash['creation_flags'], # Creation flags
|
680
|
-
env, # Environment
|
681
|
-
cwd, # Working directory
|
682
|
-
startinfo, # Startup Info
|
683
|
-
procinfo # Process Info
|
684
|
-
)
|
685
|
-
|
686
|
-
unless bool
|
687
|
-
raise SystemCallError.new("CreateProcess", FFI.errno)
|
688
|
-
end
|
689
|
-
end
|
690
|
-
|
691
|
-
# Automatically close the process and thread handles in the
|
692
|
-
# PROCESS_INFORMATION struct unless explicitly told not to.
|
693
|
-
if hash['close_handles']
|
694
|
-
CloseHandle(procinfo[:hProcess])
|
695
|
-
CloseHandle(procinfo[:hThread])
|
696
|
-
end
|
697
|
-
|
698
|
-
ProcessInfo.new(
|
699
|
-
procinfo[:hProcess],
|
700
|
-
procinfo[:hThread],
|
701
|
-
procinfo[:dwProcessId],
|
702
|
-
procinfo[:dwThreadId]
|
703
|
-
)
|
704
|
-
end
|
705
|
-
|
706
|
-
remove_method :kill
|
707
|
-
|
708
|
-
# Kill a given process with a specific signal. This overrides the default
|
709
|
-
# implementation of Process.kill. The differences mainly reside in the way
|
710
|
-
# it kills processes, but this version also gives you finer control over
|
711
|
-
# behavior.
|
712
|
-
#
|
713
|
-
# Internally, signals 2 and 3 will generate a console control event, using
|
714
|
-
# a ctrl-c or ctrl-break event, respectively. Signal 9 terminates the
|
715
|
-
# process harshly, given that process no chance to do any internal cleanup.
|
716
|
-
# Signals 1 and 4-8 kill the process more nicely, giving the process a
|
717
|
-
# chance to do internal cleanup before being killed. Signal 0 behaves the
|
718
|
-
# same as the default implementation.
|
719
|
-
#
|
720
|
-
# When using signals 1 or 4-8 you may specify additional options that
|
721
|
-
# allow finer control over how that process is killed and how your program
|
722
|
-
# behaves.
|
723
|
-
#
|
724
|
-
# Possible options for signals 1 and 4-8.
|
725
|
-
#
|
726
|
-
# :exit_proc => The name of the exit function called when signal 1 or 4-8
|
727
|
-
# is used. The default is 'ExitProcess'.
|
728
|
-
#
|
729
|
-
# :dll_module => The name of the .dll (or .exe) that contains :exit_proc.
|
730
|
-
# The default is 'kernel32'.
|
731
|
-
#
|
732
|
-
# :wait_time => The time, in milliseconds, to wait for the process to
|
733
|
-
# actually die. The default is 5ms. If you specify 0 here
|
734
|
-
# then the process does not wait if the process is not
|
735
|
-
# signaled and instead returns immediately. Alternatively,
|
736
|
-
# you may specify Process::INFINITE, and your code will
|
737
|
-
# block until the process is actually signaled.
|
738
|
-
#
|
739
|
-
# Example:
|
740
|
-
#
|
741
|
-
# Process.kill(1, 12345, :exit_proc => 'ExitProcess', :module => 'kernel32')
|
742
|
-
#
|
743
|
-
def kill(signal, *pids)
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
#
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
wait_time =
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
access =
|
810
|
-
|
811
|
-
access =
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
mod
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
proc_addr
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
thread
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
#
|
885
|
-
#
|
886
|
-
#
|
887
|
-
#
|
888
|
-
#
|
889
|
-
#
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
handle
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
exitcode
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
#
|
919
|
-
#
|
920
|
-
#
|
921
|
-
#
|
922
|
-
#
|
923
|
-
#
|
924
|
-
# :
|
925
|
-
# :
|
926
|
-
#
|
927
|
-
#
|
928
|
-
#
|
929
|
-
#
|
930
|
-
#
|
931
|
-
#
|
932
|
-
#
|
933
|
-
#
|
934
|
-
#
|
935
|
-
#
|
936
|
-
#
|
937
|
-
#
|
938
|
-
#
|
939
|
-
#
|
940
|
-
#
|
941
|
-
#
|
942
|
-
#
|
943
|
-
#
|
944
|
-
#
|
945
|
-
#
|
946
|
-
#
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
when '
|
952
|
-
flag =
|
953
|
-
when '
|
954
|
-
flag =
|
955
|
-
when '
|
956
|
-
flag =
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
handle
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
when '
|
972
|
-
array =
|
973
|
-
when '
|
974
|
-
array =
|
975
|
-
when '
|
976
|
-
array =
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
he[:
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
me[:
|
1063
|
-
me[:
|
1064
|
-
me[:
|
1065
|
-
me[:
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
me[:
|
1080
|
-
me[:
|
1081
|
-
me[:
|
1082
|
-
me[:
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
pe[:
|
1101
|
-
pe[:
|
1102
|
-
pe[:
|
1103
|
-
pe[:
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
pe[:
|
1118
|
-
pe[:
|
1119
|
-
pe[:
|
1120
|
-
pe[:
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
end
|
1141
|
-
end
|
1
|
+
require_relative 'process/functions'
|
2
|
+
require_relative 'process/constants'
|
3
|
+
require_relative 'process/structs'
|
4
|
+
require_relative 'process/helper'
|
5
|
+
|
6
|
+
module Process
|
7
|
+
include Process::Constants
|
8
|
+
extend Process::Functions
|
9
|
+
extend Process::Structs
|
10
|
+
extend Process::Constants
|
11
|
+
|
12
|
+
# The version of the win32-process library.
|
13
|
+
WIN32_PROCESS_VERSION = '0.9.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.
|
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 == 0
|
64
|
+
raise SystemCallError, FFI.errno, "OpenProcess"
|
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, 0, int)
|
103
|
+
|
104
|
+
if handle == 0
|
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, 0 , int)
|
145
|
+
|
146
|
+
if handle == 0
|
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[FFI.type_size(:pointer)*2, (rlength.read_ulong - FFI.type_size(:pointer)*2)]
|
197
|
+
|
198
|
+
if sid
|
199
|
+
string_sid
|
200
|
+
else
|
201
|
+
psid = FFI::MemoryPointer.new(:uintptr_t)
|
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, 1, @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
|
+
# WARNING: Exceeding the limit you set with this method could segfault
|
336
|
+
# the interpreter. Consider this method experimental.
|
337
|
+
#
|
338
|
+
def setrlimit(resource, current_limit, max_limit = nil)
|
339
|
+
max_limit = current_limit
|
340
|
+
|
341
|
+
handle = nil
|
342
|
+
in_job = Process.job?
|
343
|
+
|
344
|
+
unless [RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS, RLIMIT_CPU].include?(resource)
|
345
|
+
raise ArgumentError, "unsupported resource type: '#{resource}'"
|
346
|
+
end
|
347
|
+
|
348
|
+
# Put the current process in a job if it's not already in one
|
349
|
+
if in_job && defined? @win32_process_job_name
|
350
|
+
handle = OpenJobObjectA(JOB_OBJECT_SET_ATTRIBUTES, 1, @win32_process_job_name)
|
351
|
+
raise SystemCallError, FFI.errno, "OpenJobObject" if handle == 0
|
352
|
+
else
|
353
|
+
@win32_process_job_name = 'ruby_' + Process.pid.to_s
|
354
|
+
handle = CreateJobObjectA(nil, @win32_process_job_name)
|
355
|
+
raise SystemCallError, FFI.errno, "CreateJobObject" if handle == 0
|
356
|
+
end
|
357
|
+
|
358
|
+
begin
|
359
|
+
unless in_job
|
360
|
+
unless AssignProcessToJobObject(handle, GetCurrentProcess())
|
361
|
+
raise SystemCallError, FFI.errno, "AssignProcessToJobObject"
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
ptr = JOBJECT_EXTENDED_LIMIT_INFORMATION.new
|
366
|
+
|
367
|
+
# Set the LimitFlags and relevant members of the struct
|
368
|
+
if resource == RLIMIT_CPU
|
369
|
+
ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_TIME
|
370
|
+
ptr[:BasicLimitInformation][:PerProcessUserTimeLimit][:QuadPart] = max_limit
|
371
|
+
else
|
372
|
+
ptr[:BasicLimitInformation][:LimitFlags] = JOB_OBJECT_LIMIT_PROCESS_MEMORY
|
373
|
+
ptr[:ProcessMemoryLimit] = max_limit
|
374
|
+
end
|
375
|
+
|
376
|
+
bool = SetInformationJobObject(
|
377
|
+
handle,
|
378
|
+
JobObjectExtendedLimitInformation,
|
379
|
+
ptr,
|
380
|
+
ptr.size
|
381
|
+
)
|
382
|
+
|
383
|
+
unless bool
|
384
|
+
raise SystemCallError, FFI.errno, "SetInformationJobObject"
|
385
|
+
end
|
386
|
+
ensure
|
387
|
+
at_exit{ CloseHandle(handle) if handle }
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
# Process.create(key => value, ...) => ProcessInfo
|
392
|
+
#
|
393
|
+
# This is a wrapper for the CreateProcess() function. It executes a process,
|
394
|
+
# returning a ProcessInfo struct. It accepts a hash as an argument.
|
395
|
+
# There are several primary keys:
|
396
|
+
#
|
397
|
+
# * command_line (this or app_name must be present)
|
398
|
+
# * app_name (default: nil)
|
399
|
+
# * inherit (default: false)
|
400
|
+
# * process_inherit (default: false)
|
401
|
+
# * thread_inherit (default: false)
|
402
|
+
# * creation_flags (default: 0)
|
403
|
+
# * cwd (default: Dir.pwd)
|
404
|
+
# * startup_info (default: nil)
|
405
|
+
# * environment (default: nil)
|
406
|
+
# * close_handles (default: true)
|
407
|
+
# * with_logon (default: nil)
|
408
|
+
# * domain (default: nil)
|
409
|
+
# * password (default: nil, mandatory if with_logon)
|
410
|
+
#
|
411
|
+
# Of these, the 'command_line' or 'app_name' must be specified or an
|
412
|
+
# error is raised. Both may be set individually, but 'command_line' should
|
413
|
+
# be preferred if only one of them is set because it does not (necessarily)
|
414
|
+
# require an explicit path or extension to work.
|
415
|
+
#
|
416
|
+
# The 'domain' and 'password' options are only relevent in the context
|
417
|
+
# of 'with_logon'. If 'with_logon' is set, then the 'password' option is
|
418
|
+
# mandatory.
|
419
|
+
#
|
420
|
+
# The startup_info key takes a hash. Its keys are attributes that are
|
421
|
+
# part of the StartupInfo struct, and are generally only meaningful for
|
422
|
+
# GUI or console processes. See the documentation on CreateProcess()
|
423
|
+
# and the StartupInfo struct on MSDN for more information.
|
424
|
+
#
|
425
|
+
# * desktop
|
426
|
+
# * title
|
427
|
+
# * x
|
428
|
+
# * y
|
429
|
+
# * x_size
|
430
|
+
# * y_size
|
431
|
+
# * x_count_chars
|
432
|
+
# * y_count_chars
|
433
|
+
# * fill_attribute
|
434
|
+
# * sw_flags
|
435
|
+
# * startf_flags
|
436
|
+
# * stdin
|
437
|
+
# * stdout
|
438
|
+
# * stderr
|
439
|
+
#
|
440
|
+
# Note that the 'stdin', 'stdout' and 'stderr' options can be either Ruby
|
441
|
+
# IO objects or file descriptors (i.e. a fileno). However, StringIO objects
|
442
|
+
# are not currently supported. Unfortunately, setting these is not currently
|
443
|
+
# an option for JRuby.
|
444
|
+
#
|
445
|
+
# If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
|
446
|
+
# is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
|
447
|
+
# automatically OR'd to the +startf_flags+ value.
|
448
|
+
#
|
449
|
+
# The ProcessInfo struct contains the following members:
|
450
|
+
#
|
451
|
+
# * process_handle - The handle to the newly created process.
|
452
|
+
# * thread_handle - The handle to the primary thread of the process.
|
453
|
+
# * process_id - Process ID.
|
454
|
+
# * thread_id - Thread ID.
|
455
|
+
#
|
456
|
+
# If the 'close_handles' option is set to true (the default) then the
|
457
|
+
# process_handle and the thread_handle are automatically closed for you
|
458
|
+
# before the ProcessInfo struct is returned.
|
459
|
+
#
|
460
|
+
# If the 'with_logon' option is set, then the process runs the specified
|
461
|
+
# executable file in the security context of the specified credentials.
|
462
|
+
#
|
463
|
+
# To simulate Process.wait you can use this approach:
|
464
|
+
#
|
465
|
+
# sleep 0.1 while !Process.get_exitcode(info.process_id)
|
466
|
+
#
|
467
|
+
# If you really to use Process.wait, then you should use the
|
468
|
+
# Process.spawn method instead of Process.create where possible.
|
469
|
+
#
|
470
|
+
def create(args)
|
471
|
+
unless args.kind_of?(Hash)
|
472
|
+
raise TypeError, 'hash keyword arguments expected'
|
473
|
+
end
|
474
|
+
|
475
|
+
valid_keys = %w[
|
476
|
+
app_name command_line inherit creation_flags cwd environment
|
477
|
+
startup_info thread_inherit process_inherit close_handles with_logon
|
478
|
+
domain password
|
479
|
+
]
|
480
|
+
|
481
|
+
valid_si_keys = %w[
|
482
|
+
startf_flags desktop title x y x_size y_size x_count_chars
|
483
|
+
y_count_chars fill_attribute sw_flags stdin stdout stderr
|
484
|
+
]
|
485
|
+
|
486
|
+
# Set default values
|
487
|
+
hash = {
|
488
|
+
'app_name' => nil,
|
489
|
+
'creation_flags' => 0,
|
490
|
+
'close_handles' => true
|
491
|
+
}
|
492
|
+
|
493
|
+
# Validate the keys, and convert symbols and case to lowercase strings.
|
494
|
+
args.each{ |key, val|
|
495
|
+
key = key.to_s.downcase
|
496
|
+
unless valid_keys.include?(key)
|
497
|
+
raise ArgumentError, "invalid key '#{key}'"
|
498
|
+
end
|
499
|
+
hash[key] = val
|
500
|
+
}
|
501
|
+
|
502
|
+
si_hash = {}
|
503
|
+
|
504
|
+
# If the startup_info key is present, validate its subkeys
|
505
|
+
if hash['startup_info']
|
506
|
+
hash['startup_info'].each{ |key, val|
|
507
|
+
key = key.to_s.downcase
|
508
|
+
unless valid_si_keys.include?(key)
|
509
|
+
raise ArgumentError, "invalid startup_info key '#{key}'"
|
510
|
+
end
|
511
|
+
si_hash[key] = val
|
512
|
+
}
|
513
|
+
end
|
514
|
+
|
515
|
+
# The +command_line+ key is mandatory unless the +app_name+ key
|
516
|
+
# is specified.
|
517
|
+
unless hash['command_line']
|
518
|
+
if hash['app_name']
|
519
|
+
hash['command_line'] = hash['app_name']
|
520
|
+
hash['app_name'] = nil
|
521
|
+
else
|
522
|
+
raise ArgumentError, 'command_line or app_name must be specified'
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
env = nil
|
527
|
+
|
528
|
+
# The env string should be passed as a string of ';' separated paths.
|
529
|
+
if hash['environment']
|
530
|
+
env = hash['environment']
|
531
|
+
|
532
|
+
unless env.respond_to?(:join)
|
533
|
+
env = hash['environment'].split(File::PATH_SEPARATOR)
|
534
|
+
end
|
535
|
+
|
536
|
+
env = env.map{ |e| e + 0.chr }.join('') + 0.chr
|
537
|
+
env.to_wide_string! if hash['with_logon']
|
538
|
+
end
|
539
|
+
|
540
|
+
# Process SECURITY_ATTRIBUTE structure
|
541
|
+
process_security = nil
|
542
|
+
|
543
|
+
if hash['process_inherit']
|
544
|
+
process_security = SECURITY_ATTRIBUTES.new
|
545
|
+
process_security[:nLength] = 12
|
546
|
+
process_security[:bInheritHandle] = 1
|
547
|
+
end
|
548
|
+
|
549
|
+
# Thread SECURITY_ATTRIBUTE structure
|
550
|
+
thread_security = nil
|
551
|
+
|
552
|
+
if hash['thread_inherit']
|
553
|
+
thread_security = SECURITY_ATTRIBUTES.new
|
554
|
+
thread_security[:nLength] = 12
|
555
|
+
thread_security[:bInheritHandle] = 1
|
556
|
+
end
|
557
|
+
|
558
|
+
# Automatically handle stdin, stdout and stderr as either IO objects
|
559
|
+
# or file descriptors. This won't work for StringIO, however. It also
|
560
|
+
# will not work on JRuby because of the way it handles internal file
|
561
|
+
# descriptors.
|
562
|
+
#
|
563
|
+
['stdin', 'stdout', 'stderr'].each{ |io|
|
564
|
+
if si_hash[io]
|
565
|
+
if si_hash[io].respond_to?(:fileno)
|
566
|
+
handle = get_osfhandle(si_hash[io].fileno)
|
567
|
+
else
|
568
|
+
handle = get_osfhandle(si_hash[io])
|
569
|
+
end
|
570
|
+
|
571
|
+
if handle == INVALID_HANDLE_VALUE
|
572
|
+
ptr = FFI::MemoryPointer.new(:int)
|
573
|
+
|
574
|
+
if windows_version >= 6 && get_errno(ptr) == 0
|
575
|
+
errno = ptr.read_int
|
576
|
+
else
|
577
|
+
errno = FFI.errno
|
578
|
+
end
|
579
|
+
|
580
|
+
raise SystemCallError.new("get_osfhandle", errno)
|
581
|
+
end
|
582
|
+
|
583
|
+
# Most implementations of Ruby on Windows create inheritable
|
584
|
+
# handles by default, but some do not. RF bug #26988.
|
585
|
+
bool = SetHandleInformation(
|
586
|
+
handle,
|
587
|
+
HANDLE_FLAG_INHERIT,
|
588
|
+
HANDLE_FLAG_INHERIT
|
589
|
+
)
|
590
|
+
|
591
|
+
raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool
|
592
|
+
|
593
|
+
si_hash[io] = handle
|
594
|
+
si_hash['startf_flags'] ||= 0
|
595
|
+
si_hash['startf_flags'] |= STARTF_USESTDHANDLES
|
596
|
+
hash['inherit'] = true
|
597
|
+
end
|
598
|
+
}
|
599
|
+
|
600
|
+
procinfo = PROCESS_INFORMATION.new
|
601
|
+
startinfo = STARTUPINFO.new
|
602
|
+
|
603
|
+
unless si_hash.empty?
|
604
|
+
startinfo[:cb] = startinfo.size
|
605
|
+
startinfo[:lpDesktop] = si_hash['desktop'] if si_hash['desktop']
|
606
|
+
startinfo[:lpTitle] = si_hash['title'] if si_hash['title']
|
607
|
+
startinfo[:dwX] = si_hash['x'] if si_hash['x']
|
608
|
+
startinfo[:dwY] = si_hash['y'] if si_hash['y']
|
609
|
+
startinfo[:dwXSize] = si_hash['x_size'] if si_hash['x_size']
|
610
|
+
startinfo[:dwYSize] = si_hash['y_size'] if si_hash['y_size']
|
611
|
+
startinfo[:dwXCountChars] = si_hash['x_count_chars'] if si_hash['x_count_chars']
|
612
|
+
startinfo[:dwYCountChars] = si_hash['y_count_chars'] if si_hash['y_count_chars']
|
613
|
+
startinfo[:dwFillAttribute] = si_hash['fill_attribute'] if si_hash['fill_attribute']
|
614
|
+
startinfo[:dwFlags] = si_hash['startf_flags'] if si_hash['startf_flags']
|
615
|
+
startinfo[:wShowWindow] = si_hash['sw_flags'] if si_hash['sw_flags']
|
616
|
+
startinfo[:cbReserved2] = 0
|
617
|
+
startinfo[:hStdInput] = si_hash['stdin'] if si_hash['stdin']
|
618
|
+
startinfo[:hStdOutput] = si_hash['stdout'] if si_hash['stdout']
|
619
|
+
startinfo[:hStdError] = si_hash['stderr'] if si_hash['stderr']
|
620
|
+
end
|
621
|
+
|
622
|
+
app = nil
|
623
|
+
cmd = nil
|
624
|
+
|
625
|
+
# Convert strings to wide character strings if present
|
626
|
+
if hash['app_name']
|
627
|
+
app = hash['app_name'].to_wide_string
|
628
|
+
end
|
629
|
+
|
630
|
+
if hash['command_line']
|
631
|
+
cmd = hash['command_line'].to_wide_string
|
632
|
+
end
|
633
|
+
|
634
|
+
if hash['cwd']
|
635
|
+
cwd = hash['cwd'].to_wide_string
|
636
|
+
end
|
637
|
+
|
638
|
+
if hash['with_logon']
|
639
|
+
logon = hash['with_logon'].to_wide_string
|
640
|
+
|
641
|
+
if hash['password']
|
642
|
+
passwd = hash['password'].to_wide_string
|
643
|
+
else
|
644
|
+
raise ArgumentError, 'password must be specified if with_logon is used'
|
645
|
+
end
|
646
|
+
|
647
|
+
if hash['domain']
|
648
|
+
domain = hash['domain'].to_wide_string
|
649
|
+
end
|
650
|
+
|
651
|
+
hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
|
652
|
+
|
653
|
+
bool = CreateProcessWithLogonW(
|
654
|
+
logon, # User
|
655
|
+
domain, # Domain
|
656
|
+
passwd, # Password
|
657
|
+
LOGON_WITH_PROFILE, # Logon flags
|
658
|
+
app, # App name
|
659
|
+
cmd, # Command line
|
660
|
+
hash['creation_flags'], # Creation flags
|
661
|
+
env, # Environment
|
662
|
+
cwd, # Working directory
|
663
|
+
startinfo, # Startup Info
|
664
|
+
procinfo # Process Info
|
665
|
+
)
|
666
|
+
|
667
|
+
unless bool
|
668
|
+
raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
|
669
|
+
end
|
670
|
+
else
|
671
|
+
inherit = hash['inherit'] ? 1 : 0
|
672
|
+
|
673
|
+
bool = CreateProcessW(
|
674
|
+
app, # App name
|
675
|
+
cmd, # Command line
|
676
|
+
process_security, # Process attributes
|
677
|
+
thread_security, # Thread attributes
|
678
|
+
inherit, # Inherit handles?
|
679
|
+
hash['creation_flags'], # Creation flags
|
680
|
+
env, # Environment
|
681
|
+
cwd, # Working directory
|
682
|
+
startinfo, # Startup Info
|
683
|
+
procinfo # Process Info
|
684
|
+
)
|
685
|
+
|
686
|
+
unless bool
|
687
|
+
raise SystemCallError.new("CreateProcess", FFI.errno)
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
# Automatically close the process and thread handles in the
|
692
|
+
# PROCESS_INFORMATION struct unless explicitly told not to.
|
693
|
+
if hash['close_handles']
|
694
|
+
CloseHandle(procinfo[:hProcess])
|
695
|
+
CloseHandle(procinfo[:hThread])
|
696
|
+
end
|
697
|
+
|
698
|
+
ProcessInfo.new(
|
699
|
+
procinfo[:hProcess],
|
700
|
+
procinfo[:hThread],
|
701
|
+
procinfo[:dwProcessId],
|
702
|
+
procinfo[:dwThreadId]
|
703
|
+
)
|
704
|
+
end
|
705
|
+
|
706
|
+
remove_method :kill
|
707
|
+
|
708
|
+
# Kill a given process with a specific signal. This overrides the default
|
709
|
+
# implementation of Process.kill. The differences mainly reside in the way
|
710
|
+
# it kills processes, but this version also gives you finer control over
|
711
|
+
# behavior.
|
712
|
+
#
|
713
|
+
# Internally, signals 2 and 3 will generate a console control event, using
|
714
|
+
# a ctrl-c or ctrl-break event, respectively. Signal 9 terminates the
|
715
|
+
# process harshly, given that process no chance to do any internal cleanup.
|
716
|
+
# Signals 1 and 4-8 kill the process more nicely, giving the process a
|
717
|
+
# chance to do internal cleanup before being killed. Signal 0 behaves the
|
718
|
+
# same as the default implementation.
|
719
|
+
#
|
720
|
+
# When using signals 1 or 4-8 you may specify additional options that
|
721
|
+
# allow finer control over how that process is killed and how your program
|
722
|
+
# behaves.
|
723
|
+
#
|
724
|
+
# Possible options for signals 1 and 4-8.
|
725
|
+
#
|
726
|
+
# :exit_proc => The name of the exit function called when signal 1 or 4-8
|
727
|
+
# is used. The default is 'ExitProcess'.
|
728
|
+
#
|
729
|
+
# :dll_module => The name of the .dll (or .exe) that contains :exit_proc.
|
730
|
+
# The default is 'kernel32'.
|
731
|
+
#
|
732
|
+
# :wait_time => The time, in milliseconds, to wait for the process to
|
733
|
+
# actually die. The default is 5ms. If you specify 0 here
|
734
|
+
# then the process does not wait if the process is not
|
735
|
+
# signaled and instead returns immediately. Alternatively,
|
736
|
+
# you may specify Process::INFINITE, and your code will
|
737
|
+
# block until the process is actually signaled.
|
738
|
+
#
|
739
|
+
# Example:
|
740
|
+
#
|
741
|
+
# Process.kill(1, 12345, :exit_proc => 'ExitProcess', :module => 'kernel32')
|
742
|
+
#
|
743
|
+
def kill(signal, *pids)
|
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
|
+
# Older versions of JRuby did not include KILL, so we've made an explicit exception
|
761
|
+
# for that here, too.
|
762
|
+
if signal.is_a?(String) || signal.is_a?(Symbol)
|
763
|
+
signal = signal.to_s.sub('SIG', '')
|
764
|
+
unless Signal.list.keys.include?(signal) || ['KILL', 'BRK'].include?(signal)
|
765
|
+
raise ArgumentError, "unsupported name '#{signal}'"
|
766
|
+
end
|
767
|
+
end
|
768
|
+
|
769
|
+
# If the last argument is a hash, pop it and assume it's a hash of options
|
770
|
+
if pids.last.is_a?(Hash)
|
771
|
+
hash = pids.pop
|
772
|
+
opts = {}
|
773
|
+
|
774
|
+
valid = %w[exit_proc dll_module wait_time]
|
775
|
+
|
776
|
+
hash.each{ |k,v|
|
777
|
+
k = k.to_s.downcase
|
778
|
+
unless valid.include?(k)
|
779
|
+
raise ArgumentError, "invalid option '#{k}'"
|
780
|
+
end
|
781
|
+
opts[k] = v
|
782
|
+
}
|
783
|
+
|
784
|
+
exit_proc = opts['exit_proc'] || 'ExitProcess'
|
785
|
+
dll_module = opts['dll_module'] || 'kernel32'
|
786
|
+
wait_time = opts['wait_time'] || 5
|
787
|
+
else
|
788
|
+
wait_time = 5
|
789
|
+
exit_proc = 'ExitProcess'
|
790
|
+
dll_module = 'kernel32'
|
791
|
+
end
|
792
|
+
|
793
|
+
count = 0
|
794
|
+
|
795
|
+
pids.each{ |pid|
|
796
|
+
raise TypeError unless pid.is_a?(Numeric) # Match spec, pid must be a number
|
797
|
+
raise SystemCallError.new(22) if pid < 0 # Match spec, EINVAL if pid less than zero
|
798
|
+
|
799
|
+
sigint = [Signal.list['INT'], 'INT', 'SIGINT', :INT, :SIGINT, 2]
|
800
|
+
|
801
|
+
# Match the spec
|
802
|
+
if pid == 0 && !sigint.include?(signal)
|
803
|
+
raise SystemCallError.new(22)
|
804
|
+
end
|
805
|
+
|
806
|
+
if signal == 0
|
807
|
+
access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
|
808
|
+
elsif signal == 9
|
809
|
+
access = PROCESS_TERMINATE
|
810
|
+
else
|
811
|
+
access = PROCESS_ALL_ACCESS
|
812
|
+
end
|
813
|
+
|
814
|
+
begin
|
815
|
+
handle = OpenProcess(access, 0, pid)
|
816
|
+
|
817
|
+
if signal != 0 && handle == 0
|
818
|
+
raise SystemCallError, FFI.errno, "OpenProcess"
|
819
|
+
end
|
820
|
+
|
821
|
+
case signal
|
822
|
+
when 0
|
823
|
+
if handle != 0
|
824
|
+
count += 1
|
825
|
+
else
|
826
|
+
if FFI.errno == ERROR_ACCESS_DENIED
|
827
|
+
count += 1
|
828
|
+
else
|
829
|
+
raise SystemCallError.new(3) # ESRCH
|
830
|
+
end
|
831
|
+
end
|
832
|
+
when Signal.list['INT'], 'INT', 'SIGINT', :INT, :SIGINT, 2
|
833
|
+
if GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)
|
834
|
+
count += 1
|
835
|
+
else
|
836
|
+
raise SystemCallError.new("GenerateConsoleCtrlEvent", FFI.errno)
|
837
|
+
end
|
838
|
+
when Signal.list['BRK'], 'BRK', 'SIGBRK', :BRK, :SIGBRK, 3
|
839
|
+
if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)
|
840
|
+
count += 1
|
841
|
+
else
|
842
|
+
raise SystemCallError.new("GenerateConsoleCtrlEvent", FFI.errno)
|
843
|
+
end
|
844
|
+
when Signal.list['KILL'], 'KILL', 'SIGKILL', :KILL, :SIGKILL, 9
|
845
|
+
if TerminateProcess(handle, pid)
|
846
|
+
count += 1
|
847
|
+
else
|
848
|
+
raise SystemCallError.new("TerminateProcess", FFI.errno)
|
849
|
+
end
|
850
|
+
else
|
851
|
+
thread_id = FFI::MemoryPointer.new(:ulong)
|
852
|
+
|
853
|
+
mod = GetModuleHandle(dll_module)
|
854
|
+
|
855
|
+
if mod == 0
|
856
|
+
raise SystemCallError.new("GetModuleHandle: '#{dll_module}'", FFI.errno)
|
857
|
+
end
|
858
|
+
|
859
|
+
proc_addr = GetProcAddress(mod, exit_proc)
|
860
|
+
|
861
|
+
if proc_addr == 0
|
862
|
+
raise SystemCallError.new("GetProcAddress: '#{exit_proc}'", FFI.errno)
|
863
|
+
end
|
864
|
+
|
865
|
+
thread = CreateRemoteThread(handle, nil, 0, proc_addr, nil, 0, thread_id)
|
866
|
+
|
867
|
+
if thread > 0
|
868
|
+
WaitForSingleObject(thread, wait_time)
|
869
|
+
count += 1
|
870
|
+
else
|
871
|
+
raise SystemCallError.new("CreateRemoteThread", FFI.errno)
|
872
|
+
end
|
873
|
+
end
|
874
|
+
ensure
|
875
|
+
CloseHandle(handle) if handle
|
876
|
+
end
|
877
|
+
}
|
878
|
+
|
879
|
+
count
|
880
|
+
end
|
881
|
+
|
882
|
+
# Returns the exitcode of the process with given +pid+ or nil if the process
|
883
|
+
# is still running. Note that the process doesn't have to be a child process.
|
884
|
+
#
|
885
|
+
# This method is very handy for finding out if a process started with Process.create
|
886
|
+
# is still running. The usual way of calling Process.wait doesn't work when
|
887
|
+
# the process isn't recognized as a child process (ECHILD). This happens for example
|
888
|
+
# when stdin, stdout or stderr are set to custom values.
|
889
|
+
#
|
890
|
+
def get_exitcode(pid)
|
891
|
+
handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid)
|
892
|
+
|
893
|
+
if handle == INVALID_HANDLE_VALUE
|
894
|
+
raise SystemCallError.new("OpenProcess", FFI.errno)
|
895
|
+
end
|
896
|
+
|
897
|
+
begin
|
898
|
+
buf = FFI::MemoryPointer.new(:ulong, 1)
|
899
|
+
|
900
|
+
unless GetExitCodeProcess(handle, buf)
|
901
|
+
raise SystemCallError.new("GetExitCodeProcess", FFI.errno)
|
902
|
+
end
|
903
|
+
ensure
|
904
|
+
CloseHandle(handle)
|
905
|
+
end
|
906
|
+
|
907
|
+
exitcode = buf.read_int
|
908
|
+
|
909
|
+
if exitcode == STILL_ACTIVE
|
910
|
+
nil
|
911
|
+
else
|
912
|
+
exitcode
|
913
|
+
end
|
914
|
+
end
|
915
|
+
|
916
|
+
# Returns a list of process information structs in the form of a hash,
|
917
|
+
# with the pid as the key, and an array of information as the value of
|
918
|
+
# that key. The type of information in that array depends on the
|
919
|
+
# +info_type+ parameter. The possible values for +info_type+, and the
|
920
|
+
# type of information they each return is as follows:
|
921
|
+
#
|
922
|
+
# :thread => ThreadSnapInfo[:thread_id, :process_id, :base_priority]
|
923
|
+
# :heap => HeapSnapInfo[:address, :block_size, :flags, :process_id, :heap_id]
|
924
|
+
# :module => ModuleSnapInfo[:process_id, :address, :module_size, :handle, :name, :path]
|
925
|
+
# :process => ProcessSnapInfo[:process_id, :threads, :parent_process_id, :priority, :flags, :path]
|
926
|
+
#
|
927
|
+
# If no argument is provided, then :thread is assumed. Note that it is up
|
928
|
+
# to you to filter by pid if you wish.
|
929
|
+
#
|
930
|
+
# Example:
|
931
|
+
#
|
932
|
+
# # Get all thread info
|
933
|
+
# Process.snapshot.each{ |pid, v|
|
934
|
+
# puts "PID: #{pid}"
|
935
|
+
# p v
|
936
|
+
# }
|
937
|
+
#
|
938
|
+
# # Get module info for just the current process
|
939
|
+
# p Process.snapshot(:module)[Process.pid]
|
940
|
+
#
|
941
|
+
# # Get heap info for just the current process
|
942
|
+
# p Process.snapshot(:heap)[Process.pid]
|
943
|
+
#
|
944
|
+
# # Show pids of all running processes
|
945
|
+
# p Process.snapshot(:process).keys
|
946
|
+
#
|
947
|
+
def snapshot(info_type = 'thread')
|
948
|
+
case info_type.to_s.downcase
|
949
|
+
when 'thread'
|
950
|
+
flag = TH32CS_SNAPTHREAD
|
951
|
+
when 'heap'
|
952
|
+
flag = TH32CS_SNAPHEAPLIST
|
953
|
+
when 'module'
|
954
|
+
flag = TH32CS_SNAPMODULE
|
955
|
+
when 'process'
|
956
|
+
flag = TH32CS_SNAPPROCESS
|
957
|
+
else
|
958
|
+
raise ArgumentError, "info_type '#{info_type}' unsupported"
|
959
|
+
end
|
960
|
+
|
961
|
+
begin
|
962
|
+
handle = CreateToolhelp32Snapshot(flag, Process.pid)
|
963
|
+
|
964
|
+
if handle == INVALID_HANDLE_VALUE
|
965
|
+
raise SystemCallError.new('CreateToolhelp32Snapshot', FFI.errno)
|
966
|
+
end
|
967
|
+
|
968
|
+
case info_type.to_s.downcase
|
969
|
+
when 'thread'
|
970
|
+
array = get_thread_info(handle)
|
971
|
+
when 'heap'
|
972
|
+
array = get_heap_info(handle)
|
973
|
+
when 'module'
|
974
|
+
array = get_module_info(handle)
|
975
|
+
when 'process'
|
976
|
+
array = get_process_info(handle)
|
977
|
+
end
|
978
|
+
|
979
|
+
array
|
980
|
+
ensure
|
981
|
+
CloseHandle(handle) if handle
|
982
|
+
end
|
983
|
+
end
|
984
|
+
end
|
985
|
+
|
986
|
+
class << self
|
987
|
+
private
|
988
|
+
|
989
|
+
# Private method that returns the volume type, e.g. "NTFS", etc.
|
990
|
+
def volume_type
|
991
|
+
buf = FFI::MemoryPointer.new(:char, 32)
|
992
|
+
bool = GetVolumeInformationA(nil, nil, 0, nil, nil, nil, buf, buf.size)
|
993
|
+
bool ? buf.read_string : nil
|
994
|
+
end
|
995
|
+
|
996
|
+
# Return thread info for Process.snapshot
|
997
|
+
def get_thread_info(handle, pid = nil)
|
998
|
+
lpte = THREADENTRY32.new
|
999
|
+
lpte[:dwSize] = lpte.size
|
1000
|
+
|
1001
|
+
hash = Hash.new{ |h,k| h[k] = [] }
|
1002
|
+
|
1003
|
+
if Thread32First(handle, lpte)
|
1004
|
+
hash[lpte[:th32OwnerProcessID]] << ThreadSnapInfo.new(lpte[:th32ThreadID], lpte[:th32OwnerProcessID], lpte[:tpBasePri])
|
1005
|
+
else
|
1006
|
+
if FFI.errno == ERROR_NO_MORE_FILES
|
1007
|
+
return hash
|
1008
|
+
else
|
1009
|
+
raise SystemCallError.new('Thread32First', FFI.errno)
|
1010
|
+
end
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
while Thread32Next(handle, lpte)
|
1014
|
+
hash[lpte[:th32OwnerProcessID]] << ThreadSnapInfo.new(lpte[:th32ThreadID], lpte[:th32OwnerProcessID], lpte[:tpBasePri])
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
hash
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
# Return heap info for Process.snapshot
|
1021
|
+
def get_heap_info(handle)
|
1022
|
+
hash = Hash.new{ |h,k| h[k] = [] }
|
1023
|
+
|
1024
|
+
hl = HEAPLIST32.new
|
1025
|
+
hl[:dwSize] = hl.size
|
1026
|
+
|
1027
|
+
if Heap32ListFirst(handle, hl)
|
1028
|
+
while Heap32ListNext(handle, hl)
|
1029
|
+
he = HEAPENTRY32.new
|
1030
|
+
he[:dwSize] = he.size
|
1031
|
+
|
1032
|
+
if Heap32First(he, Process.pid, hl[:th32HeapID])
|
1033
|
+
hash[he[:th32ProcessID]] << HeapSnapInfo.new(he[:dwAddress], he[:dwBlockSize], he[:dwFlags], he[:th32ProcessID], he[:th32HeapID])
|
1034
|
+
else
|
1035
|
+
if FFI.errno == ERROR_NO_MORE_FILES
|
1036
|
+
break
|
1037
|
+
else
|
1038
|
+
raise SystemCallError.new('Heap32First', FFI.errno)
|
1039
|
+
end
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
while Heap32Next(he)
|
1043
|
+
hash[he[:th32ProcessID]] << HeapSnapInfo.new(he[:dwAddress], he[:dwBlockSize], he[:dwFlags], he[:th32ProcessID], he[:th32HeapID])
|
1044
|
+
end
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
hash
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
# Return module info for Process.snapshot
|
1052
|
+
def get_module_info(handle)
|
1053
|
+
hash = Hash.new{ |h,k| h[k] = [] }
|
1054
|
+
|
1055
|
+
me = MODULEENTRY32.new
|
1056
|
+
me[:dwSize] = me.size
|
1057
|
+
|
1058
|
+
if Module32First(handle, me)
|
1059
|
+
hash[me[:th32ProcessID]] << ModuleSnapInfo.new(
|
1060
|
+
me[:th32ProcessID],
|
1061
|
+
me[:modBaseAddr].to_i,
|
1062
|
+
me[:modBaseSize],
|
1063
|
+
me[:hModule],
|
1064
|
+
me[:szModule].to_s,
|
1065
|
+
me[:szExePath].to_s
|
1066
|
+
)
|
1067
|
+
else
|
1068
|
+
if FFI.errno == ERROR_NO_MORE_FILES
|
1069
|
+
return hash
|
1070
|
+
else
|
1071
|
+
raise SystemCallError.new('Module32First', FFI.errno)
|
1072
|
+
end
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
while Module32Next(handle, me)
|
1076
|
+
hash[me[:th32ProcessID]] << ModuleSnapInfo.new(
|
1077
|
+
me[:th32ProcessID],
|
1078
|
+
me[:modBaseAddr].to_i,
|
1079
|
+
me[:modBaseSize],
|
1080
|
+
me[:hModule],
|
1081
|
+
me[:szModule].to_s,
|
1082
|
+
me[:szExePath].to_s
|
1083
|
+
)
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
hash
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
# Return process info for Process.snapshot
|
1090
|
+
def get_process_info(handle)
|
1091
|
+
hash = Hash.new{ |h,k| h[k] = [] }
|
1092
|
+
|
1093
|
+
pe = PROCESSENTRY32.new
|
1094
|
+
pe[:dwSize] = pe.size
|
1095
|
+
|
1096
|
+
if Process32First(handle, pe)
|
1097
|
+
hash[pe[:th32ProcessID]] = ProcessSnapInfo.new(
|
1098
|
+
pe[:th32ProcessID],
|
1099
|
+
pe[:cntThreads],
|
1100
|
+
pe[:th32ParentProcessID],
|
1101
|
+
pe[:pcPriClassBase],
|
1102
|
+
pe[:dwFlags],
|
1103
|
+
pe[:szExeFile].to_s
|
1104
|
+
)
|
1105
|
+
else
|
1106
|
+
if FFI.errno == ERROR_NO_MORE_FILES
|
1107
|
+
return hash
|
1108
|
+
else
|
1109
|
+
raise SystemCallError.new('Process32First', FFI.errno)
|
1110
|
+
end
|
1111
|
+
end
|
1112
|
+
|
1113
|
+
while Process32Next(handle, pe)
|
1114
|
+
hash[pe[:th32ProcessID]] = ProcessSnapInfo.new(
|
1115
|
+
pe[:th32ProcessID],
|
1116
|
+
pe[:cntThreads],
|
1117
|
+
pe[:th32ParentProcessID],
|
1118
|
+
pe[:pcPriClassBase],
|
1119
|
+
pe[:dwFlags],
|
1120
|
+
pe[:szExeFile].to_s
|
1121
|
+
)
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
hash
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
# Private method that returns the Windows major version number.
|
1128
|
+
def windows_version
|
1129
|
+
ver = OSVERSIONINFO.new
|
1130
|
+
ver[:dwOSVersionInfoSize] = ver.size
|
1131
|
+
|
1132
|
+
unless GetVersionExA(ver)
|
1133
|
+
raise SystemCallError.new("GetVersionEx", FFI.errno)
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
ver[:dwMajorVersion]
|
1137
|
+
end
|
1138
|
+
end
|
1139
|
+
end
|