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.
@@ -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
- old_env = ENV.to_hash
24
+ old_env = ENV.to_hash
6
25
 
7
- ENV.update(new_env) if new_env
26
+ ENV.update(new_env) if new_env
8
27
 
9
- yield
28
+ yield
10
29
 
11
- ENV.clear
12
- ENV.update(old_env)
30
+ ENV.clear
31
+ ENV.update(old_env)
13
32
  end
14
33
  end
@@ -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
- # Set the user of the current process. Supply either a user ID
7
- # or a user name.
8
- #
9
- # Be aware that on Mac OS X / Ruby 1.8 there are bugs when the user id
10
- # is negative (i.e. it doesn't work). For example "nobody" with uid -2
11
- # won't work.
12
- def self.change_user(user)
13
- if user.kind_of?(String)
14
- user = Etc.getpwnam(user).uid
15
- end
16
-
17
- Process::Sys.setuid(user)
18
- end
19
-
20
-
21
- # Get the user of the current process. Returns the user name.
22
- def self.current_user
23
- uid = Process::Sys.getuid
24
-
25
- Etc.getpwuid(uid).name
26
- end
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
@@ -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
- # Seek to the end of the file
4
- def seek_end
5
- seek(0, IO::SEEK_END)
6
- end
7
-
8
- # Read a chunk of data and then move the file pointer backwards.
9
- #
10
- # Calling this function multiple times will return new data and traverse the file backwards.
11
- #
12
- def read_reverse(length)
13
- offset = tell
14
-
15
- if offset == 0
16
- return nil
17
- end
18
-
19
- start = [0, offset-length].max
20
-
21
- seek(start, IO::SEEK_SET)
22
-
23
- buf = read(offset-start)
24
-
25
- seek(start, IO::SEEK_SET)
26
-
27
- return buf
28
- end
29
-
30
- REVERSE_BUFFER_SIZE = 128
31
-
32
- # This function is very similar to gets but it works in reverse.
33
- #
34
- # You can use it to efficiently read a file line by line backwards.
35
- #
36
- # It returns nil when there are no more lines.
37
- def reverse_gets(sep_string=$/)
38
- end_pos = tell
39
-
40
- offset = nil
41
- buf = ""
42
-
43
- while offset == nil
44
- chunk = read_reverse(REVERSE_BUFFER_SIZE)
45
- return (buf == "" ? nil : buf) if chunk == nil
46
-
47
- buf = chunk + buf
48
-
49
- offset = buf.rindex(sep_string)
50
- end
51
-
52
- line = buf[offset...buf.size].sub(sep_string, "")
53
-
54
- seek((end_pos - buf.size) + offset, IO::SEEK_SET)
55
-
56
- return line
57
- end
58
-
59
- # Similar to each_line but works in reverse. Don't forget to call
60
- # seek_end before you start!
61
- def reverse_each_line(sep_string=$/, &block)
62
- line = reverse_gets(sep_string)
63
-
64
- while line != nil
65
- yield line
66
-
67
- line = reverse_gets(sep_string)
68
- end
69
- end
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 Williams. Released under the GNU GPLv3.
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
- # This program is distributed in the hope that it will be useful,
9
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
- # GNU General Public License for more details.
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
- # You should have received a copy of the GNU General Public License
14
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
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
- class InvalidConnectionError < Exception
23
- end
24
-
25
- @@connection_code = (Pathname.new(__FILE__).dirname + "connection.rb").read
26
- @@client_code = (Pathname.new(__FILE__).dirname + "client.rb").read
27
-
28
- # Start a remote ruby server. This function is a structural cornerstone. This code runs the command you
29
- # supply (this command should start an instance of ruby somewhere), sends it the code in
30
- # <tt>connection.rb</tt> and <tt>client.rb</tt> as well as the code you supply.
31
- #
32
- # Once the remote ruby instance is set up and ready to go, this code will return (or yield) the connection
33
- # and pid of the executed command.
34
- #
35
- # From this point, you can send and receive objects, and interact with the code you provided within a
36
- # remote ruby instance.
37
- #
38
- # If <tt>command</tt> is a shell such as "/bin/sh", and we need to start ruby separately, you can supply
39
- # <tt>options[:ruby] = "/usr/bin/ruby"</tt> to explicitly start the ruby command.
40
- def self.start_server(code, command, options = {}, &block)
41
- options[:passthrough] = :err unless options[:passthrough]
42
-
43
- send_code = Proc.new do |cin|
44
- cin.puts(@@connection_code)
45
- cin.puts(@@client_code)
46
- cin.puts(code)
47
- end
48
-
49
- if block_given?
50
- Task.open(command, options) do |process|
51
- conn = Connection.build(process, options, &send_code)
52
-
53
- yield conn, process.pid
54
- end
55
- else
56
- process = Task.open(command, options)
57
- conn = Connection.build(process, options, &send_code)
58
-
59
- return conn, process.pid
60
- end
61
- end
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 Williams. Released under the GNU GPLv3.
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
- # This program is distributed in the hope that it will be useful,
9
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
- # GNU General Public License for more details.
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
- # You should have received a copy of the GNU General Public License
14
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
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
- # This function closes all IO other than $stdin, $stdout, $stderr
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
- # Close all the supplied pipes
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
- # <tt>
116
- # spawn_daemon do
117
- # Dir.chdir("/")
118
- # File.umask 0000
119
- # puts "Hello from daemon!"
120
- # sleep(600)
121
- # puts "This code will not quit when parent process finishes..."
122
- # puts "...but $stdout might be closed unless you set it to a file."
123
- # end
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
- # <tt>
151
- # spawn_child do
152
- # puts "Hello from child!"
153
- # end
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, but provides a much more generic interface to stdin, stdout,
166
- # stderr and the pid. We also attempt to tidy up as much as possible given some kind of error or
167
- # exception. You are expected to write to output, and read from input and error.
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
- # = Options =
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
- # We can specify a pipe that will be redirected to the current processes pipe. A typical one is
172
- # :err, so that errors in the child process are printed directly to $stderr of the parent process.
173
- # <tt>:passthrough => :err</tt>
174
- # <tt>:passthrough => [:in, :out, :err]</tt> or <tt>:passthrough => :all</tt>
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
- # We can specify a set of pipes other than the standard ones for redirecting to other things, eg
177
- # <tt>:out => File.open("output.log", "a")</tt>
240
+ # [+:umask+]
241
+ # Set the umask for the new process, as per +File.umask+.
178
242
  #
179
- # If you need to supply a pipe manually, you can do that too:
180
- # <tt>:in => IO.pipe</tt>
243
+ # [+:chdir+]
244
+ # Set the current working directory for the new process, as per +Dir.chdir+.
181
245
  #
182
- # You can specify <tt>:daemonize => true</tt> to cause the child process to detatch. In this
183
- # case you will generally want to specify files for <tt>:in, :out, :err</tt> e.g.
184
- # <tt>
185
- # :in => File.open("/dev/null"),
186
- # :out => File.open("/var/log/my.log", "a"),
187
- # :err => File.open("/var/log/my.err", "a")
188
- # </tt>
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