public
Description: Pure Ruby implementation of an SSH (protocol 2) client
Homepage: http://rubyforge.org/projects/net-ssh
Clone URL: git://github.com/jamis/net-ssh.git
more docs for Net::SSH::Connection::Session. Also added busy? to hide the 
channels implementation.
jamis (author)
Fri Mar 21 14:41:16 -0700 2008
commit  2f81f7a95039eabfa956ef8527ad9cfc21eb7377
tree    7df4a1277c12dbc1af33c8f99ecceaaa84246a1c
parent  677c0e16e67f0a7e7f930062fa40569c002058bb
...
27
28
29
 
 
 
30
31
 
32
33
34
35
36
37
 
38
39
40
...
63
64
65
66
 
67
68
69
...
72
73
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
76
77
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
80
 
81
82
83
84
85
86
87
 
 
88
89
90
91
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
94
 
 
 
 
95
96
 
97
98
99
100
101
 
102
103
104
...
132
133
134
 
 
 
 
 
 
 
 
135
136
137
...
147
148
149
 
 
 
 
 
 
 
 
 
 
 
 
150
151
152
...
160
161
162
163
 
164
165
166
167
168
169
 
 
 
 
 
 
 
 
 
 
 
 
170
171
172
...
194
195
196
 
 
197
198
199
...
201
202
203
204
 
205
206
207
208
209
210
 
 
 
 
 
 
211
212
213
...
215
216
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
219
220
...
225
226
227
228
229
 
 
230
231
232
...
237
238
239
 
 
 
 
240
241
242
...
27
28
29
30
31
32
33
 
34
35
36
 
 
 
 
37
38
39
40
...
63
64
65
 
66
67
68
69
...
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
 
 
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
 
110
111
112
113
114
115
116
 
117
118
119
120
121
122
123
124
125
126
127
128
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
...
183
184
185
186
187
188
189
190
191
192
193
194
195
196
...
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
...
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
...
277
278
279
280
281
282
283
284
...
286
287
288
 
289
290
291
292
293
294
 
295
296
297
298
299
300
301
302
303
...
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
...
346
347
348
 
 
349
350
351
352
353
...
358
359
360
361
362
363
364
365
366
367
0
@@ -27,14 +27,14 @@ module Net; module SSH; module Connection
0
     # The underlying transport layer abstraction (see Net::SSH::Transport::Session).
0
     attr_reader :transport
0
 
0
+ # The map of options that were used to initialize this instance.
0
+ attr_reader :options
0
+
0
     # The map of channels, each key being the local-id for the channel.
0
- attr_reader :channels
0
+ attr_reader :channels #:nodoc:
0
 
0
     # The map of listeners that the event loop knows about. See #listen_to.
0
- attr_reader :listeners
0
-
0
- # The map of options that were used to initialize this instance.
0
- attr_reader :options
0
+ attr_reader :listeners #:nodoc:
0
 
0
     # The map of specialized handlers for opening specific channel types. See
0
     # #on_open_channel.
0
@@ -63,7 +63,7 @@ module Net; module SSH; module Connection
0
     # successfully closed, and then closes the underlying transport layer
0
     # connection.
0
     def close
0
- debug { "closing remaining channels (#{channels.length} open)" }
0
+ info { "closing remaining channels (#{channels.length} open)" }
0
       channels.each { |id, channel| channel.close }
0
       loop(0) { channels.any? }
0
       transport.close
0
@@ -72,33 +72,84 @@ module Net; module SSH; module Connection
0
     # preserve a reference to Kernel#loop
0
     alias :loop_forever :loop
0
 
0
+ # Returns +true+ if there are any channels currently active on this
0
+ # session. By default, this will not include "invisible" channels
0
+ # (such as those created by forwarding ports and such), but if you pass
0
+ # a +true+ value for +include_invisible+, then those will be counted.
0
+ #
0
+ # This can be useful for determining whether the event loop should continue
0
+ # to be run.
0
+ #
0
+ # ssh.loop { ssh.busy? }
0
+ def busy?(include_invisible=false)
0
+ if include_invisible
0
+ channels.any?
0
+ else
0
+ channels.any? { |id, ch| !ch[:invisible] }
0
+ end
0
+ end
0
+
0
     # The main event loop. Calls #process until #process returns false. If a
0
     # block is given, it is passed to #process, otherwise a default proc is
0
- # used that just returns true if there are any channels active. The +wait+
0
- # parameter is also passed through to #process.
0
+ # used that just returns true if there are any channels active (see #busy?).
0
+ # The # +wait+ parameter is also passed through to #process (where it is
0
+ # interpreted as the maximum number of seconds to wait for IO.select to return).
0
+ #
0
+ # # loop for as long as there are any channels active
0
+ # ssh.loop
0
+ #
0
+ # # loop for as long as there are any channels active, but make sure
0
+ # # the event loop runs at least once per 0.1 second
0
+ # ssh.loop(0.1)
0
+ #
0
+ # # loop until ctrl-C is pressed
0
+ # int_pressed = false
0
+ # trap("INT") { int_pressed = true }
0
+ # ssh.loop(0.1) { not int_pressed }
0
     def loop(wait=nil, &block)
