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
|
if RbConfig::CONFIG['host_os'] !~ /mswin|win32|mingw/
require 'subspawn/replace-pty' # use SubSpawn's PTY & Process implementation as our own
return
end
require 'ffi'
require 'fcntl'
module PTY
module LibUtil
extend FFI::Library
ffi_lib FFI::Library::LIBC
# openpty(3) is in libutil on linux and BSD, libc on MacOS
attach_function :openpty, [ :buffer_out, :buffer_out, :buffer_in, :buffer_in, :buffer_in ], :int
end
class ChildExited < RuntimeError
attr_reader :status
def initialize(status)
@status = status
end
def inspect
"<#{self.class.name}: #{status}>"
end
end
class << self
def spawn(*args, &block)
SubSpawn.pty_spawn(*args, &block)
end
alias :getpty :spawn
def open
masterfd, slavefd, slave_name = openpty
master = IO.for_fd(masterfd, IO::RDWR)
slave = File.for_fd(slavefd, IO::RDWR)
File.chmod(0600, slave_name)
slave.define_singleton_method(:path) do
slave_name
end
slave.define_singleton_method(:tty?) do
true
end
slave.define_singleton_method(:inspect) do
"#<File:#{slave_name}>"
end
master.define_singleton_method(:inspect) do
"#<IO:masterpty:#{slave_name}>"
end
fds = [master, slave]
fds.each do |fd|
fd.sync = true
fd.close_on_exec = true
end
return fds unless block_given?
begin
retval = yield(fds.dup)
ensure
fds.reject(&:closed?).each(&:close)
end
retval
end
def check(target_pid, exception = false)
pid, status = Process.waitpid2(target_pid, Process::WNOHANG|Process::WUNTRACED)
# I sometimes see #<Process::Status: pid 0 signal 36> here.
# See Github issue #3117
if pid == target_pid && status
if exception
raise ChildExited.new(status)
else
return status
end
end
rescue SystemCallError
nil
end
private
def openpty
master = FFI::Buffer.alloc_out :int
slave = FFI::Buffer.alloc_out :int
name = FFI::Buffer.alloc_out 1024
result = LibUtil.openpty(master, slave, name, nil, nil)
if result != 0
raise(exception_for_errno(FFI.errno) || SystemCallError.new("errno=#{FFI.errno}"))
end
[master.get_int(0), slave.get_int(0), name.get_string(0)]
end
def get_shell
if shell = ENV['SHELL']
return shell
elsif pwent = Etc.getpwuid(Process.uid) && pwent.shell
return pwent.shell
else
"/bin/sh"
end
end
def exception_for_errno(errno)
Errno.constants.each do |name|
err = Errno.const_get(name)
if err.constants.include?(:Errno) && err.const_get(:Errno) == errno
return err
end
end
SystemCallError.new("errno=#{errno}")
end
end
end
|