Class: LiveConsole

Inherits:
Object
  • Object
show all
Includes:
Socket::Constants
Defined in:
lib/live_console.rb

Overview

LiveConsole provides a socket that can be connected to via netcat or telnet to use to connect to an IRB session inside a running process. It listens on the specified address/port or Unix Domain Socket path, and presents connecting clients with an IRB shell. Using this, you can execute code on a running instance of a Ruby process to inspect the state or even patch code on the fly.

Defined Under Namespace

Modules: IOMethods

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io_method, opts = {}) ⇒ LiveConsole

call-seq: Bind a LiveConsole to localhost:3030 (only allow clients on this machine to connect): LiveConsole.new :socket, :port => 3030 Accept connections from anywhere on port 3030. Ridiculously insecure: LiveConsole.new(:socket, :port => 3030, :host => ‘0.0.0.0’) Accept connections from anywhere on port 3030, secured with a plain-text credentials file credentials_file should be of the form: username: <username> password: <password> LiveConsole.new(:socket, :port => 3030, :host => ‘0.0.0.0’, :authenticate=>true,

:credentials_file='/path/to/.consoleaccess)

Use a Unix Domain Socket (which is more secure) instead: LiveConsole.new(:unix_socket, :path => ‘/tmp/my_liveconsole.sock’,

:mode => 0600, :uid => Process.uid, :gid => Process.gid)

By default, the mode is 0600, and the uid and gid are those of the current process. These three options are for the file’s permissions. You can also supply a binding for IRB’s toplevel: LiveConsole.new(:unix_socket, :path => “/tmp/live_console_#Process.pid.sock”, :bind => binding)

Creates a new LiveConsole. You must next call LiveConsole#start or LiveConsole#start_blocking when you want to accept connections and start the console.



47
48
49
50
51
52
53
54
55
56
# File 'lib/live_console.rb', line 47

def initialize(io_method, opts = {})
  self.io_method = io_method.to_sym
  self.bind = opts.delete :bind
  self.authenticate = opts.delete :authenticate
  self.readline = opts.delete :readline
  unless IOMethods::List.include?(self.io_method)
    raise ArgumentError, "Unknown IO method: #{io_method}"
  end
  init_io opts
end

Instance Attribute Details

#authenticateObject

Returns the value of attribute authenticate.



21
22
23
# File 'lib/live_console.rb', line 21

def authenticate
  @authenticate
end

#bindObject

Returns the value of attribute bind.



21
22
23
# File 'lib/live_console.rb', line 21

def bind
  @bind
end

#ioObject

Returns the value of attribute io.



21
22
23
# File 'lib/live_console.rb', line 21

def io
  @io
end

#io_methodObject

Returns the value of attribute io_method.



21
22
23
# File 'lib/live_console.rb', line 21

def io_method
  @io_method
end

#readlineObject

Returns the value of attribute readline.



21
22
23
# File 'lib/live_console.rb', line 21

def readline
  @readline
end

#threadObject

Returns the value of attribute thread.



21
22
23
# File 'lib/live_console.rb', line 21

def thread
  @thread
end

Instance Method Details

#restartObject

Restarts. Useful for binding changes. Return value is the same as for LiveConsole#start.



133
134
135
136
137
138
139
140
# File 'lib/live_console.rb', line 133

def restart
  r = lambda { stop; start }
  if thread == Thread.current
    Thread.new &r # Leaks a thread, but works.
  else
    r.call
  end
end

#startObject

LiveConsole#start spawns a thread to listen for, accept, and provide an IRB console to new connections. If a thread is already running, this method simply returns false; otherwise, it returns the new thread.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/live_console.rb', line 61

def start
  if thread
    if thread.alive?
      return false
    else
      thread.join
      self.thread = nil
    end
  end

  self.thread = Thread.new {
    start_blocking
  }
  thread
end

#start_blockingObject

LiveConsole#start_blocking executes a loop to listen for, accept, and provide an IRB console to new connections. This is a blocking call and the only way to stop it is to kill the process



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
# File 'lib/live_console.rb', line 80

def start_blocking
  loop {
    conn = io.get_connection
    #This will block until a connection is made or a failure occurs
    if conn.start
      #fork a new process for the session to redirect stdout/stderr
      pid = fork {
        begin
          start_irb = true
          if authenticate && !conn.authenticate
            conn.stop
            start_irb = false
          end
          if start_irb
            irb_io = GenericIOMethod.new conn.raw_input, conn.raw_output, readline
            begin
              IRB.start_with_io(irb_io, bind)
            rescue Errno::EPIPE => e
              conn.stop
            end
          end
        rescue Exception => e
          puts "Error during connection: #{e.message}"
          conn.stop
        end
      }
      Process.detach(pid)
    end
  }
end

#stopObject

Ends the running thread, if it exists. Returns true if a thread was running, false otherwise.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/live_console.rb', line 113

def stop
  if thread
    if thread == Thread.current
      self.thread = nil
      Thread.current.exit!
    end

    thread.exit
    if thread.join(0.1).nil?
      thread.exit!
    end
    self.thread = nil
    true
  else
    false
  end
end