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