live_console 0.2.1 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/{doc/LICENSE → LICENSE} +9 -9
- data/{doc/README → README} +27 -30
- data/{doc → examples}/lc_example.rb +0 -0
- data/{doc → examples}/lc_unix_example.rb +0 -0
- data/lib/live_console.rb +274 -206
- data/lib/live_console/io_methods.rb +44 -51
- data/lib/live_console/io_methods/socket_io/connection.rb +52 -0
- data/lib/live_console/io_methods/socket_io/socket_io.rb +19 -0
- data/lib/live_console/io_methods/unix_socket/connection.rb +36 -0
- data/lib/live_console/io_methods/unix_socket/unix_socket.rb +22 -0
- data/lib/version.rb +3 -0
- metadata +45 -51
- data/Rakefile +0 -88
- data/lib/live_console/io_methods/socket_io.rb +0 -27
- data/lib/live_console/io_methods/unix_socket_io.rb +0 -31
- data/lib/live_console_config.rb +0 -9
data/{doc/LICENSE → LICENSE}
RENAMED
@@ -4,16 +4,16 @@ Permission is hereby granted, free of charge, to any person obtaining a
|
|
4
4
|
copy of this software and associated documentation files (the "Software"),
|
5
5
|
to deal in the Software without restriction, including without limitation
|
6
6
|
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
7
|
-
and/or sell copies of the Software, and to permit persons to whom the
|
7
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
8
8
|
Software is furnished to do so, subject to the following conditions:
|
9
9
|
|
10
|
-
The above copyright notice and this permission notice shall be included in
|
11
|
-
all copies or substantial portions of the Software.
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
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
|
16
|
-
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
18
|
-
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
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
|
16
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
18
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
19
19
|
DEALINGS IN THE SOFTWARE.
|
data/{doc/README → README}
RENAMED
@@ -11,13 +11,12 @@ For example, you can:
|
|
11
11
|
* Patch code on the fly, without a restart.
|
12
12
|
* Let anyone on the net 0wn you if you bind to a public interface. :)
|
13
13
|
It's useful as a diagnostic tool, a debugging tool, and a way to impress your
|
14
|
-
friends
|
14
|
+
friends.
|
15
15
|
|
16
16
|
== Stern Security Warning. Grrr.
|
17
17
|
|
18
18
|
Have a look at the bugs section. It should be pretty apparent that incorrect
|
19
|
-
use of this library could create a large security hole
|
20
|
-
authentication is implemented.
|
19
|
+
use of this library could create a large security hole.
|
21
20
|
|
22
21
|
== Installation
|
23
22
|
|
@@ -25,10 +24,6 @@ You can install via rubygems,
|
|
25
24
|
|
26
25
|
gem install live_console
|
27
26
|
|
28
|
-
or plain old setup.rb:
|
29
|
-
|
30
|
-
ruby setup.rb install
|
31
|
-
|
32
27
|
== How to use LiveConsole
|
33
28
|
|
34
29
|
LiveConsole is very easy to use in your own app:
|
@@ -59,17 +54,17 @@ LiveConsole is very easy to use in your own app:
|
|
59
54
|
# Of course, you must restart before IRB will see the new binding:
|
60
55
|
lc.restart
|
61
56
|
|
62
|
-
Have a look at
|
57
|
+
Have a look at examples/lc_example.rb or examples/lc_unix_example.rb for brief examples
|
63
58
|
of how to use LiveConsole.
|
64
59
|
|
65
60
|
Try just running it:
|
66
61
|
|
67
|
-
$ ruby
|
62
|
+
$ ruby examples/lc_example.rb 4000 test
|
68
63
|
# Then, in a different shell:
|
69
64
|
$ netcat localhost 4000
|
70
65
|
irb(main):001:0> puts 'Wow, magic!'
|
71
66
|
|
72
|
-
$ ruby
|
67
|
+
$ ruby examples/lc_unix_example.rb /tmp/live-console.sock
|
73
68
|
# Then, in a different shell:
|
74
69
|
$ udscat /tmp/live-console.sock
|
75
70
|
irb(main):001:0> puts 'Words cannot describe the joy I feel.'
|
@@ -80,11 +75,11 @@ you've diagnosed and fixed whatever the problem was.
|
|
80
75
|
|
81
76
|
Additionally, if you want to run LiveConsole on a server, but run netcat
|
82
77
|
locally, you can use SSH port forwarding to avoid having to open LiveConsole
|
83
|
-
to the world:
|
78
|
+
to the world:
|
84
79
|
|
85
80
|
ssh -L4000:localhost:4000 you@server
|
86
81
|
|
87
|
-
Then, locally, you can do
|
82
|
+
Then, locally, you can do
|
88
83
|
|
89
84
|
netcat localhost 4000
|
90
85
|
|
@@ -93,26 +88,27 @@ only works for the TCP socket mode.
|
|
93
88
|
|
94
89
|
== Bugs
|
95
90
|
|
96
|
-
LiveConsole lacks many of the niceties of IRB on the console, like Readline
|
97
|
-
support.
|
91
|
+
LiveConsole lacks many of the niceties of IRB on the console, like full Readline
|
92
|
+
support. LiveConsole does offer tab completion if you write a client that sends
|
93
|
+
the phrase to complete, ending with a tab and newline. LiveConsole will activate the IRB
|
94
|
+
completion proc and send back the results, terminated by newline.
|
95
|
+
|
96
|
+
Readline history support can be added if anyone finds it useful to manage history
|
97
|
+
remotely.
|
98
|
+
|
99
|
+
For full Readline support, you can actually use the wonderful rlwrap program, which
|
100
|
+
wraps an arbitrary interactive program in readline. For example, to connect to
|
101
|
+
a LiveConsole on localhost:3333, use
|
102
|
+
rlwrap netcat localhost 3333
|
103
|
+
rlwrap is available with most Linux distributions or at
|
104
|
+
http://utopia.knoware.nl/~hlub/uck/rlwrap/ . It is seriously an incredibly
|
105
|
+
useful piece of software.
|
106
|
+
|
98
107
|
|
99
108
|
Typing exit, hitting ^D, or sending signals (like INT or STOP) doesn't work.
|
100
109
|
Just exit the program you used to connect to it. This has more to do with the
|
101
110
|
program you use to connect to the socket.
|
102
111
|
|
103
|
-
For TCP connections, there is no authentication support yet, although it is
|
104
|
-
planned for the near future. This creates a security risk: anyone that can
|
105
|
-
connect to the socket can run arbitrary Ruby code as the user who owns the
|
106
|
-
process. In fact, even binding to localhost can be a security issue if you're
|
107
|
-
on a box with any untrusted users. If there's a chance you don't know what
|
108
|
-
you're doing, avoid using this library. The Unix Domain Socket version is more
|
109
|
-
secure, as you can control access via filesystem permissions.
|
110
|
-
|
111
|
-
Only one client can connect at a time. I don't think anyone needs multiple LC
|
112
|
-
connections to serve multiple instances of IRB to various clients, but if you
|
113
|
-
need it, let me know.
|
114
|
-
|
115
|
-
The README contains a slur against Lisp guys. Please stop hitting me with that PDP-10 manual. I love your language and the lambda tattoo on your chest.
|
116
112
|
|
117
113
|
Other than that, LiveConsole doesn't have any known bugs, but it is odd
|
118
114
|
software that also monkey-patches IRB, so they are likely to be there. Bug
|
@@ -120,8 +116,9 @@ reports and patches gratefully accepted.
|
|
120
116
|
|
121
117
|
== Credits
|
122
118
|
|
123
|
-
|
119
|
+
Jennifer Hickey, project owner -- (jencompgeek(a)gmail.com)
|
124
120
|
|
125
|
-
|
121
|
+
Pete Elmore, original author -- (pete.elmore(a)gmail.com)
|
126
122
|
|
127
|
-
http://
|
123
|
+
Roger D. Pack (http://betterlogic.com/roger/) provided patches and Windows
|
124
|
+
support
|
File without changes
|
File without changes
|
data/lib/live_console.rb
CHANGED
@@ -1,227 +1,295 @@
|
|
1
1
|
# LiveConsole
|
2
2
|
# Pete Elmore (pete.elmore@gmail.com), 2007-10-18
|
3
|
-
#
|
3
|
+
# Jennifer Hickey
|
4
4
|
# See doc/LICENSE.
|
5
5
|
|
6
6
|
require 'irb'
|
7
7
|
require 'irb/frame'
|
8
8
|
require 'socket'
|
9
|
-
require '
|
9
|
+
require 'irb/completion'
|
10
10
|
|
11
11
|
# LiveConsole provides a socket that can be connected to via netcat or telnet
|
12
|
-
# to use to connect to an IRB session inside a running process. It
|
13
|
-
#
|
12
|
+
# to use to connect to an IRB session inside a running process. It listens on the
|
13
|
+
# specified address/port or Unix Domain Socket path,
|
14
14
|
# and presents connecting clients with an IRB shell. Using this, you can
|
15
15
|
# execute code on a running instance of a Ruby process to inspect the state or
|
16
|
-
# even patch code on the fly.
|
16
|
+
# even patch code on the fly.
|
17
17
|
class LiveConsole
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
18
|
+
include Socket::Constants
|
19
|
+
autoload :IOMethods, 'live_console/io_methods'
|
20
|
+
|
21
|
+
attr_accessor :io_method, :io, :thread, :bind, :authenticate, :readline
|
22
|
+
private :io_method=, :io=, :thread=, :authenticate=, :readline=
|
23
|
+
|
24
|
+
# call-seq:
|
25
|
+
## Bind a LiveConsole to localhost:3030 (only allow clients on this
|
26
|
+
## machine to connect):
|
27
|
+
# LiveConsole.new :socket, :port => 3030
|
28
|
+
## Accept connections from anywhere on port 3030. Ridiculously insecure:
|
29
|
+
# LiveConsole.new(:socket, :port => 3030, :host => '0.0.0.0')
|
30
|
+
## Accept connections from anywhere on port 3030, secured with a plain-text credentials file
|
31
|
+
## credentials_file should be of the form:
|
32
|
+
### username: <username>
|
33
|
+
### password: <password>
|
34
|
+
# LiveConsole.new(:socket, :port => 3030, :host => '0.0.0.0', :authenticate=>true,
|
35
|
+
# :credentials_file='/path/to/.consoleaccess)
|
36
|
+
## Use a Unix Domain Socket (which is more secure) instead:
|
37
|
+
# LiveConsole.new(:unix_socket, :path => '/tmp/my_liveconsole.sock',
|
38
|
+
# :mode => 0600, :uid => Process.uid, :gid => Process.gid)
|
39
|
+
## By default, the mode is 0600, and the uid and gid are those of the
|
40
|
+
## current process. These three options are for the file's permissions.
|
41
|
+
## You can also supply a binding for IRB's toplevel:
|
42
|
+
# LiveConsole.new(:unix_socket,
|
43
|
+
# :path => "/tmp/live_console_#{Process.pid}.sock", :bind => binding)
|
44
|
+
#
|
45
|
+
# Creates a new LiveConsole. You must next call LiveConsole#start or LiveConsole#start_blocking
|
46
|
+
# when you want to accept connections and start the console.
|
47
|
+
def initialize(io_method, opts = {})
|
48
|
+
self.io_method = io_method.to_sym
|
49
|
+
self.bind = opts.delete :bind
|
50
|
+
self.authenticate = opts.delete :authenticate
|
51
|
+
self.readline = opts.delete :readline
|
52
|
+
unless IOMethods::List.include?(self.io_method)
|
53
|
+
raise ArgumentError, "Unknown IO method: #{io_method}"
|
54
|
+
end
|
55
|
+
init_io opts
|
56
|
+
end
|
57
|
+
|
58
|
+
# LiveConsole#start spawns a thread to listen for, accept, and provide an
|
59
|
+
# IRB console to new connections. If a thread is already running, this
|
60
|
+
# method simply returns false; otherwise, it returns the new thread.
|
61
|
+
def start
|
62
|
+
if thread
|
63
|
+
if thread.alive?
|
64
|
+
return false
|
65
|
+
else
|
66
|
+
thread.join
|
67
|
+
self.thread = nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
self.thread = Thread.new {
|
72
|
+
start_blocking
|
73
|
+
}
|
74
|
+
thread
|
75
|
+
end
|
76
|
+
|
77
|
+
# LiveConsole#start_blocking executes a loop to listen for, accept,
|
78
|
+
# and provide an IRB console to new connections.
|
79
|
+
# This is a blocking call and the only way to stop it is to kill the process
|
80
|
+
def start_blocking
|
81
|
+
loop {
|
82
|
+
conn = io.get_connection
|
83
|
+
#This will block until a connection is made or a failure occurs
|
84
|
+
if conn.start
|
85
|
+
#fork a new process for the session to redirect stdout/stderr
|
86
|
+
pid = fork {
|
87
|
+
begin
|
88
|
+
start_irb = true
|
89
|
+
if authenticate && !conn.authenticate
|
90
|
+
conn.stop
|
91
|
+
start_irb = false
|
92
|
+
end
|
93
|
+
if start_irb
|
94
|
+
irb_io = GenericIOMethod.new conn.raw_input, conn.raw_output, readline
|
95
|
+
begin
|
96
|
+
IRB.start_with_io(irb_io, bind)
|
97
|
+
rescue Errno::EPIPE => e
|
98
|
+
conn.stop
|
99
|
+
end
|
100
|
+
end
|
101
|
+
rescue Exception => e
|
102
|
+
puts "Error during connection: #{e.message}"
|
103
|
+
conn.stop
|
104
|
+
end
|
105
|
+
}
|
106
|
+
Process.detach(pid)
|
107
|
+
end
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
# Ends the running thread, if it exists. Returns true if a thread was
|
112
|
+
# running, false otherwise.
|
113
|
+
def stop
|
114
|
+
if thread
|
115
|
+
if thread == Thread.current
|
116
|
+
self.thread = nil
|
117
|
+
Thread.current.exit!
|
118
|
+
end
|
119
|
+
|
120
|
+
thread.exit
|
121
|
+
if thread.join(0.1).nil?
|
122
|
+
thread.exit!
|
123
|
+
end
|
124
|
+
self.thread = nil
|
125
|
+
true
|
126
|
+
else
|
127
|
+
false
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Restarts. Useful for binding changes. Return value is the same as for
|
132
|
+
# LiveConsole#start.
|
133
|
+
def restart
|
134
|
+
r = lambda { stop; start }
|
135
|
+
if thread == Thread.current
|
136
|
+
Thread.new &r # Leaks a thread, but works.
|
137
|
+
else
|
138
|
+
r.call
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def init_irb
|
145
|
+
return if @@irb_inited_already
|
146
|
+
IRB.setup nil
|
147
|
+
@@irb_inited_already = true
|
148
|
+
end
|
149
|
+
|
150
|
+
def init_io opts
|
151
|
+
self.io = IOMethods.send(io_method).new opts
|
152
|
+
end
|
122
153
|
end
|
123
154
|
|
124
155
|
# We need to make a couple of changes to the IRB module to account for using a
|
125
|
-
# weird I/O method and re-starting IRB from time to time.
|
156
|
+
# weird I/O method and re-starting IRB from time to time.
|
126
157
|
module IRB
|
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
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
158
|
+
@inited = false
|
159
|
+
|
160
|
+
ARGV = []
|
161
|
+
|
162
|
+
# Overridden a la FXIrb to accomodate our needs.
|
163
|
+
def IRB.start_with_io(io, bind, &block)
|
164
|
+
unless @inited
|
165
|
+
setup '/dev/null'
|
166
|
+
IRB.parse_opts
|
167
|
+
IRB.load_modules
|
168
|
+
@inited = true
|
169
|
+
end
|
170
|
+
|
171
|
+
#Redirect stdout and stderr to cover output of Ruby commands like puts
|
172
|
+
$stdout.sync=true
|
173
|
+
$stderr.sync=true
|
174
|
+
$stdout.reopen(io.output)
|
175
|
+
$stderr.reopen(io.output)
|
176
|
+
ws = IRB::WorkSpace.new(bind)
|
177
|
+
|
178
|
+
@CONF[:PROMPT_MODE] = :DEFAULT
|
179
|
+
#Remove the context from all prompts-can be too long depending on binding
|
180
|
+
@CONF[:PROMPT][:DEFAULT][:PROMPT_I] = "%N():%03n:%i> "
|
181
|
+
@CONF[:PROMPT][:DEFAULT][:PROMPT_N] = "%N():%03n:%i> "
|
182
|
+
#Add > to S and C as they don't get picked up by telnet prompt scan
|
183
|
+
@CONF[:PROMPT][:DEFAULT][:PROMPT_S] = "%N():%03n:%i%l> "
|
184
|
+
@CONF[:PROMPT][:DEFAULT][:PROMPT_C] = "%N():%03n:%i*> "
|
185
|
+
|
186
|
+
@CONF[:USE_READLINE] = false
|
187
|
+
irb = Irb.new(ws, io, io)
|
188
|
+
bind ||= IRB::Frame.top(1) rescue TOPLEVEL_BINDING
|
189
|
+
|
190
|
+
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
|
191
|
+
@CONF[:MAIN_CONTEXT] = irb.context
|
192
|
+
|
193
|
+
catch(:IRB_EXIT) {
|
194
|
+
begin
|
195
|
+
irb.eval_input
|
196
|
+
rescue StandardError => e
|
197
|
+
irb.print([e.to_s, e.backtrace].flatten.join("\n") + "\n")
|
198
|
+
retry
|
199
|
+
end
|
200
|
+
}
|
201
|
+
irb.print "\n"
|
202
|
+
end
|
203
|
+
|
204
|
+
class Context
|
205
|
+
# Fix an IRB bug; it ignores your output method.
|
206
|
+
def output *args
|
207
|
+
@output_method.print *args
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
class Irb
|
212
|
+
# Fix an IRB bug; it ignores your output method.
|
213
|
+
def printf(*args)
|
214
|
+
context.output(sprintf(*args))
|
215
|
+
end
|
216
|
+
|
217
|
+
# Fix an IRB bug; it ignores your output method.
|
218
|
+
def print(*args)
|
219
|
+
context.output *args
|
220
|
+
end
|
221
|
+
end
|
177
222
|
end
|
178
223
|
|
179
224
|
# The GenericIOMethod is a class that wraps I/O for IRB.
|
180
225
|
class GenericIOMethod < IRB::StdioInputMethod
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
226
|
+
# call-seq:
|
227
|
+
# GenericIOMethod.new io
|
228
|
+
# GenericIOMethod.new input, output
|
229
|
+
#
|
230
|
+
# Creates a GenericIOMethod, using either a single object for both input
|
231
|
+
# and output, or one object for input and another for output.
|
232
|
+
def initialize(input, output = nil, readline=false)
|
233
|
+
@input, @output = input, output
|
234
|
+
@line = []
|
235
|
+
@line_no = 0
|
236
|
+
@readline = readline
|
237
|
+
end
|
238
|
+
|
239
|
+
attr_reader :input
|
240
|
+
def output
|
241
|
+
@output || input
|
242
|
+
end
|
243
|
+
|
244
|
+
def gets
|
245
|
+
output.print @prompt
|
246
|
+
output.flush
|
247
|
+
line = input.gets
|
248
|
+
if @readline
|
249
|
+
#Return tab completion data as long as we receive input that ends in '\t'
|
250
|
+
while line.chomp =~ /\t\z/n
|
251
|
+
run_completion_proc line.chomp.chop
|
252
|
+
line = input.gets
|
253
|
+
end
|
254
|
+
end
|
255
|
+
@line[@line_no += 1] = line
|
256
|
+
@line[@line_no]
|
257
|
+
end
|
258
|
+
|
259
|
+
# Returns the user input history.
|
260
|
+
def lines
|
261
|
+
@line.dup
|
262
|
+
end
|
263
|
+
|
264
|
+
def print(*a)
|
265
|
+
output.print *a
|
266
|
+
end
|
267
|
+
|
268
|
+
def file_name
|
269
|
+
input.inspect
|
270
|
+
end
|
271
|
+
|
272
|
+
def eof?
|
273
|
+
input.eof?
|
274
|
+
end
|
275
|
+
|
276
|
+
def encoding
|
277
|
+
input.external_encoding
|
278
|
+
end
|
279
|
+
|
280
|
+
def close
|
281
|
+
input.close
|
282
|
+
output.close if @output
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
# Runs the IRB CompletionProc with the given argument and writes the array to output stream as
|
287
|
+
# a comma-separated String, terminated by newline (so clients know when all data received)
|
288
|
+
def run_completion_proc(line)
|
289
|
+
opts = IRB::InputCompletor::CompletionProc.call(line)
|
290
|
+
opts.compact!
|
291
|
+
optstrng = opts.join(",") + "\n"
|
292
|
+
output.print optstrng
|
293
|
+
output.flush
|
294
|
+
end
|
227
295
|
end
|
@@ -1,53 +1,46 @@
|
|
1
1
|
module LiveConsole::IOMethods
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
def select
|
47
|
-
IO.select [server], [], [], 1 if server
|
48
|
-
end
|
49
|
-
|
50
|
-
private
|
51
|
-
attr_accessor :server
|
52
|
-
end
|
2
|
+
List = []
|
3
|
+
|
4
|
+
Dir[File.join(File.dirname(__FILE__), 'io_methods', '*')].each { |dir|
|
5
|
+
entry = dir + '/' + File.basename(dir)
|
6
|
+
fname = entry.sub /\.rb$/, ''
|
7
|
+
classname = File.basename(entry,'.rb').capitalize.
|
8
|
+
gsub(/_(\w)/) { $1.upcase }.sub(/io$/i, 'IO').to_sym
|
9
|
+
mname = File.basename(fname).sub(/_io$/, '').to_sym
|
10
|
+
|
11
|
+
autoload classname, fname
|
12
|
+
List << mname
|
13
|
+
|
14
|
+
define_method(mname) {
|
15
|
+
const_get classname
|
16
|
+
}
|
17
|
+
}
|
18
|
+
List.freeze
|
19
|
+
|
20
|
+
extend self
|
21
|
+
|
22
|
+
module IOMethod
|
23
|
+
def initialize(opts)
|
24
|
+
self.opts = self.class::DefaultOpts.merge opts
|
25
|
+
unless missing_opts.empty?
|
26
|
+
raise ArgumentError, "Missing opts for " \
|
27
|
+
"#{self.class.name}: #{missing_opts.inspect}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def missing_opts
|
32
|
+
self.class::RequiredOpts - opts.keys
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.included(other)
|
36
|
+
other.instance_eval {
|
37
|
+
readers = [:opts]
|
38
|
+
attr_accessor *readers
|
39
|
+
private *readers.map { |r| (r.to_s + '=').to_sym }
|
40
|
+
other::RequiredOpts.each { |opt|
|
41
|
+
define_method(opt) { opts[opt] }
|
42
|
+
}
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
53
46
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
class LiveConsole::IOMethods::SocketIOConnection
|
4
|
+
|
5
|
+
attr_accessor :raw_input, :raw_output
|
6
|
+
|
7
|
+
def initialize(server, opts)
|
8
|
+
@server = server
|
9
|
+
@opts = opts
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
begin
|
14
|
+
IO.select([@server])
|
15
|
+
self.raw_input = self.raw_output = @server.accept
|
16
|
+
return true
|
17
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
|
18
|
+
Errno::EINTR => e
|
19
|
+
select
|
20
|
+
retry
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def stop
|
25
|
+
select
|
26
|
+
raw_input.close rescue nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def select
|
30
|
+
IO.select [@server], [], [], 1 if @server
|
31
|
+
end
|
32
|
+
|
33
|
+
# Retrieves credentials from I/O and matches them against the specified file
|
34
|
+
# credentials_file should be of the form:
|
35
|
+
# username: <username>
|
36
|
+
# password: <password>
|
37
|
+
def authenticate
|
38
|
+
authenticated = true
|
39
|
+
raw_output.print "Login: "
|
40
|
+
raw_output.flush
|
41
|
+
username = raw_input.gets
|
42
|
+
raw_output.print "Password: "
|
43
|
+
password = raw_input.gets
|
44
|
+
credentials_file= @opts[:credentials_file] || '.consoleaccess'
|
45
|
+
credentials =YAML.load_file(credentials_file)
|
46
|
+
if username.chomp != credentials['username'] || password.chomp != credentials['password']
|
47
|
+
raw_output.puts "Login failed."
|
48
|
+
authenticated = false
|
49
|
+
end
|
50
|
+
authenticated
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'live_console/io_methods/socket_io/connection'
|
2
|
+
|
3
|
+
class LiveConsole::IOMethods::SocketIO
|
4
|
+
DefaultOpts = {
|
5
|
+
:host => '127.0.0.1',
|
6
|
+
}.freeze
|
7
|
+
RequiredOpts = DefaultOpts.keys + [:port]
|
8
|
+
|
9
|
+
include LiveConsole::IOMethods::IOMethod
|
10
|
+
|
11
|
+
def initialize(opts)
|
12
|
+
super opts
|
13
|
+
@server = TCPServer.new host, port
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_connection
|
17
|
+
LiveConsole::IOMethods::SocketIOConnection.new(@server, opts)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
class LiveConsole::IOMethods::UnixSocketConnection
|
4
|
+
|
5
|
+
attr_accessor :raw_input, :raw_output
|
6
|
+
|
7
|
+
def initialize(server, opts)
|
8
|
+
@server = server
|
9
|
+
@opts = opts
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
begin
|
14
|
+
self.raw_input = self.raw_output = @server.accept_nonblock
|
15
|
+
raw_input.sync = true
|
16
|
+
return true
|
17
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
|
18
|
+
Errno::EINTR => e
|
19
|
+
select
|
20
|
+
retry
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def stop
|
25
|
+
select
|
26
|
+
raw_input.close
|
27
|
+
end
|
28
|
+
|
29
|
+
def select
|
30
|
+
IO.select [@server], [], [], 1 if @server
|
31
|
+
end
|
32
|
+
|
33
|
+
def authenticate
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'live_console/io_methods/unix_socket/connection'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
class LiveConsole::IOMethods::UnixSocket
|
5
|
+
DefaultOpts = {
|
6
|
+
:mode => 0600,
|
7
|
+
:uid => Process.uid,
|
8
|
+
:gid => Process.gid,
|
9
|
+
}
|
10
|
+
RequiredOpts = DefaultOpts.keys + [:path]
|
11
|
+
|
12
|
+
include LiveConsole::IOMethods::IOMethod
|
13
|
+
|
14
|
+
def initialize(opts)
|
15
|
+
super opts
|
16
|
+
@server = UNIXServer.new path
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_connection
|
20
|
+
LiveConsole::IOMethods::UnixSocketConnection.new(@server,opts)
|
21
|
+
end
|
22
|
+
end
|
data/lib/version.rb
ADDED
metadata
CHANGED
@@ -1,68 +1,62 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: live_console
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.4
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
|
-
authors:
|
7
|
+
authors:
|
7
8
|
- Pete Elmore
|
9
|
+
- Jennifer Hickey
|
8
10
|
autorequire:
|
9
11
|
bindir: bin
|
10
12
|
cert_chain: []
|
11
|
-
|
12
|
-
date: 2009-02-18 00:00:00 -08:00
|
13
|
-
default_executable:
|
13
|
+
date: 2012-03-11 00:00:00.000000000Z
|
14
14
|
dependencies: []
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
executables:
|
15
|
+
description: A library to support adding an irb console to your running application.
|
16
|
+
email: jencompgeek@gmail.com
|
17
|
+
executables:
|
19
18
|
- udscat
|
20
19
|
extensions: []
|
21
|
-
|
22
|
-
|
23
|
-
-
|
24
|
-
-
|
25
|
-
-
|
26
|
-
|
27
|
-
|
28
|
-
-
|
29
|
-
-
|
30
|
-
-
|
31
|
-
- lib/live_console/io_methods
|
32
|
-
- lib/live_console/io_methods/unix_socket_io.rb
|
33
|
-
- lib/live_console/io_methods/socket_io.rb
|
34
|
-
- lib/live_console/io_methods.rb
|
35
|
-
- doc/README
|
36
|
-
- doc/lc_example.rb
|
37
|
-
- doc/lc_unix_example.rb
|
38
|
-
- doc/LICENSE
|
20
|
+
extra_rdoc_files:
|
21
|
+
- LICENSE
|
22
|
+
- README
|
23
|
+
- examples/lc_example.rb
|
24
|
+
- examples/lc_unix_example.rb
|
25
|
+
files:
|
26
|
+
- LICENSE
|
27
|
+
- README
|
28
|
+
- examples/lc_example.rb
|
29
|
+
- examples/lc_unix_example.rb
|
39
30
|
- bin/udscat
|
40
|
-
-
|
41
|
-
|
42
|
-
|
31
|
+
- lib/live_console/io_methods/socket_io/connection.rb
|
32
|
+
- lib/live_console/io_methods/socket_io/socket_io.rb
|
33
|
+
- lib/live_console/io_methods/unix_socket/connection.rb
|
34
|
+
- lib/live_console/io_methods/unix_socket/unix_socket.rb
|
35
|
+
- lib/live_console/io_methods.rb
|
36
|
+
- lib/live_console.rb
|
37
|
+
- lib/version.rb
|
38
|
+
homepage: https://github.com/jencompgeek/live-console
|
39
|
+
licenses: []
|
43
40
|
post_install_message:
|
44
41
|
rdoc_options: []
|
45
|
-
|
46
|
-
require_paths:
|
42
|
+
require_paths:
|
47
43
|
- lib
|
48
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
60
56
|
requirements: []
|
61
|
-
|
62
|
-
|
63
|
-
rubygems_version: 1.3.1
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.8.17
|
64
59
|
signing_key:
|
65
|
-
specification_version:
|
60
|
+
specification_version: 3
|
66
61
|
summary: A library to support adding an irb console to your running application.
|
67
62
|
test_files: []
|
68
|
-
|
data/Rakefile
DELETED
@@ -1,88 +0,0 @@
|
|
1
|
-
require 'rake/gempackagetask'
|
2
|
-
require 'rake/rdoctask'
|
3
|
-
require 'lib/live_console_config'
|
4
|
-
require 'fileutils'
|
5
|
-
|
6
|
-
$: << "#{File.dirname(__FILE__)}/lib"
|
7
|
-
|
8
|
-
spec = Gem::Specification.new { |s|
|
9
|
-
s.name = LiveConsoleConfig::PkgName
|
10
|
-
s.version = LiveConsoleConfig::Version
|
11
|
-
s.author = LiveConsoleConfig::Authors
|
12
|
-
s.email = LiveConsoleConfig::Email
|
13
|
-
s.homepage = LiveConsoleConfig::URL
|
14
|
-
s.rubyforge_project = LiveConsoleConfig::Project
|
15
|
-
|
16
|
-
s.platform = Gem::Platform::RUBY
|
17
|
-
|
18
|
-
s.files = Dir["{lib,doc,bin,ext}/**/*"].delete_if {|f|
|
19
|
-
/\/rdoc(\/|$)/i.match f
|
20
|
-
} + %w(Rakefile)
|
21
|
-
s.require_path = 'lib'
|
22
|
-
s.has_rdoc = true
|
23
|
-
s.extra_rdoc_files = Dir['doc/*'].select(&File.method(:file?))
|
24
|
-
s.extensions << 'ext/extconf.rb' if File.exist? 'ext/extconf.rb'
|
25
|
-
Dir['bin/*'].map(&File.method(:basename)).map(&s.executables.method(:<<))
|
26
|
-
|
27
|
-
s.summary = 'A library to support adding an irb console to your ' \
|
28
|
-
'running application.'
|
29
|
-
%w().each &s.method(:add_dependency)
|
30
|
-
}
|
31
|
-
|
32
|
-
Rake::RDocTask.new(:doc) { |t|
|
33
|
-
t.main = 'doc/README'
|
34
|
-
t.rdoc_files.include 'lib/**/*.rb', 'doc/*', 'bin/*', 'ext/**/*.c',
|
35
|
-
'ext/**/*.rb'
|
36
|
-
t.options << '-S' << '-N'
|
37
|
-
t.rdoc_dir = 'doc/rdoc'
|
38
|
-
}
|
39
|
-
|
40
|
-
Rake::GemPackageTask.new(spec) { |pkg|
|
41
|
-
pkg.need_tar_bz2 = true
|
42
|
-
}
|
43
|
-
|
44
|
-
desc "Builds and installs the gem for #{spec.name}"
|
45
|
-
task(:install => :package) {
|
46
|
-
g = "pkg/#{spec.name}-#{spec.version}.gem"
|
47
|
-
system "sudo gem install -l #{g}"
|
48
|
-
}
|
49
|
-
|
50
|
-
desc "Runs IRB, automatically require()ing #{spec.name}."
|
51
|
-
task(:irb) {
|
52
|
-
exec "irb -Ilib -r#{spec.name}"
|
53
|
-
}
|
54
|
-
|
55
|
-
desc "Cleans up the pkg directory."
|
56
|
-
task(:clean) {
|
57
|
-
FileUtils.rm_rf 'pkg'
|
58
|
-
}
|
59
|
-
|
60
|
-
|
61
|
-
desc "Generates a static gemspec file; useful for github."
|
62
|
-
task(:static_gemspec) {
|
63
|
-
# This whole thing is hacky.
|
64
|
-
spec.validate
|
65
|
-
spec_attrs = %w(
|
66
|
-
platform author email files require_path has_rdoc extra_rdoc_files
|
67
|
-
extensions executables name summary homepage
|
68
|
-
).map { |attr|
|
69
|
-
"\ts.#{attr} = #{spec.send(attr).inspect}\n"
|
70
|
-
}.join <<
|
71
|
-
"\ts.version = #{spec.version.to_s.inspect}\n" <<
|
72
|
-
spec.dependencies.map { |dep|
|
73
|
-
"\ts.add_dependency #{dep.inspect}\n"
|
74
|
-
}.join
|
75
|
-
|
76
|
-
File.open("#{spec.name}.gemspec", 'w') { |f|
|
77
|
-
f.print <<-EOGEMSPEC
|
78
|
-
# This is a static gempsec automatically generated by rake. It's better to
|
79
|
-
# edit the Rakefile than this file. It is kept in the repository for the
|
80
|
-
# benefit of github.
|
81
|
-
|
82
|
-
spec = Gem::Specification.new { |s|
|
83
|
-
#{spec_attrs}}
|
84
|
-
|
85
|
-
Gem::Builder.new(spec).build if __FILE__ == $0
|
86
|
-
EOGEMSPEC
|
87
|
-
}
|
88
|
-
}
|
@@ -1,27 +0,0 @@
|
|
1
|
-
class LiveConsole::IOMethods::SocketIO
|
2
|
-
DefaultOpts = {
|
3
|
-
:host => '127.0.0.1',
|
4
|
-
}.freeze
|
5
|
-
RequiredOpts = DefaultOpts.keys + [:port]
|
6
|
-
|
7
|
-
include LiveConsole::IOMethods::IOMethod
|
8
|
-
|
9
|
-
def start
|
10
|
-
@server ||= TCPServer.new host, port
|
11
|
-
|
12
|
-
begin
|
13
|
-
self.raw_input = self.raw_output = server.accept_nonblock
|
14
|
-
return true
|
15
|
-
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
|
16
|
-
Errno::EINTR => e
|
17
|
-
select
|
18
|
-
retry
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def stop
|
23
|
-
select
|
24
|
-
raw_input.close rescue nil
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
|
3
|
-
class LiveConsole::IOMethods::UnixSocketIO
|
4
|
-
DefaultOpts = {
|
5
|
-
:mode => 0600,
|
6
|
-
:uid => Process.uid,
|
7
|
-
:gid => Process.gid,
|
8
|
-
}
|
9
|
-
RequiredOpts = DefaultOpts.keys + [:path]
|
10
|
-
|
11
|
-
include LiveConsole::IOMethods::IOMethod
|
12
|
-
|
13
|
-
def start
|
14
|
-
@server ||= UNIXServer.new path
|
15
|
-
|
16
|
-
begin
|
17
|
-
self.raw_input = self.raw_output = server.accept_nonblock
|
18
|
-
raw_input.sync = true
|
19
|
-
return true
|
20
|
-
rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
|
21
|
-
Errno::EINTR => e
|
22
|
-
select
|
23
|
-
retry
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def stop
|
28
|
-
select
|
29
|
-
raw_input.close
|
30
|
-
end
|
31
|
-
end
|
data/lib/live_console_config.rb
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
# This module houses a pile of informative constants for LiveConsole.
|
2
|
-
module LiveConsoleConfig
|
3
|
-
Authors = 'Pete Elmore'
|
4
|
-
Email = 'pete.elmore@gmail.com'
|
5
|
-
PkgName = 'live_console'
|
6
|
-
Version = '0.2.1'
|
7
|
-
URL = 'http://debu.gs/live-console'
|
8
|
-
Project = 'live-console'
|
9
|
-
end
|