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: 903728b770b1
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: e778b56281e1
Choose a head ref
  • 2 commits
  • 7 files changed
  • 1 contributor

Commits on May 7, 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 7, 2017
    Copy the full SHA
    3666877 View commit details

Commits on May 8, 2017

  1. Merge pull request #4592 from headius/cleanup_io_console_9k

    Clean up structure of io/console and avoid stty on Windows.
    headius authored May 8, 2017
    Copy the full SHA
    e778b56 View commit details
346 changes: 25 additions & 321 deletions lib/ruby/stdlib/io/console.rb
Original file line number Diff line number Diff line change
@@ -21,343 +21,47 @@

require 'rbconfig'

# 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
end

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

if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd|linux/

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
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
require 'io/console/common'

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
# If Windows, always use the stub version
if RbConfig::CONFIG['host_os'] =~ /(mswin)|(win32)|(ming)/
require 'io/console/stub_console'
else

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
true
rescue Exception => ex
warn "failed to load native console support: #{ex}" if $VERBOSE
# If Linux or BSD, try to load the native version
if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd|linux/
begin
`stty 2> /dev/null`
$?.exitstatus != 0
rescue Exception
nil
end
end
elsif RbConfig::CONFIG['host_os'] !~ /(mswin)|(win32)|(ming)/
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 echo=(echo)
end

def echo?
true
end

def noecho
yield self
end
# Attempt to load the native Linux and BSD console logic
# require 'io/console/native_console'
# ready = true

def winsize
[25, 80]
end

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

def iflush
end

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

def ioflush
end
end
elsif !IO.private_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 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+)'
# Native failed, try to use stty
if !ready
begin

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

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

def iflush
end
rescue Exception

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

def ioflush
end
end
end

module IO::GenericReadable
def getch(*)
getc
# If still not ready, just use stubbed version
if !ready
require 'io/console/stub_console'
end

def getpass(prompt = nil)
write(prompt) if prompt
str = gets.chomp
puts($/)
str
end
end
end
File renamed without changes.
35 changes: 35 additions & 0 deletions lib/ruby/stdlib/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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'ffi'

raise LoadError.new("native console only supported on i386, x86_64, powerpc64 and aarch64") unless FFI::Platform::ARCH =~ /i386|x86_64|powerpc64|aarch64/
raise LoadError.new("native console only supported on i386, x86_64 and powerpc64") unless FFI::Platform::ARCH =~ /i386|x86_64|powerpc64/

module IO::LibC
extend FFI::Library
153 changes: 153 additions & 0 deletions lib/ruby/stdlib/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/stdlib/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/stdlib/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