Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: ece2b8335b89
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 59d305e81e42
Choose a head ref
  • 2 commits
  • 7 files changed
  • 1 contributor

Commits on May 5, 2017

  1. 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.
    headius committed May 5, 2017
    Copy the full SHA
    43a6732 View commit details

Commits on May 8, 2017

  1. Merge pull request #4590 from headius/cleanup_io_console

    Clean up structure of io/console and avoid stty on Windows.
    headius authored May 8, 2017
    Copy the full SHA
    59d305e View commit details
306 changes: 27 additions & 279 deletions lib/ruby/1.9/io/console.rb
Original file line number Diff line number Diff line change
@@ -19,301 +19,49 @@
# we don't actually disable echo and the password is shown...we will try to
# do a better version of this in 1.7.1.

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

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

result = begin
if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd/
require File.join(File.dirname(__FILE__), 'bsd_console')

elsif RbConfig::CONFIG['host_os'].downcase =~ /linux/
require File.join(File.dirname(__FILE__), 'linux_console')

else
raise LoadError.new("no native io/console support")
end

class IO
module LibC
begin
FD_FIELD = java.io.FileDescriptor.java_class.declared_field("fd")
FD_FIELD.accessible = true

def self.fd(io)
FD_FIELD.value io.to_java.open_file_checked.main_stream_safe.descriptor.file_descriptor
end
rescue
def self.fd(io)
io.fileno
end
end
end

def ttymode
termios = LibC::Termios.new
if LibC.tcgetattr(LibC.fd(self), termios) != 0
raise SystemCallError.new("tcgetattr", FFI.errno)
end

if block_given?
yield tmp = termios.dup
if LibC.tcsetattr(LibC.fd(self), LibC::TCSADRAIN, tmp) != 0
raise SystemCallError.new("tcsetattr", FFI.errno)
end
end
termios
end

def ttymode_yield(block, &setup)
begin
orig_termios = ttymode { |t| setup.call(t) }
block.call(self)
ensure
if orig_termios && LibC.tcsetattr(LibC.fd(self), LibC::TCSADRAIN, orig_termios) != 0
raise SystemCallError.new("tcsetattr", FFI.errno)
end
end
end

TTY_RAW = Proc.new do |t|
LibC.cfmakeraw(t)
t[:c_lflag] &= ~(LibC::ECHOE|LibC::ECHOK)
end

def raw(*, &block)
ttymode_yield(block, &TTY_RAW)
end

def raw!(*)
ttymode(&TTY_RAW)
end

TTY_COOKED = Proc.new do |t|
t[:c_iflag] |= (LibC::BRKINT|LibC::ISTRIP|LibC::ICRNL|LibC::IXON)
t[:c_oflag] |= LibC::OPOST
t[:c_lflag] |= (LibC::ECHO|LibC::ECHOE|LibC::ECHOK|LibC::ECHONL|LibC::ICANON|LibC::ISIG|LibC::IEXTEN)
end

def cooked(*, &block)
ttymode_yield(block, &TTY_COOKED)
end

def cooked!(*)
ttymode(&TTY_COOKED)
end

TTY_ECHO = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL
def echo=(echo)
ttymode do |t|
if echo
t[:c_lflag] |= TTY_ECHO
else
t[:c_lflag] &= ~TTY_ECHO
end
end
end

def echo?
(ttymode[:c_lflag] & (LibC::ECHO | LibC::ECHONL)) != 0
end

def noecho(&block)
ttymode_yield(block) { |t| t[:c_lflag] &= ~(TTY_ECHO) }
end

def getch(*)
raw do
getc
end
end

def winsize
ws = LibC::Winsize.new
if LibC.ioctl(LibC.fd(self), LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0
raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno)
end
[ ws[:ws_row], ws[:ws_col] ]
end

def winsize=(size)
ws = LibC::Winsize.new
if LibC.ioctl(LibC.fd(self), LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0
raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno)
end

