childprocess 0.1.5 → 0.1.6
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/{spec/spec.opts → .rspec} +0 -0
- data/VERSION +1 -1
- data/childprocess.gemspec +3 -3
- data/lib/childprocess/abstract_io.rb +10 -1
- data/lib/childprocess/abstract_process.rb +10 -1
- data/lib/childprocess/jruby/process.rb +8 -1
- data/lib/childprocess/unix/process.rb +14 -0
- data/lib/childprocess/windows/api.rb +1 -1
- data/lib/childprocess/windows/constants.rb +2 -0
- data/lib/childprocess/windows/functions.rb +96 -8
- data/lib/childprocess/windows/process.rb +7 -2
- data/lib/childprocess/windows/structs.rb +21 -0
- data/spec/childprocess_spec.rb +28 -1
- metadata +4 -4
data/{spec/spec.opts → .rspec}
RENAMED
File without changes
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.6
|
data/childprocess.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{childprocess}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.6"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jari Bakken"]
|
12
|
-
s.date = %q{2010-12-
|
12
|
+
s.date = %q{2010-12-22}
|
13
13
|
s.description = %q{This gem aims at being a simple and reliable solution for controlling external programs running in the background on any Ruby / OS combination.}
|
14
14
|
s.email = %q{jari.bakken@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
|
+
".rspec",
|
21
22
|
"LICENSE",
|
22
23
|
"README.rdoc",
|
23
24
|
"Rakefile",
|
@@ -47,7 +48,6 @@ Gem::Specification.new do |s|
|
|
47
48
|
"spec/abstract_io_spec.rb",
|
48
49
|
"spec/childprocess_spec.rb",
|
49
50
|
"spec/jruby_spec.rb",
|
50
|
-
"spec/spec.opts",
|
51
51
|
"spec/spec_helper.rb",
|
52
52
|
"spec/unix_spec.rb",
|
53
53
|
"spec/windows_spec.rb"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module ChildProcess
|
2
2
|
class AbstractIO
|
3
|
-
attr_reader :stderr, :stdout
|
3
|
+
attr_reader :stderr, :stdout, :stdin
|
4
4
|
|
5
5
|
def inherit!
|
6
6
|
@stdout = STDOUT
|
@@ -17,6 +17,15 @@ module ChildProcess
|
|
17
17
|
@stdout = io
|
18
18
|
end
|
19
19
|
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
#
|
23
|
+
|
24
|
+
def _stdin=(io)
|
25
|
+
check_type io
|
26
|
+
@stdin = io
|
27
|
+
end
|
28
|
+
|
20
29
|
private
|
21
30
|
|
22
31
|
def check_type(io)
|
@@ -7,6 +7,10 @@ module ChildProcess
|
|
7
7
|
#
|
8
8
|
attr_accessor :detach
|
9
9
|
|
10
|
+
#
|
11
|
+
# Set this to true if you want to write to the process' stdin (process.io.stdin)
|
12
|
+
#
|
13
|
+
attr_accessor :duplex
|
10
14
|
|
11
15
|
#
|
12
16
|
# Create a new process with the given args.
|
@@ -20,6 +24,7 @@ module ChildProcess
|
|
20
24
|
@started = false
|
21
25
|
@exit_code = nil
|
22
26
|
@detach = false
|
27
|
+
@duplex = false
|
23
28
|
end
|
24
29
|
|
25
30
|
#
|
@@ -117,6 +122,10 @@ module ChildProcess
|
|
117
122
|
@detach
|
118
123
|
end
|
119
124
|
|
125
|
+
def duplex?
|
126
|
+
@duplex
|
127
|
+
end
|
128
|
+
|
120
129
|
def log(*args)
|
121
130
|
$stderr.puts "#{self.inspect} : #{args.inspect}" if $DEBUG
|
122
131
|
end
|
@@ -126,4 +135,4 @@ module ChildProcess
|
|
126
135
|
end
|
127
136
|
|
128
137
|
end # AbstractProcess
|
129
|
-
end # ChildProcess
|
138
|
+
end # ChildProcess
|
@@ -30,13 +30,14 @@ module ChildProcess
|
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
-
def launch_process
|
33
|
+
def launch_process(&blk)
|
34
34
|
pb = java.lang.ProcessBuilder.new(@args)
|
35
35
|
|
36
36
|
env = pb.environment
|
37
37
|
ENV.each { |k,v| env.put(k, v) }
|
38
38
|
|
39
39
|
@process = pb.start
|
40
|
+
|
40
41
|
setup_io
|
41
42
|
end
|
42
43
|
|
@@ -48,6 +49,12 @@ module ChildProcess
|
|
48
49
|
@process.getErrorStream.close
|
49
50
|
@process.getInputStream.close
|
50
51
|
end
|
52
|
+
|
53
|
+
if duplex?
|
54
|
+
io._stdin = @process.getOutputStream.to_io
|
55
|
+
else
|
56
|
+
@process.getOutputStream.close
|
57
|
+
end
|
51
58
|
end
|
52
59
|
|
53
60
|
def redirect(input, output)
|
@@ -72,13 +72,27 @@ module ChildProcess
|
|
72
72
|
stderr = @io.stderr
|
73
73
|
end
|
74
74
|
|
75
|
+
if duplex?
|
76
|
+
reader, writer = ::IO.pipe
|
77
|
+
end
|
78
|
+
|
75
79
|
@pid = fork {
|
76
80
|
STDOUT.reopen(stdout || "/dev/null")
|
77
81
|
STDERR.reopen(stderr || "/dev/null")
|
78
82
|
|
83
|
+
if duplex?
|
84
|
+
STDIN.reopen(reader)
|
85
|
+
writer.close
|
86
|
+
end
|
87
|
+
|
79
88
|
exec(*@args)
|
80
89
|
}
|
81
90
|
|
91
|
+
if duplex?
|
92
|
+
io._stdin = writer
|
93
|
+
reader.close
|
94
|
+
end
|
95
|
+
|
82
96
|
::Process.detach(@pid) if detach?
|
83
97
|
end
|
84
98
|
|
@@ -33,7 +33,7 @@ module ChildProcess
|
|
33
33
|
raise ArgumentError, "expected #{file.inspect} to respond to :fileno"
|
34
34
|
end
|
35
35
|
|
36
|
-
handle = Lib.
|
36
|
+
handle = Lib.handle_for(file.fileno)
|
37
37
|
|
38
38
|
ok = Lib.set_handle_information(handle, HANDLE_FLAG_INHERIT, 0)
|
39
39
|
ok or raise Error, Lib.last_error_message
|
@@ -18,8 +18,22 @@ module ChildProcess
|
|
18
18
|
si[:dwFlags] |= STARTF_USESTDHANDLES
|
19
19
|
inherit = true
|
20
20
|
|
21
|
-
si[:hStdOutput] =
|
22
|
-
si[:hStdError] =
|
21
|
+
si[:hStdOutput] = handle_for(opts[:stdout].fileno) if opts[:stdout]
|
22
|
+
si[:hStdError] = handle_for(opts[:stderr].fileno) if opts[:stderr]
|
23
|
+
end
|
24
|
+
|
25
|
+
if opts[:duplex]
|
26
|
+
read_pipe_ptr = FFI::MemoryPointer.new(:pointer)
|
27
|
+
write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
|
28
|
+
sa = SecurityAttributes.new(:inherit => true)
|
29
|
+
|
30
|
+
ok = create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
|
31
|
+
ok or raise Error, last_error_message
|
32
|
+
|
33
|
+
read_pipe = read_pipe_ptr.read_pointer
|
34
|
+
write_pipe = write_pipe_ptr.read_pointer
|
35
|
+
|
36
|
+
si[:hStdInput] = read_pipe
|
23
37
|
end
|
24
38
|
|
25
39
|
ok = create_process(nil, cmd_ptr, nil, nil, inherit, flags, nil, nil, si, pi)
|
@@ -28,6 +42,12 @@ module ChildProcess
|
|
28
42
|
close_handle pi[:hProcess]
|
29
43
|
close_handle pi[:hThread]
|
30
44
|
|
45
|
+
if opts[:duplex]
|
46
|
+
opts[:stdin] = io_for(duplicate_handle(write_pipe), File::WRONLY)
|
47
|
+
close_handle read_pipe
|
48
|
+
close_handle write_pipe
|
49
|
+
end
|
50
|
+
|
31
51
|
pi[:dwProcessId]
|
32
52
|
end
|
33
53
|
|
@@ -43,12 +63,12 @@ module ChildProcess
|
|
43
63
|
buf.read_string(size).strip
|
44
64
|
end
|
45
65
|
|
46
|
-
def self.
|
66
|
+
def self.handle_for(fd_or_io)
|
47
67
|
case fd_or_io
|
48
68
|
when IO
|
49
|
-
handle =
|
69
|
+
handle = get_osfhandle(fd.fileno)
|
50
70
|
when Fixnum
|
51
|
-
handle =
|
71
|
+
handle = get_osfhandle(fd_or_io)
|
52
72
|
else
|
53
73
|
if fd_or_io.respond_to?(:to_io)
|
54
74
|
io = fd_or_io.to_io
|
@@ -57,19 +77,42 @@ module ChildProcess
|
|
57
77
|
raise TypeError, "expected #to_io to return an instance of IO"
|
58
78
|
end
|
59
79
|
|
60
|
-
handle =
|
80
|
+
handle = get_osfhandle(io.fileno)
|
61
81
|
else
|
62
82
|
raise TypeError, "invalid type: #{fd_or_io.inspect}"
|
63
83
|
end
|
64
84
|
end
|
65
85
|
|
66
86
|
if handle == INVALID_HANDLE_VALUE
|
67
|
-
raise Error,
|
87
|
+
raise Error, last_error_message
|
68
88
|
end
|
69
89
|
|
70
90
|
handle
|
71
91
|
end
|
72
92
|
|
93
|
+
def self.io_for(handle, flags = File::RDONLY)
|
94
|
+
fd = open_osfhandle(handle, flags)
|
95
|
+
if fd == -1
|
96
|
+
raise Error, last_error_message
|
97
|
+
end
|
98
|
+
|
99
|
+
::IO.for_fd fd, flags
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.duplicate_handle(handle)
|
103
|
+
dup = FFI::MemoryPointer.new(:pointer)
|
104
|
+
proc = current_process
|
105
|
+
|
106
|
+
ok = _duplicate_handle(
|
107
|
+
proc, handle, proc, dup, 0, false, DUPLICATE_SAME_ACCESS)
|
108
|
+
|
109
|
+
ok or raise Error, last_error_message
|
110
|
+
|
111
|
+
dup.read_pointer
|
112
|
+
ensure
|
113
|
+
close_handle proc
|
114
|
+
end
|
115
|
+
|
73
116
|
#
|
74
117
|
# BOOL WINAPI CreateProcess(
|
75
118
|
# __in_opt LPCTSTR lpApplicationName,
|
@@ -181,7 +224,16 @@ module ChildProcess
|
|
181
224
|
# );
|
182
225
|
#
|
183
226
|
|
184
|
-
attach_function :
|
227
|
+
attach_function :get_osfhandle, :_get_osfhandle, [:int], :long
|
228
|
+
|
229
|
+
#
|
230
|
+
# int _open_osfhandle (
|
231
|
+
# intptr_t osfhandle,
|
232
|
+
# int flags
|
233
|
+
# );
|
234
|
+
#
|
235
|
+
|
236
|
+
attach_function :open_osfhandle, :_open_osfhandle, [:pointer, :int], :int
|
185
237
|
|
186
238
|
# BOOL WINAPI SetHandleInformation(
|
187
239
|
# __in HANDLE hObject,
|
@@ -191,6 +243,42 @@ module ChildProcess
|
|
191
243
|
|
192
244
|
attach_function :set_handle_information, :SetHandleInformation, [:long, :ulong, :ulong], :bool
|
193
245
|
|
246
|
+
# BOOL WINAPI CreatePipe(
|
247
|
+
# __out PHANDLE hReadPipe,
|
248
|
+
# __out PHANDLE hWritePipe,
|
249
|
+
# __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes,
|
250
|
+
# __in DWORD nSize
|
251
|
+
# );
|
252
|
+
|
253
|
+
attach_function :create_pipe, :CreatePipe, [:pointer, :pointer, :pointer, :ulong], :bool
|
254
|
+
|
255
|
+
#
|
256
|
+
# HANDLE WINAPI GetCurrentProcess(void);
|
257
|
+
#
|
258
|
+
|
259
|
+
attach_function :current_process, :GetCurrentProcess, [], :pointer
|
260
|
+
|
261
|
+
#
|
262
|
+
# BOOL WINAPI DuplicateHandle(
|
263
|
+
# __in HANDLE hSourceProcessHandle,
|
264
|
+
# __in HANDLE hSourceHandle,
|
265
|
+
# __in HANDLE hTargetProcessHandle,
|
266
|
+
# __out LPHANDLE lpTargetHandle,
|
267
|
+
# __in DWORD dwDesiredAccess,
|
268
|
+
# __in BOOL bInheritHandle,
|
269
|
+
# __in DWORD dwOptions
|
270
|
+
# );
|
271
|
+
#
|
272
|
+
|
273
|
+
attach_function :_duplicate_handle, :DuplicateHandle, [
|
274
|
+
:pointer,
|
275
|
+
:pointer,
|
276
|
+
:pointer,
|
277
|
+
:pointer,
|
278
|
+
:ulong,
|
279
|
+
:bool,
|
280
|
+
:ulong
|
281
|
+
], :bool
|
194
282
|
end # Lib
|
195
283
|
end # Windows
|
196
284
|
end # ChildProcess
|
@@ -40,6 +40,7 @@ module ChildProcess
|
|
40
40
|
opts = {
|
41
41
|
:inherit => false,
|
42
42
|
:detach => detach?,
|
43
|
+
:duplex => duplex?
|
43
44
|
}
|
44
45
|
|
45
46
|
if @io
|
@@ -47,12 +48,16 @@ module ChildProcess
|
|
47
48
|
opts[:stderr] = @io.stderr
|
48
49
|
end
|
49
50
|
|
50
|
-
|
51
|
-
|
51
|
+
# TODO: escape/quote arguments properly
|
52
|
+
command = @args.join ' '
|
52
53
|
|
53
54
|
@pid = Lib.create_proc(command, opts)
|
54
55
|
@handle = Handle.open(@pid)
|
55
56
|
|
57
|
+
if duplex?
|
58
|
+
io._stdin = opts[:stdin]
|
59
|
+
end
|
60
|
+
|
56
61
|
self
|
57
62
|
end
|
58
63
|
|
@@ -57,4 +57,25 @@ module ChildProcess::Windows
|
|
57
57
|
:dwThreadId, :ulong
|
58
58
|
end
|
59
59
|
|
60
|
+
#
|
61
|
+
# typedef struct _SECURITY_ATTRIBUTES {
|
62
|
+
# DWORD nLength;
|
63
|
+
# LPVOID lpSecurityDescriptor;
|
64
|
+
# BOOL bInheritHandle;
|
65
|
+
# } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
|
66
|
+
#
|
67
|
+
|
68
|
+
class SecurityAttributes < FFI::Struct
|
69
|
+
layout :nLength, :ulong,
|
70
|
+
:lpSecurityDescriptor, :pointer, # void ptr
|
71
|
+
:bInheritHandle, :int
|
72
|
+
|
73
|
+
def initialize(opts = {})
|
74
|
+
super()
|
75
|
+
|
76
|
+
self[:nLength] = self.class.size
|
77
|
+
self[:lpSecurityDescriptor] = nil
|
78
|
+
self[:bInheritHandle] = opts[:inherit] ? 1 : 0
|
79
|
+
end
|
80
|
+
end
|
60
81
|
end
|
data/spec/childprocess_spec.rb
CHANGED
@@ -66,12 +66,13 @@ describe ChildProcess do
|
|
66
66
|
pending "how do we spec this?"
|
67
67
|
end
|
68
68
|
|
69
|
-
it "can redirect stdout
|
69
|
+
it "can redirect stdout, stderr" do
|
70
70
|
process = ruby(<<-CODE)
|
71
71
|
[STDOUT, STDERR].each_with_index do |io, idx|
|
72
72
|
io.sync = true
|
73
73
|
io.puts idx
|
74
74
|
end
|
75
|
+
|
75
76
|
sleep 0.2
|
76
77
|
CODE
|
77
78
|
|
@@ -83,6 +84,7 @@ describe ChildProcess do
|
|
83
84
|
process.io.stderr = err
|
84
85
|
|
85
86
|
process.start
|
87
|
+
process.io.stdin.should be_nil
|
86
88
|
process.poll_for_exit(EXIT_TIMEOUT)
|
87
89
|
|
88
90
|
out.rewind
|
@@ -96,6 +98,31 @@ describe ChildProcess do
|
|
96
98
|
end
|
97
99
|
end
|
98
100
|
|
101
|
+
it "can write to stdin if duplex = true" do
|
102
|
+
process = ruby(<<-CODE)
|
103
|
+
puts(STDIN.gets.chomp)
|
104
|
+
CODE
|
105
|
+
|
106
|
+
out = Tempfile.new("duplex")
|
107
|
+
|
108
|
+
begin
|
109
|
+
process.io.stdout = out
|
110
|
+
process.io.stderr = out
|
111
|
+
process.duplex = true
|
112
|
+
|
113
|
+
process.start
|
114
|
+
process.io.stdin.puts "hello world"
|
115
|
+
process.io.stdin.close # JRuby seems to need this
|
116
|
+
|
117
|
+
process.poll_for_exit(EXIT_TIMEOUT)
|
118
|
+
|
119
|
+
out.rewind
|
120
|
+
out.read.should == "hello world\n"
|
121
|
+
ensure
|
122
|
+
out.close
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
99
126
|
it "can set close-on-exec when IO is inherited" do
|
100
127
|
server = TCPServer.new("localhost", 4433)
|
101
128
|
ChildProcess.close_on_exec server
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 6
|
9
|
+
version: 0.1.6
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Jari Bakken
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-12-
|
17
|
+
date: 2010-12-22 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -71,6 +71,7 @@ extra_rdoc_files:
|
|
71
71
|
- README.rdoc
|
72
72
|
files:
|
73
73
|
- .document
|
74
|
+
- .rspec
|
74
75
|
- LICENSE
|
75
76
|
- README.rdoc
|
76
77
|
- Rakefile
|
@@ -100,7 +101,6 @@ files:
|
|
100
101
|
- spec/abstract_io_spec.rb
|
101
102
|
- spec/childprocess_spec.rb
|
102
103
|
- spec/jruby_spec.rb
|
103
|
-
- spec/spec.opts
|
104
104
|
- spec/spec_helper.rb
|
105
105
|
- spec/unix_spec.rb
|
106
106
|
- spec/windows_spec.rb
|