File: pty.rb

package info (click to toggle)
jruby 9.4.8.0%2Bds-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 89,244 kB
  • sloc: ruby: 548,574; java: 276,189; yacc: 25,873; ansic: 6,178; xml: 6,111; sh: 1,855; sed: 94; makefile: 78; jsp: 48; tcl: 40; exp: 12
file content (128 lines) | stat: -rw-r--r-- 2,908 bytes parent folder | download | duplicates (2)
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