ws[:ws_row] = size[0]
ws[:ws_col] = size[1]
if LibC.ioctl(LibC.fd(self), LibC::TIOCSWINSZ, :pointer, ws.pointer) != 0
raise SystemCallError.new("ioctl(TIOCSWINSZ)", FFI.errno)
end
end

def iflush
raise SystemCallError.new("tcflush(TCIFLUSH)", FFI.errno) unless LibC.tcflush(LibC.fd(self), LibC::TCIFLUSH) == 0
end

def oflush
raise SystemCallError.new("tcflush(TCOFLUSH)", FFI.errno) unless LibC.tcflush(LibC.fd(self), LibC::TCOFLUSH) == 0
end

def ioflush
raise SystemCallError.new("tcflush(TCIOFLUSH)", FFI.errno) unless LibC.tcflush(LibC.fd(self), LibC::TCIOFLUSH) == 0
end
end
true
rescue Exception => ex
warn "failed to load native console support: #{ex}" if $VERBOSE
begin
`stty 2> /dev/null`
$?.exitstatus != 0
rescue Exception
nil
end
end
# If Windows, always use the stub version
if RbConfig::CONFIG['host_os'] =~ /(mswin)|(win32)|(ming)/
require 'io/console/stub_console'
else
result = begin
old_stderr = $stderr.dup
$stderr.reopen('/dev/null')
`stty -a`
$?.exitstatus != 0
rescue Exception
nil
ensure
$stderr.reopen(old_stderr)
end
end

if !result || RbConfig::CONFIG['host_os'] =~ /(mswin)|(win32)|(ming)/
warn "io/console not supported; tty will not be manipulated" if $VERBOSE

# Windows version is always stubbed for now
class IO
def raw(*)
yield self
end

def raw!(*)
end

def cooked(*)
yield self
end

def cooked!(*)
end

def getch(*)
getc
end

def echo=(echo)
end

def echo?
true
end

def noecho
yield self
end
# If Linux or BSD, try to load the native version
if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd|linux/
begin

def winsize
[25, 80]
end
# Attempt to load the native Linux and BSD console logic
# require 'io/console/native_console'
# ready = true

def winsize=(size)
end
rescue Exception => ex

def iflush
end
warn "failed to load native console support: #{ex}" if $VERBOSE
ready = false

def oflush
end

def ioflush
end
end
elsif !IO.method_defined?:ttymode
warn "io/console on JRuby shells out to stty for most operations"

# Non-Windows assumes stty command is available
class IO
if RbConfig::CONFIG['host_os'].downcase =~ /linux/ && File.exists?("/proc/#{Process.pid}/fd")
def stty(*args)
`stty #{args.join(' ')} < /proc/#{Process.pid}/fd/#{fileno}`
end
else
def stty(*args)
`stty #{args.join(' ')}`
end
end

def raw(*)
saved = stty('-g')
stty('raw')
yield self
ensure
stty(saved)
end

def raw!(*)
stty('raw')
end

def cooked(*)
saved = stty('-g')
stty('-raw')
yield self
ensure
stty(saved)
end

def cooked!(*)
stty('-raw')
end

def getch(*)
getc
end

def echo=(echo)
stty(echo ? 'echo' : '-echo')
end

def echo?
(stty('-a') =~ / -echo /) ? false : true
end

def noecho
saved = stty('-g')
stty('-echo')
yield self
ensure
stty(saved)
end
# Native failed, try to use stty
if !ready
begin

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

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

def winsize=(size)
stty("rows #{size[0]} cols #{size[1]}")
end
warn "failed to load stty console support: #{ex}" if $VERBOSE
ready = false

def iflush
end

def oflush
end
end

def ioflush
end
# If still not ready, just use stubbed version
if !ready
require 'io/console/stub_console'
end

end
File renamed without changes.
35 changes: 35 additions & 0 deletions lib/ruby/1.9/io/console/common.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Methods common to all backend impls
class IO
def getch(*)
raw do
getc
end
end

