Permalink
Browse files

First cut at porting the 1.8 pty C extension

  • Loading branch information...
1 parent 98f076d commit a420bda30e68bc31adf1d15806ca621207493599 @evanphx evanphx committed Jan 12, 2012
View
1 lib/18/pty.rb
@@ -0,0 +1 @@
+require 'pty/ext/pty'
View
3 lib/18/pty/.cvsignore
@@ -0,0 +1,3 @@
+Makefile
+mkmf.log
+*.def
View
65 lib/18/pty/README
@@ -0,0 +1,65 @@
+pty extension version 0.3 by A.ito
+
+1. Introduction
+
+This extension module adds ruby a functionality to execute an
+arbitrary command through pseudo tty (pty).
+
+2. Install
+
+Follow the instruction below.
+
+(1) Execute
+
+ ruby extconf.rb
+
+ then Makefile is generated.
+
+(3) Do make; make install.
+
+3. What you can do
+
+This extension module defines a module named PTY, which contains
+following module fungtions:
+
+ getpty(command)
+ spawn(command)
+
+ This function reserves a pty, executes command over the pty
+ and returns an array. The return value is an array with three
+ elements. The first element in the array is for reading and the
+ second for writing. The third element is the process ID of the
+ child process. If this function is called with an iterator block,
+ the array is passed to the block as block parameters, and the
+ function itself returns nil.
+
+ When the child process is suspended or finished, an exception is
+ raised. If this function is called with an iterator block,
+ exception is raised only within the block. Child process
+ monitor is terminated on block exit.
+
+ protect_signal
+ reset_signal
+
+ These functions are obsolete in this version of pty.
+
+4. License
+
+(C) Copyright 1998 by Akinori Ito.
+
+This software may be redistributed freely for this purpose, in full
+or in part, provided that this entire copyright notice is included
+on any copies of this software and applications and derivations thereof.
+
+This software is provided on an "as is" basis, without warranty of any
+kind, either expressed or implied, as to any matter including, but not
+limited to warranty of fitness of purpose, or merchantability, or
+results obtained from use of this software.
+
+5. Bug report
+
+Please feel free to send E-mail to
+
+ aito@ei5sun.yz.yamagata-u.ac.jp
+
+for any bug report, opinion, contribution, etc.
View
22 lib/18/pty/README.expect
@@ -0,0 +1,22 @@
+ README for expect
+ by A. Ito, 28 October, 1998
+
+ Expect library adds IO class a method called expect(), which
+does similar act to tcl's expect extension.
+
+The usage of the method is:
+
+ IO#expect(pattern,timeout=9999999)
+
+where `pattern' is an instance of String or Regexp and `timeout'
+is Fixnum, which can be omitted.
+ When the method is called without block, it waits until the
+input which matches the pattern is obtained from the IO or the time
+specified as the timeout passes. When the pattern is obtained from the
+IO, the method returns an array. The first element of the array is the
+entire string obtained from the IO until the pattern matches. The
+following elements indicates the specific pattern which matched to the
+anchor in the regular expression. If the method ends because of
+timeout, it returns nil.
+ When the method is called with block, the array is passed as
+the block parameter.
View
21 lib/18/pty/README.expect.ja
@@ -0,0 +1,21 @@
+ README for expect
+ by A. Ito, 28 October, 1998
+
+ Expect�饤�֥��ϡ�tcl �� expect �ѥå������Ȼ�褦�ʵ�ǽ��
+IO���饹���ɲä��ޤ���
+
+ �ɲä����᥽�åɤλȤ���ϼ����̤�Ǥ���
+
+ IO#expect(pattern,timeout=9999999)
+
+pattern �� String �� Regexp �Υ��󥹥��󥹡�timeout �� Fixnum
+�Υ��󥹥��󥹤Ǥ���timeout �Ͼ�ά�Ǥ��ޤ���
+ ���Υ᥽�åɤ��֥�å��ʤ��ǸƤФ줿��硤�ޤ��쥷���ФǤ���
+IO���֥������Ȥ��� pattern �˥ޥå����ѥ������ɤߤ��ޤ��
+�ޤ��Ԥ�ޤ����ѥ��������줿�顤���Υѥ�����˴ؤ��������
+�֤��ޤ�������κǽ�����Ǥϡ�pattern �˥ޥå����ޤǤ��ɤߤ�
+�ޤ줿���Ƥ�ʸ����Ǥ���2���ܰʹߤ����Ǥϡ�pattern �����ɽ��
+����˥��󥫡������ä����ˡ����Υ��󥫡��˥ޥå������ʬ�Ǥ���
+�⤷�����ॢ���Ȥ����������ϡ����Υ᥽�åɤ�nil���֤��ޤ���
+ ���Υ᥽�åɤ��֥�å��դ��ǸƤФ줿���ˤϡ��ޥå�������Ǥ�
+���󤬥֥�å����Ȥ����Ϥ��졤�֥�å���ɾ�����ޤ���
View
89 lib/18/pty/README.ja
@@ -0,0 +1,89 @@
+pty 拡張モジュール version 0.3 by A.ito
+
+1. はじめに
+
+この拡張モジュールは,仮想tty (pty) を通して適当なコマンドを
+実行する機能を ruby に提供します.
+
+2. インストール
+
+次のようにしてインストールしてください.
+
+(1) ruby extconf.rb
+
+ を実行すると Makefile が生成されます.
+
+(2) make; make install を実行してください.
+
+3. 何ができるか
+
+この拡張モジュールは,PTY というモジュールを定義します.その中
+には,次のようなモジュール関数が含まれています.
+
+ getpty(command)
+ spawn(command)
+
+ この関数は,仮想ttyを確保し,指定されたコマンドをその仮想tty
+ の向こうで実行し,配列を返します.戻り値は3つの要素からなる
+ 配列です.最初の要素は仮想ttyから読み出すためのIOオブジェクト,
+ 2番目は書きこむためのIOオブジェクト,3番目は子プロセスのプロ
+ セスIDです.この関数がイテレータとして呼ばれた場合,これらの
+ 要素はブロックパラメータとして渡され,関数自体はnilを返します.
+
+ この関数によって作られたサブプロセスが動いている間,子プロセス
+ の状態を監視するために SIGCHLD シグナルを捕捉します.子プロセス
+ が終了したり停止した場合には,例外が発生します.この間,すべての
+ SIGCHLD が PTY モジュールのシグナルハンドラに捕捉されるので,
+ サブプロセスを生成する他の関数(system() とか IO.popen()など)を
+ 使うと,予期しない例外が発生することがあります.これを防ぐため
+ には,下記のprotect_signal()を参照してください.
+
+ この関数がブロックパラメータ付きで呼ばれた場合には,そのブロック
+ の中でのみ SIGCHLD が捕捉されます.したがって,ブロックパラメータ
+ として渡されたIOオブジェクトを,ブロックの外に持ち出して使うの
+ は勧められません.
+
+
+ protect_signal
+
+ この関数はイテレータです.ここで指定されたブロックの中では,
+ 子プロセスが終了しても例外を発生しません.この関数を使うことで,
+ PTYの子プロセスが動いている間でも,system()や IO.popen()などの
+ 関数を安全に使うことができます.例えば,
+
+ PTY.spawn("command_foo") do |r,w|
+ ...
+ ...
+ PTY.protect_signal do
+ system "some other commands"
+ end
+ ...
+ end
+
+ このような記述により,"some other commands" が終了したときに
+ 例外が発生するのを防げます.
+
+ reset_signal
+
+ PTY の子プロセスが動いていても,そのプロセスの終了時に例外が発生
+ しないようにします.
+
+4. 利用について
+
+伊藤彰則が著作権を保有します.
+
+ソースプログラムまたはドキュメントに元の著作権表示が改変されずに
+表示されている場合に限り,誰でも,このソフトウェアを無償かつ著作
+権者に無断で利用・配布・改変できます.利用目的は限定されていませ
+ん.
+
+このプログラムの利用・配布その他このプログラムに関係する行為によ
+って生じたいかなる損害に対しても,作者は一切責任を負いません.
+
+5. バグ報告等
+
+バグレポートは歓迎します.
+
+ aito@ei5sun.yz.yamagata-u.ac.jp
+
+まで電子メールでバグレポートをお送りください.
View
1 lib/18/pty/depend
@@ -0,0 +1 @@
+pty.o: pty.c $(hdrdir)/ruby.h $(topdir)/config.h $(hdrdir)/defines.h
View
37 lib/18/pty/example/script.rb
@@ -0,0 +1,37 @@
+require 'pty'
+
+if ARGV.size == 0 then
+ ofile = "typescript"
+else
+ ofile = ARGV[0]
+end
+
+logfile = File.open(ofile,"a")
+
+system "stty -echo raw lnext ^_"
+
+PTY.spawn("/bin/csh") do |r_pty,w_pty,pid|
+
+ Thread.new do
+ while true
+ w_pty.print STDIN.getc.chr
+ w_pty.flush
+ end
+ end
+
+ begin
+ while true
+ c = r_pty.sysread(512)
+ break if c.nil?
+ print c
+ STDOUT.flush
+ logfile.print c
+ end
+ rescue
+ # print $@,':',$!,"\n"
+ logfile.close
+ end
+end
+
+system "stty echo -raw lnext ^v"
+
View
92 lib/18/pty/example/shl.rb
@@ -0,0 +1,92 @@
+#
+# old-fashioned 'shl' like program
+# by A. Ito
+#
+# commands:
+# c creates new shell
+# C-z suspends shell
+# p lists all shell
+# 0,1,... choose shell
+# q quit
+
+require 'pty'
+
+$shells = []
+$n_shells = 0
+
+$r_pty = nil
+$w_pty = nil
+
+def writer
+ system "stty -echo raw"
+ begin
+ while true
+ c = STDIN.getc
+ if c == 26 then # C-z
+ $reader.raise(nil)
+ return 'Suspend'
+ end
+ $w_pty.print c.chr
+ $w_pty.flush
+ end
+ rescue
+ $reader.raise(nil)
+ return 'Exit'
+ ensure
+ system "stty echo -raw"
+ end
+end
+
+$reader = Thread.new {
+ while true
+ begin
+ next if $r_pty.nil?
+ c = $r_pty.getc
+ if c.nil? then
+ Thread.stop
+ end
+ print c.chr
+ STDOUT.flush
+ rescue
+ Thread.stop
+ end
+ end
+}
+
+# $reader.raise(nil)
+
+
+while true
+ print ">> "
+ STDOUT.flush
+ case gets
+ when /^c/i
+ $shells[$n_shells] = PTY.spawn("/bin/csh")
+ $r_pty,$w_pty = $shells[$n_shells]
+ $n_shells += 1
+ $reader.run
+ if writer == 'Exit'
+ $n_shells -= 1
+ $shells[$n_shells] = nil
+ end
+ when /^p/i
+ for i in 0..$n_shells
+ unless $shells[i].nil?
+ print i,"\n"
+ end
+ end
+ when /^([0-9]+)/
+ n = $1.to_i
+ if $shells[n].nil?
+ print "\##{i} doesn't exist\n"
+ else
+ $r_pty,$w_pty = $shells[n]
+ $reader.run
+ if writer == 'Exit' then
+ $shells[n] = nil
+ end
+ end
+ when /^q/i
+ exit
+ end
+end
View
36 lib/18/pty/expect.rb
@@ -0,0 +1,36 @@
+$expect_verbose = false
+
+class IO
+ def expect(pat,timeout=9999999)
+ buf = ''
+ case pat
+ when String
+ e_pat = Regexp.new(Regexp.quote(pat))
+ when Regexp
+ e_pat = pat
+ end
+ while true
+ if !IO.select([self],nil,nil,timeout) or eof? then
+ result = nil
+ break
+ end
+ c = getc.chr
+ buf << c
+ if $expect_verbose
+ STDOUT.print c
+ STDOUT.flush
+ end
+ if mat=e_pat.match(buf) then
+ result = [buf,*mat.to_a[1..-1]]
+ break
+ end
+ end
+ if block_given? then
+ yield result
+ else
+ return result
+ end
+ nil
+ end
+end
+
View
48 lib/18/pty/expect_sample.rb
@@ -0,0 +1,48 @@
+#
+# sample program of expect.rb
+#
+# by A. Ito
+#
+# This program reports the latest version of ruby interpreter
+# by connecting to ftp server at ruby-lang.org.
+#
+require 'pty'
+require 'expect'
+
+fnames = []
+PTY.spawn("ftp ftp.ruby-lang.org") do |r_f,w_f,pid|
+ w_f.sync = true
+
+ $expect_verbose = false
+
+ if !ENV['USER'].nil?
+ username = ENV['USER']
+ elsif !ENV['LOGNAME'].nil?
+ username = ENV['LOGNAME']
+ else
+ username = 'guest'
+ end
+
+ r_f.expect(/^(Name).*: |(word):|> /) do
+ w_f.puts($1 ? "ftp" : $2 ? "#{username}@" : "cd pub/ruby")
+ end
+ r_f.expect("> ") do
+ w_f.print "dir\n"
+ end
+
+ r_f.expect(/[^\-]> /) do |output|
+ for x in output[0].split("\n")
+ if x =~ /(ruby.*?\.tar\.gz)/ then
+ fnames.push $1
+ end
+ end
+ end
+ begin
+ w_f.print "quit\n"
+ rescue
+ end
+end
+
+print "The latest ruby interpreter is "
+print fnames.sort.pop
+print "\n"
View
156 lib/18/pty/ext/Makefile
@@ -0,0 +1,156 @@
+
+SHELL = /bin/sh
+
+#### Start of system configuration section. ####
+
+srcdir = .
+topdir = /Users/evan/git/rbx/vm/capi/18/include
+hdrdir = $(topdir)
+VPATH = $(srcdir):$(topdir):$(hdrdir)
+prefix = $(DESTDIR)/Users/evan/git/rbx
+exec_prefix = $(prefix)
+install_prefix = $(DESTDIR)
+includedir = $(prefix)/include
+bindir = $(DESTDIR)/Users/evan/git/rbx/bin
+sysconfdir = $(prefix)/etc
+localedir = $(datarootdir)/locale
+rubylibdir = $(DESTDIR)/Users/evan/git/rbx/lib/site
+sitedir = $(DESTDIR)/Users/evan/git/rbx/lib/site
+oldincludedir = $(DESTDIR)/usr/include
+libexecdir = $(exec_prefix)/libexec
+rubyhdrdir = $(DESTDIR)/Users/evan/git/rbx/vm/capi/18/include
+libdir = $(exec_prefix)/lib
+dvidir = $(docdir)
+docdir = $(datarootdir)/doc/$(PACKAGE)
+psdir = $(docdir)
+infodir = $(datarootdir)/info
+datadir = $(datarootdir)
+archdir = $(DESTDIR)/Users/evan/git/rbx/lib/site/x86_64-darwin11.2.0
+sharedstatedir = $(prefix)/com
+localstatedir = $(prefix)/var
+pdfdir = $(docdir)
+htmldir = $(docdir)
+datarootdir = $(prefix)/share
+sbindir = $(exec_prefix)/sbin
+sitelibdir = $(DESTDIR)/Users/evan/git/rbx/lib/site
+mandir = $(datarootdir)/man
+sitearchdir = $(DESTDIR)/Users/evan/git/rbx/lib/site/x86_64-darwin11.2.0
+
+CC = gcc
+LIBRUBY = $(LIBRUBY_SO)
+LIBRUBY_A =
+LIBRUBYARG_SHARED =
+LIBRUBYARG_STATIC =
+
+RUBY_EXTCONF_H =
+CFLAGS = -fPIC -ggdb3 -fPIC -O2 -I/usr/local/include
+INCFLAGS = -I. -I. -I/Users/evan/git/rbx/vm/capi/18/include -I.
+DEFS =
+CPPFLAGS = -DHAVE_OPENPTY
+CXXFLAGS = $(CFLAGS)
+ldflags = -L/usr/local/lib
+dldflags =
+archflag =
+DLDFLAGS = $(ldflags) $(dldflags) $(archflag)
+LDSHARED = gcc -bundle -undefined suppress -flat_namespace
+AR = ar
+EXEEXT =
+
+RUBY_INSTALL_NAME = rbx
+RUBY_SO_NAME = rubinius-2.0.0dev
+arch = x86_64-darwin11.2.0
+sitearch = x86_64-darwin11.2.0
+ruby_version = 1.8
+ruby = /Users/evan/git/rbx/bin/rbx
+RUBY = $(ruby)
+RM = rm -f
+MAKEDIRS = mkdir -p
+INSTALL = install -c
+INSTALL_PROG = $(INSTALL) -m 0755
+INSTALL_DATA = $(INSTALL) -m 644
+COPY = cp
+
+#### End of system configuration section. ####
+
+preload =
+
+libpath = . $(libdir)
+LIBPATH = -L. -L$(libdir)
+DEFFILE =
+
+CLEANFILES = mkmf.log
+DISTCLEANFILES =
+
+extout =
+extout_prefix =
+target_prefix =
+LOCAL_LIBS =
+LIBS = $(LIBRUBYARG_STATIC) -lutil
+SRCS = pty.c
+OBJS = pty.o
+TARGET = pty
+DLLIB = $(TARGET).bundle
+EXTSTATIC =
+STATIC_LIB =
+
+BINDIR = $(bindir)
+RUBYCOMMONDIR = $(sitedir)$(target_prefix)
+RUBYLIBDIR = $(sitelibdir)$(target_prefix)
+RUBYARCHDIR = $(sitearchdir)$(target_prefix)
+
+TARGET_SO = $(DLLIB)
+CLEANLIBS = $(TARGET).bundle $(TARGET).il? $(TARGET).tds $(TARGET).map
+CLEANOBJS = *.o *.a *.s[ol] *.pdb *.exp *.bak
+
+all: $(DLLIB)
+static: $(STATIC_LIB)
+
+clean:
+ @-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES)
+
+distclean: clean
+ @-$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
+ @-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
+
+realclean: distclean
+install: install-so install-rb
+
+install-so: $(RUBYARCHDIR)
+install-so: $(RUBYARCHDIR)/$(DLLIB)
+$(RUBYARCHDIR)/$(DLLIB): $(DLLIB)
+ $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
+install-rb: pre-install-rb install-rb-default
+install-rb-default: pre-install-rb-default
+pre-install-rb: Makefile
+pre-install-rb-default: Makefile
+$(RUBYARCHDIR):
+ $(MAKEDIRS) $@
+
+site-install: site-install-so site-install-rb
+site-install-so: install-so
+site-install-rb: install-rb
+
+.SUFFIXES: .c .m .cc .cxx .cpp .C .o
+
+.cc.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.cxx.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.cpp.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.C.o:
+ $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) -c $<
+
+.c.o:
+ $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) -c $<
+
+$(DLLIB): $(OBJS) Makefile
+ @-$(RM) $@
+ $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
+
+
+
+$(OBJS): ruby.h defines.h
View
15 lib/18/pty/ext/extconf.rb
@@ -0,0 +1,15 @@
+require 'mkmf'
+
+if /mswin32|mingw|bccwin32/ !~ RUBY_PLATFORM
+ have_header("sys/stropts.h")
+ have_func("setresuid")
+ have_header("libutil.h")
+ have_header("pty.h")
+ have_library("util", "openpty")
+ if have_func("openpty") or
+ have_func("_getpty") or
+ have_func("ptsname") or
+ have_func("ioctl")
+ create_makefile('pty')
+ end
+end
View
472 lib/18/pty/ext/pty.c
@@ -0,0 +1,472 @@
+#include "config.h"
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <pwd.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_LIBUTIL_H
+#include <libutil.h>
+#endif
+#ifdef HAVE_PTY_H
+#include <pty.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#else
+#define WIFSTOPPED(status) (((status) & 0xff) == 0x7f)
+#endif
+#include <ctype.h>
+
+#include "ruby.h"
+#include "rubyio.h"
+#include "util.h"
+
+#include <signal.h>
+#ifdef HAVE_SYS_STROPTS_H
+#include <sys/stropts.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#define DEVICELEN 16
+
+#if !defined(HAVE_OPENPTY)
+#if defined(__hpux)
+static const
+char MasterDevice[] = "/dev/ptym/pty%s",
+ SlaveDevice[] = "/dev/pty/tty%s",
+ *const deviceNo[] = {
+ "p0","p1","p2","p3","p4","p5","p6","p7",
+ "p8","p9","pa","pb","pc","pd","pe","pf",
+ "q0","q1","q2","q3","q4","q5","q6","q7",
+ "q8","q9","qa","qb","qc","qd","qe","qf",
+ "r0","r1","r2","r3","r4","r5","r6","r7",
+ "r8","r9","ra","rb","rc","rd","re","rf",
+ "s0","s1","s2","s3","s4","s5","s6","s7",
+ "s8","s9","sa","sb","sc","sd","se","sf",
+ "t0","t1","t2","t3","t4","t5","t6","t7",
+ "t8","t9","ta","tb","tc","td","te","tf",
+ "u0","u1","u2","u3","u4","u5","u6","u7",
+ "u8","u9","ua","ub","uc","ud","ue","uf",
+ "v0","v1","v2","v3","v4","v5","v6","v7",
+ "v8","v9","va","vb","vc","vd","ve","vf",
+ "w0","w1","w2","w3","w4","w5","w6","w7",
+ "w8","w9","wa","wb","wc","wd","we","wf",
+ 0,
+ };
+#elif defined(_IBMESA) /* AIX/ESA */
+static const
+char MasterDevice[] = "/dev/ptyp%s",
+ SlaveDevice[] = "/dev/ttyp%s",
+ *const deviceNo[] = {
+"00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f",
+"10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f",
+"20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f",
+"30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f",
+"40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f",
+"50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f",
+"60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f",
+"70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f",
+"80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f",
+"90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f",
+"a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af",
+"b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf",
+"c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf",
+"d0","d1","d2","d3","d4","d5","d6","d7","d8","d9","da","db","dc","dd","de","df",
+"e0","e1","e2","e3","e4","e5","e6","e7","e8","e9","ea","eb","ec","ed","ee","ef",
+"f0","f1","f2","f3","f4","f5","f6","f7","f8","f9","fa","fb","fc","fd","fe","ff",
+ };
+#elif !defined(HAVE_PTSNAME)
+static const
+char MasterDevice[] = "/dev/pty%s",
+ SlaveDevice[] = "/dev/tty%s",
+ *const deviceNo[] = {
+ "p0","p1","p2","p3","p4","p5","p6","p7",
+ "p8","p9","pa","pb","pc","pd","pe","pf",
+ "q0","q1","q2","q3","q4","q5","q6","q7",
+ "q8","q9","qa","qb","qc","qd","qe","qf",
+ "r0","r1","r2","r3","r4","r5","r6","r7",
+ "r8","r9","ra","rb","rc","rd","re","rf",
+ "s0","s1","s2","s3","s4","s5","s6","s7",
+ "s8","s9","sa","sb","sc","sd","se","sf",
+ 0,
+ };
+#endif
+#endif /* !defined(HAVE_OPENPTY) */
+
+#ifndef HAVE_SETEUID
+# ifdef HAVE_SETREUID
+# define seteuid(e) setreuid(-1, (e))
+# else /* NOT HAVE_SETREUID */
+# ifdef HAVE_SETRESUID
+# define seteuid(e) setresuid(-1, (e), -1)
+# else /* NOT HAVE_SETRESUID */
+ /* I can't set euid. (;_;) */
+# endif /* HAVE_SETRESUID */
+# endif /* HAVE_SETREUID */
+#endif /* NO_SETEUID */
+
+static VALUE eChildExited;
+
+static VALUE
+echild_status(self)
+ VALUE self;
+{
+ return rb_ivar_get(self, rb_intern("status"));
+}
+
+struct pty_info {
+ int fd;
+ int child_pid;
+ VALUE thread;
+};
+
+static void
+raise_from_wait(state, info)
+ struct pty_info *info;
+ char *state;
+{
+ VALUE last_status = rb_gv_get("$?");
+ char buf[1024];
+ VALUE exc;
+
+ snprintf(buf, sizeof(buf), "pty - %s: %ld", state, (long)info->child_pid);
+ exc = rb_exc_new2(eChildExited, buf);
+ rb_iv_set(exc, "status", last_status);
+ rb_funcall(info->thread, rb_intern("raise"), 1, exc);
+}
+
+static VALUE
+pty_syswait(info)
+ struct pty_info *info;
+{
+ int cpid, status;
+
+ for (;;) {
+ cpid = rb_waitpid(info->child_pid, &status, WUNTRACED);
+ if (cpid == -1) return Qnil;
+
+#if defined(WIFSTOPPED)
+#elif defined(IF_STOPPED)
+#define WIFSTOPPED(status) IF_STOPPED(status)
+#else
+ ---->> Either IF_STOPPED or WIFSTOPPED is needed <<----
+#endif /* WIFSTOPPED | IF_STOPPED */
+ if (WIFSTOPPED(status)) { /* suspend */
+ raise_from_wait("stopped", info);
+ }
+ else if (kill(info->child_pid, 0) == 0) {
+ raise_from_wait("changed", info);
+ }
+ else {
+ raise_from_wait("exited", info);
+ return Qnil;
+ }
+ }
+}
+
+static void getDevice _((int*, int*, char [DEVICELEN]));
+
+struct exec_info {
+ int argc;
+ VALUE *argv;
+};
+
+static VALUE pty_exec _((VALUE v));
+
+static VALUE
+pty_exec(v)
+ VALUE v;
+{
+ struct exec_info *arg = (struct exec_info *)v;
+ return rb_funcall2(rb_mKernel, rb_intern("exec"),
+ arg->argc, arg->argv);
+}
+
+static void
+establishShell(argc, argv, info, SlaveName)
+ int argc;
+ VALUE *argv;
+ struct pty_info *info;
+ char SlaveName[DEVICELEN];
+{
+ int i,master,slave;
+ char *p, tmp, *getenv();
+ struct passwd *pwent;
+ VALUE v;
+ struct exec_info arg;
+ int status;
+
+ if (argc == 0) {
+ char *shellname;
+
+ if ((p = getenv("SHELL")) != NULL) {
+ shellname = p;
+ }
+ else {
+ pwent = getpwuid(getuid());
+ if (pwent && pwent->pw_shell)
+ shellname = pwent->pw_shell;
+ else
+ shellname = "/bin/sh";
+ }
+ v = rb_str_new2(shellname);
+ argc = 1;
+ argv = &v;
+ }
+ getDevice(&master, &slave, SlaveName);
+
+ info->thread = rb_thread_current();
+ VALUE rb_pid = rb_funcall(rb_mKernel, rb_intern("fork"), 0);
+
+ /* if(i < 0) { */
+ /* close(master); */
+ /* close(slave); */
+ /* rb_sys_fail("fork failed"); */
+ /* } */
+
+ if(rb_pid == Qnil) { /* child */
+ /*
+ * Set free from process group and controlling terminal
+ */
+#ifdef HAVE_SETSID
+ (void) setsid();
+#else /* HAS_SETSID */
+# ifdef HAVE_SETPGRP
+# ifdef SETGRP_VOID
+ if (setpgrp() == -1)
+ perror("setpgrp()");
+# else /* SETGRP_VOID */
+ if (setpgrp(0, getpid()) == -1)
+ rb_sys_fail("setpgrp()");
+ if ((i = open("/dev/tty", O_RDONLY)) < 0)
+ rb_sys_fail("/dev/tty");
+ else {
+ if (ioctl(i, TIOCNOTTY, (char *)0))
+ perror("ioctl(TIOCNOTTY)");
+ close(i);
+ }
+# endif /* SETGRP_VOID */
+# endif /* HAVE_SETPGRP */
+#endif /* HAS_SETSID */
+
+ /*
+ * obtain new controlling terminal
+ */
+#if defined(TIOCSCTTY)
+ close(master);
+ (void) ioctl(slave, TIOCSCTTY, (char *)0);
+ /* errors ignored for sun */
+#else
+ close(slave);
+ slave = open(SlaveName, O_RDWR);
+ if (slave < 0) {
+ perror("open: pty slave");
+ _exit(1);
+ }
+ close(master);
+#endif
+ write(slave, "", 1);
+ dup2(slave,0);
+ dup2(slave,1);
+ dup2(slave,2);
+ close(slave);
+#if defined(HAVE_SETEUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRESUID)
+ seteuid(getuid());
+#endif
+
+ arg.argc = argc;
+ arg.argv = argv;
+ rb_protect(pty_exec, (VALUE)&arg, &status);
+ sleep(1);
+ _exit(1);
+ }
+
+ read(master, &tmp, 1);
+ close(slave);
+
+ info->child_pid = FIX2INT(i);
+ info->fd = master;
+}
+
+static int
+get_device_once(master, slave, SlaveName, fail)
+ int *master, *slave, fail;
+ char SlaveName[DEVICELEN];
+{
+#if defined HAVE_OPENPTY
+ /*
+ * Use openpty(3) of 4.3BSD Reno and later,
+ * or the same interface function.
+ */
+ if (openpty(master, slave, SlaveName,
+ (struct termios *)0, (struct winsize *)0) == -1) {
+ if (!fail) return -1;
+ rb_raise(rb_eRuntimeError, "openpty() failed");
+ }
+
+ return 0;
+#elif defined HAVE__GETPTY
+ char *name;
+
+ if (!(name = _getpty(master, O_RDWR, 0622, 0))) {
+ if (!fail) return -1;
+ rb_raise(rb_eRuntimeError, "_getpty() failed");
+ }
+
+ *slave = open(name, O_RDWR);
+ strncpy(SlaveName, name, sizeof SlaveName);
+
+ return 0;
+#else /* HAVE__GETPTY */
+ int i,j;
+
+#ifdef HAVE_PTSNAME
+ char *pn;
+ void (*s)();
+
+ extern char *ptsname(int);
+ extern int unlockpt(int);
+ extern int grantpt(int);
+
+ if((i = open("/dev/ptmx", O_RDWR, 0)) != -1) {
+ s = signal(SIGCHLD, SIG_DFL);
+ if(grantpt(i) != -1) {
+ signal(SIGCHLD, s);
+ if(unlockpt(i) != -1) {
+ if((pn = ptsname(i)) != NULL) {
+ if((j = open(pn, O_RDWR, 0)) != -1) {
+#if defined I_PUSH && !defined linux
+ if(ioctl(j, I_PUSH, "ptem") != -1) {
+ if(ioctl(j, I_PUSH, "ldterm") != -1) {
+ ioctl(j, I_PUSH, "ttcompat");
+#endif
+ *master = i;
+ *slave = j;
+ strncpy(SlaveName, pn, sizeof SlaveName);
+ return 0;
+#if defined I_PUSH && !defined linux
+ }
+ }
+#endif
+ }
+ }
+ }
+ }
+ close(i);
+ }
+ if (!fail) rb_raise(rb_eRuntimeError, "can't get Master/Slave device");
+ return -1;
+#else
+ char **p;
+ char MasterName[DEVICELEN];
+
+ for (p = deviceNo; *p != NULL; p++) {
+ snprintf(MasterName, sizeof MasterName, MasterDevice, *p);
+ if ((i = open(MasterName,O_RDWR,0)) >= 0) {
+ *master = i;
+ snprintf(SlaveName, sizeof SlaveName, SlaveDevice, *p);
+ if ((j = open(SlaveName,O_RDWR,0)) >= 0) {
+ *slave = j;
+ chown(SlaveName, getuid(), getgid());
+ chmod(SlaveName, 0622);
+ return 0;
+ }
+ close(i);
+ }
+ }
+ if (fail) rb_raise(rb_eRuntimeError, "can't get %s", SlaveName);
+ return -1;
+#endif
+#endif
+}
+
+static void
+getDevice(master, slave, slavename)
+ int *master, *slave;
+ char slavename[DEVICELEN];
+{
+ if (get_device_once(master, slave, slavename, 0)) {
+ rb_gc();
+ get_device_once(master, slave, slavename, 1);
+ }
+}
+
+/* ruby function: getpty */
+static VALUE
+pty_getpty(argc, argv, self)
+ int argc;
+ VALUE *argv;
+ VALUE self;
+{
+ VALUE res, rport, wport;
+ struct pty_info info;
+ struct pty_info thinfo;
+ rb_io_t *wfptr,*rfptr;
+ char SlaveName[DEVICELEN];
+
+ establishShell(argc, argv, &info, SlaveName);
+
+ rport = rb_funcall(rb_cFile, rb_intern("new"), 2, INT2FIX(info.fd),
+ rb_str_new2("r"));
+
+ rb_ivar_set(rport, rb_intern("@path"), rb_str_new2(SlaveName));
+
+ wport = rb_funcall(rb_cFile, rb_intern("new"), 2, INT2FIX(dup(info.fd)),
+ rb_str_new2("r"));
+
+ rb_ivar_set(wport, rb_intern("@path"), rb_str_new2(SlaveName));
+ rb_funcall(wport, rb_intern("sync="), 1, Qtrue);
+
+ res = rb_ary_new2(3);
+ rb_ary_store(res,0,(VALUE)rport);
+ rb_ary_store(res,1,(VALUE)wport);
+ rb_ary_store(res,2,INT2FIX(info.child_pid));
+
+ if (rb_block_given_p()) {
+ rb_yield(res);
+ return Qnil;
+ }
+ return res;
+}
+
+/* ruby function: protect_signal - obsolete */
+static VALUE
+pty_protect(self)
+ VALUE self;
+{
+ rb_warn("PTY::protect_signal is no longer needed");
+ rb_yield(Qnil);
+ return self;
+}
+
+/* ruby function: reset_signal - obsolete */
+static VALUE
+pty_reset_signal(self)
+ VALUE self;
+{
+ rb_warn("PTY::reset_signal is no longer needed");
+ return self;
+}
+
+static VALUE cPTY;
+
+void
+Init_pty()
+{
+ cPTY = rb_define_module("PTY");
+ rb_define_module_function(cPTY,"getpty",pty_getpty,-1);
+ rb_define_module_function(cPTY,"spawn",pty_getpty,-1);
+ rb_define_module_function(cPTY,"protect_signal",pty_protect,0);
+ rb_define_module_function(cPTY,"reset_signal",pty_reset_signal,0);
+
+ eChildExited = rb_define_class_under(cPTY,"ChildExited",rb_eRuntimeError);
+ rb_define_method(eChildExited,"status",echild_status,0);
+}
View
5 rakelib/extensions.rake
@@ -177,6 +177,11 @@ compile_ext "19/openssl", :deps => ["Makefile", "extconf.h"],
compile_ext "18/dl", :deps => ["Makefile", "dlconfig.h"],
:dir => "lib/18/dl/ext", :env => "-X18"
+
+compile_ext "18/pty", :deps => ["Makefile", "extconf.h"],
+ :dir => "lib/18/pty/ext",
+ :env => "-X18"
+
compile_ext "dbm", :ignore_fail => true, :deps => ["Makefile"], :dir => "lib/dbm/ext"
compile_ext "gdbm", :ignore_fail => true, :deps => ["Makefile"], :dir => "lib/gdbm/ext"
compile_ext "sdbm", :deps => ["Makefile"], :dir => "lib/sdbm/ext"

0 comments on commit a420bda

Please sign in to comment.