Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I send SIGINT to a process startet in a Net::SSH session?

Tags:

ruby

net-ssh

I use Net::SSHv2 to connect to a server and start a script on that server. So far it is working, but I want to interrupt the script when it is running for over 10 minutes or the output file gets too big. After receiving the interrupt the script will shutdown and output some statistics.

My current code looks like this:

File.open(filename, "w") do |f|
  Net::SSH.start(host, user, password: password) do |ssh|
    ssh.exec! "do_work"  do |channel, stream, data|
      f << "data"
      #break if f.size > 1024 * 1024 * 100 #file size > 100 MB
      #channel.send_data "^C" if f.size > 1024 * 1024 * 100 #file size > 100 MB
    end
  end
end

I tried a couple of other things with opening a block for the channel and requesting a shell, but it didn't work.

like image 643
Kurt Avatar asked Nov 13 '22 01:11

Kurt


1 Answers

send_data is the correct approach, but:

  1. You need a PTY in order to send control codes to the server, which also means you need to open a channel before you exec on it.
  2. There is nothing in the code to understand the human display convention of a caret character (^) followed by a capital C character to mean "send the character that is generated by a keyboard BIOS when you press CTRL+C". You have to send the ASCII character itself, which is the ETX (end of text) control character and is represented in C hexadecimal escape sequence as \x03.
  3. This is not technically the SIGINT signal - its the terminal break signal which is unrelated to the POSIX signal IPC, but shells often interpret an ETX as "the user wants me to send a SIGINT to the process" - which is the main reason why you need a PTY - it instructs the system shell to honor this "keyboard generated control characters should be converted to signals" convention. There is currently no way to send actual signals over the SSH session with common SSH implementations. See this answer to a similar (though not Ruby specific) question for more details.

Your code should probably look like this:

File.open(filename, "w") do |f|
  Net::SSH.start(host, user, password: password) do |ssh|
    ssh.open_channel do |channel|
      channel.request_pty
      channel.exec "do_work" do |ch, success|
        raise "could not execute command: #{command.inspect}" unless success

        channel.on_data do |ch2, data|
          f << data
        end

        #break if f.size > 1024 * 1024 * 100 #file size > 100 MB
        channel.send_data "\x03" if f.size > 1024 * 1024 * 100 #file size > 100 MB
      end
    end.wait
  end
end

The channel working code was copied more or less verbatim from session.rb source code. Please refer to that for more information.

like image 144
Guss Avatar answered Nov 29 '22 13:11

Guss