def getpass(prompt = nil)
wio = self == $stdin ? $stderr : self
wio.write(prompt) if prompt
begin
str = nil
noecho do
str = gets
end
ensure
puts($/)
end
str.chomp
end

module GenericReadable
def getch(*)
getc
end

def getpass(prompt = nil)
write(prompt) if prompt
str = gets.chomp
puts($/)
str
end
end
end
File renamed without changes.
153 changes: 153 additions & 0 deletions lib/ruby/1.9/io/console/native_console.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Load appropriate native bits for BSD or Linux
case RbConfig::CONFIG['host_os'].downcase
when /darwin|openbsd|freebsd|netbsd/
require 'io/console/bsd_console'
when /linux/
require 'io/console/linux_console'
else
raise LoadError.new("no native io/console support")
end

# Common logic that uses native calls for console
class IO
def ttymode
termios = LibC::Termios.new
if LibC.tcgetattr(self.fileno, termios) != 0
raise SystemCallError.new("tcgetattr", FFI.errno)
end

if block_given?
yield tmp = termios.dup
if LibC.tcsetattr(self.fileno, LibC::TCSADRAIN, tmp) != 0
raise SystemCallError.new("tcsetattr", FFI.errno)
end
end
termios
end
private :ttymode

def ttymode_yield(block, &setup)
begin
orig_termios = ttymode { |t| setup.call(t) }
block.call(self)
ensure
if orig_termios && LibC.tcsetattr(self.fileno, LibC::TCSADRAIN, orig_termios) != 0
raise SystemCallError.new("tcsetattr", FFI.errno)
end
end
end
private :ttymode_yield

TTY_RAW = Proc.new do |t|
LibC.cfmakeraw(t)
t[:c_lflag] &= ~(LibC::ECHOE|LibC::ECHOK)
end

def raw(*, &block)
ttymode_yield(block, &TTY_RAW)
end

def raw!(*)
ttymode(&TTY_RAW)
end

TTY_COOKED = Proc.new do |t|
t[:c_iflag] |= (LibC::BRKINT|LibC::ISTRIP|LibC::ICRNL|LibC::IXON)
t[:c_oflag] |= LibC::OPOST
t[:c_lflag] |= (LibC::ECHO|LibC::ECHOE|LibC::ECHOK|LibC::ECHONL|LibC::ICANON|LibC::ISIG|LibC::IEXTEN)
end

def cooked(*, &block)
ttymode_yield(block, &TTY_COOKED)
end

def cooked!(*)
ttymode(&TTY_COOKED)
end

TTY_ECHO = LibC::ECHO | LibC::ECHOE | LibC::ECHOK | LibC::ECHONL
def echo=(echo)
ttymode do |t|
if echo
t[:c_lflag] |= TTY_ECHO
else
t[:c_lflag] &= ~TTY_ECHO
end
end
end

def echo?
(ttymode[:c_lflag] & (LibC::ECHO | LibC::ECHONL)) != 0
end

def noecho(&block)
ttymode_yield(block) { |t| t[:c_lflag] &= ~(TTY_ECHO) }
end

def winsize
ws = LibC::Winsize.new
if LibC.ioctl(self.fileno, LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0
raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno)
end
[ ws[:ws_row], ws[:ws_col] ]
end

def winsize=(size)
ws = LibC::Winsize.new
if LibC.ioctl(self.fileno, LibC::TIOCGWINSZ, :pointer, ws.pointer) != 0
raise SystemCallError.new("ioctl(TIOCGWINSZ)", FFI.errno)
end

ws[:ws_row] = size[0]
ws[:ws_col] = size[1]
if LibC.ioctl(self.fileno, LibC::TIOCSWINSZ, :pointer, ws.pointer) != 0
raise SystemCallError.new("ioctl(TIOCSWINSZ)", FFI.errno)
end
end

