Skip to content

Commit 43a6732

Browse files
committed
Clean up structure of io/console and avoid stty on Windows.
Fixes #3989. * Restructure the different impls of console into their own files. * Always use stubbed version on Windows. * Cascade from native to stty to stubbed on other platforms.
1 parent ece2b83 commit 43a6732

File tree

7 files changed

+342
-279
lines changed

7 files changed

+342
-279
lines changed

lib/ruby/1.9/io/console.rb

Lines changed: 27 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -19,301 +19,49 @@
1919
# we don't actually disable echo and the password is shown...we will try to
2020
# do a better version of this in 1.7.1.
2121

22-
# attempt to call stty; if failure, fall back on stubbed version
22+
require 'rbconfig'
2323

24-
if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd|linux/
25-
require 'java'
24+
require 'io/console/common'
2625

27-
result = begin
28-
if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd/
29-
require File.join(File.dirname(__FILE__), 'bsd_console')
30-
31-
elsif RbConfig::CONFIG['host_os'].downcase =~ /linux/
32-
require File.join(File.dirname(__FILE__), 'linux_console')
33-
34-
else
35-
raise LoadError.new("no native io/console support")
36-
end
37-
38-
class IO
39-
module LibC
40-
begin
41-
FD_FIELD = java.io.FileDescriptor.java_class.declared_field("fd")
42-
FD_FIELD.accessible = true
43-
44-
def self.fd(io)
45-
FD_FIELD.value io.to_java.open_file_checked.main_stream_safe.descriptor.file_descriptor
46-
end
47-
rescue
48-
def self.fd(io)
49-
io.fileno
50-
end
51-
end
52-
end
53-
54-
def ttymode
55-
termios = LibC::Termios.new
56-
if LibC.tcgetattr(LibC.fd(self), termios) != 0
57-
raise SystemCallError.new("tcgetattr", FFI.errno)
58-
end
59-
60-
if block_given?
61-
yield tmp = termios.dup
62-
if LibC.tcsetattr(LibC.fd(self), LibC::TCSADRAIN, tmp) != 0
63-
raise SystemCallError.new("tcsetattr", FFI.errno)
64-
end
65-
end
66-
termios
67-
end
68-
69-
def ttymode_yield(block, &setup)
70-
begin
71-
orig_termios = ttymode { |t| setup.call(t) }
72-
block.call(self)
73-
ensure
74-
if orig_termios && LibC.tcsetattr(LibC.fd(self), LibC::TCSADRAIN, orig_termios) != 0
75-
raise SystemCallError.new("tcsetattr", FFI.errno)
76-
end
77-
end
78-
end
79-
80-
TTY_RAW = Proc.new do |t|
81-
LibC.cfmakeraw(t)
82-
t[:c_lflag] &= ~(LibC::ECHOE|LibC::ECHOK)
83-
end
84-
85-
def raw(*, &block)
86-
ttymode_yield(block, &TTY_RAW)
87-
end
88-
89-
def raw!(*)
90-
ttymode(&TTY_RAW)
91-
end
92-
93-
TTY_COOKED = Proc.new do |t|
94-
t[:c_iflag] |= (LibC::BRKINT|LibC::ISTRIP|LibC::ICRNL|LibC::IXON)
95-
t[:c_oflag] |= LibC::OPOST
96-
t[:c_lflag] |= (LibC::ECHO|LibC::ECHOE|LibC::ECHOK|LibC::ECHONL|LibC::ICANON|LibC::ISIG|LibC::IEXTEN)
97-
end
98-
99-
def cooked(*, &block)
100-
ttymode_yield(block, &TTY_COOKED)
101-
end
102-
103-
def cooked!(*)
104-
ttymode(&TTY_COOKED)
105-
end
106-
107-
TTY_ECHO = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL
108-
def echo=(echo)
109-
ttymode do |t|
110-
if echo
111-
t[:c_lflag] |= TTY_ECHO
112-
else
113-
t[:c_lflag] &= ~TTY_ECHO
114-
end
115-
end
116-
end
117-
118-
def echo?
119-
(ttymode[:c_lflag] & (LibC::ECHO | LibC::ECHONL)) != 0
120-
end
121-
122-
def noecho(&block)
123-
ttymode_yield(block) { |t| t[:c_lflag] &= ~(TTY_ECHO) }
124-
end
125-
126-
def getch(*)
127-
raw do
128-
getc
129-
end
130-
end
131-
132-
def winsize
133-
ws = LibC::Winsize.new
134-
if LibC.ioctl(LibC.fd(self), LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0
135-
raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno)
136-
end
137-
[ ws[:ws_row], ws[:ws_col] ]
138-
end
139-
140-
def winsize=(size)
141-
ws = LibC::Winsize.new
142-
if LibC.ioctl(LibC.fd(self), LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0
143-
raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno)
144-
end
145-
146-
ws[:ws_row] = size[0]
147-
ws[:ws_col] = size[1]
148-
if LibC.ioctl(LibC.fd(self), LibC::TIOCSWINSZ, :pointer, ws.pointer) != 0
149-
raise SystemCallError.new("ioctl(TIOCSWINSZ)", FFI.errno)
150-
end
151-
end
152-
153-
def iflush
154-
raise SystemCallError.new("tcflush(TCIFLUSH)", FFI.errno) unless LibC.tcflush(LibC.fd(self), LibC::TCIFLUSH) == 0
155-
end
156-
157-
def oflush
158-
raise SystemCallError.new("tcflush(TCOFLUSH)", FFI.errno) unless LibC.tcflush(LibC.fd(self), LibC::TCOFLUSH) == 0
159-
end
160-
161-
def ioflush
162-
raise SystemCallError.new("tcflush(TCIOFLUSH)", FFI.errno) unless LibC.tcflush(LibC.fd(self), LibC::TCIOFLUSH) == 0
163-
end
164-
end
165-
true
166-
rescue Exception => ex
167-
warn "failed to load native console support: #{ex}" if $VERBOSE
168-
begin
169-
`stty 2> /dev/null`
170-
$?.exitstatus != 0
171-
rescue Exception
172-
nil
173-
end
174-
end
26+
# If Windows, always use the stub version
27+
if RbConfig::CONFIG['host_os'] =~ /(mswin)|(win32)|(ming)/
28+
require 'io/console/stub_console'
17529
else
176-
result = begin
177-
old_stderr = $stderr.dup
178-
$stderr.reopen('/dev/null')
179-
`stty -a`
180-
$?.exitstatus != 0
181-
rescue Exception
182-
nil
183-
ensure
184-
$stderr.reopen(old_stderr)
185-
end
186-
end
187-
188-
if !result || RbConfig::CONFIG['host_os'] =~ /(mswin)|(win32)|(ming)/
189-
warn "io/console not supported; tty will not be manipulated" if $VERBOSE
19030

