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