win32-process 0.6.1 → 0.6.2
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 -0
- data/README +7 -5
- data/Rakefile +23 -10
- data/lib/win32/process.rb +905 -736
- data/test/test_win32_process.rb +9 -2
- data/win32-process.gemspec +22 -23
- metadata +24 -4
data/CHANGES
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
= 0.6.2 - 19-Dec-2009
|
2
|
+
* Fixed an issue where stdin, stdout and stderr might not be inheritable
|
3
|
+
even if the inherit option was set. Thanks go to Michael Buselli for the
|
4
|
+
spot and the patch.
|
5
|
+
* Added a basic implementation of Process.getrlimit.
|
6
|
+
* Added the Process.get_affinity method.
|
7
|
+
* Added test-unit 2.x and sys-proctable as development dependencies.
|
8
|
+
* Added the :uninstall and :build_gem Rake tasks to the Rakefile.
|
9
|
+
* Bumped required version of windows-pr to 1.0.6.
|
10
|
+
|
1
11
|
= 0.6.1 - 16-Jul-2009
|
2
12
|
* Added the Process.uid method. This method returns a user id (really, the RID
|
3
13
|
of the SID) by default, but can also take an optional parameter to return
|
data/README
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
= Description
|
2
2
|
This package provides the fork, wait, wait2, waitpid, and waitpid2 methods
|
3
3
|
for MS Windows. In addition, it provides a different implementation of the
|
4
|
-
kill method,
|
4
|
+
kill method, a proper implementation of Process.ppid, and decent analogues
|
5
|
+
of Process.getpriority, Process.setpriority and Process.getrlimit.
|
5
6
|
|
6
7
|
= Prerequisites
|
7
8
|
Ruby 1.8.2 or later.
|
8
|
-
The
|
9
|
-
The
|
9
|
+
The windows-pr library, 0.8.6 or later.
|
10
|
+
The sys-proctable library, 0.7.6 or later (test suite only).
|
11
|
+
The test-unit library, 2.0.3 or later (test suite only).
|
10
12
|
|
11
13
|
= Supported Platforms
|
12
14
|
This library is supported on Windows 2000 or later.
|
@@ -100,10 +102,10 @@
|
|
100
102
|
Other suggestions welcome.
|
101
103
|
|
102
104
|
== License
|
103
|
-
|
105
|
+
Artistic 2.0
|
104
106
|
|
105
107
|
== Copyright
|
106
|
-
(C) 2003-
|
108
|
+
(C) 2003-2009 Daniel J. Berger
|
107
109
|
All Rights Reserved
|
108
110
|
|
109
111
|
== Warranty
|
data/Rakefile
CHANGED
@@ -3,20 +3,33 @@ require 'rake/testtask'
|
|
3
3
|
require 'rbconfig'
|
4
4
|
include Config
|
5
5
|
|
6
|
-
desc 'Install the win32-process
|
6
|
+
desc 'Install the win32-process library (non-gem)'
|
7
7
|
task :install do
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
install_dir = File.join(CONFIG['sitelibdir'], 'win32')
|
9
|
+
Dir.mkdir(install_dir) unless File.exists?(install_dir)
|
10
|
+
cp 'lib/win32/process.rb', install_dir, :verbose => true
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
desc 'Removes the win32-process library (non-gem)'
|
14
|
+
task :uninstall do
|
15
|
+
file = File.join(CONFIG['sitelibdir'], 'win32', 'process.rb')
|
16
|
+
if File.exists?(file)
|
17
|
+
rm_f file, :verbose => true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Builds a gem for the win32-process library'
|
22
|
+
task :gem do
|
23
|
+
spec = eval(IO.read('win32-process.gemspec'))
|
24
|
+
Gem::Builder.new(spec).build
|
25
|
+
end
|
26
|
+
|
27
|
+
task :install_gem => [:gem] do
|
28
|
+
file = Dir["*.gem"].first
|
29
|
+
sh "gem install #{file}"
|
17
30
|
end
|
18
31
|
|
19
32
|
Rake::TestTask.new do |t|
|
20
|
-
|
21
|
-
|
33
|
+
t.verbose = true
|
34
|
+
t.warning = true
|
22
35
|
end
|
data/lib/win32/process.rb
CHANGED
@@ -12,824 +12,993 @@ require 'windows/security'
|
|
12
12
|
require 'windows/msvcrt/string'
|
13
13
|
|
14
14
|
module Process
|
15
|
-
|
15
|
+
# The Process::Error class is typically raised if any of the custom
|
16
|
+
# Process methods fail.
|
17
|
+
class Error < RuntimeError; end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
19
|
+
# Eliminates redefinition warnings.
|
20
|
+
undef_method :getpriority, :kill, :getrlimit, :ppid, :setpriority
|
21
|
+
undef_method :wait, :wait2, :waitpid, :waitpid2, :uid
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
# The version of the win32-process library
|
24
|
+
WIN32_PROCESS_VERSION = '0.6.2'
|
25
|
+
|
26
|
+
include Windows::Process
|
27
|
+
include Windows::Thread
|
28
|
+
include Windows::Error
|
29
|
+
include Windows::Library
|
30
|
+
include Windows::Console
|
31
|
+
include Windows::Handle
|
32
|
+
include Windows::Security
|
33
|
+
include Windows::Synchronize
|
34
|
+
include Windows::Window
|
35
|
+
include Windows::Unicode
|
36
|
+
include Windows::ToolHelper
|
37
|
+
include Windows::MSVCRT::String
|
38
|
+
|
39
|
+
extend Windows::Error
|
40
|
+
extend Windows::Process
|
41
|
+
extend Windows::Thread
|
42
|
+
extend Windows::Security
|
43
|
+
extend Windows::Synchronize
|
44
|
+
extend Windows::Handle
|
45
|
+
extend Windows::Library
|
46
|
+
extend Windows::Console
|
47
|
+
extend Windows::Unicode
|
48
|
+
extend Windows::ToolHelper
|
49
|
+
extend Windows::MSVCRT::String
|
23
50
|
|
24
|
-
|
25
|
-
include Windows::Thread
|
26
|
-
include Windows::Error
|
27
|
-
include Windows::Library
|
28
|
-
include Windows::Console
|
29
|
-
include Windows::Handle
|
30
|
-
include Windows::Security
|
31
|
-
include Windows::Synchronize
|
32
|
-
include Windows::Window
|
33
|
-
include Windows::Unicode
|
34
|
-
include Windows::ToolHelper
|
35
|
-
include Windows::MSVCRT::String
|
36
|
-
|
37
|
-
extend Windows::Error
|
38
|
-
extend Windows::Process
|
39
|
-
extend Windows::Thread
|
40
|
-
extend Windows::Security
|
41
|
-
extend Windows::Synchronize
|
42
|
-
extend Windows::Handle
|
43
|
-
extend Windows::Library
|
44
|
-
extend Windows::Console
|
45
|
-
extend Windows::Unicode
|
46
|
-
extend Windows::ToolHelper
|
47
|
-
extend Windows::MSVCRT::String
|
51
|
+
# :stopdoc:
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
# Used by Process.create
|
54
|
+
ProcessInfo = Struct.new("ProcessInfo",
|
55
|
+
:process_handle,
|
56
|
+
:thread_handle,
|
57
|
+
:process_id,
|
58
|
+
:thread_id
|
59
|
+
)
|
56
60
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
61
|
+
@child_pids = [] # Static variable used for Process.fork
|
62
|
+
@i = -1 # Static variable used for Process.fork
|
63
|
+
|
64
|
+
# These are probably not defined on MS Windows by default
|
65
|
+
unless defined? RLIMIT_CPU
|
66
|
+
RLIMIT_CPU = 0 # PerProcessUserTimeLimit
|
67
|
+
RLIMIT_FSIZE = 1 # Hard coded at 4TB - 64K (assumes NTFS)
|
68
|
+
RLIMIT_AS = 5 # ProcessMemoryLimit
|
69
|
+
RLIMIT_RSS = 5 # ProcessMemoryLimit
|
70
|
+
RLIMIT_VMEM = 5 # ProcessMemoryLimit
|
71
|
+
end
|
72
|
+
|
73
|
+
# :startdoc:
|
74
|
+
|
75
|
+
# Returns the process and system affinity mask for the given +pid+, or the
|
76
|
+
# current process if no pid is provided. The return value is a two element
|
77
|
+
# array, with the first containing the process affinity mask, and the second
|
78
|
+
# containing the system affinity mask. Both are decimal values.
|
79
|
+
#
|
80
|
+
# A process affinity mask is a bit vector indicating the processors that a
|
81
|
+
# process is allowed to run on. A system affinity mask is a bit vector in
|
82
|
+
# which each bit represents the processors that are configured into a
|
83
|
+
# system.
|
84
|
+
#
|
85
|
+
# Example:
|
86
|
+
#
|
87
|
+
# # System has 4 processors, current process is allowed to run on all
|
88
|
+
# Process.get_affinity # => [[15], [15]]
|
89
|
+
#
|
90
|
+
# # System has 4 processors, current process only allowed on 1 and 4 only
|
91
|
+
# Process.get_affinity # => [[9], [15]]
|
92
|
+
#
|
93
|
+
# If you want to convert a decimal bit vector into an array of 0's and 1's
|
94
|
+
# indicating the flag value of each processor, you can use something like
|
95
|
+
# this approach:
|
96
|
+
#
|
97
|
+
# mask = Process.get_affinity.first
|
98
|
+
# (0..mask).to_a.map{ |n| mask[n] }
|
99
|
+
#
|
100
|
+
def get_affinity(int = Process.pid)
|
101
|
+
pmask = 0.chr * 4
|
102
|
+
smask = 0.chr * 4
|
103
|
+
|
104
|
+
if int == Process.pid
|
105
|
+
unless GetProcessAffinityMask(GetCurrentProcess(), pmask, smask)
|
106
|
+
raise Error, get_last_error
|
84
107
|
end
|
108
|
+
else
|
109
|
+
begin
|
110
|
+
handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
|
85
111
|
|
86
|
-
|
112
|
+
if handle == INVALID_HANDLE_VALUE
|
113
|
+
raise Error, get_last_error
|
114
|
+
end
|
87
115
|
|
88
|
-
|
89
|
-
|
116
|
+
unless GetProcessAffinityMask(handle, pmask, smask)
|
117
|
+
raise Error, get_last_error
|
118
|
+
end
|
119
|
+
ensure
|
120
|
+
CloseHandle(handle) if handle != INVALID_HANDLE_VALUE
|
90
121
|
end
|
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
|
-
|
122
|
+
end
|
123
|
+
|
124
|
+
pmask = pmask.unpack('L').first
|
125
|
+
smask = smask.unpack('L').first
|
126
|
+
|
127
|
+
[pmask, smask]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Gets the resource limit of the current process. Only a limited number
|
131
|
+
# of flags are supported.
|
132
|
+
#
|
133
|
+
# Process::RLIMIT_CPU
|
134
|
+
# Process::RLIMIT_FSIZE
|
135
|
+
# Process::RLIMIT_AS
|
136
|
+
# Process::RLIMIT_RSS
|
137
|
+
# Process::RLIMIT_VMEM
|
138
|
+
#
|
139
|
+
# The Process:RLIMIT_AS, Process::RLIMIT_RSS and Process::VMEM constants
|
140
|
+
# all refer to the Process memory limit. The Process::RLIMIT_CPU constant
|
141
|
+
# refers to the per process user time limit. The Process::RLIMIT_FSIZE
|
142
|
+
# constant is hard coded to the maximum file size on an NTFS filesystem,
|
143
|
+
# approximately 4TB.
|
144
|
+
#
|
145
|
+
# While a two element array is returned in order to comply with the spec,
|
146
|
+
# there is no separate hard and soft limit. The values will always be the
|
147
|
+
# same.
|
148
|
+
#
|
149
|
+
# If [0,0] is returned then it means no limit has been set.
|
150
|
+
#
|
151
|
+
def getrlimit(resource)
|
152
|
+
# Strictly for API compatibility (actually 4 GB on FAT32)
|
153
|
+
# TODO: Check FS type. If FAT32, return 4 GB.
|
154
|
+
if resource == RLIMIT_FSIZE
|
155
|
+
return ((1024**4) * 4) - (1024 * 64)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Put the current Ruby process in its own job unless it's already
|
159
|
+
# part of a job.
|
160
|
+
bool = 0.chr * 4
|
161
|
+
IsProcessInJob(GetCurrentProcess(), nil, bool)
|
162
|
+
bool = bool.unpack('L').first == 0 ? false : true
|
163
|
+
|
164
|
+
unless bool
|
165
|
+
job_name = 'ruby_' + Time.now.to_s
|
166
|
+
|
167
|
+
# Create a job object and add the current process to it
|
168
|
+
handle = CreateJobObject(nil, job_name)
|
169
|
+
|
170
|
+
if handle == 0
|
171
|
+
raise Error, get_last_error
|
118
172
|
end
|
173
|
+
end
|
119
174
|
|
120
|
-
|
121
|
-
|
175
|
+
begin
|
176
|
+
unless bool
|
177
|
+
unless AssignProcessToJobObject(handle, GetCurrentProcess())
|
178
|
+
raise Error, get_last_error
|
179
|
+
end
|
122
180
|
end
|
123
181
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
# Returns the uid of the current process. Specifically, it returns the
|
128
|
-
# RID of the SID associated with the owner of the process.
|
129
|
-
#
|
130
|
-
# If +sid+ is set to true, then a binary sid is returned. Otherwise, a
|
131
|
-
# numeric id is returned (the default).
|
132
|
-
#--
|
133
|
-
# The Process.uid method in core Ruby always returns 0 on MS Windows.
|
134
|
-
#
|
135
|
-
def uid(sid = false)
|
136
|
-
token = 0.chr * 4
|
137
|
-
|
138
|
-
unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
|
139
|
-
raise Error, get_last_error
|
140
|
-
end
|
182
|
+
buf = 0.chr * 112 # sizeof(struct JOBJECT_EXTENDED_LIMIT_INFORMATION)
|
183
|
+
val = nil # value returned at end of method
|
141
184
|
|
142
|
-
|
143
|
-
|
144
|
-
|
185
|
+
case resource
|
186
|
+
when RLIMIT_CPU
|
187
|
+
buf[16,4] = [JOB_OBJECT_LIMIT_PROCESS_TIME].pack('L')
|
188
|
+
when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
|
189
|
+
buf[16,4] = [JOB_OBJECT_LIMIT_PROCESS_MEMORY].pack('L')
|
190
|
+
end
|
145
191
|
|
146
|
-
bool =
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
192
|
+
bool = QueryInformationJobObject(
|
193
|
+
handle,
|
194
|
+
JobObjectExtendedLimitInformation,
|
195
|
+
buf,
|
196
|
+
buf.size,
|
197
|
+
nil
|
152
198
|
)
|
153
199
|
|
154
200
|
unless bool
|
155
|
-
|
201
|
+
raise Error, get_last_error
|
156
202
|
end
|
157
203
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
sid_addr = [lsid].pack('p*').unpack('L')[0]
|
164
|
-
sid_buf = 0.chr * 80
|
165
|
-
sid_ptr = 0.chr * 4
|
166
|
-
|
167
|
-
unless ConvertSidToStringSid(sid_addr, sid_ptr)
|
168
|
-
raise Error, get_last_error
|
169
|
-
end
|
170
|
-
|
171
|
-
strcpy(sid_buf, sid_ptr.unpack('L')[0])
|
172
|
-
sid_buf.strip.split('-').last.to_i
|
204
|
+
case resource
|
205
|
+
when Process::RLIMIT_CPU
|
206
|
+
val = buf[0,8].unpack('Q').first
|
207
|
+
when RLIMIT_AS, RLIMIT_VMEM, RLIMIT_RSS
|
208
|
+
val = buf[96,4].unpack('L').first
|
173
209
|
end
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
210
|
+
ensure
|
211
|
+
CloseHandle(handle)
|
212
|
+
end
|
213
|
+
|
214
|
+
[val, val] # Return an array of two values to comply with spec
|
215
|
+
end
|
216
|
+
|
217
|
+
# Retrieves the priority class for the specified process id +int+. Unlike
|
218
|
+
# the default implementation, lower values do not necessarily correspond to
|
219
|
+
# higher priority classes.
|
220
|
+
#
|
221
|
+
# The +kind+ parameter is ignored but present for API compatibility.
|
222
|
+
# You can only retrieve process information, not process group or user
|
223
|
+
# information, so it is effectively always Process::PRIO_PROCESS.
|
224
|
+
#
|
225
|
+
# Possible return values are:
|
226
|
+
#
|
227
|
+
# 32 - Process::NORMAL_PRIORITY_CLASS
|
228
|
+
# 64 - Process::IDLE_PRIORITY_CLASS
|
229
|
+
# 128 - Process::HIGH_PRIORITY_CLASS
|
230
|
+
# 256 - Process::REALTIME_PRIORITY_CLASS
|
231
|
+
# 16384 - Process::BELOW_NORMAL_PRIORITY_CLASS
|
232
|
+
# 32768 - Process::ABOVE_NORMAL_PRIORITY_CLASS
|
233
|
+
#
|
234
|
+
def getpriority(kind = Process::PRIO_PROCESS, int = nil)
|
235
|
+
raise ArgumentError unless int
|
236
|
+
|
237
|
+
handle = OpenProcess(PROCESS_QUERY_INFORMATION, 0 , int)
|
238
|
+
|
239
|
+
if handle == INVALID_HANDLE_VALUE
|
240
|
+
raise Error, get_last_error
|
241
|
+
end
|
242
|
+
|
243
|
+
priority_class = GetPriorityClass(handle)
|
244
|
+
|
245
|
+
if priority_class == 0
|
246
|
+
raise Error, get_last_error
|
247
|
+
end
|
248
|
+
|
249
|
+
priority_class
|
250
|
+
end
|
251
|
+
|
252
|
+
# Sets the priority class for the specified process id +int+.
|
253
|
+
#
|
254
|
+
# The +kind+ parameter is ignored but present for API compatibility.
|
255
|
+
# You can only retrieve process information, not process group or user
|
256
|
+
# information, so it is effectively always Process::PRIO_PROCESS.
|
257
|
+
#
|
258
|
+
# Possible +int_priority+ values are:
|
259
|
+
#
|
260
|
+
# * Process::NORMAL_PRIORITY_CLASS
|
261
|
+
# * Process::IDLE_PRIORITY_CLASS
|
262
|
+
# * Process::HIGH_PRIORITY_CLASS
|
263
|
+
# * Process::REALTIME_PRIORITY_CLASS
|
264
|
+
# * Process::BELOW_NORMAL_PRIORITY_CLASS
|
265
|
+
# * Process::ABOVE_NORMAL_PRIORITY_CLASS
|
266
|
+
#
|
267
|
+
def setpriority(kind = nil, int = nil, int_priority = nil)
|
268
|
+
raise ArgumentError unless int
|
269
|
+
raise ArgumentError unless int_priority
|
270
|
+
|
271
|
+
handle = OpenProcess(PROCESS_SET_INFORMATION, 0 , int)
|
272
|
+
|
273
|
+
if handle == INVALID_HANDLE_VALUE
|
274
|
+
raise Error, get_last_error
|
275
|
+
end
|
276
|
+
|
277
|
+
unless SetPriorityClass(handle, int_priority)
|
278
|
+
raise Error, get_last_error
|
279
|
+
end
|
280
|
+
|
281
|
+
return 0 # Match the spec
|
282
|
+
end
|
283
|
+
|
284
|
+
# Returns the uid of the current process. Specifically, it returns the
|
285
|
+
# RID of the SID associated with the owner of the process.
|
286
|
+
#
|
287
|
+
# If +sid+ is set to true, then a binary sid is returned. Otherwise, a
|
288
|
+
# numeric id is returned (the default).
|
289
|
+
#--
|
290
|
+
# The Process.uid method in core Ruby always returns 0 on MS Windows.
|
291
|
+
#
|
292
|
+
def uid(sid = false)
|
293
|
+
token = 0.chr * 4
|
294
|
+
|
295
|
+
unless OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, token)
|
296
|
+
raise Error, get_last_error
|
297
|
+
end
|
298
|
+
|
299
|
+
token = token.unpack('V')[0]
|
300
|
+
rlength = 0.chr * 4
|
301
|
+
tuser = 0.chr * 512
|
302
|
+
|
303
|
+
bool = GetTokenInformation(
|
304
|
+
token,
|
305
|
+
TokenUser,
|
306
|
+
tuser,
|
307
|
+
tuser.size,
|
308
|
+
rlength
|
309
|
+
)
|
310
|
+
|
311
|
+
unless bool
|
312
|
+
raise Error, get_last_error
|
313
|
+
end
|
314
|
+
|
315
|
+
lsid = tuser[8, (rlength.unpack('L').first - 8)]
|
316
|
+
|
317
|
+
if sid
|
318
|
+
lsid
|
319
|
+
else
|
320
|
+
sid_addr = [lsid].pack('p*').unpack('L')[0]
|
321
|
+
sid_buf = 0.chr * 80
|
322
|
+
sid_ptr = 0.chr * 4
|
323
|
+
|
324
|
+
unless ConvertSidToStringSid(sid_addr, sid_ptr)
|
325
|
+
raise Error, get_last_error
|
187
326
|
end
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
327
|
+
|
328
|
+
strcpy(sid_buf, sid_ptr.unpack('L')[0])
|
329
|
+
sid_buf.strip.split('-').last.to_i
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Waits for the given child process to exit and returns that pid.
|
334
|
+
#
|
335
|
+
# Note that the $? (Process::Status) global variable is NOT set. This
|
336
|
+
# may be addressed in a future release.
|
337
|
+
#
|
338
|
+
def waitpid(pid)
|
339
|
+
exit_code = [0].pack('L')
|
340
|
+
handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
|
341
|
+
|
342
|
+
if handle == INVALID_HANDLE_VALUE
|
343
|
+
raise Error, get_last_error
|
344
|
+
end
|
345
|
+
|
346
|
+
# TODO: update the $? global variable (if/when possible)
|
347
|
+
status = WaitForSingleObject(handle, INFINITE)
|
348
|
+
|
349
|
+
begin
|
350
|
+
unless GetExitCodeProcess(handle, exit_code)
|
351
|
+
raise Error, get_last_error
|
198
352
|
end
|
353
|
+
ensure
|
354
|
+
CloseHandle(handle)
|
355
|
+
end
|
199
356
|
|
200
|
-
|
357
|
+
@child_pids.delete(pid)
|
201
358
|
|
202
|
-
|
203
|
-
|
359
|
+
# TODO: update the $? global variable (if/when possible)
|
360
|
+
exit_code = exit_code.unpack('L').first
|
204
361
|
|
205
|
-
|
206
|
-
|
362
|
+
pid
|
363
|
+
end
|
207
364
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
end
|
232
|
-
ensure
|
233
|
-
CloseHandle(handle)
|
365
|
+
# Waits for the given child process to exit and returns an array containing
|
366
|
+
# the process id and the exit status.
|
367
|
+
#
|
368
|
+
# Note that the $? (Process::Status) global variable is NOT set. This
|
369
|
+
# may be addressed in a future release if/when possible.
|
370
|
+
#--
|
371
|
+
# Ruby does not provide a way to hook into $? so there's no way for us
|
372
|
+
# to set it.
|
373
|
+
#
|
374
|
+
def waitpid2(pid)
|
375
|
+
exit_code = [0].pack('L')
|
376
|
+
handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
|
377
|
+
|
378
|
+
if handle == INVALID_HANDLE_VALUE
|
379
|
+
raise Error, get_last_error
|
380
|
+
end
|
381
|
+
|
382
|
+
# TODO: update the $? global variable (if/when possible)
|
383
|
+
status = WaitForSingleObject(handle, INFINITE)
|
384
|
+
|
385
|
+
begin
|
386
|
+
unless GetExitCodeProcess(handle, exit_code)
|
387
|
+
raise Error, get_last_error
|
234
388
|
end
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
389
|
+
ensure
|
390
|
+
CloseHandle(handle)
|
391
|
+
end
|
392
|
+
|
393
|
+
@child_pids.delete(pid)
|
394
|
+
|
395
|
+
# TODO: update the $? global variable (if/when possible)
|
396
|
+
exit_code = exit_code.unpack('L').first
|
397
|
+
|
398
|
+
[pid, exit_code]
|
399
|
+
end
|
243
400
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
401
|
+
# Sends the given +signal+ to an array of process id's. The +signal+ may
|
402
|
+
# be any value from 0 to 9, or the special strings 'SIGINT' (or 'INT'),
|
403
|
+
# 'SIGBRK' (or 'BRK') and 'SIGKILL' (or 'KILL'). An array of successfully
|
404
|
+
# killed pids is returned.
|
405
|
+
#
|
406
|
+
# Signal 0 merely tests if the process is running without killing it.
|
407
|
+
# Signal 2 sends a CTRL_C_EVENT to the process.
|
408
|
+
# Signal 3 sends a CTRL_BRK_EVENT to the process.
|
409
|
+
# Signal 9 kills the process in a harsh manner.
|
410
|
+
# Signals 1 and 4-8 kill the process in a nice manner.
|
411
|
+
#
|
412
|
+
# SIGINT/INT corresponds to signal 2
|
413
|
+
# SIGBRK/BRK corresponds to signal 3
|
414
|
+
# SIGKILL/KILL corresponds to signal 9
|
415
|
+
#
|
416
|
+
# Signals 2 and 3 only affect console processes, and then only if the
|
417
|
+
# process was created with the CREATE_NEW_PROCESS_GROUP flag.
|
418
|
+
#
|
419
|
+
def kill(signal, *pids)
|
420
|
+
case signal
|
421
|
+
when 'SIGINT', 'INT'
|
422
|
+
signal = 2
|
423
|
+
when 'SIGBRK', 'BRK'
|
424
|
+
signal = 3
|
425
|
+
when 'SIGKILL', 'KILL'
|
426
|
+
signal = 9
|
427
|
+
when 0..9
|
428
|
+
# Do nothing
|
429
|
+
else
|
430
|
+
raise Error, "Invalid signal '#{signal}'"
|
431
|
+
end
|
275
432
|
|
276
|
-
|
433
|
+
killed_pids = []
|
277
434
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
435
|
+
pids.each{ |pid|
|
436
|
+
# Send the signal to the current process if the pid is zero
|
437
|
+
if pid == 0
|
438
|
+
pid = Process.pid
|
439
|
+
end
|
283
440
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
441
|
+
# No need for full access if the signal is zero
|
442
|
+
if signal == 0
|
443
|
+
access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
|
444
|
+
handle = OpenProcess(access, 0 , pid)
|
445
|
+
else
|
446
|
+
handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
|
447
|
+
end
|
291
448
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
end
|
305
|
-
when 2
|
306
|
-
if GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)
|
307
|
-
killed_pids.push(pid)
|
308
|
-
end
|
309
|
-
when 3
|
310
|
-
if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)
|
311
|
-
killed_pids.push(pid)
|
312
|
-
end
|
313
|
-
when 9
|
314
|
-
if TerminateProcess(handle, pid)
|
315
|
-
killed_pids.push(pid)
|
316
|
-
@child_pids.delete(pid)
|
317
|
-
else
|
318
|
-
raise Error, get_last_error
|
319
|
-
end
|
320
|
-
else
|
321
|
-
if handle != 0
|
322
|
-
thread_id = [0].pack('L')
|
323
|
-
dll = 'kernel32'
|
324
|
-
eproc = 'ExitProcess'
|
325
|
-
|
326
|
-
thread = CreateRemoteThread(
|
327
|
-
handle,
|
328
|
-
0,
|
329
|
-
0,
|
330
|
-
GetProcAddress(GetModuleHandle(dll), eproc),
|
331
|
-
0,
|
332
|
-
0,
|
333
|
-
thread_id
|
334
|
-
)
|
335
|
-
|
336
|
-
if thread
|
337
|
-
WaitForSingleObject(thread, 5)
|
338
|
-
killed_pids.push(pid)
|
339
|
-
@child_pids.delete(pid)
|
340
|
-
else
|
341
|
-
raise Error, get_last_error
|
342
|
-
end
|
343
|
-
else
|
344
|
-
raise Error, get_last_error
|
345
|
-
end
|
346
|
-
@child_pids.delete(pid)
|
449
|
+
begin
|
450
|
+
case signal
|
451
|
+
when 0
|
452
|
+
if handle != 0
|
453
|
+
killed_pids.push(pid)
|
454
|
+
else
|
455
|
+
# If ERROR_ACCESS_DENIED is returned, we know it's running
|
456
|
+
if GetLastError() == ERROR_ACCESS_DENIED
|
457
|
+
killed_pids.push(pid)
|
458
|
+
else
|
459
|
+
raise Error, get_last_error
|
460
|
+
end
|
347
461
|
end
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
462
|
+
when 2
|
463
|
+
if GenerateConsoleCtrlEvent(CTRL_C_EVENT, pid)
|
464
|
+
killed_pids.push(pid)
|
465
|
+
end
|
466
|
+
when 3
|
467
|
+
if GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)
|
468
|
+
killed_pids.push(pid)
|
469
|
+
end
|
470
|
+
when 9
|
471
|
+
if TerminateProcess(handle, pid)
|
472
|
+
killed_pids.push(pid)
|
473
|
+
@child_pids.delete(pid)
|
474
|
+
else
|
475
|
+
raise Error, get_last_error
|
476
|
+
end
|
477
|
+
else
|
478
|
+
if handle != 0
|
479
|
+
thread_id = [0].pack('L')
|
480
|
+
dll = 'kernel32'
|
481
|
+
eproc = 'ExitProcess'
|
482
|
+
|
483
|
+
thread = CreateRemoteThread(
|
484
|
+
handle,
|
485
|
+
0,
|
486
|
+
0,
|
487
|
+
GetProcAddress(GetModuleHandle(dll), eproc),
|
488
|
+
0,
|
489
|
+
0,
|
490
|
+
thread_id
|
491
|
+
)
|
492
|
+
|
493
|
+
if thread
|
494
|
+
WaitForSingleObject(thread, 5)
|
495
|
+
killed_pids.push(pid)
|
496
|
+
@child_pids.delete(pid)
|
497
|
+
else
|
498
|
+
raise Error, get_last_error
|
499
|
+
end
|
500
|
+
else
|
501
|
+
raise Error, get_last_error
|
502
|
+
end
|
503
|
+
|
504
|
+
@child_pids.delete(pid)
|
505
|
+
end
|
506
|
+
ensure
|
507
|
+
CloseHandle(handle) unless handle == INVALID_HANDLE_VALUE
|
508
|
+
end
|
509
|
+
}
|
352
510
|
|
353
|
-
|
354
|
-
|
511
|
+
killed_pids
|
512
|
+
end
|
355
513
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
514
|
+
# Process.create(key => value, ...) => ProcessInfo
|
515
|
+
#
|
516
|
+
# This is a wrapper for the CreateProcess() function. It executes a process,
|
517
|
+
# returning a ProcessInfo struct. It accepts a hash as an argument.
|
518
|
+
# There are several primary keys:
|
361
519
|
#
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
520
|
+
# * command_line (mandatory)
|
521
|
+
# * app_name (default: nil)
|
522
|
+
# * inherit (default: false)
|
523
|
+
# * process_inherit (default: false)
|
524
|
+
# * thread_inherit (default: false)
|
525
|
+
# * creation_flags (default: 0)
|
526
|
+
# * cwd (default: Dir.pwd)
|
527
|
+
# * startup_info (default: nil)
|
528
|
+
# * environment (default: nil)
|
529
|
+
# * close_handles (default: true)
|
530
|
+
# * with_logon (default: nil)
|
531
|
+
# * domain (default: nil)
|
532
|
+
# * password (default: nil)
|
375
533
|
#
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
534
|
+
# Of these, the 'command_line' or 'app_name' must be specified or an
|
535
|
+
# error is raised. Both may be set individually, but 'command_line' should
|
536
|
+
# be preferred if only one of them is set because it does not (necessarily)
|
537
|
+
# require an explicit path or extension to work.
|
538
|
+
#
|
539
|
+
# The 'domain' and 'password' options are only relevent in the context
|
540
|
+
# of 'with_logon'.
|
383
541
|
#
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
542
|
+
# The startup_info key takes a hash. Its keys are attributes that are
|
543
|
+
# part of the StartupInfo struct, and are generally only meaningful for
|
544
|
+
# GUI or console processes. See the documentation on CreateProcess()
|
545
|
+
# and the StartupInfo struct on MSDN for more information.
|
388
546
|
#
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
547
|
+
# * desktop
|
548
|
+
# * title
|
549
|
+
# * x
|
550
|
+
# * y
|
551
|
+
# * x_size
|
552
|
+
# * y_size
|
553
|
+
# * x_count_chars
|
554
|
+
# * y_count_chars
|
555
|
+
# * fill_attribute
|
556
|
+
# * sw_flags
|
557
|
+
# * startf_flags
|
558
|
+
# * stdin
|
559
|
+
# * stdout
|
560
|
+
# * stderr
|
561
|
+
#
|
562
|
+
# The relevant constants for 'creation_flags', 'sw_flags' and 'startf_flags'
|
563
|
+
# are included in the Windows::Process, Windows::Console and Windows::Window
|
564
|
+
# modules. These come with the windows-pr library, a prerequisite of this
|
565
|
+
# library. Note that the 'stdin', 'stdout' and 'stderr' options can be
|
566
|
+
# either Ruby IO objects or file descriptors (i.e. a fileno). However,
|
567
|
+
# StringIO objects are not currently supported.
|
568
|
+
#
|
569
|
+
# If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
|
570
|
+
# is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
|
571
|
+
# automatically OR'd to the +startf_flags+ value.
|
572
|
+
#
|
573
|
+
# The ProcessInfo struct contains the following members:
|
574
|
+
#
|
575
|
+
# * process_handle - The handle to the newly created process.
|
576
|
+
# * thread_handle - The handle to the primary thread of the process.
|
577
|
+
# * process_id - Process ID.
|
578
|
+
# * thread_id - Thread ID.
|
579
|
+
#
|
580
|
+
# If the 'close_handles' option is set to true (the default) then the
|
581
|
+
# process_handle and the thread_handle are automatically closed for you
|
582
|
+
# before the ProcessInfo struct is returned.
|
583
|
+
#
|
584
|
+
# If the 'with_logon' option is set, then the process runs the specified
|
585
|
+
# executable file in the security context of the specified credentials.
|
586
|
+
#
|
587
|
+
def create(args)
|
588
|
+
unless args.kind_of?(Hash)
|
589
|
+
raise TypeError, 'Expecting hash-style keyword arguments'
|
590
|
+
end
|
591
|
+
|
592
|
+
valid_keys = %w/
|
593
|
+
app_name command_line inherit creation_flags cwd environment
|
594
|
+
startup_info thread_inherit process_inherit close_handles with_logon
|
595
|
+
domain password
|
596
|
+
/
|
597
|
+
|
598
|
+
valid_si_keys = %/
|
599
|
+
startf_flags desktop title x y x_size y_size x_count_chars
|
600
|
+
y_count_chars fill_attribute sw_flags stdin stdout stderr
|
601
|
+
/
|
602
|
+
|
603
|
+
# Set default values
|
604
|
+
hash = {
|
605
|
+
'app_name' => nil,
|
606
|
+
'creation_flags' => 0,
|
607
|
+
'close_handles' => true
|
608
|
+
}
|
609
|
+
|
610
|
+
# Validate the keys, and convert symbols and case to lowercase strings.
|
611
|
+
args.each{ |key, val|
|
612
|
+
key = key.to_s.downcase
|
613
|
+
unless valid_keys.include?(key)
|
614
|
+
raise Error, "invalid key '#{key}'"
|
432
615
|
end
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
hash = {
|
447
|
-
'app_name' => nil,
|
448
|
-
'creation_flags' => 0,
|
449
|
-
'close_handles' => true
|
616
|
+
hash[key] = val
|
617
|
+
}
|
618
|
+
|
619
|
+
si_hash = {}
|
620
|
+
|
621
|
+
# If the startup_info key is present, validate its subkeys
|
622
|
+
if hash['startup_info']
|
623
|
+
hash['startup_info'].each{ |key, val|
|
624
|
+
key = key.to_s.downcase
|
625
|
+
unless valid_si_keys.include?(key)
|
626
|
+
raise Error, "invalid startup_info key '#{key}'"
|
627
|
+
end
|
628
|
+
si_hash[key] = val
|
450
629
|
}
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
si_hash = {}
|
462
|
-
|
463
|
-
# If the startup_info key is present, validate its subkeys
|
464
|
-
if hash['startup_info']
|
465
|
-
hash['startup_info'].each{ |key, val|
|
466
|
-
key = key.to_s.downcase
|
467
|
-
unless valid_si_keys.include?(key)
|
468
|
-
raise Error, "invalid startup_info key '#{key}'"
|
469
|
-
end
|
470
|
-
si_hash[key] = val
|
471
|
-
}
|
472
|
-
end
|
473
|
-
|
474
|
-
# The +command_line+ key is mandatory unless the +app_name+ key
|
475
|
-
# is specified.
|
476
|
-
unless hash['command_line']
|
477
|
-
if hash['app_name']
|
478
|
-
hash['command_line'] = hash['app_name']
|
479
|
-
hash['app_name'] = nil
|
480
|
-
else
|
481
|
-
raise Error, 'command_line or app_name must be specified'
|
482
|
-
end
|
630
|
+
end
|
631
|
+
|
632
|
+
# The +command_line+ key is mandatory unless the +app_name+ key
|
633
|
+
# is specified.
|
634
|
+
unless hash['command_line']
|
635
|
+
if hash['app_name']
|
636
|
+
hash['command_line'] = hash['app_name']
|
637
|
+
hash['app_name'] = nil
|
638
|
+
else
|
639
|
+
raise Error, 'command_line or app_name must be specified'
|
483
640
|
end
|
641
|
+
end
|
484
642
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
else
|
493
|
-
env = [env.join("\0")].pack('p*').unpack('L').first
|
494
|
-
end
|
643
|
+
# The environment string should be passed as a string of ';' separated
|
644
|
+
# paths.
|
645
|
+
if hash['environment']
|
646
|
+
env = hash['environment'].split(File::PATH_SEPARATOR) << 0.chr
|
647
|
+
if hash['with_logon']
|
648
|
+
env = env.map{ |e| multi_to_wide(e) }
|
649
|
+
env = [env.join("\0\0")].pack('p*').unpack('L').first
|
495
650
|
else
|
496
|
-
|
497
|
-
end
|
498
|
-
|
499
|
-
startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
500
|
-
startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
|
501
|
-
procinfo = [0,0,0,0].pack('LLLL')
|
502
|
-
|
503
|
-
# Process SECURITY_ATTRIBUTE structure
|
504
|
-
process_security = 0
|
505
|
-
if hash['process_inherit']
|
506
|
-
process_security = [0,0,0].pack('LLL')
|
507
|
-
process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
|
508
|
-
process_security[8,4] = [1].pack('L') # TRUE
|
651
|
+
env = [env.join("\0")].pack('p*').unpack('L').first
|
509
652
|
end
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
[
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
653
|
+
else
|
654
|
+
env = nil
|
655
|
+
end
|
656
|
+
|
657
|
+
startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
658
|
+
startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
|
659
|
+
procinfo = [0,0,0,0].pack('LLLL')
|
660
|
+
|
661
|
+
# Process SECURITY_ATTRIBUTE structure
|
662
|
+
process_security = 0
|
663
|
+
if hash['process_inherit']
|
664
|
+
process_security = [0,0,0].pack('LLL')
|
665
|
+
process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
|
666
|
+
process_security[8,4] = [1].pack('L') # TRUE
|
667
|
+
end
|
668
|
+
|
669
|
+
# Thread SECURITY_ATTRIBUTE structure
|
670
|
+
thread_security = 0
|
671
|
+
if hash['thread_inherit']
|
672
|
+
thread_security = [0,0,0].pack('LLL')
|
673
|
+
thread_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
|
674
|
+
thread_security[8,4] = [1].pack('L') # TRUE
|
675
|
+
end
|
676
|
+
|
677
|
+
# Automatically handle stdin, stdout and stderr as either IO objects
|
678
|
+
# or file descriptors. This won't work for StringIO, however.
|
679
|
+
['stdin', 'stdout', 'stderr'].each{ |io|
|
680
|
+
if si_hash[io]
|
681
|
+
if si_hash[io].respond_to?(:fileno)
|
682
|
+
handle = get_osfhandle(si_hash[io].fileno)
|
683
|
+
else
|
684
|
+
handle = get_osfhandle(si_hash[io])
|
685
|
+
end
|
528
686
|
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
|
548
|
-
startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
|
549
|
-
startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
|
550
|
-
startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
|
551
|
-
startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
|
552
|
-
startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
|
553
|
-
startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
|
554
|
-
startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
|
555
|
-
startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
|
556
|
-
startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
|
687
|
+
if handle == INVALID_HANDLE_VALUE
|
688
|
+
raise Error, get_last_error
|
689
|
+
end
|
690
|
+
|
691
|
+
# Most implementations of Ruby on Windows create inheritable
|
692
|
+
# handles by default, but some do not. RF bug #26988.
|
693
|
+
bool = SetHandleInformation(
|
694
|
+
handle,
|
695
|
+
HANDLE_FLAG_INHERIT,
|
696
|
+
HANDLE_FLAG_INHERIT
|
697
|
+
)
|
698
|
+
|
699
|
+
raise Error, get_last_error unless bool
|
700
|
+
|
701
|
+
si_hash[io] = handle
|
702
|
+
si_hash['startf_flags'] ||= 0
|
703
|
+
si_hash['startf_flags'] |= STARTF_USESTDHANDLES
|
704
|
+
hash['inherit'] = true
|
557
705
|
end
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
706
|
+
}
|
707
|
+
|
708
|
+
# The bytes not covered here are reserved (null)
|
709
|
+
unless si_hash.empty?
|
710
|
+
startinfo[0,4] = [startinfo.size].pack('L')
|
711
|
+
startinfo[8,4] = [si_hash['desktop']].pack('p*') if si_hash['desktop']
|
712
|
+
startinfo[12,4] = [si_hash['title']].pack('p*') if si_hash['title']
|
713
|
+
startinfo[16,4] = [si_hash['x']].pack('L') if si_hash['x']
|
714
|
+
startinfo[20,4] = [si_hash['y']].pack('L') if si_hash['y']
|
715
|
+
startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
|
716
|
+
startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
|
717
|
+
startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
|
718
|
+
startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
|
719
|
+
startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
|
720
|
+
startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
|
721
|
+
startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
|
722
|
+
startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
|
723
|
+
startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
|
724
|
+
startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
|
725
|
+
end
|
726
|
+
|
727
|
+
if hash['with_logon']
|
728
|
+
logon = multi_to_wide(hash['with_logon'])
|
729
|
+
domain = multi_to_wide(hash['domain'])
|
730
|
+
app = hash['app_name'].nil? ? nil : multi_to_wide(hash['app_name'])
|
731
|
+
cmd = hash['command_line'].nil? ? nil : multi_to_wide(hash['command_line'])
|
732
|
+
cwd = multi_to_wide(hash['cwd'])
|
733
|
+
passwd = multi_to_wide(hash['password'])
|
566
734
|
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
)
|
582
|
-
else
|
583
|
-
bool = CreateProcess(
|
584
|
-
hash['app_name'], # App name
|
585
|
-
hash['command_line'], # Command line
|
586
|
-
process_security, # Process attributes
|
587
|
-
thread_security, # Thread attributes
|
588
|
-
hash['inherit'], # Inherit handles?
|
589
|
-
hash['creation_flags'], # Creation flags
|
590
|
-
env, # Environment
|
591
|
-
hash['cwd'], # Working directory
|
592
|
-
startinfo, # Startup Info
|
593
|
-
procinfo # Process Info
|
594
|
-
)
|
595
|
-
end
|
596
|
-
|
597
|
-
# TODO: Close stdin, stdout and stderr handles in the si_hash unless
|
598
|
-
# they're pointing to one of the standard handles already. [Maybe]
|
599
|
-
unless bool
|
600
|
-
raise Error, "CreateProcess() failed: ", get_last_error
|
601
|
-
end
|
602
|
-
|
603
|
-
# Automatically close the process and thread handles in the
|
604
|
-
# PROCESS_INFORMATION struct unless explicitly told not to.
|
605
|
-
if hash['close_handles']
|
606
|
-
CloseHandle(procinfo[0,4].unpack('L').first)
|
607
|
-
CloseHandle(procinfo[4,4].unpack('L').first)
|
608
|
-
end
|
609
|
-
|
610
|
-
ProcessInfo.new(
|
611
|
-
procinfo[0,4].unpack('L').first, # hProcess
|
612
|
-
procinfo[4,4].unpack('L').first, # hThread
|
613
|
-
procinfo[8,4].unpack('L').first, # hProcessId
|
614
|
-
procinfo[12,4].unpack('L').first # hThreadId
|
735
|
+
hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT
|
736
|
+
|
737
|
+
bool = CreateProcessWithLogonW(
|
738
|
+
logon, # User
|
739
|
+
domain, # Domain
|
740
|
+
passwd, # Password
|
741
|
+
LOGON_WITH_PROFILE, # Logon flags
|
742
|
+
app, # App name
|
743
|
+
cmd, # Command line
|
744
|
+
hash['creation_flags'], # Creation flags
|
745
|
+
env, # Environment
|
746
|
+
cwd, # Working directory
|
747
|
+
startinfo, # Startup Info
|
748
|
+
procinfo # Process Info
|
615
749
|
)
|
616
|
-
|
750
|
+
else
|
751
|
+
bool = CreateProcess(
|
752
|
+
hash['app_name'], # App name
|
753
|
+
hash['command_line'], # Command line
|
754
|
+
process_security, # Process attributes
|
755
|
+
thread_security, # Thread attributes
|
756
|
+
hash['inherit'], # Inherit handles?
|
757
|
+
hash['creation_flags'], # Creation flags
|
758
|
+
env, # Environment
|
759
|
+
hash['cwd'], # Working directory
|
760
|
+
startinfo, # Startup Info
|
761
|
+
procinfo # Process Info
|
762
|
+
)
|
763
|
+
end
|
764
|
+
|
765
|
+
# TODO: Close stdin, stdout and stderr handles in the si_hash unless
|
766
|
+
# they're pointing to one of the standard handles already. [Maybe]
|
767
|
+
unless bool
|
768
|
+
raise Error, "CreateProcess() failed: ", get_last_error
|
769
|
+
end
|
770
|
+
|
771
|
+
# Automatically close the process and thread handles in the
|
772
|
+
# PROCESS_INFORMATION struct unless explicitly told not to.
|
773
|
+
if hash['close_handles']
|
774
|
+
CloseHandle(procinfo[0,4].unpack('L').first)
|
775
|
+
CloseHandle(procinfo[4,4].unpack('L').first)
|
776
|
+
end
|
777
|
+
|
778
|
+
ProcessInfo.new(
|
779
|
+
procinfo[0,4].unpack('L').first, # hProcess
|
780
|
+
procinfo[4,4].unpack('L').first, # hThread
|
781
|
+
procinfo[8,4].unpack('L').first, # hProcessId
|
782
|
+
procinfo[12,4].unpack('L').first # hThreadId
|
783
|
+
)
|
784
|
+
end
|
617
785
|
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
786
|
+
# Waits for any child process to exit and returns the process id of that
|
787
|
+
# child.
|
788
|
+
#
|
789
|
+
# Note that the $? (Process::Status) global variable is NOT set. This
|
790
|
+
# may be addressed in a future release.
|
791
|
+
#--
|
792
|
+
# The GetProcessId() function is not defined in Windows 2000 or earlier
|
793
|
+
# so we have to do some extra work for those platforms.
|
794
|
+
#
|
795
|
+
def wait
|
796
|
+
handles = []
|
797
|
+
|
798
|
+
# Windows 2000 or earlier
|
799
|
+
unless defined? GetProcessId
|
800
|
+
pids = []
|
801
|
+
end
|
802
|
+
|
803
|
+
@child_pids.each_with_index{ |pid, i|
|
804
|
+
handles[i] = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
|
805
|
+
|
806
|
+
if handles[i] == INVALID_HANDLE_VALUE
|
807
|
+
err = "unable to get HANDLE on process associated with pid #{pid}"
|
808
|
+
raise Error, err
|
633
809
|
end
|
634
|
-
|
635
|
-
@child_pids.each_with_index{ |pid, i|
|
636
|
-
handles[i] = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
|
637
|
-
|
638
|
-
if handles[i] == INVALID_HANDLE_VALUE
|
639
|
-
err = "unable to get HANDLE on process associated with pid #{pid}"
|
640
|
-
raise Error, err
|
641
|
-
end
|
642
810
|
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
811
|
+
unless defined? GetProcessId
|
812
|
+
pids[i] = pid
|
813
|
+
end
|
814
|
+
}
|
647
815
|
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
816
|
+
wait = WaitForMultipleObjects(
|
817
|
+
handles.size,
|
818
|
+
handles.pack('L*'),
|
819
|
+
0,
|
820
|
+
INFINITE
|
821
|
+
)
|
822
|
+
|
823
|
+
if wait >= WAIT_OBJECT_0 && wait <= WAIT_OBJECT_0 + @child_pids.size - 1
|
824
|
+
index = wait - WAIT_OBJECT_0
|
825
|
+
handle = handles[index]
|
658
826
|
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
end
|
664
|
-
|
665
|
-
@child_pids.delete(pid)
|
666
|
-
handles.each{ |handle| CloseHandle(handle) }
|
667
|
-
return pid
|
827
|
+
if defined? GetProcessId
|
828
|
+
pid = GetProcessId(handle)
|
829
|
+
else
|
830
|
+
pid = pids[index]
|
668
831
|
end
|
832
|
+
|
833
|
+
@child_pids.delete(pid)
|
834
|
+
handles.each{ |handle| CloseHandle(handle) }
|
835
|
+
return pid
|
836
|
+
end
|
669
837
|
|
670
|
-
|
671
|
-
|
838
|
+
nil
|
839
|
+
end
|
672
840
|
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
841
|
+
# Waits for any child process to exit and returns an array containing the
|
842
|
+
# process id and the exit status of that child.
|
843
|
+
#
|
844
|
+
# Note that the $? (Process::Status) global variable is NOT set. This
|
845
|
+
# may be addressed in a future release.
|
846
|
+
#--
|
847
|
+
# The GetProcessId() function is not defined in Windows 2000 or earlier
|
848
|
+
# so we have to do some extra work for those platforms.
|
849
|
+
#
|
850
|
+
def wait2
|
851
|
+
handles = []
|
852
|
+
|
853
|
+
# Windows 2000 or earlier
|
854
|
+
unless defined? GetProcessId
|
855
|
+
pids = []
|
856
|
+
end
|
857
|
+
|
858
|
+
@child_pids.each_with_index{ |pid, i|
|
859
|
+
handles[i] = OpenProcess(PROCESS_ALL_ACCESS, 0, pid)
|
860
|
+
|
861
|
+
if handles[i] == INVALID_HANDLE_VALUE
|
862
|
+
err = "unable to get HANDLE on process associated with pid #{pid}"
|
863
|
+
raise Error, err
|
688
864
|
end
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
err = "unable to get HANDLE on process associated with pid #{pid}"
|
695
|
-
raise Error, err
|
696
|
-
end
|
697
|
-
|
698
|
-
unless defined? GetProcessId
|
699
|
-
pids[i] = pid
|
700
|
-
end
|
701
|
-
}
|
865
|
+
|
866
|
+
unless defined? GetProcessId
|
867
|
+
pids[i] = pid
|
868
|
+
end
|
869
|
+
}
|
702
870
|
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
871
|
+
wait = WaitForMultipleObjects(
|
872
|
+
handles.size,
|
873
|
+
handles.pack('L*'),
|
874
|
+
0,
|
875
|
+
INFINITE
|
876
|
+
)
|
877
|
+
|
878
|
+
if wait >= WAIT_OBJECT_0 && wait <= WAIT_OBJECT_0 + @child_pids.size - 1
|
879
|
+
index = wait - WAIT_OBJECT_0
|
880
|
+
handle = handles[index]
|
881
|
+
|
882
|
+
if defined? GetProcessId
|
883
|
+
pid = GetProcessId(handle)
|
884
|
+
else
|
885
|
+
pid = pids[index]
|
886
|
+
end
|
719
887
|
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
888
|
+
exit_code = [0].pack('l')
|
889
|
+
|
890
|
+
unless GetExitCodeProcess(handle, exit_code)
|
891
|
+
raise get_last_error
|
892
|
+
end
|
724
893
|
|
725
|
-
|
894
|
+
@child_pids.delete(pid)
|
726
895
|
|
727
|
-
|
728
|
-
|
729
|
-
|
896
|
+
handles.each{ |handle| CloseHandle(handle) }
|
897
|
+
return [pid, exit_code.unpack('l').first]
|
898
|
+
end
|
730
899
|
|
731
|
-
|
732
|
-
|
900
|
+
nil
|
901
|
+
end
|
733
902
|
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
903
|
+
# Returns the process ID of the parent of this process.
|
904
|
+
#--
|
905
|
+
# In MRI this method always returns 0.
|
906
|
+
#
|
907
|
+
def ppid
|
908
|
+
ppid = 0
|
740
909
|
|
741
|
-
|
910
|
+
return ppid if Process.pid == 0 # Paranoia
|
742
911
|
|
743
|
-
|
912
|
+
handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
|
744
913
|
|
745
|
-
|
746
|
-
|
747
|
-
|
914
|
+
if handle == INVALID_HANDLE_VALUE
|
915
|
+
raise Error, get_last_error
|
916
|
+
end
|
748
917
|
|
749
|
-
|
750
|
-
|
918
|
+
proc_entry = 0.chr * 296 # 36 + 260
|
919
|
+
proc_entry[0, 4] = [proc_entry.size].pack('L') # Set dwSize member
|
751
920
|
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
end
|
757
|
-
|
758
|
-
while Process32Next(handle, proc_entry)
|
759
|
-
if proc_entry[8, 4].unpack('L')[0] == Process.pid
|
760
|
-
ppid = proc_entry[24, 4].unpack('L')[0] # th32ParentProcessID
|
761
|
-
break
|
762
|
-
end
|
763
|
-
end
|
764
|
-
ensure
|
765
|
-
CloseHandle(handle)
|
921
|
+
begin
|
922
|
+
unless Process32First(handle, proc_entry)
|
923
|
+
error = get_last_error
|
924
|
+
raise Error, error
|
766
925
|
end
|
767
926
|
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
# Kernel.fork method for Unix. Unlike the Unix fork, this method starts
|
774
|
-
# from the top of the script rather than the point of the call.
|
775
|
-
#
|
776
|
-
# WARNING: This implementation should be considered experimental. It is
|
777
|
-
# not recommended for production use.
|
778
|
-
#
|
779
|
-
def fork
|
780
|
-
last_arg = ARGV.last
|
781
|
-
|
782
|
-
# Look for the 'child#xxx' tag
|
783
|
-
if last_arg =~ /child#\d+/
|
784
|
-
@i += 1
|
785
|
-
num = last_arg.split('#').last.to_i
|
786
|
-
if num == @i
|
787
|
-
if block_given?
|
788
|
-
status = 0
|
789
|
-
begin
|
790
|
-
yield
|
791
|
-
rescue Exception
|
792
|
-
status = -1 # Any non-zero result is failure
|
793
|
-
ensure
|
794
|
-
return status
|
795
|
-
end
|
796
|
-
end
|
797
|
-
return nil
|
798
|
-
else
|
799
|
-
return false
|
800
|
-
end
|
927
|
+
while Process32Next(handle, proc_entry)
|
928
|
+
if proc_entry[8, 4].unpack('L')[0] == Process.pid
|
929
|
+
ppid = proc_entry[24, 4].unpack('L')[0] # th32ParentProcessID
|
930
|
+
break
|
931
|
+
end
|
801
932
|
end
|
933
|
+
ensure
|
934
|
+
CloseHandle(handle)
|
935
|
+
end
|
936
|
+
|
937
|
+
ppid
|
938
|
+
end
|
939
|
+
|
940
|
+
# Creates the equivalent of a subshell via the CreateProcess() function.
|
941
|
+
# This behaves in a manner that is similar, but not identical to, the
|
942
|
+
# Kernel.fork method for Unix. Unlike the Unix fork, this method starts
|
943
|
+
# from the top of the script rather than the point of the call.
|
944
|
+
#
|
945
|
+
# WARNING: This implementation should be considered experimental. It is
|
946
|
+
# not recommended for production use.
|
947
|
+
#
|
948
|
+
def fork
|
949
|
+
last_arg = ARGV.last
|
950
|
+
|
951
|
+
# Look for the 'child#xxx' tag
|
952
|
+
if last_arg =~ /child#\d+/
|
953
|
+
@i += 1
|
954
|
+
num = last_arg.split('#').last.to_i
|
955
|
+
if num == @i
|
956
|
+
if block_given?
|
957
|
+
status = 0
|
958
|
+
begin
|
959
|
+
yield
|
960
|
+
rescue Exception
|
961
|
+
status = -1 # Any non-zero result is failure
|
962
|
+
ensure
|
963
|
+
return status
|
964
|
+
end
|
965
|
+
end
|
966
|
+
return nil
|
967
|
+
else
|
968
|
+
return false
|
969
|
+
end
|
970
|
+
end
|
802
971
|
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
972
|
+
# Tag the command with the word 'child#xxx' to distinguish it
|
973
|
+
# from the calling process.
|
974
|
+
cmd = 'ruby -I "' + $LOAD_PATH.join(File::PATH_SEPARATOR) << '" "'
|
975
|
+
cmd << File.expand_path($PROGRAM_NAME) << '" ' << ARGV.join(' ')
|
976
|
+
cmd << ' child#' << @child_pids.length.to_s
|
808
977
|
|
809
|
-
|
810
|
-
|
811
|
-
|
978
|
+
startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
979
|
+
startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
|
980
|
+
procinfo = [0,0,0,0].pack('LLLL')
|
812
981
|
|
813
|
-
|
982
|
+
rv = CreateProcess(0, cmd, 0, 0, 1, 0, 0, 0, startinfo, procinfo)
|
814
983
|
|
815
|
-
|
816
|
-
|
817
|
-
|
984
|
+
if rv == 0
|
985
|
+
raise Error, get_last_error
|
986
|
+
end
|
818
987
|
|
819
|
-
|
820
|
-
|
988
|
+
pid = procinfo[8,4].unpack('L').first
|
989
|
+
@child_pids.push(pid)
|
821
990
|
|
822
|
-
|
823
|
-
|
991
|
+
pid
|
992
|
+
end
|
824
993
|
|
825
|
-
|
826
|
-
|
994
|
+
module_function :create, :fork, :get_affinity, :getrlimit, :getpriority, :kill, :ppid
|
995
|
+
module_function :setpriority, :wait, :wait2, :waitpid, :waitpid2, :uid
|
827
996
|
end
|
828
997
|
|
829
998
|
# Create a global fork method
|
830
999
|
module Kernel
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
1000
|
+
undef_method :fork # Eliminate redefinition warning
|
1001
|
+
def fork(&block)
|
1002
|
+
Process.fork(&block)
|
1003
|
+
end
|
835
1004
|
end
|