WvStreams
wvpty.cc
00001 /* -*- Mode: C++ -*-
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2004 Net Integration Technologies, Inc.
00004  *  
00005  * WvStreams implementation of ptys under Linux.
00006  *
00007  * For more information on programming ptys, see chapter 19 of
00008  * Stevens' "Advanced Programming in the UNIX Environment"
00009  */
00010 
00011 #include "wvpty.h"
00012 
00013 #include <grp.h>
00014 #include <termios.h>
00015 #include <fcntl.h>
00016 #include <sys/ioctl.h>
00017 #include <sys/signal.h>
00018 #include <sys/types.h>
00019 #include <sys/wait.h>
00020 #include <sys/stat.h>
00021 
00022 #define DPRINTF(format, args...)
00023 //#define DPRINTF(format, args...) fprintf(stderr, "WvPty:" format, ##args)
00024 
00025 bool WvPty::open_pty(WvString &master, int &master_fd,
00026         WvString &slave, int &slave_fd)
00027 {
00028     const char *xvals = "pqrstuvwxyzPQRST";
00029     const char *yvals = "0123456789abcdef";
00030     char pty[] = "/dev/ptyXY";
00031     char tty[] = "/dev/ttyXY";
00032 
00033     for (int i=0; xvals[i]; ++i)
00034     {
00035         pty[8] = tty[8] = xvals[i];
00036  
00037         for (int j=0; yvals[j]; ++j)
00038         {
00039             pty[9] = tty[9] = yvals[j];
00040 
00041             master_fd = ::open(pty, O_RDWR);
00042             if (master_fd >= 0)
00043                 slave_fd = ::open(tty, O_RDWR);
00044             else slave_fd = -1;
00045             if (master_fd < 0 || slave_fd < 0)
00046             {
00047                 int saved_errno = errno;
00048                 if (master_fd >= 0) ::close(master_fd);
00049                 if (slave_fd >= 0) ::close(slave_fd);
00050                 if (saved_errno == ENOENT)
00051                 {
00052                     DPRINTF("No more PTYs (ENOENT)\n");
00053                     return false; // no more ptys
00054                 }
00055             }
00056             else
00057             {
00058                 DPRINTF("PTY is %s\n", (master = WvString(pty)).edit());
00059                 DPRINTF("TTY is %s\n", (slave = WvString(tty)).edit());
00060 
00061                 // try to change owner and permissions of slave.
00062                 // this will only work if we 
00063                 // are root; if we're not root, we don't care.
00064                 struct group *gr = ::getgrnam("tty");
00065                 ::fchown(slave_fd, ::getuid(), gr? gr->gr_gid: (gid_t)-1);
00066                 ::fchmod(slave_fd, S_IRUSR | S_IWUSR | S_IWGRP);
00067 
00068                 return true;
00069             }
00070         }
00071     }
00072 
00073     DPRINTF("No more PTYs\n");
00074     return false;
00075 }
00076 
00077 WvPty::WvPty(const char *program, const char * const *argv,
00078         Callback _pre_exec_cb, Callback _post_exec_cb)
00079         : _pid(-1), _exit_status(242),
00080           pre_exec_cb(_pre_exec_cb), post_exec_cb(_post_exec_cb)
00081 {
00082     int master_fd, slave_fd;
00083     if (!open_pty(_master, master_fd, _slave, slave_fd)
00084             || (_pid = ::fork()) < 0)
00085     {
00086         // error
00087         _pid = -1;
00088         setfd(-1);
00089     }
00090     else if (_pid == 0)
00091     {
00092         // child
00093         static const int std_fds[] = {
00094             STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, -1
00095         };
00096         const int *std_fd;
00097         
00098         if (::close(master_fd) < 0)
00099         {
00100             DPRINTF("close(master_fd) failed: %s\n", strerror(errno));
00101             goto _error;
00102         }
00103         if (::setsid() < 0)
00104         {
00105             DPRINTF("setsid() failed: %s\n", strerror(errno));
00106             goto _error;
00107         }
00108         ::ioctl(slave_fd, TIOCSCTTY, NULL); // This may fail in case opening the 
00109                                             // ptys in open_slave proactively gave us a
00110                                             // controling terminal
00111         for (std_fd = std_fds; *std_fd != -1; ++std_fd)
00112         {
00113             if (::dup2(slave_fd, *std_fd) < 0)
00114             {
00115                 DPRINTF("dup2(slave_fd, %s) failed: %s\n", *std_fd,
00116                         strerror(errno));
00117                 goto _error;
00118             }
00119         }
00120         if (slave_fd > STDERR_FILENO && ::close(slave_fd) < 0)
00121         {
00122             DPRINTF("close(slave_fd) failed: %s\n", strerror(errno));
00123             goto _error;
00124         }
00125         
00126         for (std_fd = std_fds; *std_fd != -1; ++std_fd)
00127         {
00128             if (::fcntl(*std_fd, F_SETFL,
00129                     fcntl(*std_fd, F_GETFL) & (O_APPEND|O_ASYNC)))
00130             {
00131                 DPRINTF("fcntl(%s, F_SETFL) failed: %s\n", *std_fd,
00132                         strerror(errno));
00133                 goto _error;
00134             }
00135         }
00136 
00137         if (pre_exec_cb && !pre_exec_cb(*this)) goto _error;
00138         execvp(program, (char * const *)argv);
00139         if (post_exec_cb) post_exec_cb(*this);
00140 
00141 _error:
00142         _exit(242);
00143     }
00144     else
00145     {
00146         // parent
00147         if (::close(slave_fd) < 0)
00148         {
00149             DPRINTF("close(slave_fd) failed: %s\n", strerror(errno));
00150             goto _error;
00151         }
00152         setfd(master_fd);
00153     }
00154 }
00155 
00156 void WvPty::kill(int signum)
00157 {
00158     if (_pid != -1)
00159         ::kill(_pid, signum);
00160 }
00161 
00162 void WvPty::monitor_child(bool wait)
00163 {
00164     if (_pid != -1)
00165     {
00166         int status;
00167         if (::waitpid(_pid, &status, wait? 0: WNOHANG) == _pid)
00168         {
00169             _pid = -1;
00170             _exit_status = status;
00171         }
00172     }
00173 }
00174 
00175 bool WvPty::child_exited()
00176 {
00177     monitor_child(false);
00178     return _pid == -1;     
00179 }
00180 
00181 bool WvPty::child_killed()
00182 {
00183     monitor_child(false);
00184     return _pid == -1 && WIFSIGNALED(_exit_status);
00185 }
00186 
00187 int WvPty::finish()
00188 {
00189     monitor_child(true);
00190     return WEXITSTATUS(_exit_status);
00191 }
00192 
00193 int WvPty::exit_status()
00194 {
00195     monitor_child(false);
00196     if (_pid == -1)
00197     {
00198         if (child_killed())
00199             return WTERMSIG(_exit_status);
00200         else
00201             return WEXITSTATUS(_exit_status);
00202     }
00203     else
00204         return 242;
00205 }
00206