0
- running = block || Proc.new { channels.any? { |id,ch| !ch[:invisible] } }
0
+ running = block || Proc.new { busy? }
0
       loop_forever { break unless process(wait, &running) }
0
     end
0
 
0
     # The core of the event loop. It processes a single iteration of the event
0
     # loop. If a block is given, it should return false when the processing
0
     # should abort, which causes #process to return false. Otherwise,
0
- # #process returns true.
0
+ # #process returns true. The session itself is yielded to the block as its
0
+ # only argument.
0
     #
0
     # If +wait+ is nil (the default), this method will block until any of the
0
     # monitored IO objects are ready to be read from or written to. If you want
0
     # it to not block, you can pass 0, or you can pass any other numeric value
0
     # to indicate that it should block for no more than that many seconds.
0
+ # Passing 0 is a good way to poll the connection, but if you do it too
0
+ # frequently it can make your CPU quite busy!
0
+ #
0
+ # This will also cause all active channels to be processed once each (see
0
+ # Net::SSH::Connection::Channel#on_process).
0
+ #
0
+ # # process multiple Net::SSH connections in parallel
0
+ # connections = [
0
+ # Net::SSH.start("host1", ...),
0
+ # Net::SSH.start("host2", ...)
0
+ # ]
0
+ #
0
+ # connections.each do |ssh|
0
+ # ssh.exec "grep something /in/some/files"
0
+ # end
0
+ #
0
+ # condition = Proc.new { |s| s.busy? }
0
     #
0
- # This will cause all active channels to be processed once each.
0
+ # loop do
0
+ # connections.delete_if { |ssh| !ssh.process(0.1, &condition) }
0
+ # break if connections.empty?
0
+ # end
0
     def process(wait=nil)
0
- return false if block_given? && !yield
0
+ return false if block_given? && !yield(self)
0
 
0
       dispatch_incoming_packets
0
       channels.each { |id, channel| channel.process unless channel.closing? }
0
 
0
- return false if block_given? && !yield
0
+ return false if block_given? && !yield(self)
0
 
0
       r = listeners.keys
0
       w = r.select { |w| w.pending_write? }
0
@@ -132,6 +183,14 @@ module Net; module SSH; module Connection
0
     # success or failure is indicated by the callback being invoked, with the
0
     # first parameter being true or false (success, or failure), and the second
0
     # being the packet itself.
0
+ #
0
+ # Generally, Net::SSH will manage global requests that need to be sent
0
+ # (e.g. port forward requests and such are handled in the Net::SSH::Service::Forward
0
+ # class, for instance). However, there may be times when you need to
0
+ # send a global request that isn't explicitly handled by Net::SSH, and so
0
+ # this method is available to you.
0
+ #
0
+ # ssh.send_global_request("keep-alive@openssh.com")
0
     def send_global_request(type, *extra, &callback)
0
       info { "sending global request #{type}" }
0
       msg = Buffer.from(:byte, GLOBAL_REQUEST, :string, type.to_s, :bool, !callback.nil?, *extra)
0
@@ -147,6 +206,18 @@ module Net; module SSH; module Connection
0
     # Net::SSH::Buffer.from. If a callback is given, it will be invoked when
0
     # the server confirms that the channel opened successfully. The sole parameter
0
     # for the callback is the channel object itself.
0
+ #
0
+ # In general, you'll use #open_channel without any arguments; the only
0
+ # time you'd want to set the channel type or pass additional initialization
0
+ # data is if you were implementing an SSH extension.
0
+ #
0
+ # channel = ssh.open_channel do |ch|
0
+ # ch.exec "grep something /some/files" do |ch, success|
0
+ # ...
0
+ # end
0
+ # end
0
+ #
0
+ # channel.wait
0
     def open_channel(type="session", *extra, &on_confirm)
0
       local_id = get_next_channel_id
0
       channel = Channel.new(self, type, local_id, &on_confirm)
0
@@ -160,13 +231,25 @@ module Net; module SSH; module Connection
0
     end
0
 
0
     # A convenience method for executing a command and interacting with it. If
0
- # no block is given, all output printed via $stdout and $stderr. Otherwise,
0
+ # no block is given, all output is printed via $stdout and $stderr. Otherwise,
0
     # the block is called for each data and extended data packet, with three
0
     # arguments: the channel object, a symbol indicating the data type
0
     # (:stdout or :stderr), and the data (as a string).
0
     #
0
     # Note that this method returns immediately, and requires an event loop
0
     # (see Session#loop) in order for the command to actually execute.
