ext/pty/pty.c


DEFINITIONS

This source file includes following functions.
  1. seteuid
  2. seteuid
  3. echild_status
  4. pty_syswait
  5. pty_exec
  6. establishShell
  7. pty_kill_child
  8. getDevice
  9. getDevice
  10. getDevice
  11. freeDevice
  12. pty_getpty
  13. pty_protect
  14. pty_reset_signal
  15. Init_pty


   1  #include        "config.h"
   2  #include        <stdio.h>
   3  #include        <sys/types.h>
   4  #include        <sys/stat.h>
   5  #include        <sys/file.h>
   6  #include        <fcntl.h>
   7  #include        <errno.h>
   8  #include        <pwd.h>
   9  #if !defined(HAVE_OPENPTY) && !defined(HAVE__GETPTY)
  10  #include        <sys/ioctl.h>
  11  #endif
  12  #ifdef HAVE_LIBUTIL_H
  13  #include        <libutil.h>
  14  #endif
  15  #ifdef HAVE_PTY_H
  16  #include        <pty.h>
  17  #endif
  18  #ifdef HAVE_SYS_WAIT_H
  19  #include <sys/wait.h>
  20  #else
  21  #define WIFSTOPPED(status)    (((status) & 0xff) == 0x7f)
  22  #endif
  23  #include <ctype.h>
  24  
  25  #include "ruby.h"
  26  #include "rubyio.h"
  27  
  28  #include <signal.h>
  29  #ifdef HAVE_SYS_STROPTS_H
  30  #include <sys/stropts.h>
  31  #endif
  32  
  33  #ifdef HAVE_UNISTD_H
  34  #include <unistd.h>
  35  #endif
  36  
  37  #define DEVICELEN       16
  38  
  39  #if !defined(HAVE_OPENPTY)
  40  #ifdef __hpux
  41  static
  42  char    *MasterDevice = "/dev/ptym/pty%s",
  43          *SlaveDevice =  "/dev/pty/tty%s",
  44          *deviceNo[] = {
  45                  "p0","p1","p2","p3","p4","p5","p6","p7",
  46                  "p8","p9","pa","pb","pc","pd","pe","pf",
  47                  "q0","q1","q2","q3","q4","q5","q6","q7",
  48                  "q8","q9","qa","qb","qc","qd","qe","qf",
  49                  "r0","r1","r2","r3","r4","r5","r6","r7",
  50                  "r8","r9","ra","rb","rc","rd","re","rf",
  51                  "s0","s1","s2","s3","s4","s5","s6","s7",
  52                  "s8","s9","sa","sb","sc","sd","se","sf",
  53                  "t0","t1","t2","t3","t4","t5","t6","t7",
  54                  "t8","t9","ta","tb","tc","td","te","tf",
  55                  "u0","u1","u2","u3","u4","u5","u6","u7",
  56                  "u8","u9","ua","ub","uc","ud","ue","uf",
  57                  "v0","v1","v2","v3","v4","v5","v6","v7",
  58                  "v8","v9","va","vb","vc","vd","ve","vf",
  59                  "w0","w1","w2","w3","w4","w5","w6","w7",
  60                  "w8","w9","wa","wb","wc","wd","we","wf",
  61                  0,
  62          };
  63  #else  /* NOT HPUX */
  64  #ifdef _IBMESA  /* AIX/ESA */
  65  static 
  66  char    *MasterDevice = "/dev/ptyp%s",
  67          *SlaveDevice = "/dev/ttyp%s",
  68          *deviceNo[] = {
  69  "00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f",
  70  "10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f",
  71  "20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f",
  72  "30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f",
  73  "40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f",
  74  "50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f",
  75  "60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f",
  76  "70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f",
  77  "80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f",
  78  "90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f",
  79  "a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af",
  80  "b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf",
  81  "c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf",
  82  "d0","d1","d2","d3","d4","d5","d6","d7","d8","d9","da","db","dc","dd","de","df",
  83  "e0","e1","e2","e3","e4","e5","e6","e7","e8","e9","ea","eb","ec","ed","ee","ef",
  84  "f0","f1","f2","f3","f4","f5","f6","f7","f8","f9","fa","fb","fc","fd","fe","ff",
  85                  };
  86  #else
  87  static 
  88  char    *MasterDevice = "/dev/pty%s",
  89          *SlaveDevice = "/dev/tty%s",
  90          *deviceNo[] = {
  91                  "p0","p1","p2","p3","p4","p5","p6","p7",
  92                  "p8","p9","pa","pb","pc","pd","pe","pf",
  93                  "q0","q1","q2","q3","q4","q5","q6","q7",
  94                  "q8","q9","qa","qb","qc","qd","qe","qf",
  95                  "r0","r1","r2","r3","r4","r5","r6","r7",
  96                  "r8","r9","ra","rb","rc","rd","re","rf",
  97                  "s0","s1","s2","s3","s4","s5","s6","s7",
  98                  "s8","s9","sa","sb","sc","sd","se","sf",
  99                  0,
 100          };
 101  #endif /* _IBMESA */
 102  #endif /* HPUX */
 103  #endif /* !defined(HAVE_OPENPTY) */
 104  
 105  static char SlaveName[DEVICELEN];
 106  
 107  extern int errno;
 108  
 109  #ifndef HAVE_SETEUID
 110  # ifdef HAVE_SETREUID
 111  #  define seteuid(e)    setreuid(-1, (e))
 112  # else /* NOT HAVE_SETREUID */
 113  #  ifdef HAVE_SETRESUID
 114  #   define seteuid(e)   setresuid(-1, (e), -1)
 115  #  else /* NOT HAVE_SETRESUID */
 116      /* I can't set euid. (;_;) */
 117  #  endif /* HAVE_SETRESUID */
 118  # endif /* HAVE_SETREUID */
 119  #endif /* NO_SETEUID */
 120  
 121  static VALUE eChildExited;
 122  
 123  static VALUE
 124  echild_status(self)
 125      VALUE self;
 126  {
 127      return rb_ivar_get(self, rb_intern("status"));
 128  }
 129  
 130  struct pty_info {
 131      int fd;
 132      pid_t child_pid;
 133      VALUE thread;
 134  };
 135  
 136  static VALUE
 137  pty_syswait(info)
 138      struct pty_info *info;
 139  {
 140      extern VALUE rb_last_status;
 141      int cpid, status;
 142      char buf[1024];
 143      VALUE exc, st;
 144      char *state = "changed";
 145  
 146      cpid = rb_waitpid(info->child_pid, &status, WUNTRACED);
 147      st = rb_last_status;
 148      
 149      if (cpid == 0 || cpid == -1)
 150          return Qnil;
 151  
 152  #ifdef IF_STOPPED
 153      if (IF_STOPPED(status)) { /* suspend */
 154          state = "stopped";
 155      }
 156  #else
 157  #ifdef WIFSTOPPED
 158      if (WIFSTOPPED(status)) { /* suspend */
 159          state = "stopped";
 160      }
 161  #else
 162  ---->> Either IF_STOPPED or WIFSTOPPED is needed <<----
 163  #endif /* WIFSTOPPED */
 164  #endif /* IF_STOPPED */
 165      if (WIFEXITED(status)) {
 166          state = "exit";
 167      }
 168      
 169      snprintf(buf, sizeof(buf), "pty - %s: %d", state, cpid);
 170      exc = rb_exc_new2(eChildExited, buf);
 171      rb_iv_set(exc, "status", st);
 172      rb_funcall(info->thread, rb_intern("raise"), 1, exc);
 173      return Qnil;
 174  }
 175  
 176  static void getDevice _((int*, int*));
 177  
 178  struct exec_info {
 179      int argc;
 180      VALUE *argv;
 181  };
 182  
 183  static VALUE
 184  pty_exec(arg)
 185      struct exec_info *arg;
 186  {
 187      return rb_f_exec(arg->argc, arg->argv);
 188  }
 189  
 190  static void
 191  establishShell(argc, argv, info)
 192      int argc;
 193      VALUE *argv;
 194      struct pty_info *info;
 195  {       
 196      static int          i,master,slave,currentPid;
 197      char                *p,*getenv();
 198      struct passwd       *pwent;
 199      VALUE               v;
 200      struct exec_info    arg;
 201      int                 status;
 202  
 203      if (argc == 0) {
 204          char *shellname;
 205  
 206          if ((p = getenv("SHELL")) != NULL) {
 207              shellname = p;
 208          }
 209          else {
 210              pwent = getpwuid(getuid());
 211              if (pwent && pwent->pw_shell)
 212                  shellname = pwent->pw_shell;
 213              else
 214                  shellname = "/bin/sh";
 215          }
 216          v = rb_str_new2(shellname);
 217          argc = 1;
 218          argv = &v;
 219      }
 220      getDevice(&master,&slave);
 221  
 222      info->thread = rb_thread_current();
 223      currentPid = getpid();
 224      if((i = fork()) < 0) {
 225          rb_sys_fail("fork failed");
 226      }
 227  
 228      if(i == 0) {        /* child */
 229          currentPid = getpid();  
 230  
 231          /*
 232           * Set free from process group and controlling terminal
 233           */
 234  #ifdef HAVE_SETSID
 235          (void) setsid();
 236  #else /* HAS_SETSID */
 237  # ifdef HAVE_SETPGRP
 238  #  ifdef SETGRP_VOID
 239          if (setpgrp() == -1)
 240              perror("setpgrp()");
 241  #  else /* SETGRP_VOID */
 242          if (setpgrp(0, currentPid) == -1)
 243              rb_sys_fail("setpgrp()");
 244          if ((i = open("/dev/tty", O_RDONLY)) < 0)
 245              rb_sys_fail("/dev/tty");
 246          else {
 247              if (ioctl(i, TIOCNOTTY, (char *)0))
 248                  perror("ioctl(TIOCNOTTY)");
 249              close(i);
 250          }
 251  #  endif /* SETGRP_VOID */
 252  # endif /* HAVE_SETPGRP */
 253  #endif /* HAS_SETSID */
 254  
 255          /*
 256           * obtain new controlling terminal
 257           */
 258  #if defined(TIOCSCTTY)
 259          close(master);
 260          (void) ioctl(slave, TIOCSCTTY, (char *)0);
 261          /* errors ignored for sun */
 262  #else
 263          close(slave);
 264          slave = open(SlaveName, O_RDWR);
 265          if (slave < 0) {
 266              perror("open: pty slave");
 267              _exit(1);
 268          }
 269          close(master);
 270  #endif
 271          dup2(slave,0);
 272          dup2(slave,1);
 273          dup2(slave,2);
 274          close(slave);
 275  #if defined(HAVE_SETEUID) || defined(HAVE_SETREUID) || defined(HAVE_SETRESUID)
 276          seteuid(getuid());
 277  #endif
 278  
 279          arg.argc = argc;
 280          arg.argv = argv;
 281          rb_protect(pty_exec, (VALUE)&arg, &status);
 282          sleep(1);
 283          _exit(1);
 284      }
 285  
 286      close(slave);
 287  
 288      info->child_pid = i;
 289      info->fd = master;
 290  }
 291  
 292  static VALUE
 293  pty_kill_child(info)
 294      struct pty_info *info;
 295  {
 296      if (rb_funcall(info->thread, rb_intern("alive?"), 0, 0) == Qtrue &&
 297          kill(info->child_pid, 0) == 0) {
 298          rb_thread_schedule();
 299          if (kill(info->child_pid, SIGTERM) == 0) {
 300              rb_thread_schedule();
 301              if (kill(info->child_pid, 0) == 0) {
 302                  kill(info->child_pid, SIGINT);
 303                  rb_thread_schedule();
 304                  if (kill(info->child_pid, 0) == 0)
 305                      kill(info->child_pid, SIGKILL);
 306              }
 307          }
 308      }
 309      rb_funcall(info->thread, rb_intern("join"), 0, 0);
 310      return Qnil;
 311  }
 312  
 313  
 314  #ifdef HAVE_OPENPTY
 315  /*
 316   * Use openpty(3) of 4.3BSD Reno and later,
 317   * or the same interface function.
 318   */
 319  static void
 320  getDevice(master,slave)
 321      int *master,*slave;
 322  {
 323      if (openpty(master, slave, SlaveName,
 324                  (struct termios *)0, (struct winsize *)0) == -1) {
 325          rb_raise(rb_eRuntimeError, "openpty() failed");
 326      }
 327  }
 328  #else /* HAVE_OPENPTY */
 329  #ifdef HAVE__GETPTY
 330  static void
 331  getDevice(master,slave)
 332      int *master,*slave;
 333  {
 334      char *name;
 335  
 336      if (!(name = _getpty(master, O_RDWR, 0622, 0))) {
 337          rb_raise(rb_eRuntimeError, "_getpty() failed");
 338      }
 339  
 340      *slave = open(name, O_RDWR);
 341      strcpy(SlaveName, name);
 342  }
 343  #else /* HAVE__GETPTY */
 344  static void
 345  getDevice(master,slave)
 346      int *master,*slave;
 347  {
 348      char **p;
 349      int  i,j;
 350      char MasterName[DEVICELEN];
 351  
 352  #ifdef HAVE_PTSNAME
 353      char *pn;
 354      void (*s)();
 355  
 356      extern char *ptsname(int);
 357      extern int unlockpt(int);
 358      extern int grantpt(int);
 359  
 360      if((i = open("/dev/ptmx", O_RDWR, 0)) != -1) {
 361          s = signal(SIGCHLD, SIG_DFL);
 362          if(grantpt(i) != -1) {
 363              signal(SIGCHLD, s);
 364              if(unlockpt(i) != -1) {
 365                  if((pn = ptsname(i)) != NULL) {
 366                      if((j = open(pn, O_RDWR, 0)) != -1) {
 367  #if defined I_PUSH && !defined linux
 368                          if(ioctl(j, I_PUSH, "ptem") != -1) {
 369                              if(ioctl(j, I_PUSH, "ldterm") != -1) {
 370  #endif
 371                                  *master = i;
 372                                  *slave = j;
 373                                  strcpy(SlaveName, pn);
 374                                  return;
 375  #if defined I_PUSH && !defined linux
 376                              }
 377                          }
 378  #endif
 379                      }
 380                  }
 381              }
 382          }
 383          close(i);
 384      }
 385      rb_raise(rb_eRuntimeError, "Cannot get Master/Slave device");
 386  #else
 387      for (p = deviceNo; *p != NULL; p++) {
 388          sprintf(MasterName,MasterDevice,*p);
 389          if ((i = open(MasterName,O_RDWR,0)) >= 0) {
 390              *master = i;
 391              sprintf(SlaveName,SlaveDevice,*p);
 392              if ((j = open(SlaveName,O_RDWR,0)) >= 0) {
 393                  *slave = j;
 394                  chown(SlaveName, getuid(), getgid());
 395                  chmod(SlaveName, 0622);
 396                  return;
 397              }
 398              close(i);
 399          }
 400      }
 401      rb_raise(rb_eRuntimeError, "Cannot get %s", SlaveName);
 402  #endif
 403  }
 404  #endif /* HAVE__GETPTY */
 405  #endif /* HAVE_OPENPTY */
 406  
 407  static void
 408  freeDevice()
 409  {
 410      chmod(SlaveName, 0666);
 411      chown(SlaveName, 0, 0);
 412  }
 413  
 414  /* ruby function: getpty */
 415  static VALUE
 416  pty_getpty(argc, argv, self)
 417      int argc;
 418      VALUE *argv;
 419      VALUE self;
 420  {
 421      VALUE res, th;
 422      struct pty_info info, thinfo;
 423      OpenFile *wfptr,*rfptr;
 424      VALUE rport = rb_obj_alloc(rb_cFile);
 425      VALUE wport = rb_obj_alloc(rb_cFile);
 426    
 427      MakeOpenFile(rport, rfptr);
 428      MakeOpenFile(wport, wfptr);
 429  
 430      establishShell(argc, argv, &info);
 431  
 432      rfptr->mode = rb_io_mode_flags("r");
 433      rfptr->f = fdopen(info.fd, "r");
 434      rfptr->path = strdup(SlaveName);
 435  
 436      wfptr->mode = rb_io_mode_flags("w") | FMODE_SYNC;
 437      wfptr->f = fdopen(dup(info.fd), "w");
 438      wfptr->path = strdup(SlaveName);
 439  
 440      res = rb_ary_new2(3);
 441      rb_ary_store(res,0,(VALUE)rport);
 442      rb_ary_store(res,1,(VALUE)wport);
 443      rb_ary_store(res,2,INT2FIX(info.child_pid));
 444  
 445      th = rb_thread_create(pty_syswait, (void*)&info);
 446      thinfo.thread = th;
 447      thinfo.child_pid = info.child_pid;
 448  
 449      if (rb_block_given_p()) {
 450          rb_ensure(rb_yield, res, pty_kill_child, (VALUE)&thinfo);
 451          return Qnil;
 452      }
 453      return res;
 454  }
 455  
 456  /* ruby function: protect_signal - obsolete */
 457  static VALUE
 458  pty_protect(self)
 459      VALUE self;
 460  {
 461      rb_warn("PTY::protect_signal is no longer needed");
 462      rb_yield(Qnil);
 463      return self;
 464  }
 465  
 466  /* ruby function: reset_signal - obsolete */
 467  static VALUE
 468  pty_reset_signal(self)
 469      VALUE self;
 470  {
 471      rb_warn("PTY::reset_signal is no longer needed");
 472      return self;
 473  }
 474  
 475  static VALUE cPTY;
 476  
 477  void
 478  Init_pty()
 479  {
 480      cPTY = rb_define_module("PTY");
 481      rb_define_module_function(cPTY,"getpty",pty_getpty,-1);
 482      rb_define_module_function(cPTY,"spawn",pty_getpty,-1);
 483      rb_define_module_function(cPTY,"protect_signal",pty_protect,0);
 484      rb_define_module_function(cPTY,"reset_signal",pty_reset_signal,0);
 485  
 486      eChildExited = rb_define_class_under(cPTY,"ChildExited",rb_eRuntimeError);
 487      rb_define_method(eChildExited,"status",echild_status,0);
 488  }