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