Ruby 1.9.3p327(2012-11-10revision37606)
ext/pty/pty.c
Go to the documentation of this file.
00001 #include        "ruby/config.h"
00002 #ifdef RUBY_EXTCONF_H
00003 #include RUBY_EXTCONF_H
00004 #endif
00005 #include        <stdlib.h>
00006 #include        <stdio.h>
00007 #include        <sys/types.h>
00008 #include        <sys/stat.h>
00009 #include        <sys/file.h>
00010 #include        <fcntl.h>
00011 #include        <errno.h>
00012 #include        <pwd.h>
00013 #ifdef HAVE_SYS_IOCTL_H
00014 #include        <sys/ioctl.h>
00015 #endif
00016 #ifdef HAVE_LIBUTIL_H
00017 #include        <libutil.h>
00018 #endif
00019 #ifdef HAVE_UTIL_H
00020 #include        <util.h>
00021 #endif
00022 #ifdef HAVE_PTY_H
00023 #include        <pty.h>
00024 #endif
00025 #ifdef HAVE_SYS_WAIT_H
00026 #include <sys/wait.h>
00027 #else
00028 #define WIFSTOPPED(status)    (((status) & 0xff) == 0x7f)
00029 #endif
00030 #include <ctype.h>
00031 
00032 #include "ruby/ruby.h"
00033 #include "ruby/io.h"
00034 #include "ruby/util.h"
00035 
00036 #include <signal.h>
00037 #ifdef HAVE_SYS_STROPTS_H
00038 #include <sys/stropts.h>
00039 #endif
00040 
00041 #ifdef HAVE_UNISTD_H
00042 #include <unistd.h>
00043 #endif
00044 
00045 #define DEVICELEN       16
00046 
00047 #if !defined(HAVE_OPENPTY)
00048 #if defined(__hpux)
00049 static const
00050 char    MasterDevice[] = "/dev/ptym/pty%s",
00051         SlaveDevice[] =  "/dev/pty/tty%s",
00052         *const deviceNo[] = {
00053                 "p0","p1","p2","p3","p4","p5","p6","p7",
00054                 "p8","p9","pa","pb","pc","pd","pe","pf",
00055                 "q0","q1","q2","q3","q4","q5","q6","q7",
00056                 "q8","q9","qa","qb","qc","qd","qe","qf",
00057                 "r0","r1","r2","r3","r4","r5","r6","r7",
00058                 "r8","r9","ra","rb","rc","rd","re","rf",
00059                 "s0","s1","s2","s3","s4","s5","s6","s7",
00060                 "s8","s9","sa","sb","sc","sd","se","sf",
00061                 "t0","t1","t2","t3","t4","t5","t6","t7",
00062                 "t8","t9","ta","tb","tc","td","te","tf",
00063                 "u0","u1","u2","u3","u4","u5","u6","u7",
00064                 "u8","u9","ua","ub","uc","ud","ue","uf",
00065                 "v0","v1","v2","v3","v4","v5","v6","v7",
00066                 "v8","v9","va","vb","vc","vd","ve","vf",
00067                 "w0","w1","w2","w3","w4","w5","w6","w7",
00068                 "w8","w9","wa","wb","wc","wd","we","wf",
00069                 0,
00070         };
00071 #elif defined(_IBMESA)  /* AIX/ESA */
00072 static const
00073 char    MasterDevice[] = "/dev/ptyp%s",
00074         SlaveDevice[] = "/dev/ttyp%s",
00075         *const deviceNo[] = {
00076 "00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f",
00077 "10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f",
00078 "20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f",
00079 "30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f",
00080 "40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f",
00081 "50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f",
00082 "60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f",
00083 "70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f",
00084 "80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f",
00085 "90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f",
00086 "a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af",
00087 "b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf",
00088 "c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf",
00089 "d0","d1","d2","d3","d4","d5","d6","d7","d8","d9","da","db","dc","dd","de","df",
00090 "e0","e1","e2","e3","e4","e5","e6","e7","e8","e9","ea","eb","ec","ed","ee","ef",
00091 "f0","f1","f2","f3","f4","f5","f6","f7","f8","f9","fa","fb","fc","fd","fe","ff",
00092                 };
00093 #elif !defined(HAVE_PTSNAME)
00094 static const
00095 char    MasterDevice[] = "/dev/pty%s",
00096         SlaveDevice[] = "/dev/tty%s",
00097         *const deviceNo[] = {
00098                 "p0","p1","p2","p3","p4","p5","p6","p7",
00099                 "p8","p9","pa","pb","pc","pd","pe","pf",
00100                 "q0","q1","q2","q3","q4","q5","q6","q7",
00101                 "q8","q9","qa","qb","qc","qd","qe","qf",
00102                 "r0","r1","r2","r3","r4","r5","r6","r7",
00103                 "r8","r9","ra","rb","rc","rd","re","rf",
00104                 "s0","s1","s2","s3","s4","s5","s6","s7",
00105                 "s8","s9","sa","sb","sc","sd","se","sf",
00106                 0,
00107         };
00108 #endif
00109 #endif /* !defined(HAVE_OPENPTY) */
00110 
00111 #ifndef HAVE_SETEUID
00112 # ifdef HAVE_SETREUID
00113 #  define seteuid(e)    setreuid(-1, (e))
00114 # else /* NOT HAVE_SETREUID */
00115 #  ifdef HAVE_SETRESUID
00116 #   define seteuid(e)   setresuid(-1, (e), -1)
00117 #  else /* NOT HAVE_SETRESUID */
00118     /* I can't set euid. (;_;) */
00119 #  endif /* HAVE_SETRESUID */
00120 # endif /* HAVE_SETREUID */
00121 #endif /* NO_SETEUID */
00122 
00123 static VALUE eChildExited;
00124 
00125 /* Returns the exit status of the child for which PTY#check
00126  * raised this exception
00127  */
00128 static VALUE
00129 echild_status(VALUE self)
00130 {
00131     return rb_ivar_get(self, rb_intern("status"));
00132 }
00133 
00134 struct pty_info {
00135     int fd;
00136     rb_pid_t child_pid;
00137 };
00138 
00139 static void getDevice(int*, int*, char [DEVICELEN], int);
00140 
00141 struct child_info {
00142     int master, slave;
00143     char *slavename;
00144     int argc;
00145     VALUE *argv;
00146 };
00147 
00148 static int
00149 chfunc(void *data, char *errbuf, size_t errbuf_len)
00150 {
00151     struct child_info *carg = data;
00152     int master = carg->master;
00153     int slave = carg->slave;
00154     int argc = carg->argc;
00155     VALUE *argv = carg->argv;
00156 
00157 #define ERROR_EXIT(str) do { \
00158         strlcpy(errbuf, (str), errbuf_len); \
00159         return -1; \
00160     } while (0)
00161 
00162     rb_thread_atfork_before_exec();
00163 
00164     /*
00165      * Set free from process group and controlling terminal
00166      */
00167 #ifdef HAVE_SETSID
00168     (void) setsid();
00169 #else /* HAS_SETSID */
00170 # ifdef HAVE_SETPGRP
00171 #  ifdef SETGRP_VOID
00172     if (setpgrp() == -1)
00173         ERROR_EXIT("setpgrp()");
00174 #  else /* SETGRP_VOID */
00175     if (setpgrp(0, getpid()) == -1)
00176         ERROR_EXIT("setpgrp()");
00177     {
00178         int i = open("/dev/tty", O_RDONLY);
00179         if (i < 0) ERROR_EXIT("/dev/tty");
00180         rb_update_max_fd(i);
00181         if (ioctl(i, TIOCNOTTY, (char *)0))
00182             ERROR_EXIT("ioctl(TIOCNOTTY)");
00183         close(i);
00184     }
00185 #  endif /* SETGRP_VOID */
00186 # endif /* HAVE_SETPGRP */
00187 #endif /* HAS_SETSID */
00188 
00189     /*
00190      * obtain new controlling terminal
00191      */
00192 #if defined(TIOCSCTTY)
00193     close(master);
00194     (void) ioctl(slave, TIOCSCTTY, (char *)0);
00195     /* errors ignored for sun */
00196 #else
00197     close(slave);
00198     slave = open(carg->slavename, O_RDWR);
00199     if (slave < 0) {
00200         ERROR_EXIT("open: pty slave");
00201     }
00202     rb_update_max_fd(slave);
00203     close(master);
00204 #endif
00205     dup2(slave,0);
00206     dup2(slave,1);
00207     dup2(slave,2);
00208     close(slave);
00209 #if defined(HAVE_SETEUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRESUID)
00210     seteuid(getuid());
00211 #endif
00212 
00213     rb_f_exec(argc, argv);
00214     return 0;
00215 #undef ERROR_EXIT
00216 }
00217 
00218 static void
00219 establishShell(int argc, VALUE *argv, struct pty_info *info,
00220                char SlaveName[DEVICELEN])
00221 {
00222     int                 master,slave;
00223     rb_pid_t            pid;
00224     char                *p, *getenv();
00225     struct passwd       *pwent;
00226     VALUE               v;
00227     struct child_info   carg;
00228     char                errbuf[32];
00229 
00230     if (argc == 0) {
00231         const char *shellname;
00232 
00233         if ((p = getenv("SHELL")) != NULL) {
00234             shellname = p;
00235         }
00236         else {
00237             pwent = getpwuid(getuid());
00238             if (pwent && pwent->pw_shell)
00239                 shellname = pwent->pw_shell;
00240             else
00241                 shellname = "/bin/sh";
00242         }
00243         v = rb_str_new2(shellname);
00244         argc = 1;
00245         argv = &v;
00246     }
00247 
00248     getDevice(&master, &slave, SlaveName, 0);
00249 
00250     carg.master = master;
00251     carg.slave = slave;
00252     carg.slavename = SlaveName;
00253     carg.argc = argc;
00254     carg.argv = argv;
00255     errbuf[0] = '\0';
00256     pid = rb_fork_err(0, chfunc, &carg, Qnil, errbuf, sizeof(errbuf));
00257 
00258     if (pid < 0) {
00259         int e = errno;
00260         close(master);
00261         close(slave);
00262         errno = e;
00263         rb_sys_fail(errbuf[0] ? errbuf : "fork failed");
00264     }
00265 
00266     close(slave);
00267 
00268     info->child_pid = pid;
00269     info->fd = master;
00270 }
00271 
00272 static int
00273 no_mesg(char *slavedevice, int nomesg)
00274 {
00275     if (nomesg)
00276         return chmod(slavedevice, 0600);
00277     else
00278         return 0;
00279 }
00280 
00281 static int
00282 get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, int fail)
00283 {
00284 #if defined(HAVE_POSIX_OPENPT)
00285     int masterfd = -1, slavefd = -1;
00286     char *slavedevice;
00287     struct sigaction dfl, old;
00288 
00289     dfl.sa_handler = SIG_DFL;
00290     dfl.sa_flags = 0;
00291     sigemptyset(&dfl.sa_mask);
00292 
00293     if ((masterfd = posix_openpt(O_RDWR|O_NOCTTY)) == -1) goto error;
00294     rb_update_max_fd(masterfd);
00295     if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error;
00296     if (grantpt(masterfd) == -1) goto grantpt_error;
00297     if (sigaction(SIGCHLD, &old, NULL) == -1) goto error;
00298     if (unlockpt(masterfd) == -1) goto error;
00299     if ((slavedevice = ptsname(masterfd)) == NULL) goto error;
00300     if (no_mesg(slavedevice, nomesg) == -1) goto error;
00301     if ((slavefd = open(slavedevice, O_RDWR|O_NOCTTY, 0)) == -1) goto error;
00302     rb_update_max_fd(slavefd);
00303 
00304 #if defined I_PUSH && !defined linux
00305     if (ioctl(slavefd, I_PUSH, "ptem") == -1) goto error;
00306     if (ioctl(slavefd, I_PUSH, "ldterm") == -1) goto error;
00307     if (ioctl(slavefd, I_PUSH, "ttcompat") == -1) goto error;
00308 #endif
00309 
00310     *master = masterfd;
00311     *slave = slavefd;
00312     strlcpy(SlaveName, slavedevice, DEVICELEN);
00313     return 0;
00314 
00315   grantpt_error:
00316     sigaction(SIGCHLD, &old, NULL);
00317   error:
00318     if (slavefd != -1) close(slavefd);
00319     if (masterfd != -1) close(masterfd);
00320     if (fail) {
00321         rb_raise(rb_eRuntimeError, "can't get Master/Slave device");
00322     }
00323     return -1;
00324 #elif defined HAVE_OPENPTY
00325 /*
00326  * Use openpty(3) of 4.3BSD Reno and later,
00327  * or the same interface function.
00328  */
00329     if (openpty(master, slave, SlaveName,
00330                 (struct termios *)0, (struct winsize *)0) == -1) {
00331         if (!fail) return -1;
00332         rb_raise(rb_eRuntimeError, "openpty() failed");
00333     }
00334     rb_update_max_fd(*master);
00335     rb_update_max_fd(*slave);
00336     if (no_mesg(SlaveName, nomesg) == -1) {
00337         if (!fail) return -1;
00338         rb_raise(rb_eRuntimeError, "can't chmod slave pty");
00339     }
00340 
00341     return 0;
00342 
00343 #elif defined HAVE__GETPTY
00344     char *name;
00345     mode_t mode = nomesg ? 0600 : 0622;
00346 
00347     if (!(name = _getpty(master, O_RDWR, mode, 0))) {
00348         if (!fail) return -1;
00349         rb_raise(rb_eRuntimeError, "_getpty() failed");
00350     }
00351     rb_update_max_fd(*master);
00352 
00353     *slave = open(name, O_RDWR);
00354     /* error check? */
00355     rb_update_max_fd(*slave);
00356     strlcpy(SlaveName, name, DEVICELEN);
00357 
00358     return 0;
00359 #elif defined(HAVE_PTSNAME)
00360     int  masterfd = -1, slavefd = -1;
00361     char *slavedevice;
00362     void (*s)();
00363 
00364     extern char *ptsname(int);
00365     extern int unlockpt(int);
00366     extern int grantpt(int);
00367 
00368     if((masterfd = open("/dev/ptmx", O_RDWR, 0)) == -1) goto error;
00369     rb_update_max_fd(masterfd);
00370     s = signal(SIGCHLD, SIG_DFL);
00371     if(grantpt(masterfd) == -1) goto error;
00372     signal(SIGCHLD, s);
00373     if(unlockpt(masterfd) == -1) goto error;
00374     if((slavedevice = ptsname(masterfd)) == NULL) goto error;
00375     if (no_mesg(slavedevice, nomesg) == -1) goto error;
00376     if((slavefd = open(slavedevice, O_RDWR, 0)) == -1) goto error;
00377     rb_update_max_fd(slavefd);
00378 #if defined I_PUSH && !defined linux
00379     if(ioctl(slavefd, I_PUSH, "ptem") == -1) goto error;
00380     if(ioctl(slavefd, I_PUSH, "ldterm") == -1) goto error;
00381     ioctl(slavefd, I_PUSH, "ttcompat");
00382 #endif
00383     *master = masterfd;
00384     *slave = slavefd;
00385     strlcpy(SlaveName, slavedevice, DEVICELEN);
00386     return 0;
00387 
00388   error:
00389     if (slavefd != -1) close(slavefd);
00390     if (masterfd != -1) close(masterfd);
00391     if (fail) rb_raise(rb_eRuntimeError, "can't get Master/Slave device");
00392     return -1;
00393 #else
00394     int  masterfd = -1, slavefd = -1;
00395     const char *const *p;
00396     char MasterName[DEVICELEN];
00397 
00398     for (p = deviceNo; *p != NULL; p++) {
00399         snprintf(MasterName, sizeof MasterName, MasterDevice, *p);
00400         if ((masterfd = open(MasterName,O_RDWR,0)) >= 0) {
00401             rb_update_max_fd(masterfd);
00402             *master = masterfd;
00403             snprintf(SlaveName, DEVICELEN, SlaveDevice, *p);
00404             if ((slavefd = open(SlaveName,O_RDWR,0)) >= 0) {
00405                 rb_update_max_fd(slavefd);
00406                 *slave = slavefd;
00407                 if (chown(SlaveName, getuid(), getgid()) != 0) goto error;
00408                 if (chmod(SlaveName, nomesg ? 0600 : 0622) != 0) goto error;
00409                 return 0;
00410             }
00411             close(masterfd);
00412         }
00413     }
00414   error:
00415     if (slavefd != -1) close(slavefd);
00416     if (masterfd != -1) close(masterfd);
00417     if (fail) rb_raise(rb_eRuntimeError, "can't get %s", SlaveName);
00418     return -1;
00419 #endif
00420 }
00421 
00422 static void
00423 getDevice(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg)
00424 {
00425     if (get_device_once(master, slave, SlaveName, nomesg, 0)) {
00426         rb_gc();
00427         get_device_once(master, slave, SlaveName, nomesg, 1);
00428     }
00429 }
00430 
00431 static VALUE
00432 pty_close_pty(VALUE assoc)
00433 {
00434     VALUE io;
00435     int i;
00436 
00437     for (i = 0; i < 2; i++) {
00438         io = rb_ary_entry(assoc, i);
00439         if (TYPE(io) == T_FILE && 0 <= RFILE(io)->fptr->fd)
00440             rb_io_close(io);
00441     }
00442     return Qnil;
00443 }
00444 
00445 /*
00446  * call-seq:
00447  *   PTY.open => [master_io, slave_file]
00448  *   PTY.open {|master_io, slave_file| ... } => block value
00449  *
00450  * Allocates a pty (pseudo-terminal).
00451  *
00452  * In the non-block form, returns a two element array, <tt>[master_io,
00453  * slave_file]</tt>.
00454  *
00455  * In the block form, yields two arguments <tt>master_io, slave_file</tt>
00456  * and the value of the block is returned from +open+.
00457  *
00458  * The IO and File are both closed after the block completes if they haven't
00459  * been already closed.
00460  *
00461  * The arguments in both forms are:
00462  *
00463  * <tt>master_io</tt>:: the master of the pty, as an IO.
00464  * <tt>slave_file</tt>:: the slave of the pty, as a File.  The path to the
00465  *                       terminal device is available via
00466  *                       <tt>slave_file.path</tt>
00467  *
00468  * === Example
00469  *
00470  *   PTY.open {|m, s|
00471  *     p m      #=> #<IO:masterpty:/dev/pts/1>
00472  *     p s      #=> #<File:/dev/pts/1>
00473  *     p s.path #=> "/dev/pts/1"
00474  *   }
00475  *
00476  *   # Change the buffering type in factor command,
00477  *   # assuming that factor uses stdio for stdout buffering.
00478  *   # If IO.pipe is used instead of PTY.open,
00479  *   # this code deadlocks because factor's stdout is fully buffered.
00480  *   require 'io/console' # for IO#raw!
00481  *   m, s = PTY.open
00482  *   s.raw! # disable newline conversion.
00483  *   r, w = IO.pipe
00484  *   pid = spawn("factor", :in=>r, :out=>s)
00485  *   r.close
00486  *   s.close
00487  *   w.puts "42"
00488  *   p m.gets #=> "42: 2 3 7\n"
00489  *   w.puts "144"
00490  *   p m.gets #=> "144: 2 2 2 2 3 3\n"
00491  *   w.close
00492  *   # The result of read operation when pty slave is closed is platform
00493  *   # dependent.
00494  *   ret = begin
00495  *           m.gets          # FreeBSD returns nil.
00496  *         rescue Errno::EIO # GNU/Linux raises EIO.
00497  *           nil
00498  *         end
00499  *   p ret #=> nil
00500  *
00501  */
00502 static VALUE
00503 pty_open(VALUE klass)
00504 {
00505     int master_fd, slave_fd;
00506     char slavename[DEVICELEN];
00507     VALUE master_io, slave_file;
00508     rb_io_t *master_fptr, *slave_fptr;
00509     VALUE assoc;
00510 
00511     getDevice(&master_fd, &slave_fd, slavename, 1);
00512 
00513     master_io = rb_obj_alloc(rb_cIO);
00514     MakeOpenFile(master_io, master_fptr);
00515     master_fptr->mode = FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX;
00516     master_fptr->fd = master_fd;
00517     master_fptr->pathv = rb_obj_freeze(rb_sprintf("masterpty:%s", slavename));
00518 
00519     slave_file = rb_obj_alloc(rb_cFile);
00520     MakeOpenFile(slave_file, slave_fptr);
00521     slave_fptr->mode = FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX | FMODE_TTY;
00522     slave_fptr->fd = slave_fd;
00523     slave_fptr->pathv = rb_obj_freeze(rb_str_new_cstr(slavename));
00524 
00525     assoc = rb_assoc_new(master_io, slave_file);
00526     if (rb_block_given_p()) {
00527         return rb_ensure(rb_yield, assoc, pty_close_pty, assoc);
00528     }
00529     return assoc;
00530 }
00531 
00532 static VALUE
00533 pty_detach_process(struct pty_info *info)
00534 {
00535     rb_detach_process(info->child_pid);
00536     return Qnil;
00537 }
00538 
00539 /*
00540  * call-seq:
00541  *   PTY.spawn(command_line)  { |r, w, pid| ... }
00542  *   PTY.spawn(command_line)  => [r, w, pid]
00543  *   PTY.spawn(command, args, ...)  { |r, w, pid| ... }
00544  *   PTY.spawn(command, args, ...)  => [r, w, pid]
00545  *   PTY.getpty(command_line)  { |r, w, pid| ... }
00546  *   PTY.getpty(command_line)  => [r, w, pid]
00547  *   PTY.getpty(command, args, ...)  { |r, w, pid| ... }
00548  *   PTY.getpty(command, args, ...)  => [r, w, pid]
00549  *
00550  * Spawns the specified command on a newly allocated pty.
00551  *
00552  * The command's controlling tty is set to the slave device of the pty
00553  * and its standard input/output/error is redirected to the slave device.
00554  *
00555  * <tt>command_line</tt>:: The full command line to run
00556  * <tt>command</tt>:: The command to run, as a String.
00557  * <tt>args</tt>:: Zero or more arguments, as Strings, representing
00558  *                 the arguments to +command+
00559  *
00560  * In the non-block form this returns an array of size three,
00561  * <tt>[r, w, pid]</tt>.  In the block form the block will be called with
00562  * these as arguments, <tt>|r,w,pid|</tt>:
00563  *
00564  * +r+:: An IO that can be read from that contains the command's
00565  *       standard output and standard error
00566  * +w+:: An IO that can be written to that is the command's
00567  *       standard input
00568  * +pid+:: The process identifier for the command.
00569  */
00570 static VALUE
00571 pty_getpty(int argc, VALUE *argv, VALUE self)
00572 {
00573     VALUE res;
00574     struct pty_info info;
00575     rb_io_t *wfptr,*rfptr;
00576     VALUE rport = rb_obj_alloc(rb_cFile);
00577     VALUE wport = rb_obj_alloc(rb_cFile);
00578     char SlaveName[DEVICELEN];
00579 
00580     MakeOpenFile(rport, rfptr);
00581     MakeOpenFile(wport, wfptr);
00582 
00583     establishShell(argc, argv, &info, SlaveName);
00584 
00585     rfptr->mode = rb_io_mode_flags("r");
00586     rfptr->fd = info.fd;
00587     rfptr->pathv = rb_obj_freeze(rb_str_new_cstr(SlaveName));
00588 
00589     wfptr->mode = rb_io_mode_flags("w") | FMODE_SYNC;
00590     wfptr->fd = dup(info.fd);
00591     if (wfptr->fd == -1)
00592         rb_sys_fail("dup()");
00593     rb_update_max_fd(wfptr->fd);
00594     wfptr->pathv = rfptr->pathv;
00595 
00596     res = rb_ary_new2(3);
00597     rb_ary_store(res,0,(VALUE)rport);
00598     rb_ary_store(res,1,(VALUE)wport);
00599     rb_ary_store(res,2,PIDT2NUM(info.child_pid));
00600 
00601     if (rb_block_given_p()) {
00602         rb_ensure(rb_yield, res, pty_detach_process, (VALUE)&info);
00603         return Qnil;
00604     }
00605     return res;
00606 }
00607 
00608 static void
00609 raise_from_check(pid_t pid, int status)
00610 {
00611     const char *state;
00612     char buf[1024];
00613     VALUE exc;
00614 
00615 #if defined(WIFSTOPPED)
00616 #elif defined(IF_STOPPED)
00617 #define WIFSTOPPED(status) IF_STOPPED(status)
00618 #else
00619 ---->> Either IF_STOPPED or WIFSTOPPED is needed <<----
00620 #endif /* WIFSTOPPED | IF_STOPPED */
00621     if (WIFSTOPPED(status)) { /* suspend */
00622         state = "stopped";
00623     }
00624     else if (kill(pid, 0) == 0) {
00625         state = "changed";
00626     }
00627     else {
00628         state = "exited";
00629     }
00630     snprintf(buf, sizeof(buf), "pty - %s: %ld", state, (long)pid);
00631     exc = rb_exc_new2(eChildExited, buf);
00632     rb_iv_set(exc, "status", rb_last_status_get());
00633     rb_exc_raise(exc);
00634 }
00635 
00636 /*
00637  * call-seq:
00638  *   PTY.check(pid, raise = false) => Process::Status or nil
00639  *   PTY.check(pid, true)          => nil or raises PTY::ChildExited
00640  *
00641  * Checks the status of the child process specified by +pid+.
00642  * Returns +nil+ if the process is still alive.  If the process
00643  * is not alive, will return a <tt>Process::Status</tt> or raise
00644  * a <tt>PTY::ChildExited</tt> (if +raise+ was true).
00645  *
00646  * +pid+:: The process id of the process to check
00647  * +raise+:: If true and the process identified by +pid+ is no longer
00648  *           alive a <tt>PTY::ChildExited</tt> is raised.
00649  *
00650  * Returns nil or a <tt>Process::Status</tt> when +raise+ is false.
00651  */
00652 static VALUE
00653 pty_check(int argc, VALUE *argv, VALUE self)
00654 {
00655     VALUE pid, exc;
00656     pid_t cpid;
00657     int status;
00658 
00659     rb_scan_args(argc, argv, "11", &pid, &exc);
00660     cpid = rb_waitpid(NUM2PIDT(pid), &status, WNOHANG|WUNTRACED);
00661     if (cpid == -1 || cpid == 0) return Qnil;
00662 
00663     if (!RTEST(exc)) return rb_last_status_get();
00664     raise_from_check(cpid, status);
00665     return Qnil;                /* not reached */
00666 }
00667 
00668 static VALUE cPTY;
00669 
00670 /*
00671  * Document-class: PTY::ChildExited
00672  *
00673  * Thrown when PTY#check is called for a pid that represents a process that
00674  * has exited.
00675  */
00676 
00677 /*
00678  * Document-class: PTY
00679  *
00680  * Creates and managed pseudo terminals (PTYs).  See also
00681  * http://en.wikipedia.org/wiki/Pseudo_terminal
00682  */
00683 
00684 void
00685 Init_pty()
00686 {
00687     cPTY = rb_define_module("PTY");
00688     rb_define_module_function(cPTY,"getpty",pty_getpty,-1);
00689     rb_define_module_function(cPTY,"spawn",pty_getpty,-1);
00690     rb_define_singleton_method(cPTY,"check",pty_check,-1);
00691     rb_define_singleton_method(cPTY,"open",pty_open,0);
00692 
00693     eChildExited = rb_define_class_under(cPTY,"ChildExited",rb_eRuntimeError);
00694     rb_define_method(eChildExited,"status",echild_status,0);
00695 }
00696