1#include	"ruby/config.h"
2#ifdef RUBY_EXTCONF_H
3#include RUBY_EXTCONF_H
4#endif
5#include	<stdlib.h>
6#include	<stdio.h>
7#include	<sys/types.h>
8#include	<sys/stat.h>
9#include	<sys/file.h>
10#include	<fcntl.h>
11#include	<errno.h>
12#include	<pwd.h>
13#ifdef HAVE_SYS_IOCTL_H
14#include	<sys/ioctl.h>
15#endif
16#ifdef HAVE_LIBUTIL_H
17#include	<libutil.h>
18#endif
19#ifdef HAVE_UTIL_H
20#include	<util.h>
21#endif
22#ifdef HAVE_PTY_H
23#include	<pty.h>
24#endif
25#ifdef HAVE_SYS_WAIT_H
26#include <sys/wait.h>
27#else
28#define WIFSTOPPED(status)    (((status) & 0xff) == 0x7f)
29#endif
30#include <ctype.h>
31
32#include "ruby/ruby.h"
33#include "ruby/io.h"
34#include "ruby/util.h"
35#include "internal.h"
36
37#include <signal.h>
38#ifdef HAVE_SYS_STROPTS_H
39#include <sys/stropts.h>
40#endif
41
42#ifdef HAVE_UNISTD_H
43#include <unistd.h>
44#endif
45
46#define	DEVICELEN	16
47
48#ifndef HAVE_SETEUID
49# ifdef HAVE_SETREUID
50#  define seteuid(e)	setreuid(-1, (e))
51# else /* NOT HAVE_SETREUID */
52#  ifdef HAVE_SETRESUID
53#   define seteuid(e)	setresuid(-1, (e), -1)
54#  else /* NOT HAVE_SETRESUID */
55    /* I can't set euid. (;_;) */
56#  endif /* HAVE_SETRESUID */
57# endif /* HAVE_SETREUID */
58#endif /* NO_SETEUID */
59
60static VALUE eChildExited;
61
62/* Returns the exit status of the child for which PTY#check
63 * raised this exception
64 */
65static VALUE
66echild_status(VALUE self)
67{
68    return rb_ivar_get(self, rb_intern("status"));
69}
70
71struct pty_info {
72    int fd;
73    rb_pid_t child_pid;
74};
75
76static void getDevice(int*, int*, char [DEVICELEN], int);
77
78struct child_info {
79    int master, slave;
80    char *slavename;
81    VALUE execarg_obj;
82    struct rb_execarg *eargp;
83};
84
85static int
86chfunc(void *data, char *errbuf, size_t errbuf_len)
87{
88    struct child_info *carg = data;
89    int master = carg->master;
90    int slave = carg->slave;
91
92#define ERROR_EXIT(str) do { \
93	strlcpy(errbuf, (str), errbuf_len); \
94	return -1; \
95    } while (0)
96
97    /*
98     * Set free from process group and controlling terminal
99     */
100#ifdef HAVE_SETSID
101    (void) setsid();
102#else /* HAS_SETSID */
103# ifdef HAVE_SETPGRP
104#  ifdef SETGRP_VOID
105    if (setpgrp() == -1)
106        ERROR_EXIT("setpgrp()");
107#  else /* SETGRP_VOID */
108    if (setpgrp(0, getpid()) == -1)
109        ERROR_EXIT("setpgrp()");
110    {
111        int i = rb_cloexec_open("/dev/tty", O_RDONLY, 0);
112        if (i < 0) ERROR_EXIT("/dev/tty");
113        rb_update_max_fd(i);
114        if (ioctl(i, TIOCNOTTY, (char *)0))
115            ERROR_EXIT("ioctl(TIOCNOTTY)");
116        close(i);
117    }
118#  endif /* SETGRP_VOID */
119# endif /* HAVE_SETPGRP */
120#endif /* HAS_SETSID */
121
122    /*
123     * obtain new controlling terminal
124     */
125#if defined(TIOCSCTTY)
126    close(master);
127    (void) ioctl(slave, TIOCSCTTY, (char *)0);
128    /* errors ignored for sun */
129#else
130    close(slave);
131    slave = rb_cloexec_open(carg->slavename, O_RDWR, 0);
132    if (slave < 0) {
133        ERROR_EXIT("open: pty slave");
134    }
135    rb_update_max_fd(slave);
136    close(master);
137#endif
138    dup2(slave,0);
139    dup2(slave,1);
140    dup2(slave,2);
141    close(slave);
142#if defined(HAVE_SETEUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRESUID)
143    seteuid(getuid());
144#endif
145
146    return rb_exec_async_signal_safe(carg->eargp, errbuf, sizeof(errbuf_len));
147#undef ERROR_EXIT
148}
149
150static void
151establishShell(int argc, VALUE *argv, struct pty_info *info,
152	       char SlaveName[DEVICELEN])
153{
154    int 		master, slave, status = 0;
155    rb_pid_t		pid;
156    char		*p, *getenv();
157    struct passwd	*pwent;
158    VALUE		v;
159    struct child_info   carg;
160    char		errbuf[32];
161
162    if (argc == 0) {
163	const char *shellname;
164
165	if ((p = getenv("SHELL")) != NULL) {
166	    shellname = p;
167	}
168	else {
169	    pwent = getpwuid(getuid());
170	    if (pwent && pwent->pw_shell)
171		shellname = pwent->pw_shell;
172	    else
173		shellname = "/bin/sh";
174	}
175	v = rb_str_new2(shellname);
176	argc = 1;
177	argv = &v;
178    }
179
180    carg.execarg_obj = rb_execarg_new(argc, argv, 1);
181    carg.eargp = rb_execarg_get(carg.execarg_obj);
182    rb_execarg_fixup(carg.execarg_obj);
183
184    getDevice(&master, &slave, SlaveName, 0);
185
186    carg.master = master;
187    carg.slave = slave;
188    carg.slavename = SlaveName;
189    errbuf[0] = '\0';
190    pid = rb_fork_async_signal_safe(&status, chfunc, &carg, Qnil, errbuf, sizeof(errbuf));
191
192    if (pid < 0) {
193	int e = errno;
194	close(master);
195	close(slave);
196	errno = e;
197	if (status) rb_jump_tag(status);
198	rb_sys_fail(errbuf[0] ? errbuf : "fork failed");
199    }
200
201    close(slave);
202
203    info->child_pid = pid;
204    info->fd = master;
205
206    RB_GC_GUARD(carg.execarg_obj);
207}
208
209static int
210no_mesg(char *slavedevice, int nomesg)
211{
212    if (nomesg)
213        return chmod(slavedevice, 0600);
214    else
215        return 0;
216}
217
218static int
219get_device_once(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg, int fail)
220{
221#if defined(HAVE_POSIX_OPENPT)
222    /* Unix98 PTY */
223    int masterfd = -1, slavefd = -1;
224    char *slavedevice;
225    struct sigaction dfl, old;
226
227    dfl.sa_handler = SIG_DFL;
228    dfl.sa_flags = 0;
229    sigemptyset(&dfl.sa_mask);
230
231#if defined(__sun) || defined(__FreeBSD__)
232    /* workaround for Solaris 10: grantpt() doesn't work if FD_CLOEXEC is set.  [ruby-dev:44688] */
233    /* FreeBSD 8 supported O_CLOEXEC for posix_openpt, but FreeBSD 9 removed it.
234     * http://www.freebsd.org/cgi/query-pr.cgi?pr=162374 */
235    if ((masterfd = posix_openpt(O_RDWR|O_NOCTTY)) == -1) goto error;
236    if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error;
237    if (grantpt(masterfd) == -1) goto grantpt_error;
238    rb_fd_fix_cloexec(masterfd);
239#else
240    {
241	int flags = O_RDWR|O_NOCTTY;
242# if defined(O_CLOEXEC)
243	/* glibc posix_openpt() in GNU/Linux calls open("/dev/ptmx", flags) internally.
244	 * So version dependency on GNU/Linux is same as O_CLOEXEC with open().
245	 * O_CLOEXEC is available since Linux 2.6.23.  Linux 2.6.18 silently ignore it. */
246	flags |= O_CLOEXEC;
247# endif
248	if ((masterfd = posix_openpt(flags)) == -1) goto error;
249    }
250    rb_fd_fix_cloexec(masterfd);
251    if (sigaction(SIGCHLD, &dfl, &old) == -1) goto error;
252    if (grantpt(masterfd) == -1) goto grantpt_error;
253#endif
254    if (sigaction(SIGCHLD, &old, NULL) == -1) goto error;
255    if (unlockpt(masterfd) == -1) goto error;
256    if ((slavedevice = ptsname(masterfd)) == NULL) goto error;
257    if (no_mesg(slavedevice, nomesg) == -1) goto error;
258    if ((slavefd = rb_cloexec_open(slavedevice, O_RDWR|O_NOCTTY, 0)) == -1) goto error;
259    rb_update_max_fd(slavefd);
260
261#if defined(I_PUSH) && !defined(__linux__)
262    if (ioctl(slavefd, I_PUSH, "ptem") == -1) goto error;
263    if (ioctl(slavefd, I_PUSH, "ldterm") == -1) goto error;
264    if (ioctl(slavefd, I_PUSH, "ttcompat") == -1) goto error;
265#endif
266
267    *master = masterfd;
268    *slave = slavefd;
269    strlcpy(SlaveName, slavedevice, DEVICELEN);
270    return 0;
271
272  grantpt_error:
273    sigaction(SIGCHLD, &old, NULL);
274  error:
275    if (slavefd != -1) close(slavefd);
276    if (masterfd != -1) close(masterfd);
277    if (fail) {
278        rb_raise(rb_eRuntimeError, "can't get Master/Slave device");
279    }
280    return -1;
281#elif defined HAVE_OPENPTY
282/*
283 * Use openpty(3) of 4.3BSD Reno and later,
284 * or the same interface function.
285 */
286    if (openpty(master, slave, SlaveName,
287		(struct termios *)0, (struct winsize *)0) == -1) {
288	if (!fail) return -1;
289	rb_raise(rb_eRuntimeError, "openpty() failed");
290    }
291    rb_fd_fix_cloexec(*master);
292    rb_fd_fix_cloexec(*slave);
293    if (no_mesg(SlaveName, nomesg) == -1) {
294	if (!fail) return -1;
295	rb_raise(rb_eRuntimeError, "can't chmod slave pty");
296    }
297
298    return 0;
299
300#elif defined HAVE__GETPTY
301    /* SGI IRIX */
302    char *name;
303    mode_t mode = nomesg ? 0600 : 0622;
304
305    if (!(name = _getpty(master, O_RDWR, mode, 0))) {
306	if (!fail) return -1;
307	rb_raise(rb_eRuntimeError, "_getpty() failed");
308    }
309    rb_fd_fix_cloexec(*master);
310
311    *slave = rb_cloexec_open(name, O_RDWR, 0);
312    /* error check? */
313    rb_update_max_fd(*slave);
314    strlcpy(SlaveName, name, DEVICELEN);
315
316    return 0;
317#elif defined(HAVE_PTSNAME)
318    /* System V */
319    int	 masterfd = -1, slavefd = -1;
320    char *slavedevice;
321    void (*s)();
322
323    extern char *ptsname(int);
324    extern int unlockpt(int);
325    extern int grantpt(int);
326
327#if defined(__sun)
328    /* workaround for Solaris 10: grantpt() doesn't work if FD_CLOEXEC is set.  [ruby-dev:44688] */
329    if((masterfd = open("/dev/ptmx", O_RDWR, 0)) == -1) goto error;
330    s = signal(SIGCHLD, SIG_DFL);
331    if(grantpt(masterfd) == -1) goto error;
332    rb_fd_fix_cloexec(masterfd);
333#else
334    if((masterfd = rb_cloexec_open("/dev/ptmx", O_RDWR, 0)) == -1) goto error;
335    rb_update_max_fd(masterfd);
336    s = signal(SIGCHLD, SIG_DFL);
337    if(grantpt(masterfd) == -1) goto error;
338#endif
339    signal(SIGCHLD, s);
340    if(unlockpt(masterfd) == -1) goto error;
341    if((slavedevice = ptsname(masterfd)) == NULL) goto error;
342    if (no_mesg(slavedevice, nomesg) == -1) goto error;
343    if((slavefd = rb_cloexec_open(slavedevice, O_RDWR, 0)) == -1) goto error;
344    rb_update_max_fd(slavefd);
345#if defined(I_PUSH) && !defined(__linux__)
346    if(ioctl(slavefd, I_PUSH, "ptem") == -1) goto error;
347    if(ioctl(slavefd, I_PUSH, "ldterm") == -1) goto error;
348    ioctl(slavefd, I_PUSH, "ttcompat");
349#endif
350    *master = masterfd;
351    *slave = slavefd;
352    strlcpy(SlaveName, slavedevice, DEVICELEN);
353    return 0;
354
355  error:
356    if (slavefd != -1) close(slavefd);
357    if (masterfd != -1) close(masterfd);
358    if (fail) rb_raise(rb_eRuntimeError, "can't get Master/Slave device");
359    return -1;
360#else
361    /* BSD */
362    int	 masterfd = -1, slavefd = -1;
363    const char *const *p;
364    char MasterName[DEVICELEN];
365
366#if defined(__hpux)
367    static const char MasterDevice[] = "/dev/ptym/pty%s";
368    static const char SlaveDevice[] =  "/dev/pty/tty%s";
369    static const char *const deviceNo[] = {
370    "p0","p1","p2","p3","p4","p5","p6","p7",
371    "p8","p9","pa","pb","pc","pd","pe","pf",
372    "q0","q1","q2","q3","q4","q5","q6","q7",
373    "q8","q9","qa","qb","qc","qd","qe","qf",
374    "r0","r1","r2","r3","r4","r5","r6","r7",
375    "r8","r9","ra","rb","rc","rd","re","rf",
376    "s0","s1","s2","s3","s4","s5","s6","s7",
377    "s8","s9","sa","sb","sc","sd","se","sf",
378    "t0","t1","t2","t3","t4","t5","t6","t7",
379    "t8","t9","ta","tb","tc","td","te","tf",
380    "u0","u1","u2","u3","u4","u5","u6","u7",
381    "u8","u9","ua","ub","uc","ud","ue","uf",
382    "v0","v1","v2","v3","v4","v5","v6","v7",
383    "v8","v9","va","vb","vc","vd","ve","vf",
384    "w0","w1","w2","w3","w4","w5","w6","w7",
385    "w8","w9","wa","wb","wc","wd","we","wf",
386    0
387    };
388#elif defined(_IBMESA)  /* AIX/ESA */
389    static const char MasterDevice[] = "/dev/ptyp%s";
390    static const char SlaveDevice[] = "/dev/ttyp%s";
391    static const char *const deviceNo[] = {
392    "00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f",
393    "10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f",
394    "20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f",
395    "30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f",
396    "40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f",
397    "50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f",
398    "60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f",
399    "70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f",
400    "80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f",
401    "90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f",
402    "a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af",
403    "b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf",
404    "c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf",
405    "d0","d1","d2","d3","d4","d5","d6","d7","d8","d9","da","db","dc","dd","de","df",
406    "e0","e1","e2","e3","e4","e5","e6","e7","e8","e9","ea","eb","ec","ed","ee","ef",
407    "f0","f1","f2","f3","f4","f5","f6","f7","f8","f9","fa","fb","fc","fd","fe","ff",
408    0
409    };
410#else /* 4.2BSD */
411    static const char MasterDevice[] = "/dev/pty%s";
412    static const char SlaveDevice[] = "/dev/tty%s";
413    static const char *const deviceNo[] = {
414    "p0","p1","p2","p3","p4","p5","p6","p7",
415    "p8","p9","pa","pb","pc","pd","pe","pf",
416    "q0","q1","q2","q3","q4","q5","q6","q7",
417    "q8","q9","qa","qb","qc","qd","qe","qf",
418    "r0","r1","r2","r3","r4","r5","r6","r7",
419    "r8","r9","ra","rb","rc","rd","re","rf",
420    "s0","s1","s2","s3","s4","s5","s6","s7",
421    "s8","s9","sa","sb","sc","sd","se","sf",
422    0
423    };
424#endif
425    for (p = deviceNo; *p != NULL; p++) {
426	snprintf(MasterName, sizeof MasterName, MasterDevice, *p);
427	if ((masterfd = rb_cloexec_open(MasterName,O_RDWR,0)) >= 0) {
428            rb_update_max_fd(masterfd);
429	    *master = masterfd;
430	    snprintf(SlaveName, DEVICELEN, SlaveDevice, *p);
431	    if ((slavefd = rb_cloexec_open(SlaveName,O_RDWR,0)) >= 0) {
432                rb_update_max_fd(slavefd);
433		*slave = slavefd;
434		if (chown(SlaveName, getuid(), getgid()) != 0) goto error;
435		if (chmod(SlaveName, nomesg ? 0600 : 0622) != 0) goto error;
436		return 0;
437	    }
438	    close(masterfd);
439	}
440    }
441  error:
442    if (slavefd != -1) close(slavefd);
443    if (masterfd != -1) close(masterfd);
444    if (fail) rb_raise(rb_eRuntimeError, "can't get %s", SlaveName);
445    return -1;
446#endif
447}
448
449static void
450getDevice(int *master, int *slave, char SlaveName[DEVICELEN], int nomesg)
451{
452    if (get_device_once(master, slave, SlaveName, nomesg, 0)) {
453	rb_gc();
454	get_device_once(master, slave, SlaveName, nomesg, 1);
455    }
456}
457
458static VALUE
459pty_close_pty(VALUE assoc)
460{
461    VALUE io;
462    int i;
463
464    for (i = 0; i < 2; i++) {
465        io = rb_ary_entry(assoc, i);
466        if (RB_TYPE_P(io, T_FILE) && 0 <= RFILE(io)->fptr->fd)
467            rb_io_close(io);
468    }
469    return Qnil;
470}
471
472/*
473 * call-seq:
474 *   PTY.open => [master_io, slave_file]
475 *   PTY.open {|master_io, slave_file| ... } => block value
476 *
477 * Allocates a pty (pseudo-terminal).
478 *
479 * In the block form, yields two arguments <tt>master_io, slave_file</tt>
480 * and the value of the block is returned from +open+.
481 *
482 * The IO and File are both closed after the block completes if they haven't
483 * been already closed.
484 *
485 *   PTY.open {|master, slave|
486 *     p master      #=> #<IO:masterpty:/dev/pts/1>
487 *     p slave      #=> #<File:/dev/pts/1>
488 *     p slave.path #=> "/dev/pts/1"
489 *   }
490 *
491 * In the non-block form, returns a two element array, <tt>[master_io,
492 * slave_file]</tt>.
493 *
494 *   master, slave = PTY.open
495 *   # do something with master for IO, or the slave file
496 *
497 * The arguments in both forms are:
498 *
499 * +master_io+::    the master of the pty, as an IO.
500 * +slave_file+::   the slave of the pty, as a File.  The path to the
501 *		    terminal device is available via +slave_file.path+
502 *
503 */
504static VALUE
505pty_open(VALUE klass)
506{
507    int master_fd, slave_fd;
508    char slavename[DEVICELEN];
509    VALUE master_io, slave_file;
510    rb_io_t *master_fptr, *slave_fptr;
511    VALUE assoc;
512
513    getDevice(&master_fd, &slave_fd, slavename, 1);
514
515    master_io = rb_obj_alloc(rb_cIO);
516    MakeOpenFile(master_io, master_fptr);
517    master_fptr->mode = FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX;
518    master_fptr->fd = master_fd;
519    master_fptr->pathv = rb_obj_freeze(rb_sprintf("masterpty:%s", slavename));
520
521    slave_file = rb_obj_alloc(rb_cFile);
522    MakeOpenFile(slave_file, slave_fptr);
523    slave_fptr->mode = FMODE_READWRITE | FMODE_SYNC | FMODE_DUPLEX | FMODE_TTY;
524    slave_fptr->fd = slave_fd;
525    slave_fptr->pathv = rb_obj_freeze(rb_str_new_cstr(slavename));
526
527    assoc = rb_assoc_new(master_io, slave_file);
528    if (rb_block_given_p()) {
529	return rb_ensure(rb_yield, assoc, pty_close_pty, assoc);
530    }
531    return assoc;
532}
533
534static VALUE
535pty_detach_process(struct pty_info *info)
536{
537    rb_detach_process(info->child_pid);
538    return Qnil;
539}
540
541/*
542 * call-seq:
543 *   PTY.spawn(command_line)  { |r, w, pid| ... }
544 *   PTY.spawn(command_line)  => [r, w, pid]
545 *   PTY.spawn(command, arguments, ...)  { |r, w, pid| ... }
546 *   PTY.spawn(command, arguments, ...)  => [r, w, pid]
547 *
548 * Spawns the specified command on a newly allocated pty. You can also use the
549 * alias ::getpty.
550 *
551 * The command's controlling tty is set to the slave device of the pty
552 * and its standard input/output/error is redirected to the slave device.
553 *
554 * +command+ and +command_line+ are the full commands to run, given a String.
555 * Any additional +arguments+ will be passed to the command.
556 *
557 * === Return values
558 *
559 * In the non-block form this returns an array of size three,
560 * <tt>[r, w, pid]</tt>.
561 *
562 * In the block form these same values will be yielded to the block:
563 *
564 * +r+:: A readable IO that that contains the command's
565 *       standard output and standard error
566 * +w+:: A writable IO that is the command's standard input
567 * +pid+:: The process identifier for the command.
568 */
569static VALUE
570pty_getpty(int argc, VALUE *argv, VALUE self)
571{
572    VALUE res;
573    struct pty_info info;
574    rb_io_t *wfptr,*rfptr;
575    VALUE rport = rb_obj_alloc(rb_cFile);
576    VALUE wport = rb_obj_alloc(rb_cFile);
577    char SlaveName[DEVICELEN];
578
579    MakeOpenFile(rport, rfptr);
580    MakeOpenFile(wport, wfptr);
581
582    establishShell(argc, argv, &info, SlaveName);
583
584    rfptr->mode = rb_io_mode_flags("r");
585    rfptr->fd = info.fd;
586    rfptr->pathv = rb_obj_freeze(rb_str_new_cstr(SlaveName));
587
588    wfptr->mode = rb_io_mode_flags("w") | FMODE_SYNC;
589    wfptr->fd = rb_cloexec_dup(info.fd);
590    if (wfptr->fd == -1)
591        rb_sys_fail("dup()");
592    rb_update_max_fd(wfptr->fd);
593    wfptr->pathv = rfptr->pathv;
594
595    res = rb_ary_new2(3);
596    rb_ary_store(res,0,(VALUE)rport);
597    rb_ary_store(res,1,(VALUE)wport);
598    rb_ary_store(res,2,PIDT2NUM(info.child_pid));
599
600    if (rb_block_given_p()) {
601	rb_ensure(rb_yield, res, pty_detach_process, (VALUE)&info);
602	return Qnil;
603    }
604    return res;
605}
606
607NORETURN(static void raise_from_check(pid_t pid, int status));
608static void
609raise_from_check(pid_t pid, int status)
610{
611    const char *state;
612    VALUE msg;
613    VALUE exc;
614
615#if defined(WIFSTOPPED)
616#elif defined(IF_STOPPED)
617#define WIFSTOPPED(status) IF_STOPPED(status)
618#else
619---->> Either IF_STOPPED or WIFSTOPPED is needed <<----
620#endif /* WIFSTOPPED | IF_STOPPED */
621    if (WIFSTOPPED(status)) { /* suspend */
622	state = "stopped";
623    }
624    else if (kill(pid, 0) == 0) {
625	state = "changed";
626    }
627    else {
628	state = "exited";
629    }
630    msg = rb_sprintf("pty - %s: %ld", state, (long)pid);
631    exc = rb_exc_new3(eChildExited, msg);
632    rb_iv_set(exc, "status", rb_last_status_get());
633    rb_exc_raise(exc);
634}
635
636/*
637 * call-seq:
638 *   PTY.check(pid, raise = false) => Process::Status or nil
639 *   PTY.check(pid, true)          => nil or raises PTY::ChildExited
640 *
641 * Checks the status of the child process specified by +pid+.
642 * Returns +nil+ if the process is still alive.
643 *
644 * If the process is not alive, and +raise+ was true, a PTY::ChildExited
645 * exception will be raised. Otherwise it will return a Process::Status
646 * instance.
647 *
648 * +pid+:: The process id of the process to check
649 * +raise+:: If +true+ and the process identified by +pid+ is no longer
650 *           alive a PTY::ChildExited is raised.
651 *
652 */
653static VALUE
654pty_check(int argc, VALUE *argv, VALUE self)
655{
656    VALUE pid, exc;
657    pid_t cpid;
658    int status;
659
660    rb_scan_args(argc, argv, "11", &pid, &exc);
661    cpid = rb_waitpid(NUM2PIDT(pid), &status, WNOHANG|WUNTRACED);
662    if (cpid == -1 || cpid == 0) return Qnil;
663
664    if (!RTEST(exc)) return rb_last_status_get();
665    raise_from_check(cpid, status);
666
667    UNREACHABLE;
668}
669
670static VALUE cPTY;
671
672/*
673 * Document-class: PTY::ChildExited
674 *
675 * Thrown when PTY::check is called for a pid that represents a process that
676 * has exited.
677 */
678
679/*
680 * Document-class: PTY
681 *
682 * Creates and managed pseudo terminals (PTYs).  See also
683 * http://en.wikipedia.org/wiki/Pseudo_terminal
684 *
685 * PTY allows you to allocate new terminals using ::open or ::spawn a new
686 * terminal with a specific command.
687 *
688 * == Example
689 *
690 * In this example we will change the buffering type in the +factor+ command,
691 * assuming that factor uses stdio for stdout buffering.
692 *
693 * If IO.pipe is used instead of PTY.open, this code deadlocks because factor's
694 * stdout is fully buffered.
695 *
696 *   # start by requiring the standard library PTY
697 *   require 'pty'
698 *
699 *   master, slave = PTY.open
700 *   read, write = IO.pipe
701 *   pid = spawn("factor", :in=>read, :out=>slave)
702 *   read.close	    # we dont need the read
703 *   slave.close    # or the slave
704 *
705 *   # pipe "42" to the factor command
706 *   write.puts "42"
707 *   # output the response from factor
708 *   p master.gets #=> "42: 2 3 7\n"
709 *
710 *   # pipe "144" to factor and print out the response
711 *   write.puts "144"
712 *   p master.gets #=> "144: 2 2 2 2 3 3\n"
713 *   write.close # close the pipe
714 *
715 *   # The result of read operation when pty slave is closed is platform
716 *   # dependent.
717 *   ret = begin
718 *           m.gets          # FreeBSD returns nil.
719 *         rescue Errno::EIO # GNU/Linux raises EIO.
720 *           nil
721 *         end
722 *   p ret #=> nil
723 *
724 * == License
725 *
726 *  C) Copyright 1998 by Akinori Ito.
727 *
728 *  This software may be redistributed freely for this purpose, in full
729 *  or in part, provided that this entire copyright notice is included
730 *  on any copies of this software and applications and derivations thereof.
731 *
732 *  This software is provided on an "as is" basis, without warranty of any
733 *  kind, either expressed or implied, as to any matter including, but not
734 *  limited to warranty of fitness of purpose, or merchantability, or
735 *  results obtained from use of this software.
736 */
737
738void
739Init_pty()
740{
741    cPTY = rb_define_module("PTY");
742    /* :nodoc */
743    rb_define_module_function(cPTY,"getpty",pty_getpty,-1);
744    rb_define_module_function(cPTY,"spawn",pty_getpty,-1);
745    rb_define_singleton_method(cPTY,"check",pty_check,-1);
746    rb_define_singleton_method(cPTY,"open",pty_open,0);
747
748    eChildExited = rb_define_class_under(cPTY,"ChildExited",rb_eRuntimeError);
749    rb_define_method(eChildExited,"status",echild_status,0);
750}
751