0
+ #
0
+ # This is effectively identical to calling #open_channel, and then
0
+ # Net::SSH::Connection::Channel#exec, and then setting up the channel
0
+ # callbacks. However, for most uses, this will be sufficient.
0
+ #
0
+ # ssh.exec "grep something /some/files" do |ch, stream, data|
0
+ # if stream == :stderr
0
+ # puts "ERROR: #{data}"
0
+ # else
0
+ # puts data
0
+ # end
0
+ # end
0
     def exec(command, &block)
0
       open_channel do |channel|
0
         channel.exec(command) do |ch, success|
0
@@ -194,6 +277,8 @@ module Net; module SSH; module Connection
0
     # Same as #exec, except this will block until the command finishes. Also,
0
     # if a block is not given, this will return all output (stdout and stderr)
0
     # as a single string.
0
+ #
0
+ # matches = ssh.exec!("grep something /some/files")
0
     def exec!(command, &block)
0
       block ||= Proc.new do |ch, type, data|
0
         ch[:result] ||= ""
0
@@ -201,13 +286,18 @@ module Net; module SSH; module Connection
0
       end
0
 
0
       channel = exec(command, &block)
0
- loop { channel.active? }
0
+ channel.wait
0
 
0
       return channel[:result]
0
     end
0
 
0
     # Enqueues a message to be sent to the server as soon as the socket is
0
- # available for writing.
0
+ # available for writing. Most programs will never need to call this, but
0
+ # if you are implementing an extension to the SSH protocol, or if you
0
+ # need to send a packet that Net::SSH does not directly support, you can
0
+ # use this to send it.
0
+ #
0
+ # ssh.send_message(Buffer.from(:byte, REQUEST_SUCCESS).to_s)
0
     def send_message(message)
0
       transport.enqueue_message(message)
0
     end
0
@@ -215,6 +305,37 @@ module Net; module SSH; module Connection
0
     # Adds an IO object for the event loop to listen to. If a callback
0
     # is given, it will be invoked when the io is ready to be read, otherwise,
0
     # the io will merely have its #fill method invoked.
0
+ #
0
+ # Any +io+ value passed to this method _must_ have mixed into it the
0
+ # Net::SSH::BufferedIo functionality, typically by calling #extend on the
0
+ # object.
0
+ #
0
+ # The following example executes a process on the remote server, opens
0
+ # a socket to somewhere, and then pipes data from that socket to the
0
+ # remote process' stdin stream:
0
+ #
0
+ # channel = ssh.open_channel do |ch|
0
+ # ch.exec "/some/process/that/wants/input" do |ch, success|
0
+ # abort "can't execute!" unless success
0
+ #
0
+ # io = TCPSocket.new(somewhere, port)
0
+ # io.extend(Net::SSH::BufferedIo)
0
+ # ssh.listen_to(io)
0
+ #
0
+ # ch.on_process do
0
+ # if io.available > 0
0
+ # ch.send_data(io.read_available)
0
+ # end
0
+ # end
0
+ #
0
+ # ch.on_close do
0
+ # ssh.stop_listening_to(io)
0
+ # io.close
0
+ # end
0
+ # end
0
+ # end
0
+ #
0
+ # channel.wait
0
     def listen_to(io, &callback)
0
       listeners[io] = callback
0
     end
0
@@ -225,8 +346,8 @@ module Net; module SSH; module Connection
0
       listeners.delete(io)
0
     end
0
 
0
- # Returns a reference to the Service::Forward service, which can be used
0
- # for forwarding ports over SSH.
0
+ # Returns a reference to the Net::SSH::Service::Forward service, which can
0
+ # be used for forwarding ports over SSH.
0
     def forward
0
       @forward ||= Service::Forward.new(self)
0
     end
0
@@ -237,6 +358,10 @@ module Net; module SSH; module Connection
0
     # raise ChannelOpenFailed if it is unable to open the channel for some
0
     # reason. Otherwise, the channel will be opened and a confirmation message
0
     # sent to the server.
0
+ #
0
+ # This is used by the Net::SSH::Service::Forward service to open a channel
0
+ # when a remote forwarded port receives a connection. However, you are
0
+ # welcome to register handlers for other channel types, as needed.
0
     def on_open_channel(type, &block)
0
       channel_open_handlers[type] = block
0
     end
...
337
338
339
 
340
341
 
342
343
344
...
337
338
339
340
341
 
342
343
344
345
0
@@ -337,8 +337,9 @@ module Connection
0
     def test_ready_readers_that_are_registered_with_a_block_should_call_block_instead_of_fill
0
       io = stub("io", :pending_write? => false)
0
       flag = false
0
+ session.stop_listening_to(socket) # so that we only have to test the presence of a single IO object
0
       session.listen_to(io) { flag = true }
0
- IO.expects(:select).with([socket,io],[],nil,nil).returns([[io],[],[]])
0
+ IO.expects(:select).with([io],[],nil,nil).returns([[io],[],[]])
0
       session.process
0
       assert flag, "callback should have been invoked"
0
     end

Comments

    No one has commented yet.