1 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 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 122 123 124 125 126 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 177 178 179 180 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 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
|
# This implementation of io/console is a little hacky. It shells out to `stty`
# for most operations, which does not work on Windows, in secured environments,
# and so on. In addition, because on Java 6 we can't actually launch
# subprocesses with tty control, stty will not actually manipulate the
# controlling terminal.
#
# For platforms where shelling to stty does not work, most operations will
# just be pass-throughs. This allows them to function, but does not actually
# change any tty flags.
#
# Finally, since we're using stty to shell out, we can only manipulate stdin/
# stdout tty rather than manipulating whatever terminal is actually associated
# with the IO we're calling against. This will produce surprising results if
# anyone is actually using io/console against non-stdio ttys...but that case
# seems like it would be pretty rare.
#
# Note: we are incorporating this into 1.7.0 since RubyGems uses io/console
# when pushing gems, in order to mask the password entry. Worst case is that
# 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
if RbConfig::CONFIG['host_os'].downcase =~ /darwin|openbsd|freebsd|netbsd|linux/
require 'java'
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
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
def winsize
[25, 80]
end
def winsize=(size)
end
def iflush
end
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
# 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
end
|