live_console 0.2.1 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|