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