191-
# Windows version is always stubbed for now
192-
class IO
193-
def raw(*)
194-
yield self
195-
end
196-
197-
def raw!(*)
198-
end
199-
200-
def cooked(*)
201-
yield self
202-
end
203-
204-
def cooked!(*)
205-
end
206-
207-
def getch(*)
208-
getc
209-
end
210-
211-
def echo=(echo)
212-
end
213-
214-
def echo?
215-
true
216-
end
217-
218-
def noecho
219-
yield self
220-
end
31+
# If Linux or BSD, try to load the native version
32+
if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd|linux/
33+
begin
22134

222-
def winsize
223-
[25, 80]
224-
end
35+
# Attempt to load the native Linux and BSD console logic
36+
# require 'io/console/native_console'
37+
# ready = true
22538

226-
def winsize=(size)
227-
end
39+
rescue Exception => ex
22840

229-
def iflush
230-
end
41+
warn "failed to load native console support: #{ex}" if $VERBOSE
42+
ready = false
23143

232-
def oflush
233-
end
234-
235-
def ioflush
23644
end
23745
end
238-
elsif !IO.method_defined?:ttymode
239-
warn "io/console on JRuby shells out to stty for most operations"
240-
241-
# Non-Windows assumes stty command is available
242-
class IO
243-
if RbConfig::CONFIG['host_os'].downcase =~ /linux/ && File.exists?("/proc/#{Process.pid}/fd")
244-
def stty(*args)
245-
`stty #{args.join(' ')} < /proc/#{Process.pid}/fd/#{fileno}`
246-
end
247-
else
248-
def stty(*args)
249-
`stty #{args.join(' ')}`
250-
end
251-
end
252-
253-
def raw(*)
254-
saved = stty('-g')
255-
stty('raw')
256-
yield self
257-
ensure
258-
stty(saved)
259-
end
260-
261-
def raw!(*)
262-
stty('raw')
263-
end
264-
265-
def cooked(*)
266-
saved = stty('-g')
267-
stty('-raw')
268-
yield self
269-
ensure
270-
stty(saved)
271-
end
272-
273-
def cooked!(*)
274-
stty('-raw')
275-
end
276-
277-
def getch(*)
278-
getc
279-
end
28046

281-
def echo=(echo)
282-
stty(echo ? 'echo' : '-echo')
283-
end
284-
285-
def echo?
286-
(stty('-a') =~ / -echo /) ? false : true
287-
end
288-
289-
def noecho
290-
saved = stty('-g')
291-
stty('-echo')
292-
yield self
293-
ensure
294-
stty(saved)
295-
end
47+
# Native failed, try to use stty
48+
if !ready
49+
begin
29650

297-
# Not all systems return same format of stty -a output
298-
IEEE_STD_1003_2 = '(?<rows>\d+) rows; (?<columns>\d+) columns'
299-
UBUNTU = 'rows (?<rows>\d+); columns (?<columns>\d+)'
51+
require 'io/console/stty_console'
52+
ready = true
30053

301-
def winsize
302-
match = stty('-a').match(/#{IEEE_STD_1003_2}|#{UBUNTU}/)
303-
[match[:rows].to_i, match[:columns].to_i]
304-
end
54+
rescue Exception
30555

306-
def winsize=(size)
307-
stty("rows #{size[0]} cols #{size[1]}")
308-
end
56+
warn "failed to load stty console support: #{ex}" if $VERBOSE
57+
ready = false
30958

310-
def iflush
311-
end
312-
313-
def oflush
31459
end
60+
end
31561

316-
def ioflush
317-
end
62+
# If still not ready, just use stubbed version
63+
if !ready
64+
require 'io/console/stub_console'
31865
end
66+
31967
end

lib/ruby/1.9/io/console/common.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Methods common to all backend impls
2+
class IO
3+
def getch(*)
4+
raw do
5+
getc
6+
end
7+
end
8+
9+
def getpass(prompt = nil)
10+
wio = self == $stdin ? $stderr : self
11+
wio.write(prompt) if prompt
12+
begin
13+
str = nil
14+
noecho do
15+
str = gets
16+
end
17+
ensure
18+
puts($/)
19+
end
20+
str.chomp
21+
end
22+
23+
module GenericReadable
24+
def getch(*)
25+
getc
26+
end
27+
28+
def getpass(prompt = nil)
29+
write(prompt) if prompt
30+
str = gets.chomp
31+
puts($/)
32+
str
33+
end
34+
end
35+
end

0 commit comments

Comments
 (0)