rexec 1.2.1 → 1.2.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/README.rdoc +59 -12
- data/bin/daemon-exec +17 -12
- data/lib/rexec.rb +22 -26
- data/lib/rexec/client.rb +2 -14
- data/lib/rexec/connection.rb +138 -146
- data/lib/rexec/daemon.rb +20 -20
- data/lib/rexec/daemon/base.rb +157 -142
- data/lib/rexec/daemon/controller.rb +18 -13
- data/lib/rexec/daemon/pidfile.rb +57 -52
- data/lib/rexec/environment.rb +24 -5
- data/lib/rexec/priviledges.rb +42 -23
- data/lib/rexec/reverse_io.rb +86 -67
- data/lib/rexec/server.rb +86 -53
- data/lib/rexec/task.rb +145 -49
- data/lib/rexec/version.rb +23 -18
- data/test/client.rb +17 -12
- data/test/daemon.rb +17 -12
- data/test/daemon_test.rb +17 -12
- data/test/listing_example.rb +17 -12
- data/test/remote_server_test.rb +18 -13
- data/test/server_test.rb +17 -12
- data/test/task.rb +17 -12
- data/test/task_test.rb +17 -12
- metadata +5 -4
data/lib/rexec/environment.rb
CHANGED
@@ -1,14 +1,33 @@
|
|
1
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
1
20
|
|
2
21
|
module RExec
|
3
22
|
# Updates the global ENV for the duration of block. Not multi-thread safe.
|
4
23
|
def self.env (new_env = nil, &block)
|
5
|
-
|
24
|
+
old_env = ENV.to_hash
|
6
25
|
|
7
|
-
|
26
|
+
ENV.update(new_env) if new_env
|
8
27
|
|
9
|
-
|
28
|
+
yield
|
10
29
|
|
11
|
-
|
12
|
-
|
30
|
+
ENV.clear
|
31
|
+
ENV.update(old_env)
|
13
32
|
end
|
14
33
|
end
|
data/lib/rexec/priviledges.rb
CHANGED
@@ -1,28 +1,47 @@
|
|
1
|
+
# Copyright (c) 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
1
20
|
|
2
21
|
require 'etc'
|
3
22
|
|
4
23
|
module RExec
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
|
25
|
+
# Set the user of the current process. Supply either a user ID
|
26
|
+
# or a user name.
|
27
|
+
#
|
28
|
+
# Be aware that on Mac OS X / Ruby 1.8 there are bugs when the user id
|
29
|
+
# is negative (i.e. it doesn't work). For example "nobody" with uid -2
|
30
|
+
# won't work.
|
31
|
+
def self.change_user(user)
|
32
|
+
if user.kind_of?(String)
|
33
|
+
user = Etc.getpwnam(user).uid
|
34
|
+
end
|
35
|
+
|
36
|
+
Process::Sys.setuid(user)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# Get the user of the current process. Returns the user name.
|
41
|
+
def self.current_user
|
42
|
+
uid = Process::Sys.getuid
|
43
|
+
|
44
|
+
Etc.getpwuid(uid).name
|
45
|
+
end
|
46
|
+
|
28
47
|
end
|
data/lib/rexec/reverse_io.rb
CHANGED
@@ -1,70 +1,89 @@
|
|
1
|
+
# Copyright (c) 2007, 2009, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
1
20
|
|
2
21
|
class File
|
3
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
22
|
+
# Seek to the end of the file
|
23
|
+
def seek_end
|
24
|
+
seek(0, IO::SEEK_END)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Read a chunk of data and then move the file pointer backwards.
|
28
|
+
#
|
29
|
+
# Calling this function multiple times will return new data and traverse the file backwards.
|
30
|
+
#
|
31
|
+
def read_reverse(length)
|
32
|
+
offset = tell
|
33
|
+
|
34
|
+
if offset == 0
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
|
38
|
+
start = [0, offset-length].max
|
39
|
+
|
40
|
+
seek(start, IO::SEEK_SET)
|
41
|
+
|
42
|
+
buf = read(offset-start)
|
43
|
+
|
44
|
+
seek(start, IO::SEEK_SET)
|
45
|
+
|
46
|
+
return buf
|
47
|
+
end
|
48
|
+
|
49
|
+
REVERSE_BUFFER_SIZE = 128
|
50
|
+
|
51
|
+
# This function is very similar to gets but it works in reverse.
|
52
|
+
#
|
53
|
+
# You can use it to efficiently read a file line by line backwards.
|
54
|
+
#
|
55
|
+
# It returns nil when there are no more lines.
|
56
|
+
def reverse_gets(sep_string=$/)
|
57
|
+
end_pos = tell
|
58
|
+
|
59
|
+
offset = nil
|
60
|
+
buf = ""
|
61
|
+
|
62
|
+
while offset == nil
|
63
|
+
chunk = read_reverse(REVERSE_BUFFER_SIZE)
|
64
|
+
return (buf == "" ? nil : buf) if chunk == nil
|
65
|
+
|
66
|
+
buf = chunk + buf
|
67
|
+
|
68
|
+
offset = buf.rindex(sep_string)
|
69
|
+
end
|
70
|
+
|
71
|
+
line = buf[offset...buf.size].sub(sep_string, "")
|
72
|
+
|
73
|
+
seek((end_pos - buf.size) + offset, IO::SEEK_SET)
|
74
|
+
|
75
|
+
return line
|
76
|
+
end
|
77
|
+
|
78
|
+
# Similar to each_line but works in reverse. Don't forget to call
|
79
|
+
# seek_end before you start!
|
80
|
+
def reverse_each_line(sep_string=$/, &block)
|
81
|
+
line = reverse_gets(sep_string)
|
82
|
+
|
83
|
+
while line != nil
|
84
|
+
yield line
|
85
|
+
|
86
|
+
line = reverse_gets(sep_string)
|
87
|
+
end
|
88
|
+
end
|
70
89
|
end
|
data/lib/rexec/server.rb
CHANGED
@@ -1,62 +1,95 @@
|
|
1
|
-
# Copyright (c) 2007 Samuel
|
2
|
-
#
|
3
|
-
# This program is free software: you can redistribute it and/or modify
|
4
|
-
# it under the terms of the GNU General Public License as published by
|
5
|
-
# the Free Software Foundation, either version 3 of the License, or
|
6
|
-
# (at your option) any later version.
|
1
|
+
# Copyright (c) 2007, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
7
2
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
12
9
|
#
|
13
|
-
#
|
14
|
-
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
15
20
|
|
16
21
|
require 'pathname'
|
17
22
|
require 'rexec/task'
|
18
23
|
require 'rexec/connection'
|
19
24
|
|
20
25
|
module RExec
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
26
|
+
|
27
|
+
# Indicates that a connection could not be established because the pipes were not available or not connected.
|
28
|
+
class InvalidConnectionError < Exception
|
29
|
+
end
|
30
|
+
|
31
|
+
# The connection code which is sent to the client to be used for bi-directional communication.
|
32
|
+
CONNECTION_CODE = (Pathname.new(__FILE__).dirname + "connection.rb").read
|
33
|
+
|
34
|
+
# The client code which sets up the connection object and initialises communciation.
|
35
|
+
CLIENT_CODE = (Pathname.new(__FILE__).dirname + "client.rb").read
|
36
|
+
|
37
|
+
# Start a remote ruby server. This function is a structural cornerstone. This code runs the command you
|
38
|
+
# supply (this command should start an instance of ruby somewhere), sends it the code in
|
39
|
+
# +connection.rb+ and +client.rb+ as well as the code you supply.
|
40
|
+
#
|
41
|
+
# Once the remote ruby instance is set up and ready to go, this code will return (or yield) the connection
|
42
|
+
# and pid of the executed command.
|
43
|
+
#
|
44
|
+
# From this point, you can send and receive objects, and interact with the code you provided within a
|
45
|
+
# remote ruby instance.
|
46
|
+
#
|
47
|
+
# For a local shell, you could specify +"ruby"+ as the command. For a remote shell via SSH, you could specify
|
48
|
+
# +"ssh example.com ruby"+.
|
49
|
+
#
|
50
|
+
# ==== Example
|
51
|
+
# Create a file called +client.rb+ on the server. This file contains code to be executed on the client. This
|
52
|
+
# file can assume the existance of an object called +$connection+:
|
53
|
+
#
|
54
|
+
# $connection.run do |object|
|
55
|
+
# case(object[0])
|
56
|
+
# when :bounce
|
57
|
+
# $connection.send_object(object[1])
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# Then, on the server, create a new program +server.rb+ which will be used to coordinate the execution of code:
|
62
|
+
#
|
63
|
+
# shell = "ssh example.com ruby"
|
64
|
+
# client_code = (Pathname.new(__FILE__).dirname + "./client.rb").read
|
65
|
+
#
|
66
|
+
# RExec::start_server(client_code, shell) do |connection, pid|
|
67
|
+
# connection.send_object([:bounce, "Hello World!"])
|
68
|
+
# result = connection.receive_object
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
def self.start_server(code, command, options = {}, &block)
|
72
|
+
options[:passthrough] = :err unless options[:passthrough]
|
73
|
+
|
74
|
+
send_code = Proc.new do |cin|
|
75
|
+
cin.puts(CONNECTION_CODE)
|
76
|
+
cin.puts(CLIENT_CODE)
|
77
|
+
cin.puts(code)
|
78
|
+
end
|
79
|
+
|
80
|
+
if block_given?
|
81
|
+
Task.open(command, options) do |process|
|
82
|
+
conn = Connection.build(process, options, &send_code)
|
83
|
+
|
84
|
+
yield conn, process.pid
|
85
|
+
|
86
|
+
conn.stop
|
87
|
+
end
|
88
|
+
else
|
89
|
+
process = Task.open(command, options)
|
90
|
+
conn = Connection.build(process, options, &send_code)
|
91
|
+
|
92
|
+
return conn, process.pid
|
93
|
+
end
|
94
|
+
end
|
62
95
|
end
|
data/lib/rexec/task.rb
CHANGED
@@ -1,25 +1,34 @@
|
|
1
|
-
# Copyright (c) 2007 Samuel
|
2
|
-
#
|
3
|
-
# This program is free software: you can redistribute it and/or modify
|
4
|
-
# it under the terms of the GNU General Public License as published by
|
5
|
-
# the Free Software Foundation, either version 3 of the License, or
|
6
|
-
# (at your option) any later version.
|
1
|
+
# Copyright (c) 2007, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
|
7
2
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
12
9
|
#
|
13
|
-
#
|
14
|
-
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
15
20
|
|
16
21
|
require 'thread'
|
17
22
|
|
18
23
|
module RExec
|
24
|
+
private
|
25
|
+
|
19
26
|
RD = 0
|
20
27
|
WR = 1
|
28
|
+
|
29
|
+
public
|
21
30
|
|
22
|
-
#
|
31
|
+
# Cloose all IO other than $stdin, $stdout, $stderr (or those given by the argument except)
|
23
32
|
def self.close_io(except = [$stdin, $stdout, $stderr])
|
24
33
|
# Make sure all file descriptors are closed
|
25
34
|
ObjectSpace.each_object(IO) do |io|
|
@@ -29,6 +38,8 @@ module RExec
|
|
29
38
|
end
|
30
39
|
end
|
31
40
|
|
41
|
+
# Represents a running process, either a child process or a background/daemon process.
|
42
|
+
# Provides an easy high level interface for managing process life-cycle.
|
32
43
|
class Task
|
33
44
|
private
|
34
45
|
def self.pipes_for_options(options)
|
@@ -72,8 +83,10 @@ module RExec
|
|
72
83
|
return pipes
|
73
84
|
end
|
74
85
|
|
75
|
-
#
|
86
|
+
# The standard process pipes.
|
76
87
|
STDPIPES = [STDIN, STDOUT, STDERR]
|
88
|
+
|
89
|
+
# Close all the supplied pipes.
|
77
90
|
def self.close_pipes(*pipes)
|
78
91
|
pipes = pipes.compact.reject{|pipe| STDPIPES.include?(pipe)}
|
79
92
|
|
@@ -112,16 +125,15 @@ module RExec
|
|
112
125
|
|
113
126
|
# Very simple method to spawn a child daemon. A daemon is detatched from the controlling tty, and thus is
|
114
127
|
# not killed when the parent process finishes.
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
# </tt>
|
128
|
+
#
|
129
|
+
# spawn_daemon do
|
130
|
+
# Dir.chdir("/")
|
131
|
+
# File.umask 0000
|
132
|
+
# puts "Hello from daemon!"
|
133
|
+
# sleep(600)
|
134
|
+
# puts "This code will not quit when parent process finishes..."
|
135
|
+
# puts "...but $stdout might be closed unless you set it to a file."
|
136
|
+
# end
|
125
137
|
def self.spawn_daemon(&block)
|
126
138
|
pid_pipe = IO.pipe
|
127
139
|
|
@@ -147,11 +159,10 @@ module RExec
|
|
147
159
|
end
|
148
160
|
|
149
161
|
# Very simple method to spawn a child process
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
154
|
-
# </tt>
|
162
|
+
#
|
163
|
+
# spawn_child do
|
164
|
+
# puts "Hello from child!"
|
165
|
+
# end
|
155
166
|
def self.spawn_child(&block)
|
156
167
|
pid = fork do
|
157
168
|
yield
|
@@ -162,30 +173,87 @@ module RExec
|
|
162
173
|
return pid
|
163
174
|
end
|
164
175
|
|
165
|
-
# Open a process. Similar to IO.popen
|
166
|
-
# stderr and the pid
|
167
|
-
# exception. You
|
176
|
+
# Open a process. Similar to +IO.popen+, but provides a much more generic interface to +stdin+, +stdout+,
|
177
|
+
# +stderr+ and the +pid+. We also attempt to tidy up as much as possible given some kind of error or
|
178
|
+
# exception. You may write to +output+, and read from +input+ and +error+.
|
179
|
+
#
|
180
|
+
# Typical usage looks similar to +IO.popen+:
|
181
|
+
# count = 0
|
182
|
+
# result = Task.open(["ls", "-la"], :passthrough => :err) do |task|
|
183
|
+
# count = task.output.read.split(/\n/).size
|
184
|
+
# end
|
185
|
+
# puts "Count: #{count}" if result.exitstatus == 0
|
186
|
+
#
|
187
|
+
# The basic command can simply be a string, and this will be passed to +Kernel#exec+ which will perform
|
188
|
+
# shell expansion on the arguments.
|
189
|
+
#
|
190
|
+
# If the command passed is an array, this will be executed without shell expansion.
|
191
|
+
#
|
192
|
+
# If a +Proc+ (or anything that +respond_to? :call+) is provided, this will be executed in the child
|
193
|
+
# process. Here is an example of a long running background process:
|
194
|
+
#
|
195
|
+
# daemon = Proc.new do
|
196
|
+
# # Long running process
|
197
|
+
# sleep(1000)
|
198
|
+
# end
|
199
|
+
#
|
200
|
+
# task = Task.open(daemon, :daemonize => true, :in => ..., :out => ..., :err => ...)
|
201
|
+
# exit(0)
|
202
|
+
#
|
203
|
+
# ==== Options
|
168
204
|
#
|
169
|
-
#
|
205
|
+
# [+:passthrough+, +:in+, +:out+, +:err+]
|
206
|
+
# The current process (e.g. ruby) has a set of existing pipes +$stdin+, +$stdout+ and
|
207
|
+
# +$stderr+. These pipes can also be used by the child process. The passthrough option
|
208
|
+
# allows you to specify which pipes are retained from the parent process by the child.
|
209
|
+
#
|
210
|
+
# Typically it is useful to passthrough $stderr, so that errors in the child process
|
211
|
+
# are printed out in the terminal of the parent process:
|
212
|
+
# Task.open([...], :passthrough => :err)
|
213
|
+
# Task.open([...], :passthrough => [:in, :out, :err])
|
214
|
+
# Task.open([...], :passthrough => :all)
|
215
|
+
#
|
216
|
+
# It is also possible to redirect to files, which can be useful if you want to keep a
|
217
|
+
# a log file:
|
218
|
+
# Task.open([...], :out => File.open("output.log"))
|
219
|
+
#
|
220
|
+
# The default behaviour is to create a new pipe, but any pipe (e.g. a network socket)
|
221
|
+
# could be used:
|
222
|
+
# Task.open([...], :in => IO.pipe)
|
223
|
+
#
|
224
|
+
# [+:daemonize+]
|
225
|
+
# The process that is opened may be detached from the parent process. This allows the
|
226
|
+
# child process to exist even if the parent process exits. In this case, you will also
|
227
|
+
# probably want to specify the +:passthrough+ option for log files:
|
228
|
+
# Task.open([...],
|
229
|
+
# :daemonize => true,
|
230
|
+
# :in => File.open("/dev/null"),
|
231
|
+
# :out => File.open("/var/log/child.log", "a"),
|
232
|
+
# :err => File.open("/var/log/child.err", "a")
|
233
|
+
# )
|
170
234
|
#
|
171
|
-
#
|
172
|
-
#
|
173
|
-
#
|
174
|
-
#
|
235
|
+
# [+:env+, +:env!+]
|
236
|
+
# Provide a environment which will be used by the child process. Use +:env+ to update
|
237
|
+
# the exsting environment and +:env!+ to replace it completely.
|
238
|
+
# Task.open([...], :env => {'foo' => 'bar'})
|
175
239
|
#
|
176
|
-
#
|
177
|
-
#
|
240
|
+
# [+:umask+]
|
241
|
+
# Set the umask for the new process, as per +File.umask+.
|
178
242
|
#
|
179
|
-
#
|
180
|
-
#
|
243
|
+
# [+:chdir+]
|
244
|
+
# Set the current working directory for the new process, as per +Dir.chdir+.
|
181
245
|
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
#
|
188
|
-
#
|
246
|
+
# [+:preflight+]
|
247
|
+
# Similar to a proc based command, but executed before execing the given process.
|
248
|
+
# preflight = Proc.new do |command, options|
|
249
|
+
# # Setup some default state before exec the new process.
|
250
|
+
# end
|
251
|
+
#
|
252
|
+
# Task.open([...], :preflight => preflight)
|
253
|
+
#
|
254
|
+
# The options hash is passed directly so you can supply custom arguments to the preflight
|
255
|
+
# function.
|
256
|
+
|
189
257
|
def self.open(command, options = {}, &block)
|
190
258
|
cin, cout, cerr = pipes_for_options(options)
|
191
259
|
spawn = options[:daemonize] ? :spawn_daemon : :spawn_child
|
@@ -197,6 +265,25 @@ module RExec
|
|
197
265
|
STDOUT.reopen(cout[WR]) if cout[WR]
|
198
266
|
STDERR.reopen(cerr[WR]) if cerr[WR]
|
199
267
|
|
268
|
+
if options[:env!]
|
269
|
+
ENV.clear
|
270
|
+
ENV.update(options[:env!])
|
271
|
+
elsif options[:env]
|
272
|
+
ENV.update(options[:env])
|
273
|
+
end
|
274
|
+
|
275
|
+
if options[:umask]
|
276
|
+
File.umask(options[:umask])
|
277
|
+
end
|
278
|
+
|
279
|
+
if options[:chdir]
|
280
|
+
Dir.chdir(options[:chdir])
|
281
|
+
end
|
282
|
+
|
283
|
+
if options[:preflight]
|
284
|
+
preflight.call(command, options)
|
285
|
+
end
|
286
|
+
|
200
287
|
if command.respond_to? :call
|
201
288
|
command.call
|
202
289
|
elsif Array === command
|
@@ -233,7 +320,7 @@ module RExec
|
|
233
320
|
end
|
234
321
|
end
|
235
322
|
|
236
|
-
def initialize(input, output, error, pid)
|
323
|
+
def initialize(input, output, error, pid) # :nodoc:
|
237
324
|
@input = input
|
238
325
|
@output = output
|
239
326
|
@error = error
|
@@ -246,10 +333,19 @@ module RExec
|
|
246
333
|
@result_available = ConditionVariable.new
|
247
334
|
end
|
248
335
|
|
336
|
+
# Standard input to the running task.
|
249
337
|
attr :input
|
338
|
+
|
339
|
+
# Standard output from the running task.
|
250
340
|
attr :output
|
341
|
+
|
342
|
+
# Standard error from the running task.
|
251
343
|
attr :error
|
344
|
+
|
345
|
+
# The PID of the running task.
|
252
346
|
attr :pid
|
347
|
+
|
348
|
+
# The status of the task after calling task.wait.
|
253
349
|
attr :result
|
254
350
|
|
255
351
|
# Returns true if the current task is still running
|