Back to index

lightning-sunbird  0.9+nobinonly
ptystream.c
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is lineterm.
00015  *
00016  * The Initial Developer of the Original Code is
00017  * Ramalingam Saravanan.
00018  * Portions created by the Initial Developer are Copyright (C) 1999
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *
00023  * Alternatively, the contents of this file may be used under the terms of
00024  * either the GNU General Public License Version 2 or later (the "GPL"), or
00025  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00026  * in which case the provisions of the GPL or the LGPL are applicable instead
00027  * of those above. If you wish to allow use of your version of this file only
00028  * under the terms of either the GPL or the LGPL, and not to allow others to
00029  * use your version of this file under the terms of the MPL, indicate your
00030  * decision by deleting the provisions above and replace them with the notice
00031  * and other provisions required by the GPL or the LGPL. If you do not delete
00032  * the provisions above, a recipient may use your version of this file under
00033  * the terms of any one of the MPL, the GPL or the LGPL.
00034  *
00035  * ***** END LICENSE BLOCK ***** */
00036 
00037 /* ptystream.c: pseudo-TTY stream implementation
00038  * CPP options:
00039  *   LINUX:         for Linux2.0/glibc
00040  *   SOLARIS:       for Solaris2.6
00041  *   BSDFAMILY:     for FreeBSD, ...
00042  *   NOERRMSG:      for suppressing all error messages
00043  *   USE_NSPR_BASE: use NSPR to log error messages (defines NOERRMSG as well)
00044  *   DEBUG_LTERM:   for printing some debugging output to STDERR
00045  */
00046 
00047 /* system header files */
00048 
00049 
00050 #ifdef LINUX
00051 #define _BSD_SOURCE  1
00052 #endif
00053 
00054 #include <termios.h>
00055 #include <signal.h>
00056 #include <sys/types.h>
00057 #include <sys/fcntl.h>
00058 
00059 #if defined(LINUX) || defined(BSDFAMILY) || defined(HPUX11)
00060 #include <sys/ioctl.h>
00061 #endif
00062 
00063 #include <unistd.h>
00064 #include <sys/stat.h>
00065 
00066 #include <stdio.h>
00067 #include <stdlib.h>
00068 #include <string.h>
00069 #include <assert.h>
00070 
00071 #ifdef USE_NSPR_BASE
00072 #include "prlog.h"
00073 #define NOERRMSG 1
00074 #endif
00075 
00076 /* public declarations */
00077 #include "ptystream.h"
00078 
00079 /* private declarations */
00080 static int openPTY(struct ptys *ptyp, int noblock);
00081 static int attachToTTY(struct ptys *ptyp, int errfd, int noecho);
00082 static int setTTYAttr(int ttyFD, int noecho);
00083 static void pty_error(const char *errmsg, const char *errmsg2);
00084 
00085 /* parameters */
00086 #define C_CTL_C        '\003'     /* ^C */
00087 #define C_CTL_D   '\004'     /* ^D */
00088 #define C_CTL_H        '\010'     /* ^H */
00089 #define C_CTL_O        '\017'     /* ^O */
00090 #define C_CTL_Q        '\021'     /* ^Q */
00091 #define C_CTL_R        '\022'     /* ^R */
00092 #define C_CTL_S        '\023'     /* ^S */
00093 #define C_CTL_U        '\025'     /* ^U */
00094 #define C_CTL_V        '\026'     /* ^V */
00095 #define C_CTL_W   '\027'     /* ^W */
00096 #define C_CTL_Y        '\031'     /* ^Y */
00097 #define C_CTL_Z        '\032'     /* ^Z */
00098 #define C_CTL_BSL '\034'     /* ^\ */
00099 
00100 /* Disable special character functions */
00101 #ifdef _POSIX_VDISABLE
00102 #define VDISABLE     _POSIX_VDISABLE
00103 #else
00104 #define VDISABLE     255
00105 #endif
00106 
00107 #define       PTYCHAR1    "pqrstuvwxyzPQRSTUVWXYZ"
00108 #define       PTYCHAR2    "0123456789abcdef"
00109 
00110 
00111 /* creates a new pseudo-TTY */
00112 int pty_create(struct ptys *ptyp, char *const argv[],
00113                int rows, int cols, int x_pixels, int y_pixels,
00114                int errfd, int noblock, int noecho, int noexport, int debug)
00115 {
00116   pid_t child_pid;
00117   int errfd2;
00118 
00119   if (!ptyp) {
00120     pty_error("pty_create: NULL value for PTY structure", NULL);
00121     return -1;
00122   }
00123 
00124   /* Set debug flag */
00125   ptyp->debug = debug;
00126 
00127 #ifdef DEBUG_LTERM
00128   if (ptyp->debug)
00129     fprintf(stderr, "00-pty_create: errfd=%d, noblock=%d, noecho=%d, noexport=%d\n",
00130                      errfd, noblock, noecho, noexport);
00131 #endif
00132 
00133   /* Open PTY */
00134   if (openPTY(ptyp, noblock) == -1) return -1;
00135 
00136 #ifndef BSDFAMILY
00137   /* Set default TTY size */
00138   if (pty_resize(ptyp, rows, cols, x_pixels, y_pixels) != 0)
00139     return -1;
00140 #endif
00141 
00142   if (errfd >= -1) {
00143     /* No STDERR pipe */
00144     ptyp->errpipeFD = -1;
00145     errfd2 = errfd;
00146 
00147   } else {
00148     /* Create pipe to handle STDERR output */
00149     int pipeFD[2];
00150 
00151     if (pipe(pipeFD) == -1) {
00152       pty_error("pty_create: STDERR pipe creation failed", NULL);
00153       return -1;
00154     }
00155 
00156     /* Copy pipe file descriptors */
00157     ptyp->errpipeFD = pipeFD[0];
00158     errfd2          = pipeFD[1];
00159   }
00160 
00161   /* Fork a child process (VFORK) */
00162   child_pid = vfork();
00163   if (child_pid < 0) {
00164     pty_error("pty_create: vfork failed", NULL);
00165     return -1;
00166   }
00167 
00168   ptyp->pid = child_pid;
00169 
00170 #ifdef DEBUG_LTERM
00171   if (ptyp->debug)
00172     fprintf(stderr, "00-pty_create: Fork child pid = %d, initially attached to %s\n",
00173                      child_pid, ttyname(0));
00174 #endif
00175 
00176   if (child_pid == 0) {
00177     /* Child process */
00178 
00179     /* Attach child to slave TTY */
00180     if (attachToTTY(ptyp, errfd2, noecho) == -1) return -1;
00181 
00182 #ifdef BSDFAMILY
00183     /* Set default TTY size */
00184     if (pty_resize(NULL, rows, cols, x_pixels, y_pixels) != 0)
00185       return -1;
00186 #endif
00187 
00188     /* Set default signal handling */
00189     signal(SIGINT, SIG_DFL);
00190     signal(SIGQUIT, SIG_DFL);
00191     signal(SIGCHLD, SIG_DFL);
00192 
00193     /* Set ignore signal handling */
00194     signal(SIGTSTP, SIG_IGN);
00195     signal(SIGTTIN, SIG_IGN);
00196     signal(SIGTTOU, SIG_IGN);
00197 
00198     if (argv != NULL) {
00199       /* Execute specified command with arguments */
00200       if (noexport)
00201         execve(argv[0], argv, NULL);
00202       else
00203         execvp(argv[0], argv);
00204 
00205       pty_error("Error in executing command ", argv[0]);
00206       return -1;
00207 
00208     } else {
00209       /* Execute $SHELL or /bin/sh by default */
00210       char *shell = (char *) getenv("SHELL");
00211 
00212       if ((shell == NULL) || (*shell == '\0'))
00213         shell = "/bin/sh";
00214 
00215       if (noexport)
00216         execle(shell, shell, NULL, NULL);
00217       else
00218         execlp(shell, shell, NULL);
00219 
00220       pty_error("pty_create: Error in executing command ", shell);
00221       return -1;
00222     }
00223 
00224   }
00225 
00226   if (errfd < -1) {
00227     /* Close write end of STDERR pipe in parent process */
00228     close(errfd2);
00229   }
00230 
00231   /* Return from parent */
00232   return 0;
00233 }
00234 
00235 
00236 /* closes a pseudo-TTY */
00237 int pty_close(struct ptys *ptyp)
00238 {
00239   if (!ptyp) {
00240     pty_error("pty_close: NULL value for PTY structure", NULL);
00241     return -1;
00242   }
00243 
00244   kill(ptyp->pid, SIGKILL);
00245   ptyp->pid = 0;
00246 
00247   close(ptyp->ptyFD);
00248   ptyp->ptyFD = -1;
00249 
00250   if (ptyp->errpipeFD >= 0) {
00251     close(ptyp->errpipeFD);
00252     ptyp->errpipeFD = -1;
00253   }
00254 
00255   return 0;
00256 }
00257 
00258 
00259 /* resizes a PTY; if ptyp is null, resizes file desciptor 0,
00260  * returning 0 on success and -1 on error.
00261  */
00262 int pty_resize(struct ptys *ptyp, int rows, int cols,
00263                                   int xpix, int ypix)
00264 {
00265   struct winsize wsize;
00266   int fd = ptyp ? ptyp->ptyFD : 0;
00267 
00268   /* Set TTY window size */
00269   wsize.ws_row = (unsigned short) rows;
00270   wsize.ws_col = (unsigned short) cols;
00271   wsize.ws_xpixel = (unsigned short) xpix;
00272   wsize.ws_ypixel = (unsigned short) ypix;
00273 
00274   if (ioctl(fd, TIOCSWINSZ, &wsize ) == -1) {
00275     pty_error("pty_resize: Failed to set TTY window size", NULL);
00276     return -1;
00277   }
00278 
00279   return 0;
00280 }
00281 
00282 
00283 static int openPTY(struct ptys *ptyp, int noblock)
00284 {
00285   char ptyName[PTYNAMELEN+1], ttyName[PTYNAMELEN+1];
00286 
00287   int plen, tlen, ptyFD, letIndex, devIndex;
00288 
00289   (void) strncpy(ptyName, "/dev/pty??", PTYNAMELEN+1);
00290   (void) strncpy(ttyName, "/dev/tty??", PTYNAMELEN+1);
00291 
00292   plen = strlen(ptyName);
00293   tlen = strlen(ttyName);
00294 
00295   assert(ptyp != NULL);
00296   assert(plen <= PTYNAMELEN);
00297   assert(tlen <= PTYNAMELEN);
00298 
00299   ptyFD = -1;
00300   letIndex = 0;
00301   while (PTYCHAR1[letIndex] && (ptyFD == -1)) {
00302     ttyName[tlen - 2] =
00303       ptyName[plen - 2] = PTYCHAR1 [letIndex];
00304 
00305     devIndex = 0;
00306     while (PTYCHAR2[devIndex] && (ptyFD == -1)) {
00307       ttyName [tlen - 1] =
00308         ptyName [plen - 1] = PTYCHAR2 [devIndex];
00309 
00310       if ((ptyFD = open(ptyName, O_RDWR)) >= 0) {
00311         if (access(ttyName, R_OK | W_OK) != 0) {
00312           close(ptyFD);
00313           ptyFD = -1;
00314         }
00315       }
00316       devIndex++;
00317     }
00318 
00319     letIndex++;
00320   }
00321 
00322   if (ptyFD == -1) {
00323     pty_error("openPTY: Unable to open pseudo-tty", NULL);
00324     return -1;
00325   }
00326 
00327   if (noblock) {
00328     /* Set non-blocking mode */
00329     fcntl(ptyFD, F_SETFL, O_NDELAY);
00330   }
00331 
00332   strncpy(ptyp->ptydev, ptyName, PTYNAMELEN+1);
00333   strncpy(ptyp->ttydev, ttyName, PTYNAMELEN+1);
00334 
00335   ptyp->ptyFD = ptyFD;
00336 
00337 #ifdef DEBUG_LTERM
00338   if (ptyp->debug)
00339     fprintf(stderr, "00-openPTY: Opened pty %s on fd %d\n", ptyName, ptyFD);
00340 #endif
00341 
00342   return 0;
00343 }
00344 
00345 
00346 /* attaches new process to slave TTY */
00347 static int attachToTTY(struct ptys *ptyp, int errfd, int noecho)
00348 {
00349   int          ttyFD, fd, fdMax;
00350   pid_t        sid;
00351   gid_t        gid;
00352   unsigned int ttyMode = 0622;
00353 
00354   assert(ptyp != NULL);
00355 
00356   /* Create new session */
00357   sid = setsid();
00358 
00359 #ifdef DEBUG_LTERM
00360   if (ptyp->debug)
00361     fprintf(stderr, "00-attachToTTY: Returned %d from setsid\n", sid);
00362 #endif
00363 
00364   if (sid < 0) {
00365 #ifndef NOERRMSG
00366     perror("attachToTTY");
00367 #endif
00368     return -1;
00369   }
00370 
00371   if ((ttyFD = open(ptyp->ttydev, O_RDWR)) < 0) {
00372     pty_error("attachToTTY: Unable to open slave tty ", ptyp->ttydev );
00373     return -1;
00374   }
00375 
00376 #ifdef DEBUG_LTERM
00377   if (ptyp->debug)
00378     fprintf(stderr,"00-attachToTTY: Attaching process %d to TTY %s on fd %d\n",
00379                     getpid(), ptyp->ttydev, ttyFD);
00380 #endif
00381 
00382   /* Change TTY ownership and permissions*/
00383   gid = getgid();
00384 
00385   fchown(ttyFD, getuid(), gid);
00386   fchmod(ttyFD, ttyMode);
00387 
00388   /* Set TTY attributes (this actually seems to be harmful; so commented out!)
00389    */
00390   /* if (setTTYAttr(ttyFD, noecho) == -1) return -1; */
00391 
00392   /* Redirect to specified descriptor or to PTY */
00393   if (errfd >= 0) {
00394     /* Redirect STDERR to specified file descriptor */
00395     if (dup2(errfd, 2) == -1) {
00396       pty_error("attachToTTY: Failed dup2 for specified stderr", NULL);
00397       return -1;
00398     }
00399 
00400   } else {
00401     /* Redirect STDERR to PTY */
00402     if (dup2(ttyFD, 2) == -1) {
00403       pty_error("attachToTTY: Failed dup2 for default stderr", NULL);
00404       return -1;
00405     }
00406   }
00407 
00408   /* Redirect STDIN and STDOUT to PTY */
00409   if (dup2(ttyFD, 0) == -1) {
00410     pty_error("attachToTTY: Failed dup2 for stdin", NULL);
00411     return -1;
00412   }
00413 
00414   if (dup2(ttyFD, 1) == -1) {
00415     pty_error("attachToTTY: Failed dup2 for stdout", NULL);
00416     return -1;
00417   }
00418 
00419   /* Close all other file descriptors in child process */
00420   fdMax = sysconf(_SC_OPEN_MAX);
00421   for (fd = 3; fd < fdMax; fd++)
00422     close(fd);
00423 
00424 #ifdef BSDFAMILY
00425   ioctl(0, TIOCSCTTY, 0);
00426 #endif
00427 
00428   /* Set process group */
00429   tcsetpgrp(0, sid);
00430 
00431   /* close(open(ptyp->ttydev, O_RDWR, 0)); */
00432 
00433   return 0;
00434 }
00435 
00436 
00437 /* sets slave TTY attributes (NOT USED) */
00438 static int setTTYAttr(int ttyFD, int noecho)
00439 {
00440   struct termios tios;
00441 
00442   /* Get TTY attributes */
00443   if (tcgetattr(ttyFD, &tios ) == -1) {
00444 #ifndef NOERRMSG
00445     perror("setTTYAttr");
00446 #endif
00447     pty_error("setTTYattr: Failed to get TTY attributes", NULL);
00448     return -1;
00449   }
00450   memset(&tios, 0, sizeof(struct termios));
00451 
00452   /* TERMIOS settings for TTY */
00453   tios.c_cc[VINTR]    = C_CTL_C;
00454   tios.c_cc[VQUIT]    = C_CTL_BSL;
00455   tios.c_cc[VERASE]   = C_CTL_H;
00456   tios.c_cc[VKILL]    = C_CTL_U;
00457   tios.c_cc[VEOF]     = C_CTL_D;
00458   tios.c_cc[VEOL]     = VDISABLE;
00459   tios.c_cc[VEOL2]    = VDISABLE;
00460 #ifdef SOLARIS
00461   tios.c_cc[VSWTCH]   = VDISABLE;
00462 #endif
00463   tios.c_cc[VSTART]   = C_CTL_Q;
00464   tios.c_cc[VSTOP]    = C_CTL_S;
00465   tios.c_cc[VSUSP]    = C_CTL_Z;
00466 #ifndef HPUX11
00467   tios.c_cc[VREPRINT] = C_CTL_R;
00468   tios.c_cc[VDISCARD] = C_CTL_O;
00469 #endif /* !HPUX11 */
00470   tios.c_cc[VWERASE]  = C_CTL_W;
00471   tios.c_cc[VLNEXT]   = C_CTL_V;
00472 
00473   tios.c_cc[VMIN]     = 1;    /* Wait for at least 1 char of input */
00474   tios.c_cc[VTIME]    = 0;    /* Wait indefinitely (block)  */
00475 
00476 #ifdef BSDFAMILY
00477   tios.c_iflag = (BRKINT | IGNPAR | ICRNL | IXON
00478                   | IMAXBEL);
00479   tios.c_oflag = (OPOST | ONLCR);
00480 
00481 #else  /* !BSDFAMILY */
00482   /* Input modes */
00483   tios.c_iflag &= ~IUCLC;     /* Disable map of upper case input to lower*/
00484   tios.c_iflag &= ~IGNBRK;    /* Do not ignore break */
00485   tios.c_iflag &= ~BRKINT;    /* Do not signal interrupt on break either */
00486 
00487   /* Output modes */
00488   tios.c_oflag &= ~OPOST;     /* Disable output postprocessing */
00489   tios.c_oflag &= ~ONLCR;     /* Disable mapping of NL to CR-NL on output */
00490   tios.c_oflag &= ~OLCUC;     /* Disable map of lower case output to upper */
00491                               /* No output delays */
00492   tios.c_oflag &= ~(NLDLY|CRDLY|TABDLY|BSDLY|VTDLY|FFDLY);
00493 
00494 #endif  /* !BSDFAMILY */
00495 
00496   /* control modes */
00497   tios.c_cflag |= (CS8 | CREAD);
00498 
00499   /* line discipline modes */
00500   if (noecho)
00501     tios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHOKE | ECHONL | ECHOPRT |
00502                       ECHOCTL); /* Disable echo */
00503   else
00504     tios.c_lflag |=  (ECHO | ECHOE | ECHOK | ECHOKE | ECHONL | ECHOPRT |
00505                       ECHOCTL); /* Enable echo */
00506 
00507   tios.c_lflag |= ISIG;       /* Enable signals */
00508   tios.c_lflag |= ICANON;     /* Enable erase/kill and eof processing */
00509 
00510   /* NOTE: tcsh does not echo to be turned off if TERM=xterm;
00511            setting TERM=dumb allows echo to be turned off,
00512            but command completion is turned off as well */
00513 
00514   /* Set TTY attributes */
00515   cfsetospeed (&tios, B38400);
00516   cfsetispeed (&tios, B38400);
00517 
00518   if (tcsetattr(ttyFD, TCSADRAIN, &tios ) == -1) {
00519 #ifndef NOERRMSG
00520     perror("setTTYAttr");
00521 #endif
00522     pty_error("setTTYattr: Failed to set TTY attributes", NULL);
00523     return -1;
00524   }
00525 
00526   return 0;
00527 }
00528 
00529 
00530 /* displays an error message, optionally concatenated with another */
00531 static void pty_error(const char *errmsg, const char *errmsg2) {
00532 
00533 #ifndef NOERRMSG
00534   if (errmsg != NULL) {
00535     if (errmsg2 != NULL) {
00536       fprintf(stderr, "%s%s\n", errmsg, errmsg2);
00537     } else {
00538       fprintf(stderr, "%s\n", errmsg);
00539     }
00540   }
00541 #else
00542 #ifdef USE_NSPR_BASE
00543   if (errmsg != NULL) {
00544     if (errmsg2 != NULL) {
00545       PR_LogPrint("%s%s\n", errmsg, errmsg2);
00546     } else {
00547       PR_LogPrint("%s\n", errmsg);
00548     }
00549   }
00550 #endif
00551 #endif
00552 
00553 }