class Net::SSH::Connection::Channel

values.
well as the remaining window size, using the reader attributes for those
if you so desire, but you can always inspect the current maximums, as
by Net::SSH::Connection::Channel, so you may remain blissfully ignorant
sizes and maximum window sizes. These details are managed internally
Note that data sent across SSH channels are governed by maximum packet
multiple open channels at the same time.
the writing of state machines, especially when you may be juggling
store arbitrary state information on a channel object. This helps simplify
Channels also have a basic hash-like interface, that allows programs to
ssh.loop
end
end
end
puts “channel is closing!”
channel.on_close do |ch|
end
puts “got stderr: #{data}”
channel.on_extended_data do |ch, type, data|
end
channel.send_data “something for stdinn”
puts “got stdout: #{data}”
channel.on_data do |ch, data|
abort “could not execute command” unless success
channel.exec(“/invoke/some/command”) do |ch, success|
ssh.open_channel do |channel|
state machines can be built on top of the SSH protocol.
wrap a channel. See Net::SCP and Net::SFTP for examples of how complex
state machines. Complex programs are best implemented as objects that
Programming with Net::SSH works best if you think of your programs as
opened channel, which are called in response to the corresponding events.
callback is invoked. Then, you set various other callbacks on the newly
opened (via Connection::Session#open_channel), and when it is opened, your
Channels are intended to be used asynchronously. You request that one be
with the Net::SSH library will involve using one or more channels.
This class represents a single such channel. Most operations performed
single SSH channel, each operating independently and seemingly in parallel.
The channel abstraction. Multiple “channels” can be multiplexed onto a

def [](name)

A shortcut for accessing properties of the channel (see #properties).
def [](name)
  @properties[name]
end

def []=(name, value)

A shortcut for setting properties of the channel (see #properties).
def []=(name, value)
  @properties[name] = value
end

def active?

ssh.loop { channel.active? }

been closed or not.
and false otherwise. This can be used to determine whether a channel has
Returns true if the channel exists in the channel list of the session,
def active?
  connection.channels.key?(local_id)
end

def close

the CHANNEL_CLOSE message will be sent from event loop
Requests that the channel be closed. It only marks the channel to be closed
def close
  return if @closing
  @closing = true
end

def closing?

sending buffered output to server.
This may be true for awhile before closed? returns true if we are still
closed?() to determine if we have actually sent CHANNEL_CLOSE to server.
be sent then the channel will close after all the data is sent. See
True if close() has been called; NOTE: if the channel has data waiting to
def closing?
  @closing
end

def do_close

The channel is the only argument.
Invokes the #on_close callback when the server closes a channel.
def do_close
  @on_close.call(self) if @on_close
end

def do_data(data) # :nodoc:

:nodoc:
as the first argument, and the data as the second.
the window size is too small. The callback is invoked with the channel
but does not actually throttle requests that come in illegally when
channel. This will reduce the available window size on the local end,
Invokes the #on_data callback when the server sends data to the
def do_data(data) # :nodoc:
  update_local_window_size(data.length)
  @on_data.call(self, data) if @on_data
end

def do_eof

as the argument.
further data is forthcoming. The callback is invoked with the channel
Invokes the #on_eof callback when the server indicates that no
def do_eof
  @on_eof.call(self) if @on_eof
end

def do_extended_data(type, data)

type, and data.
size on the local end. The callback is invoked with the channel,
extended data to the channel. This will reduce the available window
Invokes the #on_extended_data callback when the server sends
def do_extended_data(type, data)
  update_local_window_size(data.length)
  @on_extended_data.call(self, type, data) if @on_extended_data
end

def do_failure

argument.
Invokes the next pending request callback with +false+ as the second
def do_failure
  if callback = pending_requests.shift
    callback.call(self, false)
  else
    error { "channel failure received with no pending request to handle it (bug?)" }
  end
end

def do_open_confirmation(remote_id, max_window, max_packet) # :nodoc:

:nodoc:
the channel itself as the sole argument.
given when the channel was created, it is invoked at this time with
packet sizes, respectively. If an open-confirmation callback was
and max_window and max_packet are the maximum window and maximum
The remote_id is the id of the channel as assigned by the remote host,
Invoked when the server confirms that a channel has been opened.
def do_open_confirmation(remote_id, max_window, max_packet) # :nodoc:
  @remote_id = remote_id
  @remote_window_size = @remote_maximum_window_size = max_window
  @remote_maximum_packet_size = max_packet
  connection.forward.agent(self) if connection.options[:forward_agent] && type == "session"
  forward_local_env(connection.options[:send_env]) if connection.options[:send_env]
  set_remote_env(connection.options[:set_env]) if connection.options[:set_env]
  @on_confirm_open.call(self) if @on_confirm_open
end

def do_open_failed(reason_code, description)

will be raised.
and description as arguments. Otherwise, a ChannelOpenFailed exception
callback was specified, it will be invoked with the channel, reason code,
Invoked when the server failed to open the channel. If an #on_open_failed
def do_open_failed(reason_code, description)
  if @on_open_failed
    @on_open_failed.call(self, reason_code, description)
  else
    raise ChannelOpenFailed.new(reason_code, description)
  end
end

def do_request(request, want_reply, data) # :nodoc:

:nodoc:
request-specific data as the second.
callback should accept the channel as the first argument, and the
CHANNEL_SUCCESS, unless the callback raised ChannelRequestFailed. The
to handle the request, CHANNEL_FAILURE will be sent. Otherwise,
either CHANNEL_SUCCESS or CHANNEL_FAILURE type. If there was no callback
it is invoked. If +want_reply+ is true, a packet will be sent of
callback has been registered for the specific type of this request,
Invoked when the server sends a channel request. If any #on_request
def do_request(request, want_reply, data) # :nodoc:
  result = true
  begin
    callback = @on_request[request] or raise ChannelRequestFailed
    callback.call(self, data)
  rescue ChannelRequestFailed
    result = false
  end
  if want_reply
    msg = Buffer.from(:byte, result ? CHANNEL_SUCCESS : CHANNEL_FAILURE, :long, remote_id)
    connection.send_message(msg)
  end
end

def do_success

argument.
Invokes the next pending request callback with +true+ as the second
def do_success
  if callback = pending_requests.shift
    callback.call(self, true)
  else
    error { "channel success received with no pending request to handle it (bug?)" }
  end
end

def do_window_adjust(bytes) # :nodoc:

:nodoc:
from the local end to the remote end of the channel.
number of bytes. This has the effect of allowing more data to be sent
causes the remote window size to be adjusted upwards by the given
Invoked when the server sends a CHANNEL_WINDOW_ADJUST packet, and
def do_window_adjust(bytes) # :nodoc:
  @remote_maximum_window_size += bytes
  @remote_window_size += bytes
end

def enqueue_pending_output # :nodoc:

:nodoc:
generally not need to invoke it directly.
is called from the event loop (Connection::Session#process). You will
#do_open_confirmation). This is called automatically by #process, which
does nothing if the channel has not yet been confirmed open (see
Enqueues pending output at the connection as CHANNEL_DATA packets. This
def enqueue_pending_output # :nodoc:
  return unless remote_id
  while output.length > 0
    length = output.length
    length = remote_window_size if length > remote_window_size
    length = remote_maximum_packet_size if length > remote_maximum_packet_size
    if length > 0
      connection.send_message(Buffer.from(:byte, CHANNEL_DATA, :long, remote_id, :string, output.read(length)))
      output.consume!
      @remote_window_size -= length
    else
      break
    end
  end
end

def env(variable_name, variable_value, &block)

channel.env "PATH", "/usr/local/bin"

environment variables you want to send.
need to update the AcceptEnv setting in the sshd_config to include the
discretion. If you are connecting to an OpenSSH server, you will
refuse to set certain environment variables, or all, at the server's
process' environment. Note that for security reasons, the server may
Syntactic sugar for setting an environment variable in the remote
def env(variable_name, variable_value, &block)
  send_channel_request("env", :string, variable_name, :string, variable_value, &block)
end

def eof!

The CHANNEL_EOF packet will be sent once the output buffer is empty.
from this end of the channel. The remote end may still send data.
Tells the remote end of the channel that no more data is forthcoming
def eof!
  return if eof?
  @eof = true
end

def eof?

this is true will result in an exception being raised.
data is forthcoming (see #eof!). Trying to send data via #send_data when
Returns true if the local end of the channel has declared that no more
def eof?
  @eof
end

def exec(command, &block)

end
end
puts "alas! the command could not be invoked!"
else
# this is a good place to hang callbacks like #on_data...
puts "command has begun executing..."
if success
channel.exec "ls -l /home" do |ch, success|

command altogether failed to be executed.
is being executed, not that it has completed, and failure means that the
request succeeded or not. In this case, success means that the command
channel, and the second will be true or false, indicating whether the
called when the server responds. The first parameter will be the
that the given command be invoked. If the block is given, it will be
Syntactic sugar for executing a command. Sends a channel request asking
def exec(command, &block)
  send_channel_request("exec", :string, command, &block)
end

def forward_local_env(env_variable_patterns)

channel.forward_local_env [/^GIT_.*$/, "LANG"]

A variable name can either be described by a +Regexp+ or +String+.
environment.
Gets an +Array+ of local environment variables in the remote process'
def forward_local_env(env_variable_patterns)
  Array(env_variable_patterns).each do |env_variable_pattern|
    matched_variables = ENV.find_all do |env_name, _|
      case env_variable_pattern
      when Regexp then env_name =~ env_variable_pattern
      when String then env_name == env_variable_pattern
      end
    end
    matched_variables.each do |env_name, env_value|
      self.env(env_name, env_value)
    end
  end
end

def initialize(connection, type, local_id, max_pkt_size = 0x8000, max_win_size = 0x20000, &on_confirm_open)

This also sets the default maximum packet size and maximum window size.

that time (see #do_open_confirmation).
the channel is confirmed open by the server, and will be invoked at
and with the given id. If a block is given, it will be remembered until
Instantiates a new channel on the given connection, of the given type,
def initialize(connection, type, local_id, max_pkt_size = 0x8000, max_win_size = 0x20000, &on_confirm_open)
  self.logger = connection.logger
  @connection = connection
  @type       = type
  @local_id   = local_id
  @local_maximum_packet_size = max_pkt_size
  @local_window_size = @local_maximum_window_size = max_win_size
  @on_confirm_open = on_confirm_open
  @output = Buffer.new
  @properties = {}
  @pending_requests = []
  @on_open_failed = @on_data = @on_extended_data = @on_process = @on_close = @on_eof = nil
  @on_request = {}
  @closing = @eof = @sent_eof = @local_closed = @remote_closed = false
end

def local_closed?

True if we have sent CHANNEL_CLOSE to the remote server.
def local_closed?
  @local_closed
end

def on_close(&block)

end
puts "remote end is closing!"
channel.on_close do |ch|

channel is closed. This is invoked with the channel as the sole argument.
Registers a callback to be invoked when the server acknowledges that a
def on_close(&block)
  old, @on_close = @on_close, block
  old
end

def on_data(&block)

process to its +stdout+ stream.
Data received this way is typically the data written by the remote

end
puts "got data: #{data.inspect}"
channel.on_data do |ch, data|

and the data as the second.
channel. The callback is called with the channel as the first argument,
Registers a callback to be invoked when data packets are received by the
def on_data(&block)
  old, @on_data = @on_data, block
  old
end

def on_eof(&block)

end
puts "remote end is done sending data"
channel.on_eof do |ch|

data to the server). The channel is the sole argument to the callback.
data will be sent to the channel (although the channel can still send
Registers a callback to be invoked when the server indicates that no more
def on_eof(&block)
  old, @on_eof = @on_eof, block
  old
end

def on_extended_data(&block)

end
puts "got stderr: #{data.inspect}"
channel.on_extended_data do |ch, type, data|

protocol.
(+type+ == 1). Other extended data types are not defined by the SSH
the third. Extended data is almost exclusively used to send +stderr+ data
argument, the data type (as an integer) as the second, and the data as
by the channel. The callback is called with the channel as the first
Registers a callback to be invoked when extended data packets are received
def on_extended_data(&block)
  old, @on_extended_data = @on_extended_data, block
  old
end

def on_open_failed(&block)

channel.on_open_failed { |ch, code, desc| ... }

end
# ..
channel = session.open_channel do |ch|

description of the failure from the server.
along with the integer "reason code" for the failure, and a textual
the requested channel. The channel itself will be passed to the block,
Registers a callback to be invoked when the server was unable to open
def on_open_failed(&block)
  old, @on_open_failed = @on_open_failed, block
  old
end

def on_process(&block)

end
end
channel[:data] = $'
puts $&
if channel[:data] =~ /^.*?\n/
channel.on_process do |ch|

end
channel[:data] << data
channel.on_data do |ch, data|

channel[:data] = ""

at a time when the channel is processed:
the channel itself, and displays individual lines in the input one
Here's an example that accumulates the channel data into a variable on

as the sole argument.
connection (not the channel). This callback is invoked with the channel
but it will be called roughly once for each packet received by the
this channel. There are no guarantees on timeliness in the event loop,
Registers a callback to be invoked for each pass of the event loop for
def on_process(&block)
  old, @on_process = @on_process, block
  old
end

def on_request(type, &block)

end
puts "process terminated with exit status: #{data.read_long}"
channel.on_request "exit-status" do |ch, data|

request type.)
data, via data.read_string. (Not all SSH servers support this channel
being sent to it, the signal will be reported as a string in the
* "exit-signal" : if the remote process died as a result of a signal
data.read_long.
as a long integer in the data buffer, which you can grab via
* "exit-status" : the exit status of the remote process will be reported

for are:
Some common channel requests that your programs might want to listen

Net::SSH::ChannelRequestFailed.
registered callback to result in a CHANNEL_FAILURE response, just raise
callback, and CHANNEL_FAILURE for any that wasn't, but if you want your
CHANNEL_SUCCESS response for any request that was handled by a registered
By default, if the request wants a reply, Net::SSH will send a

according to the kind of request you are watching.
will be a Net::SSH::Buffer that you will need to parse, yourself,
argument, and the associated (unparsed) data as the second. The data
type is received. The callback will receive the channel as the first
Registers a callback to be invoked when a channel request of the given
def on_request(type, &block)
  old, @on_request[type] = @on_request[type], block
  old
end

def process

pending output to be enqueued as CHANNEL_DATA packets (see #enqueue_pending_output).
invoked (passing the channel itself as an argument). It also causes all
If an #on_process handler has been set up, this will cause it to be
def process
  @on_process.call(self) if @on_process
  enqueue_pending_output
  if @eof and not @sent_eof and output.empty? and remote_id and not @local_closed
    connection.send_message(Buffer.from(:byte, CHANNEL_EOF, :long, remote_id))
    @sent_eof = true
  end
  if @closing and not @local_closed and output.empty? and remote_id
    connection.send_message(Buffer.from(:byte, CHANNEL_CLOSE, :long, remote_id))
    @local_closed = true
    connection.cleanup_channel(self)
  end
end

def remote_closed!

def remote_closed!
  @remote_closed = true
end

def remote_closed?

def remote_closed?
  @remote_closed
end

def request_pty(opts = {}, &block)

end
end
puts "could not obtain pty"
else
puts "pty successfully obtained"
if success
channel.request_pty do |ch, success|

run when a pty is not present.
scripts (.bashrc and such) are not run by default, whereas they are
Note, too, that when a pty is requested, user's shell configuration

instead of prompt if they ever need some user interaction.
some systems, will not be able to run interactively, and will error
Note, that without a pty some programs (e.g. sudo, or subversion) on

screen-based program (e.g., vim, or some menuing system).
This is useful when you want to invoke and interact with some kind of
Requests that a pseudo-tty (or "pty") be made available for this channel.
def request_pty(opts = {}, &block)
  extra = opts.keys - VALID_PTY_OPTIONS.keys
  raise ArgumentError, "invalid option(s) to request_pty: #{extra.inspect}" if extra.any?
  opts = VALID_PTY_OPTIONS.merge(opts)
  modes = opts[:modes].inject(Buffer.new) do |memo, (mode, data)|
    memo.write_byte(mode).write_long(data)
  end
  # mark the end of the mode opcode list with a 0 byte
  modes.write_byte(0)
  send_channel_request("pty-req", :string, opts[:term],
                       :long, opts[:chars_wide], :long, opts[:chars_high],
                       :long, opts[:pixels_wide], :long, opts[:pixels_high],
                       :string, modes.to_s, &block)
end

def send_channel_request(request_name, *data, &callback)

convenient helper methods (see #exec and #subsystem).
Most channel requests you'll want to send are already wrapped in more

end
end
puts "could not start user shell"
else
puts "user shell started successfully"
if success
channel.send_channel_request "shell" do |ch, success|

is dependent on the specific request that was sent.
succeeded or not. The meaning of "success" and "failure" in this context
either true or false as the second, depending on whether the request
callback to be invoked with the channel as the first argument, and
response to this request. Responses, where required, will cause the
reply is required. If no block is given, the server will send no
request, and the packet will be flagged so that the server knows a
If a block is given, it is registered as a callback for a pending
arguments. See Net::SSH::Buffer.from for a description of their format.
parameter must either be empty, or consist of an even number of
Sends a new channel request with the given name. The extra +data+
def send_channel_request(request_name, *data, &callback)
  info { "sending channel request #{request_name.inspect}" }
  fail "Channel open not yet confirmed, please call send_channel_request(or exec) from block of open_channel" unless remote_id
  msg = Buffer.from(:byte, CHANNEL_REQUEST,
                    :long, remote_id, :string, request_name,
                    :bool, !callback.nil?, *data)
  connection.send_message(msg)
  pending_requests << callback if callback
end

def send_data(data)

channel.send_data("the password\n")

that no more data will be sent (see #eof!).
This will raise an exception if the channel has previously declared

the remote end-point.)
instance, it has filled its data window and has not yet been resized by
is accepting data. (A connection might not be accepting data if, for
preparatory to being packaged up and sent out the next time the connection
but instead merely appends the given data to the channel's output buffer,
Note that it does not immediately send the data across the channel,
effect of sending the given string to the remote process' stdin stream.
Sends data to the channel's remote endpoint. This usually has the
def send_data(data)
  raise EOFError, "cannot send data if channel has declared eof" if eof?
  output.append(data.to_s)
end

def set_remote_env(env)

channel.set_remote_env foo: 'bar', baz: 'whale'

Set a +Hash+ of environment variables in the remote process' environment.
def set_remote_env(env)
  env.each { |key, value| self.env(key, value) }
end

def subsystem(subsystem, &block)

end
end
puts "subsystem could not be started"
else
puts "subsystem successfully started"
if success
channel.subsystem("sftp") do |ch, success|

SFTP.
you are the implementor of something that consumes an SSH subsystem, like
the transport. Generally, you'll never need to call this directly unless
are a way for other protocols (like SFTP) to be run, using SSH as
Syntactic sugar for requesting that a subsystem be started. Subsystems
def subsystem(subsystem, &block)
  send_channel_request("subsystem", :string, subsystem, &block)
end

def update_local_window_size(size)

server telling it that the window size has grown.
threshold), a CHANNEL_WINDOW_ADJUST message will be sent to the
size drops to less than half of the local maximum (an arbitrary
Updates the local window size by the given amount. If the window
def update_local_window_size(size)
  @local_window_size -= size
  if local_window_size < local_maximum_window_size / 2
    connection.send_message(
      Buffer.from(:byte, CHANNEL_WINDOW_ADJUST, :long, remote_id, :long, LOCAL_WINDOW_SIZE_INCREMENT)
    )
    @local_window_size += LOCAL_WINDOW_SIZE_INCREMENT
    if @local_maximum_window_size < @local_window_size || @local_maximum_window_size < GOOD_LOCAL_MAXIUMUM_WINDOW_SIZE
      @local_maximum_window_size += LOCAL_WINDOW_SIZE_INCREMENT
    end
  end
end

def wait

channel.wait
channel.exec("grep ...") { ... }

handy for blocking while you wait for some channel to finish.
Runs the SSH event loop until the channel is no longer active. This is
def wait
  connection.loop { active? }
end

def wait_until_open_confirmed

experimental api
Runs the SSH event loop until the remote confirmed channel open
def wait_until_open_confirmed
  connection.loop { !remote_id }
end