diff options
Diffstat (limited to 'ext/pty')
| -rw-r--r-- | ext/pty/depend | 189 | ||||
| -rw-r--r-- | ext/pty/extconf.rb | 21 | ||||
| -rw-r--r-- | ext/pty/lib/expect.rb | 34 | ||||
| -rw-r--r-- | ext/pty/pty.c | 756 |
4 files changed, 688 insertions, 312 deletions
diff --git a/ext/pty/depend b/ext/pty/depend index 2249eb8fcd..8fa018d084 100644 --- a/ext/pty/depend +++ b/ext/pty/depend @@ -1 +1,188 @@ -pty.o: pty.c $(hdrdir)/ruby.h $(topdir)/config.h $(hdrdir)/defines.h $(hdrdir)/io.h +# AUTOGENERATED DEPENDENCIES START +pty.o: $(RUBY_EXTCONF_H) +pty.o: $(arch_hdrdir)/ruby/config.h +pty.o: $(hdrdir)/ruby/assert.h +pty.o: $(hdrdir)/ruby/backward.h +pty.o: $(hdrdir)/ruby/backward/2/assume.h +pty.o: $(hdrdir)/ruby/backward/2/attributes.h +pty.o: $(hdrdir)/ruby/backward/2/bool.h +pty.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h +pty.o: $(hdrdir)/ruby/backward/2/inttypes.h +pty.o: $(hdrdir)/ruby/backward/2/limits.h +pty.o: $(hdrdir)/ruby/backward/2/long_long.h +pty.o: $(hdrdir)/ruby/backward/2/stdalign.h +pty.o: $(hdrdir)/ruby/backward/2/stdarg.h +pty.o: $(hdrdir)/ruby/defines.h +pty.o: $(hdrdir)/ruby/encoding.h +pty.o: $(hdrdir)/ruby/intern.h +pty.o: $(hdrdir)/ruby/internal/abi.h +pty.o: $(hdrdir)/ruby/internal/anyargs.h +pty.o: $(hdrdir)/ruby/internal/arithmetic.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/char.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/double.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/int.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/long.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/short.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +pty.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +pty.o: $(hdrdir)/ruby/internal/assume.h +pty.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +pty.o: $(hdrdir)/ruby/internal/attr/artificial.h +pty.o: $(hdrdir)/ruby/internal/attr/cold.h +pty.o: $(hdrdir)/ruby/internal/attr/const.h +pty.o: $(hdrdir)/ruby/internal/attr/constexpr.h +pty.o: $(hdrdir)/ruby/internal/attr/deprecated.h +pty.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +pty.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +pty.o: $(hdrdir)/ruby/internal/attr/error.h +pty.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +pty.o: $(hdrdir)/ruby/internal/attr/forceinline.h +pty.o: $(hdrdir)/ruby/internal/attr/format.h +pty.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +pty.o: $(hdrdir)/ruby/internal/attr/noalias.h +pty.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +pty.o: $(hdrdir)/ruby/internal/attr/noexcept.h +pty.o: $(hdrdir)/ruby/internal/attr/noinline.h +pty.o: $(hdrdir)/ruby/internal/attr/nonnull.h +pty.o: $(hdrdir)/ruby/internal/attr/noreturn.h +pty.o: $(hdrdir)/ruby/internal/attr/packed_struct.h +pty.o: $(hdrdir)/ruby/internal/attr/pure.h +pty.o: $(hdrdir)/ruby/internal/attr/restrict.h +pty.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +pty.o: $(hdrdir)/ruby/internal/attr/warning.h +pty.o: $(hdrdir)/ruby/internal/attr/weakref.h +pty.o: $(hdrdir)/ruby/internal/cast.h +pty.o: $(hdrdir)/ruby/internal/compiler_is.h +pty.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +pty.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +pty.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +pty.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +pty.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +pty.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +pty.o: $(hdrdir)/ruby/internal/compiler_since.h +pty.o: $(hdrdir)/ruby/internal/config.h +pty.o: $(hdrdir)/ruby/internal/constant_p.h +pty.o: $(hdrdir)/ruby/internal/core.h +pty.o: $(hdrdir)/ruby/internal/core/rarray.h +pty.o: $(hdrdir)/ruby/internal/core/rbasic.h +pty.o: $(hdrdir)/ruby/internal/core/rbignum.h +pty.o: $(hdrdir)/ruby/internal/core/rclass.h +pty.o: $(hdrdir)/ruby/internal/core/rdata.h +pty.o: $(hdrdir)/ruby/internal/core/rfile.h +pty.o: $(hdrdir)/ruby/internal/core/rhash.h +pty.o: $(hdrdir)/ruby/internal/core/robject.h +pty.o: $(hdrdir)/ruby/internal/core/rregexp.h +pty.o: $(hdrdir)/ruby/internal/core/rstring.h +pty.o: $(hdrdir)/ruby/internal/core/rstruct.h +pty.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +pty.o: $(hdrdir)/ruby/internal/ctype.h +pty.o: $(hdrdir)/ruby/internal/dllexport.h +pty.o: $(hdrdir)/ruby/internal/dosish.h +pty.o: $(hdrdir)/ruby/internal/encoding/coderange.h +pty.o: $(hdrdir)/ruby/internal/encoding/ctype.h +pty.o: $(hdrdir)/ruby/internal/encoding/encoding.h +pty.o: $(hdrdir)/ruby/internal/encoding/pathname.h +pty.o: $(hdrdir)/ruby/internal/encoding/re.h +pty.o: $(hdrdir)/ruby/internal/encoding/sprintf.h +pty.o: $(hdrdir)/ruby/internal/encoding/string.h +pty.o: $(hdrdir)/ruby/internal/encoding/symbol.h +pty.o: $(hdrdir)/ruby/internal/encoding/transcode.h +pty.o: $(hdrdir)/ruby/internal/error.h +pty.o: $(hdrdir)/ruby/internal/eval.h +pty.o: $(hdrdir)/ruby/internal/event.h +pty.o: $(hdrdir)/ruby/internal/fl_type.h +pty.o: $(hdrdir)/ruby/internal/gc.h +pty.o: $(hdrdir)/ruby/internal/glob.h +pty.o: $(hdrdir)/ruby/internal/globals.h +pty.o: $(hdrdir)/ruby/internal/has/attribute.h +pty.o: $(hdrdir)/ruby/internal/has/builtin.h +pty.o: $(hdrdir)/ruby/internal/has/c_attribute.h +pty.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +pty.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +pty.o: $(hdrdir)/ruby/internal/has/extension.h +pty.o: $(hdrdir)/ruby/internal/has/feature.h +pty.o: $(hdrdir)/ruby/internal/has/warning.h +pty.o: $(hdrdir)/ruby/internal/intern/array.h +pty.o: $(hdrdir)/ruby/internal/intern/bignum.h +pty.o: $(hdrdir)/ruby/internal/intern/class.h +pty.o: $(hdrdir)/ruby/internal/intern/compar.h +pty.o: $(hdrdir)/ruby/internal/intern/complex.h +pty.o: $(hdrdir)/ruby/internal/intern/cont.h +pty.o: $(hdrdir)/ruby/internal/intern/dir.h +pty.o: $(hdrdir)/ruby/internal/intern/enum.h +pty.o: $(hdrdir)/ruby/internal/intern/enumerator.h +pty.o: $(hdrdir)/ruby/internal/intern/error.h +pty.o: $(hdrdir)/ruby/internal/intern/eval.h +pty.o: $(hdrdir)/ruby/internal/intern/file.h +pty.o: $(hdrdir)/ruby/internal/intern/hash.h +pty.o: $(hdrdir)/ruby/internal/intern/io.h +pty.o: $(hdrdir)/ruby/internal/intern/load.h +pty.o: $(hdrdir)/ruby/internal/intern/marshal.h +pty.o: $(hdrdir)/ruby/internal/intern/numeric.h +pty.o: $(hdrdir)/ruby/internal/intern/object.h +pty.o: $(hdrdir)/ruby/internal/intern/parse.h +pty.o: $(hdrdir)/ruby/internal/intern/proc.h +pty.o: $(hdrdir)/ruby/internal/intern/process.h +pty.o: $(hdrdir)/ruby/internal/intern/random.h +pty.o: $(hdrdir)/ruby/internal/intern/range.h +pty.o: $(hdrdir)/ruby/internal/intern/rational.h +pty.o: $(hdrdir)/ruby/internal/intern/re.h +pty.o: $(hdrdir)/ruby/internal/intern/ruby.h +pty.o: $(hdrdir)/ruby/internal/intern/select.h +pty.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +pty.o: $(hdrdir)/ruby/internal/intern/set.h +pty.o: $(hdrdir)/ruby/internal/intern/signal.h +pty.o: $(hdrdir)/ruby/internal/intern/sprintf.h +pty.o: $(hdrdir)/ruby/internal/intern/string.h +pty.o: $(hdrdir)/ruby/internal/intern/struct.h +pty.o: $(hdrdir)/ruby/internal/intern/thread.h +pty.o: $(hdrdir)/ruby/internal/intern/time.h +pty.o: $(hdrdir)/ruby/internal/intern/variable.h +pty.o: $(hdrdir)/ruby/internal/intern/vm.h +pty.o: $(hdrdir)/ruby/internal/interpreter.h +pty.o: $(hdrdir)/ruby/internal/iterator.h +pty.o: $(hdrdir)/ruby/internal/memory.h +pty.o: $(hdrdir)/ruby/internal/method.h +pty.o: $(hdrdir)/ruby/internal/module.h +pty.o: $(hdrdir)/ruby/internal/newobj.h +pty.o: $(hdrdir)/ruby/internal/scan_args.h +pty.o: $(hdrdir)/ruby/internal/special_consts.h +pty.o: $(hdrdir)/ruby/internal/static_assert.h +pty.o: $(hdrdir)/ruby/internal/stdalign.h +pty.o: $(hdrdir)/ruby/internal/stdbool.h +pty.o: $(hdrdir)/ruby/internal/stdckdint.h +pty.o: $(hdrdir)/ruby/internal/symbol.h +pty.o: $(hdrdir)/ruby/internal/value.h +pty.o: $(hdrdir)/ruby/internal/value_type.h +pty.o: $(hdrdir)/ruby/internal/variable.h +pty.o: $(hdrdir)/ruby/internal/warning_push.h +pty.o: $(hdrdir)/ruby/internal/xmalloc.h +pty.o: $(hdrdir)/ruby/io.h +pty.o: $(hdrdir)/ruby/missing.h +pty.o: $(hdrdir)/ruby/onigmo.h +pty.o: $(hdrdir)/ruby/oniguruma.h +pty.o: $(hdrdir)/ruby/ruby.h +pty.o: $(hdrdir)/ruby/st.h +pty.o: $(hdrdir)/ruby/subst.h +pty.o: $(hdrdir)/ruby/util.h +pty.o: $(top_srcdir)/id_table.h +pty.o: $(top_srcdir)/internal.h +pty.o: $(top_srcdir)/internal/array.h +pty.o: $(top_srcdir)/internal/compilers.h +pty.o: $(top_srcdir)/internal/gc.h +pty.o: $(top_srcdir)/internal/imemo.h +pty.o: $(top_srcdir)/internal/process.h +pty.o: $(top_srcdir)/internal/signal.h +pty.o: $(top_srcdir)/internal/static_assert.h +pty.o: $(top_srcdir)/internal/warnings.h +pty.o: $(top_srcdir)/shape.h +pty.o: pty.c +# AUTOGENERATED DEPENDENCIES END diff --git a/ext/pty/extconf.rb b/ext/pty/extconf.rb index 3d6d0f1e46..ae2cb45d3c 100644 --- a/ext/pty/extconf.rb +++ b/ext/pty/extconf.rb @@ -1,17 +1,28 @@ +# frozen_string_literal: true require 'mkmf' +$INCFLAGS << " -I$(topdir) -I$(top_srcdir)" + if /mswin|mingw|bccwin/ !~ RUBY_PLATFORM have_header("sys/stropts.h") have_func("setresuid") have_header("libutil.h") - have_header("util.h") # OpenBSD openpty have_header("pty.h") - have_library("util", "openpty") - if have_func("posix_openpt") or - have_func("openpty") or + have_header("pwd.h") + if /openbsd/ =~ RUBY_PLATFORM + have_header("util.h") # OpenBSD openpty + util = have_library("util", "openpty") + end + openpt = have_func("posix_openpt") + if openpt + have_func("ptsname_r") or have_func("ptsname") + end + if openpt or + (util or have_func("openpty")) or have_func("_getpty") or - have_func("ptsname") or have_func("ioctl") + have_macro("HAVE_FCHMOD") or have_func("fchmod") + have_macro("HAVE_FCHOWN") or have_func("fchown") create_makefile('pty') end end diff --git a/ext/pty/lib/expect.rb b/ext/pty/lib/expect.rb index c15044bd2a..22cbf54115 100644 --- a/ext/pty/lib/expect.rb +++ b/ext/pty/lib/expect.rb @@ -1,34 +1,55 @@ +# frozen_string_literal: true $expect_verbose = false class IO - # Reads from the IO until pattern +pat+ matches or the +timeout+ is over. + # call-seq: + # IO#expect(pattern,timeout=9999999) -> Array + # IO#expect(pattern,timeout=9999999) { |result| ... } -> nil + # + # The +expect+ library adds instance method IO#expect, + # which is similar to the + # {TCL expect extension}[https://www.tcl.tk/man/expect5.31/expect.1.html]. + # + # To use this method, you must require +expect+: + # + # require 'expect' + # + # Reads from the IO until the given +pattern+ matches or the +timeout+ is over. + # # It returns an array with the read buffer, followed by the matches. # If a block is given, the result is yielded to the block and returns nil. # + # When called without a block, it waits until the input that matches the + # given +pattern+ is obtained from the IO or the time specified as the + # timeout passes. An array is returned when the pattern is obtained from the + # IO. The first element of the array is the entire string obtained from the + # IO until the pattern matches, followed by elements indicating which the + # pattern which matched to the anchor in the regular expression. + # # The optional timeout parameter defines, in seconds, the total time to wait # for the pattern. If the timeout expires or eof is found, nil is returned # or yielded. However, the buffer in a timeout session is kept for the next # expect call. The default timeout is 9999999 seconds. def expect(pat,timeout=9999999) - buf = '' + buf = ''.dup case pat when String e_pat = Regexp.new(Regexp.quote(pat)) when Regexp e_pat = pat else - raise TypeError, "unsupported pattern class: #{pattern.class}" + raise TypeError, "unsupported pattern class: #{pat.class}" end @unusedBuf ||= '' while true if not @unusedBuf.empty? - c = @unusedBuf.slice!(0).chr + c = @unusedBuf.slice!(0) elsif !IO.select([self],nil,nil,timeout) or eof? then result = nil @unusedBuf = buf break else - c = getc.chr + c = getc end buf << c if $expect_verbose @@ -36,7 +57,7 @@ class IO STDOUT.flush end if mat=e_pat.match(buf) then - result = [buf,*mat.to_a[1..-1]] + result = [buf,*mat.captures] break end end @@ -48,4 +69,3 @@ class IO nil end end - diff --git a/ext/pty/pty.c b/ext/pty/pty.c index b18eb68f77..3d5977707f 100644 --- a/ext/pty/pty.c +++ b/ext/pty/pty.c @@ -1,39 +1,51 @@ -#include "ruby/config.h" +#include "ruby/config.h" + #ifdef RUBY_EXTCONF_H -#include RUBY_EXTCONF_H +# include RUBY_EXTCONF_H #endif -#include <stdlib.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> + +#include <ctype.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <fcntl.h> + +#ifdef HAVE_PWD_H +# include <pwd.h> +#endif + #ifdef HAVE_SYS_IOCTL_H -#include <sys/ioctl.h> +# include <sys/ioctl.h> #endif + #ifdef HAVE_LIBUTIL_H -#include <libutil.h> +# include <libutil.h> #endif + #ifdef HAVE_UTIL_H -#include <util.h> +# include <util.h> #endif + #ifdef HAVE_PTY_H -#include <pty.h> +# include <pty.h> #endif + +#if defined(HAVE_SYS_PARAM_H) + /* for __FreeBSD_version */ +# include <sys/param.h> +#endif + #ifdef HAVE_SYS_WAIT_H -#include <sys/wait.h> +# include <sys/wait.h> #else -#define WIFSTOPPED(status) (((status) & 0xff) == 0x7f) +# define WIFSTOPPED(status) (((status) & 0xff) == 0x7f) #endif -#include <ctype.h> - -#include "ruby/ruby.h" -#include "ruby/io.h" -#include "ruby/util.h" -#include <signal.h> #ifdef HAVE_SYS_STROPTS_H #include <sys/stropts.h> #endif @@ -42,71 +54,13 @@ #include <unistd.h> #endif -#define DEVICELEN 16 +#include "internal.h" +#include "internal/process.h" +#include "internal/signal.h" +#include "ruby/io.h" +#include "ruby/util.h" -#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) */ +#define DEVICELEN 16 #ifndef HAVE_SETEUID # ifdef HAVE_SETREUID @@ -138,64 +92,85 @@ struct pty_info { static void getDevice(int*, int*, char [DEVICELEN], int); +static int start_new_session(char *errbuf, size_t errbuf_len); +static int obtain_ctty(int master, int slave, const char *slavename, char *errbuf, size_t errbuf_len); +static int drop_privilege(char *errbuf, size_t errbuf_len); + struct child_info { int master, slave; - char *slavename; - int argc; - VALUE *argv; + const char *slavename; + VALUE execarg_obj; + struct rb_execarg *eargp; }; static int chfunc(void *data, char *errbuf, size_t errbuf_len) { - struct child_info *carg = data; + const struct child_info *carg = data; int master = carg->master; int slave = carg->slave; - int argc = carg->argc; - VALUE *argv = carg->argv; + const char *slavename = carg->slavename; + + if (start_new_session(errbuf, errbuf_len)) + return -1; + + if (obtain_ctty(master, slave, slavename, errbuf, errbuf_len)) + return -1; + + if (drop_privilege(errbuf, errbuf_len)) + return -1; + + return rb_exec_async_signal_safe(carg->eargp, errbuf, errbuf_len); +} #define ERROR_EXIT(str) do { \ - strlcpy(errbuf, (str), errbuf_len); \ - return -1; \ + strlcpy(errbuf, (str), errbuf_len); \ + return -1; \ } while (0) - rb_thread_atfork_before_exec(); - - /* - * Set free from process group and controlling terminal - */ +/* + * Set free from process group and controlling terminal + */ +static int +start_new_session(char *errbuf, size_t errbuf_len) +{ #ifdef HAVE_SETSID (void) setsid(); #else /* HAS_SETSID */ # ifdef HAVE_SETPGRP -# ifdef SETGRP_VOID +# ifdef SETPGRP_VOID if (setpgrp() == -1) ERROR_EXIT("setpgrp()"); -# else /* SETGRP_VOID */ +# else /* SETPGRP_VOID */ if (setpgrp(0, getpid()) == -1) ERROR_EXIT("setpgrp()"); { - int i = open("/dev/tty", O_RDONLY); + int i = rb_cloexec_open("/dev/tty", O_RDONLY, 0); if (i < 0) ERROR_EXIT("/dev/tty"); rb_update_max_fd(i); if (ioctl(i, TIOCNOTTY, (char *)0)) ERROR_EXIT("ioctl(TIOCNOTTY)"); close(i); } -# endif /* SETGRP_VOID */ +# endif /* SETPGRP_VOID */ # endif /* HAVE_SETPGRP */ #endif /* HAS_SETSID */ + return 0; +} - /* - * obtain new controlling terminal - */ +/* + * obtain new controlling terminal + */ +static int +obtain_ctty(int master, int slave, const char *slavename, char *errbuf, size_t errbuf_len) +{ #if defined(TIOCSCTTY) close(master); (void) ioctl(slave, TIOCSCTTY, (char *)0); /* errors ignored for sun */ #else close(slave); - slave = open(carg->slavename, O_RDWR); + slave = rb_cloexec_open(slavename, O_RDWR, 0); if (slave < 0) { ERROR_EXIT("open: pty slave"); } @@ -205,83 +180,142 @@ chfunc(void *data, char *errbuf, size_t errbuf_len) dup2(slave,0); dup2(slave,1); dup2(slave,2); - close(slave); + if (slave > 2) (void)!close(slave); + return 0; +} + +static int +drop_privilege(char *errbuf, size_t errbuf_len) +{ #if defined(HAVE_SETEUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRESUID) - seteuid(getuid()); + if (seteuid(getuid())) ERROR_EXIT("seteuid()"); #endif - - rb_f_exec(argc, argv); return 0; -#undef ERROR_EXIT } +#undef ERROR_EXIT + static void establishShell(int argc, VALUE *argv, struct pty_info *info, - char SlaveName[DEVICELEN]) + char SlaveName[DEVICELEN]) { - int master,slave; + int master, slave, status = 0; rb_pid_t pid; - char *p, *getenv(); - struct passwd *pwent; + char *p; VALUE v; struct child_info carg; char errbuf[32]; if (argc == 0) { - const 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; + const char *shellname = "/bin/sh"; + + if ((p = getenv("SHELL")) != NULL) { + shellname = p; + } + else { +#if defined HAVE_PWD_H + const char *username = getenv("USER"); + if (username == NULL) + username = getlogin(); + if (username != NULL) { + struct passwd *pwent = getpwnam(username); + if (pwent && pwent->pw_shell) + shellname = pwent->pw_shell; + } +#endif + } + v = rb_str_new2(shellname); + argc = 1; + argv = &v; } + carg.execarg_obj = rb_execarg_new(argc, argv, 1, 0); + carg.eargp = rb_execarg_get(carg.execarg_obj); + rb_execarg_parent_start(carg.execarg_obj); + getDevice(&master, &slave, SlaveName, 0); carg.master = master; carg.slave = slave; carg.slavename = SlaveName; - carg.argc = argc; - carg.argv = argv; errbuf[0] = '\0'; - pid = rb_fork_err(0, chfunc, &carg, Qnil, errbuf, sizeof(errbuf)); + pid = rb_fork_async_signal_safe(&status, chfunc, &carg, Qnil, errbuf, sizeof(errbuf)); if (pid < 0) { - int e = errno; - close(master); - close(slave); - errno = e; - rb_sys_fail(errbuf[0] ? errbuf : "fork failed"); + int e = errno; + close(master); + close(slave); + rb_execarg_parent_end(carg.execarg_obj); + errno = e; + if (status) rb_jump_tag(status); + rb_sys_fail(errbuf[0] ? errbuf : "fork failed"); } close(slave); + rb_execarg_parent_end(carg.execarg_obj); info->child_pid = pid; info->fd = master; + + RB_GC_GUARD(carg.execarg_obj); } +#if (defined(HAVE_POSIX_OPENPT) || defined(HAVE_PTSNAME)) && !defined(HAVE_PTSNAME_R) +/* glibc only, not obsolete interface on Tru64 or HP-UX */ static int -no_mesg(char *slavedevice, int nomesg) +ptsname_r(int fd, char *buf, size_t buflen) +{ + extern char *ptsname(int); + char *name = ptsname(fd); + if (!name) return -1; + strlcpy(buf, name, buflen); + return 0; +} +# define HAVE_PTSNAME_R 1 +#endif + +#ifdef HAVE_FCHMOD +# define change_mode(name, fd, mode) fchmod(fd, mode) +#else +# define change_mode(name, fd, mode) chmod(name, mode) +#endif +#ifdef HAVE_FCHOWN +# define change_owner(name, fd, uid, gid) fchown(fd, uid, gid) +#else +# define change_owner(name, fd, uid, gid) chown(name, uid, gid) +#endif + +#if defined(HAVE_POSIX_OPENPT) || defined(HAVE_OPENPTY) || defined(HAVE_PTSNAME_R) +static inline int +prevent_messages(const char *slavedevice, int fd, int nomesg) { if (nomesg) - return chmod(slavedevice, 0600); + return change_mode(slavedevice, fd, 0600); else return 0; } +#endif + +#if defined(I_PUSH) && !defined(__linux__) && !defined(_AIX) +static inline int +ioctl_I_PUSH(int fd, const char *const name) +{ + int ret = 0; +# if defined(I_FIND) + ret = ioctl(fd, I_FIND, name); +# endif + if (ret == 0) { + ret = ioctl(fd, I_PUSH, name); + } + return ret; +} +#endif static int get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, int fail) { #if defined(HAVE_POSIX_OPENPT) + /* Unix98 PTY */ int masterfd = -1, slavefd = -1; char *slavedevice; struct sigaction dfl, old; @@ -290,26 +324,45 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, dfl.sa_flags = 0; sigemptyset(&dfl.sa_mask); +#if defined(__sun) || defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD_version < 902000) + /* workaround for Solaris 10: grantpt() doesn't work if FD_CLOEXEC is set. [ruby-dev:44688] */ + /* FreeBSD 9.2 or later supports O_CLOEXEC + * http://www.freebsd.org/cgi/query-pr.cgi?pr=162374 */ if ((masterfd = posix_openpt(O_RDWR|O_NOCTTY)) == -1) goto error; - rb_update_max_fd(masterfd); if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error; if (grantpt(masterfd) == -1) goto grantpt_error; + rb_fd_fix_cloexec(masterfd); +#else + { + int flags = O_RDWR|O_NOCTTY; +# if defined(O_CLOEXEC) + /* glibc posix_openpt() in GNU/Linux calls open("/dev/ptmx", flags) internally. + * So version dependency on GNU/Linux is the same as O_CLOEXEC with open(). + * O_CLOEXEC is available since Linux 2.6.23. Linux 2.6.18 silently ignore it. */ + flags |= O_CLOEXEC; +# endif + if ((masterfd = posix_openpt(flags)) == -1) goto error; + } + rb_fd_fix_cloexec(masterfd); + if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error; + if (grantpt(masterfd) == -1) goto grantpt_error; +#endif if (sigaction(SIGCHLD, &old, NULL) == -1) goto error; if (unlockpt(masterfd) == -1) goto error; - if ((slavedevice = ptsname(masterfd)) == NULL) goto error; - if (no_mesg(slavedevice, nomesg) == -1) goto error; - if ((slavefd = open(slavedevice, O_RDWR|O_NOCTTY, 0)) == -1) goto error; + if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; + slavedevice = SlaveName; + if ((slavefd = rb_cloexec_open(slavedevice, O_RDWR|O_NOCTTY, 0)) == -1) goto error; + if (prevent_messages(slavedevice, slavefd, nomesg) == -1) goto error; rb_update_max_fd(slavefd); -#if defined I_PUSH && !defined linux - if (ioctl(slavefd, I_PUSH, "ptem") == -1) goto error; - if (ioctl(slavefd, I_PUSH, "ldterm") == -1) goto error; - if (ioctl(slavefd, I_PUSH, "ttcompat") == -1) goto error; +#if defined(I_PUSH) && !defined(__linux__) && !defined(_AIX) + if (ioctl_I_PUSH(slavefd, "ptem") == -1) goto error; + if (ioctl_I_PUSH(slavefd, "ldterm") == -1) goto error; + if (ioctl_I_PUSH(slavefd, "ttcompat") == -1) goto error; #endif *master = masterfd; *slave = slavefd; - strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; grantpt_error: @@ -327,62 +380,73 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, * 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"); + (struct termios *)0, (struct winsize *)0) == -1) { + if (!fail) return -1; + rb_raise(rb_eRuntimeError, "openpty() failed"); } - rb_update_max_fd(*master); - rb_update_max_fd(*slave); - if (no_mesg(SlaveName, nomesg) == -1) { - if (!fail) return -1; - rb_raise(rb_eRuntimeError, "can't chmod slave pty"); + rb_fd_fix_cloexec(*master); + rb_fd_fix_cloexec(*slave); + if (prevent_messages(SlaveName, *slave, nomesg) == -1) { + close(*master); + close(*slave); + if (!fail) return -1; + rb_raise(rb_eRuntimeError, "can't chmod slave pty"); } return 0; #elif defined HAVE__GETPTY + /* SGI IRIX */ char *name; mode_t mode = nomesg ? 0600 : 0622; if (!(name = _getpty(master, O_RDWR, mode, 0))) { - if (!fail) return -1; - rb_raise(rb_eRuntimeError, "_getpty() failed"); + if (!fail) return -1; + rb_raise(rb_eRuntimeError, "_getpty() failed"); } - rb_update_max_fd(*master); + rb_fd_fix_cloexec(*master); - *slave = open(name, O_RDWR); + *slave = rb_cloexec_open(name, O_RDWR, 0); /* error check? */ rb_update_max_fd(*slave); strlcpy(SlaveName, name, DEVICELEN); return 0; #elif defined(HAVE_PTSNAME) + /* System V */ int masterfd = -1, slavefd = -1; char *slavedevice; void (*s)(); - extern char *ptsname(int); extern int unlockpt(int); extern int grantpt(int); +#if defined(__sun) + /* workaround for Solaris 10: grantpt() doesn't work if FD_CLOEXEC is set. [ruby-dev:44688] */ if((masterfd = open("/dev/ptmx", O_RDWR, 0)) == -1) goto error; + s = signal(SIGCHLD, SIG_DFL); + if(grantpt(masterfd) == -1) goto error; + rb_fd_fix_cloexec(masterfd); +#else + if((masterfd = rb_cloexec_open("/dev/ptmx", O_RDWR, 0)) == -1) goto error; rb_update_max_fd(masterfd); s = signal(SIGCHLD, SIG_DFL); if(grantpt(masterfd) == -1) goto error; +#endif signal(SIGCHLD, s); if(unlockpt(masterfd) == -1) goto error; - if((slavedevice = ptsname(masterfd)) == NULL) goto error; - if (no_mesg(slavedevice, nomesg) == -1) goto error; - if((slavefd = open(slavedevice, O_RDWR, 0)) == -1) goto error; + if (ptsname_r(masterfd, SlaveName, DEVICELEN) != 0) goto error; + slavedevice = SlaveName; + if((slavefd = rb_cloexec_open(slavedevice, O_RDWR, 0)) == -1) goto error; + if (prevent_messages(slavedevice, slavefd, nomesg) == -1) goto error; rb_update_max_fd(slavefd); -#if defined I_PUSH && !defined linux - if(ioctl(slavefd, I_PUSH, "ptem") == -1) goto error; - if(ioctl(slavefd, I_PUSH, "ldterm") == -1) goto error; - ioctl(slavefd, I_PUSH, "ttcompat"); +#if defined(I_PUSH) && !defined(__linux__) && !defined(_AIX) + if(ioctl_I_PUSH(slavefd, "ptem") == -1) goto error; + if(ioctl_I_PUSH(slavefd, "ldterm") == -1) goto error; + ioctl_I_PUSH(slavefd, "ttcompat"); #endif *master = masterfd; *slave = slavefd; - strlcpy(SlaveName, slavedevice, DEVICELEN); return 0; error: @@ -391,25 +455,48 @@ get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, if (fail) rb_raise(rb_eRuntimeError, "can't get Master/Slave device"); return -1; #else + /* BSD */ int masterfd = -1, slavefd = -1; - const char *const *p; + int i; char MasterName[DEVICELEN]; - for (p = deviceNo; *p != NULL; p++) { - snprintf(MasterName, sizeof MasterName, MasterDevice, *p); - if ((masterfd = open(MasterName,O_RDWR,0)) >= 0) { +#define HEX1(c) \ + c"0",c"1",c"2",c"3",c"4",c"5",c"6",c"7", \ + c"8",c"9",c"a",c"b",c"c",c"d",c"e",c"f" + +#if defined(_IBMESA) /* AIX/ESA */ + static const char MasterDevice[] = "/dev/ptyp%s"; + static const char SlaveDevice[] = "/dev/ttyp%s"; + static const char deviceNo[][3] = { + HEX1("0"), HEX1("1"), HEX1("2"), HEX1("3"), + HEX1("4"), HEX1("5"), HEX1("6"), HEX1("7"), + HEX1("8"), HEX1("9"), HEX1("a"), HEX1("b"), + HEX1("c"), HEX1("d"), HEX1("e"), HEX1("f"), + }; +#else /* 4.2BSD */ + static const char MasterDevice[] = "/dev/pty%s"; + static const char SlaveDevice[] = "/dev/tty%s"; + static const char deviceNo[][3] = { + HEX1("p"), HEX1("q"), HEX1("r"), HEX1("s"), + }; +#endif +#undef HEX1 + for (i = 0; i < numberof(deviceNo); i++) { + const char *const devno = deviceNo[i]; + snprintf(MasterName, sizeof MasterName, MasterDevice, devno); + if ((masterfd = rb_cloexec_open(MasterName,O_RDWR,0)) >= 0) { rb_update_max_fd(masterfd); - *master = masterfd; - snprintf(SlaveName, DEVICELEN, SlaveDevice, *p); - if ((slavefd = open(SlaveName,O_RDWR,0)) >= 0) { + *master = masterfd; + snprintf(SlaveName, DEVICELEN, SlaveDevice, devno); + if ((slavefd = rb_cloexec_open(SlaveName,O_RDWR,0)) >= 0) { rb_update_max_fd(slavefd); - *slave = slavefd; - if (chown(SlaveName, getuid(), getgid()) != 0) goto error; - if (chmod(SlaveName, nomesg ? 0600 : 0622) != 0) goto error; - return 0; - } - close(masterfd); - } + *slave = slavefd; + if (change_owner(SlaveName, slavefd, getuid(), getgid()) != 0) goto error; + if (change_mode(SlaveName, slavefd, nomesg ? 0600 : 0622) != 0) goto error; + return 0; + } + close(masterfd); + } } error: if (slavefd != -1) close(slavefd); @@ -423,8 +510,8 @@ static void getDevice(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg) { if (get_device_once(master, slave, SlaveName, nomesg, 0)) { - rb_gc(); - get_device_once(master, slave, SlaveName, nomesg, 1); + rb_gc(); + get_device_once(master, slave, SlaveName, nomesg, 1); } } @@ -436,8 +523,10 @@ pty_close_pty(VALUE assoc) for (i = 0; i < 2; i++) { io = rb_ary_entry(assoc, i); - if (TYPE(io) == T_FILE && 0 <= RFILE(io)->fptr->fd) + if (RB_TYPE_P(io, T_FILE)) { + /* it's OK to call rb_io_close again even if it's already closed */ rb_io_close(io); + } } return Qnil; } @@ -445,171 +534,172 @@ pty_close_pty(VALUE assoc) /* * call-seq: * PTY.open => [master_io, slave_file] - * PTY.open {|master_io, slave_file| ... } => block value + * PTY.open {|(master_io, slave_file)| ... } => block value * * Allocates a pty (pseudo-terminal). * - * In the non-block form, returns a two element array, <tt>[master_io, - * slave_file]</tt>. - * - * In the block form, yields two arguments <tt>master_io, slave_file</tt> + * In the block form, yields an array of two elements (<tt>master_io, slave_file</tt>) * and the value of the block is returned from +open+. * * The IO and File are both closed after the block completes if they haven't * been already closed. * + * PTY.open {|master, slave| + * p master #=> #<IO:masterpty:/dev/pts/1> + * p slave #=> #<File:/dev/pts/1> + * p slave.path #=> "/dev/pts/1" + * } + * + * In the non-block form, returns a two element array, <tt>[master_io, + * slave_file]</tt>. + * + * master, slave = PTY.open + * # do something with master for IO, or the slave file + * * The arguments in both forms are: * - * <tt>master_io</tt>:: the master of the pty, as an IO. - * <tt>slave_file</tt>:: the slave of the pty, as a File. The path to the - * terminal device is available via - * <tt>slave_file.path</tt> + * +master_io+:: the master of the pty, as an IO. + * +slave_file+:: the slave of the pty, as a File. The path to the + * terminal device is available via +slave_file.path+ * - * === Example + * IO#raw! is usable to disable newline conversions: * + * require 'io/console' * PTY.open {|m, s| - * p m #=> #<IO:masterpty:/dev/pts/1> - * p s #=> #<File:/dev/pts/1> - * p s.path #=> "/dev/pts/1" + * s.raw! + * # ... * } * - * # Change the buffering type in factor command, - * # assuming that factor uses stdio for stdout buffering. - * # If IO.pipe is used instead of PTY.open, - * # this code deadlocks because factor's stdout is fully buffered. - * require 'io/console' # for IO#raw! - * m, s = PTY.open - * s.raw! # disable newline conversion. - * r, w = IO.pipe - * pid = spawn("factor", :in=>r, :out=>s) - * r.close - * s.close - * w.puts "42" - * p m.gets #=> "42: 2 3 7\n" - * w.puts "144" - * p m.gets #=> "144: 2 2 2 2 3 3\n" - * w.close - * # The result of read operation when pty slave is closed is platform - * # dependent. - * ret = begin - * m.gets # FreeBSD returns nil. - * rescue Errno::EIO # GNU/Linux raises EIO. - * nil - * end - * p ret #=> nil - * */ static VALUE pty_open(VALUE klass) { int master_fd, slave_fd; char slavename[DEVICELEN]; - VALUE master_io, slave_file; - rb_io_t *master_fptr, *slave_fptr; - VALUE assoc; getDevice(&master_fd, &slave_fd, slavename, 1); - master_io = rb_obj_alloc(rb_cIO); - MakeOpenFile(master_io, master_fptr); - master_fptr->mode = FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX; - master_fptr->fd = master_fd; - master_fptr->pathv = rb_obj_freeze(rb_sprintf("masterpty:%s", slavename)); + VALUE master_path = rb_obj_freeze(rb_sprintf("masterpty:%s", slavename)); + VALUE master_io = rb_io_open_descriptor(rb_cIO, master_fd, FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX, master_path, RUBY_IO_TIMEOUT_DEFAULT, NULL); - slave_file = rb_obj_alloc(rb_cFile); - MakeOpenFile(slave_file, slave_fptr); - slave_fptr->mode = FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX | FMODE_TTY; - slave_fptr->fd = slave_fd; - slave_fptr->pathv = rb_obj_freeze(rb_str_new_cstr(slavename)); + VALUE slave_path = rb_obj_freeze(rb_str_new_cstr(slavename)); + VALUE slave_file = rb_io_open_descriptor(rb_cFile, slave_fd, FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX | FMODE_TTY, slave_path, RUBY_IO_TIMEOUT_DEFAULT, NULL); + + VALUE assoc = rb_assoc_new(master_io, slave_file); - assoc = rb_assoc_new(master_io, slave_file); if (rb_block_given_p()) { - return rb_ensure(rb_yield, assoc, pty_close_pty, assoc); + return rb_ensure(rb_yield, assoc, pty_close_pty, assoc); } + return assoc; } static VALUE -pty_detach_process(struct pty_info *info) +pty_detach_process(VALUE v) { + struct pty_info *info = (void *)v; +#ifdef WNOHANG + int st; + if (rb_waitpid(info->child_pid, &st, WNOHANG) <= 0) + return Qnil; +#endif rb_detach_process(info->child_pid); return Qnil; } /* * call-seq: - * PTY.spawn(command_line) { |r, w, pid| ... } - * PTY.spawn(command_line) => [r, w, pid] - * PTY.spawn(command, args, ...) { |r, w, pid| ... } - * PTY.spawn(command, args, ...) => [r, w, pid] - * PTY.getpty(command_line) { |r, w, pid| ... } - * PTY.getpty(command_line) => [r, w, pid] - * PTY.getpty(command, args, ...) { |r, w, pid| ... } - * PTY.getpty(command, args, ...) => [r, w, pid] + * PTY.spawn([env,] command_line) { |r, w, pid| ... } + * PTY.spawn([env,] command_line) => [r, w, pid] + * PTY.spawn([env,] command, arguments, ...) { |r, w, pid| ... } + * PTY.spawn([env,] command, arguments, ...) => [r, w, pid] * - * Spawns the specified command on a newly allocated pty. + * Spawns the specified command on a newly allocated pty. You can also use the + * alias ::getpty. * * The command's controlling tty is set to the slave device of the pty * and its standard input/output/error is redirected to the slave device. * - * <tt>command_line</tt>:: The full command line to run - * <tt>command</tt>:: The command to run, as a String. - * <tt>args</tt>:: Zero or more arguments, as Strings, representing - * the arguments to +command+ + * +env+ is an optional hash that provides additional environment variables to the spawned pty. + * + * # sets FOO to "bar" + * PTY.spawn({"FOO"=>"bar"}, "printenv", "FOO") do |r, w, pid| + * p r.read #=> "bar\r\n" + * ensure + * r.close; w.close; Process.wait(pid) + * end + * # unsets FOO + * PTY.spawn({"FOO"=>nil}, "printenv", "FOO") do |r, w, pid| + * p r.read #=> "" + * ensure + * r.close; w.close; Process.wait(pid) + * end + * + * +command+ and +command_line+ are the full commands to run, given a String. + * Any additional +arguments+ will be passed to the command. + * + * === Return values * * In the non-block form this returns an array of size three, - * <tt>[r, w, pid]</tt>. In the block form the block will be called with - * these as arguments, <tt>|r,w,pid|</tt>: + * <tt>[r, w, pid]</tt>. + * + * In the block form these same values will be yielded to the block: * - * +r+:: An IO that can be read from that contains the command's + * +r+:: A readable IO that contains the command's * standard output and standard error - * +w+:: An IO that can be written to that is the command's - * standard input + * +w+:: A writable IO that is the command's standard input * +pid+:: The process identifier for the command. + * + * === Clean up + * + * This method does not clean up like closing IOs or waiting for child + * process, except that the process is detached in the block form to + * prevent it from becoming a zombie (see Process.detach). Any other + * cleanup is the responsibility of the caller. If waiting for +pid+, + * be sure to close both +r+ and +w+ before doing so; doing it in the + * reverse order may cause deadlock on some OSes. */ static VALUE pty_getpty(int argc, VALUE *argv, VALUE self) { VALUE res; struct pty_info info; - rb_io_t *wfptr,*rfptr; - VALUE rport = rb_obj_alloc(rb_cFile); - VALUE wport = rb_obj_alloc(rb_cFile); char SlaveName[DEVICELEN]; - MakeOpenFile(rport, rfptr); - MakeOpenFile(wport, wfptr); - establishShell(argc, argv, &info, SlaveName); - rfptr->mode = rb_io_mode_flags("r"); - rfptr->fd = info.fd; - rfptr->pathv = rb_obj_freeze(rb_str_new_cstr(SlaveName)); + VALUE pty_path = rb_obj_freeze(rb_str_new_cstr(SlaveName)); + VALUE rport = rb_io_open_descriptor( + rb_cFile, info.fd, FMODE_READABLE, pty_path, RUBY_IO_TIMEOUT_DEFAULT, NULL + ); - wfptr->mode = rb_io_mode_flags("w") | FMODE_SYNC; - wfptr->fd = dup(info.fd); - if (wfptr->fd == -1) + int wpty_fd = rb_cloexec_dup(info.fd); + if (wpty_fd == -1) { rb_sys_fail("dup()"); - rb_update_max_fd(wfptr->fd); - wfptr->pathv = rfptr->pathv; + } + VALUE wport = rb_io_open_descriptor( + rb_cFile, wpty_fd, FMODE_WRITABLE | FMODE_TRUNC | FMODE_CREATE | FMODE_SYNC, + pty_path, RUBY_IO_TIMEOUT_DEFAULT, NULL + ); res = rb_ary_new2(3); - rb_ary_store(res,0,(VALUE)rport); - rb_ary_store(res,1,(VALUE)wport); + rb_ary_store(res, 0, rport); + rb_ary_store(res, 1, wport); rb_ary_store(res,2,PIDT2NUM(info.child_pid)); if (rb_block_given_p()) { - rb_ensure(rb_yield, res, pty_detach_process, (VALUE)&info); - return Qnil; + rb_ensure(rb_yield, res, pty_detach_process, (VALUE)&info); + return Qnil; } return res; } +NORETURN(static void raise_from_check(rb_pid_t pid, int status)); static void -raise_from_check(pid_t pid, int status) +raise_from_check(rb_pid_t pid, int status) { const char *state; - char buf[1024]; + VALUE msg; VALUE exc; #if defined(WIFSTOPPED) @@ -619,16 +709,16 @@ raise_from_check(pid_t pid, int status) ---->> Either IF_STOPPED or WIFSTOPPED is needed <<---- #endif /* WIFSTOPPED | IF_STOPPED */ if (WIFSTOPPED(status)) { /* suspend */ - state = "stopped"; + state = "stopped"; } else if (kill(pid, 0) == 0) { - state = "changed"; + state = "changed"; } else { - state = "exited"; + state = "exited"; } - snprintf(buf, sizeof(buf), "pty - %s: %ld", state, (long)pid); - exc = rb_exc_new2(eChildExited, buf); + msg = rb_sprintf("pty - %s: %ld", state, (long)pid); + exc = rb_exc_new_str(eChildExited, msg); rb_iv_set(exc, "status", rb_last_status_get()); rb_exc_raise(exc); } @@ -639,30 +729,40 @@ raise_from_check(pid_t pid, int status) * PTY.check(pid, true) => nil or raises PTY::ChildExited * * Checks the status of the child process specified by +pid+. - * Returns +nil+ if the process is still alive. If the process - * is not alive, will return a <tt>Process::Status</tt> or raise - * a <tt>PTY::ChildExited</tt> (if +raise+ was true). + * Returns +nil+ if the process is still alive. + * + * If the process is not alive, and +raise+ was true, a PTY::ChildExited + * exception will be raised. Otherwise it will return a Process::Status + * instance. * * +pid+:: The process id of the process to check - * +raise+:: If true and the process identified by +pid+ is no longer - * alive a <tt>PTY::ChildExited</tt> is raised. + * +raise+:: If +true+ and the process identified by +pid+ is no longer + * alive a PTY::ChildExited is raised. * - * Returns nil or a <tt>Process::Status</tt> when +raise+ is false. */ static VALUE pty_check(int argc, VALUE *argv, VALUE self) { VALUE pid, exc; - pid_t cpid; + rb_pid_t cpid; int status; + const int flag = +#ifdef WNOHANG + WNOHANG| +#endif +#ifdef WUNTRACED + WUNTRACED| +#endif + 0; rb_scan_args(argc, argv, "11", &pid, &exc); - cpid = rb_waitpid(NUM2PIDT(pid), &status, WNOHANG|WUNTRACED); + cpid = rb_waitpid(NUM2PIDT(pid), &status, flag); if (cpid == -1 || cpid == 0) return Qnil; if (!RTEST(exc)) return rb_last_status_get(); raise_from_check(cpid, status); - return Qnil; /* not reached */ + + UNREACHABLE_RETURN(Qnil); } static VALUE cPTY; @@ -670,22 +770,80 @@ static VALUE cPTY; /* * Document-class: PTY::ChildExited * - * Thrown when PTY#check is called for a pid that represents a process that + * Thrown when PTY::check is called for a pid that represents a process that * has exited. */ /* * Document-class: PTY * - * Creates and managed pseudo terminals (PTYs). See also - * http://en.wikipedia.org/wiki/Pseudo_terminal + * Creates and manages pseudo terminals (PTYs). See also + * https://en.wikipedia.org/wiki/Pseudo_terminal + * + * PTY allows you to allocate new terminals using ::open or ::spawn a new + * terminal with a specific command. + * + * == Example + * + * In this example we will change the buffering type in the +factor+ command, + * assuming that factor uses stdio for stdout buffering. + * + * If IO.pipe is used instead of PTY.open, this code deadlocks because factor's + * stdout is fully buffered. + * + * # start by requiring the standard library PTY + * require 'pty' + * + * master, slave = PTY.open + * read, write = IO.pipe + * pid = spawn("factor", :in=>read, :out=>slave) + * read.close # we dont need the read + * slave.close # or the slave + * + * # pipe "42" to the factor command + * write.puts "42" + * # output the response from factor + * p master.gets #=> "42: 2 3 7\n" + * + * # pipe "144" to factor and print out the response + * write.puts "144" + * p master.gets #=> "144: 2 2 2 2 3 3\n" + * write.close # close the pipe + * + * # The result of read operation when pty slave is closed is platform + * # dependent. + * ret = begin + * master.gets # FreeBSD returns nil. + * rescue Errno::EIO # GNU/Linux raises EIO. + * nil + * end + * p ret #=> nil + * + * == 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. */ void -Init_pty() +Init_pty(void) { cPTY = rb_define_module("PTY"); - rb_define_module_function(cPTY,"getpty",pty_getpty,-1); +#if 1 + rb_define_module_function(cPTY,"get""pty",pty_getpty,-1); +#else /* for RDoc */ + /* show getpty as an alias of spawn */ + VALUE sPTY = rb_singleton_class(cPTY); + rb_define_alias(sPTY, "getpty", "spawn"); +#endif rb_define_module_function(cPTY,"spawn",pty_getpty,-1); rb_define_singleton_method(cPTY,"check",pty_check,-1); rb_define_singleton_method(cPTY,"open",pty_open,0); |
