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