def iflush
raise SystemCallError.new("tcflush(TCIFLUSH)", FFI.errno) unless LibC.tcflush(self.fileno, LibC::TCIFLUSH) == 0
end

def oflush
raise SystemCallError.new("tcflush(TCOFLUSH)", FFI.errno) unless LibC.tcflush(self.fileno, LibC::TCOFLUSH) == 0
end

def ioflush
raise SystemCallError.new("tcflush(TCIOFLUSH)", FFI.errno) unless LibC.tcflush(self.fileno, LibC::TCIOFLUSH) == 0
end

# TODO: Windows version uses "conin$" and "conout$" instead of /dev/tty
def self.console(sym = nil, *args)
raise TypeError, "expected Symbol, got #{sym.class}" unless sym.nil? || sym.kind_of?(Symbol)

# klass = self == IO ? File : self
if defined?(@console) # using ivar instead of hidden const as in MRI
con = @console
# MRI checks IO internals : (!RB_TYPE_P(con, T_FILE) || (!(fptr = RFILE(con)->fptr) || GetReadFD(fptr) == -1))
if !con.kind_of?(File) || (con.kind_of?(IO) && (con.closed? || !FileTest.readable?(con)))
remove_instance_variable :@console
con = nil
end
end

if sym
if sym == :close
if con
con.close
remove_instance_variable :@console if defined?(@console)
end
return nil
end
end

if !con && $stdin.tty?
con = File.open('/dev/tty', 'r+')
con.sync = true
@console = con
end

return con.send(sym, *args) if sym
return con
end
end
82 changes: 82 additions & 0 deletions lib/ruby/1.9/io/console/stty_console.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# attempt to call stty; if failure, raise error
`stty 2> /dev/null`
if $?.exitstatus != 0
raise "stty command returned nonzero exit status"
end

warn "io/console on JRuby shells out to stty for most operations"

# Non-Windows assumes stty command is available
class IO
if RbConfig::CONFIG['host_os'].downcase =~ /linux/ && File.exists?("/proc/#{Process.pid}/fd")
def stty(*args)
`stty #{args.join(' ')} < /proc/#{Process.pid}/fd/#{fileno}`
end
else
def stty(*args)
`stty #{args.join(' ')}`
end
end

def raw(*)
saved = stty('-g')
stty('raw')
yield self
ensure
stty(saved)
end

def raw!(*)
stty('raw')
end

def cooked(*)
saved = stty('-g')
stty('-raw')
yield self
ensure
stty(saved)
end

def cooked!(*)
stty('-raw')
end

def echo=(echo)
stty(echo ? 'echo' : '-echo')
end

def echo?
(stty('-a') =~ / -echo /) ? false : true
end

def noecho
saved = stty('-g')
stty('-echo')
yield self
ensure
stty(saved)
end

# Not all systems return same format of stty -a output
IEEE_STD_1003_2 = '(?<rows>\d+) rows; (?<columns>\d+) columns'
UBUNTU = 'rows (?<rows>\d+); columns (?<columns>\d+)'

def winsize
match = stty('-a').match(/#{IEEE_STD_1003_2}|#{UBUNTU}/)
[match[:rows].to_i, match[:columns].to_i]
end

def winsize=(size)
stty("rows #{size[0]} cols #{size[1]}")
end

def iflush
end

def oflush
end

def ioflush
end
end
45 changes: 45 additions & 0 deletions lib/ruby/1.9/io/console/stub_console.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
warn "io/console not supported; tty will not be manipulated" if $VERBOSE

# Windows version is always stubbed for now
class IO
def raw(*)
yield self
end

def raw!(*)
end

def cooked(*)
yield self
end

def cooked!(*)
end

def echo=(echo)
end

def echo?
true
end

def noecho
yield self
end

def winsize
[25, 80]
end

def winsize=(size)
end

def iflush
end

def oflush
end

def ioflush
end
end