term.c

Go to the documentation of this file.
00001 /* term - terminal simulator            Author: Andy Tanenbaum */
00002 
00003 /* This program allows the user to turn a MINIX system into a dumb
00004  * terminal to communicate with a remote computer through one of the ttys.
00005  * It forks into two processes.  The parent sits in a tight loop copying
00006  * from stdin to the tty.  The child sits in a tight loop copying from
00007  * the tty to stdout.
00008  *
00009  * 2 Sept 88 BDE (Bruce D. Evans): Massive changes to make current settings the
00010  * default, allow any file as the "tty", support fancy baud rates and remove
00011  * references to and dependencies on modems and keyboards, so (e.g.)
00012  * a local login on /dev/tty1 can do an external login on /dev/tty2.
00013  *
00014  * 3 Sept 88 BDE: Split parent again to main process copies from stdin to a
00015  * pipe which is copied to the tty.  This stops a blocked write to the
00016  * tty from hanging the program.
00017  *
00018  * 11 Oct 88 BDE: Cleaned up baud rates and parity stripping.
00019  *
00020  * 09 Oct 90 MAT (Michael A. Temari): Fixed bug where terminal isn't reset
00021  * if an error occurs.
00022  *
00023  * Nov 90 BDE: Don't broadcast kill(0, SIGINT) since two or more of these
00024  * in a row will kill the parent shell.
00025  *
00026  * 19 Oct 89 RW (Ralf Wenk): Adapted to MINIX ST 1.1 + RS232 driver. Split
00027  * error into error_n and error. Added resetting of the terminal settings
00028  * in error.
00029  *
00030  * 24 Nov 90 RW: Adapted to MINIX ST 1.5.10.2. Forked processes are now
00031  * doing an exec to get a better performance. This idea is stolen from
00032  * a terminal program written by Felix Croes.
00033  *
00034  * 01 May 91 RW: Merged the MINIX ST patches with Andys current version.
00035  * Most of the 19 Oct 89 patches are deleted because they are already there.
00036  *
00037  * 10 Mar 96 KJB: Termios adaption, cleanup, command key interface.
00038  *
00039  * 27 Nov 96 KJB: Add -c flag that binds commands to keys.
00040  *
00041  * Example usage:
00042  *      term                    : baud, bits/char, parity from /dev/tty1
00043  *      term 9600 7 even        : 9600 baud, 7 bits/char, even parity
00044  *      term odd 300 7          :  300 baud, 7 bits/char, odd parity
00045  *      term /dev/tty2          : use /dev/tty2 rather than /dev/tty1
00046  *                              : Any argument starting with "/" is
00047  *                              : taken as the communication device.
00048  *      term 8 57600 /dev/tty2 -atdt4441234     : if an argument begins with
00049  *                                              : - , the rest of that arg is
00050  *                                              : sent to the modem as a
00051  *                                              : dial string
00052  */
00053 
00054 #include <sys/types.h>
00055 #include <fcntl.h>
00056 #include <termios.h>
00057 #include <signal.h>
00058 #include <stdlib.h>
00059 #include <string.h>
00060 #include <unistd.h>
00061 #include <stdarg.h>
00062 #include <errno.h>
00063 #include <sys/wait.h>
00064 #include <sys/stat.h>
00065 
00066 #define CHUNK 1024              /* how much to read at once */
00067 
00068 char TERM_LINE[] = "/dev/modem";/* default serial port to use */
00069 
00070                                 /* device lock file */
00071 char lockfile[] = "/usr/spool/locks/LK.iii.jjj.kkk";
00072 
00073 char *commdev;                  /* communications device a.k.a. "modem". */
00074 int commfd;                     /* open file no. for comm device */
00075 struct termios tccomm;          /* terminal parameters for commfd */
00076 struct termios tcstdin;         /* terminal parameters for stdin */
00077 struct termios tcsavestdin;     /* saved terminal parameters for stdin */
00078 
00079 /* Special key to get term's attention. */
00080 #define HOTKEY  '\035'          /* CTRL-] */
00081 
00082 struct param_s {
00083   char *pattern;
00084   unsigned value;
00085   enum { BAD, BITS, PARITY, SPEED } type;
00086 } params[] = {
00087   { "5",        CS5,            BITS    },
00088   { "6",        CS6,            BITS    },
00089   { "7",        CS7,            BITS    },
00090   { "8",        CS8,            BITS    },
00091 
00092   { "even",     PARENB,         PARITY  },
00093   { "odd",      PARENB|PARODD,  PARITY  },
00094 
00095   { "50",       B50,            SPEED   },
00096   { "75",       B75,            SPEED   },
00097   { "110",      B110,           SPEED   },
00098   { "134",      B134,           SPEED   },
00099   { "200",      B200,           SPEED   },
00100   { "300",      B300,           SPEED   },
00101   { "600",      B600,           SPEED   },
00102   { "1200",     B1200,          SPEED   },
00103   { "1800",     B1800,          SPEED   },
00104   { "2400",     B2400,          SPEED   },
00105   { "4800",     B4800,          SPEED   },
00106   { "9600",     B9600,          SPEED   },
00107   { "19200",    B19200,         SPEED   },
00108   { "38400",    B38400,         SPEED   },
00109   { "57600",    B57600,         SPEED   },
00110   { "115200",   B115200,        SPEED   },
00111   { "",         0,              BAD     },      /* BAD type to end list */
00112 };
00113 
00114 #define NIL ((char *) NULL)             /* tell(fd, ..., NIL) */
00115 
00116 _PROTOTYPE(int main, (int argc, char *argv[]));
00117 _PROTOTYPE(int isdialstr, (char *arg));
00118 _PROTOTYPE(void tell, (int fd, ...));
00119 _PROTOTYPE(void reader, (int on));
00120 _PROTOTYPE(void shell, (char *cmd));
00121 _PROTOTYPE(void lock_device, (char *device));
00122 _PROTOTYPE(void fatal, (char *label));
00123 _PROTOTYPE(void setnum, (char *s, int n));
00124 _PROTOTYPE(void set_uart, (int argc, char *argv[], struct termios *tcp));
00125 _PROTOTYPE(void set_raw, (struct termios *tcp));
00126 _PROTOTYPE(void quit, (int code));
00127 
00128 int main(argc, argv)
00129 int argc;
00130 char *argv[];
00131 {
00132   int i;
00133   unsigned char key;
00134   int candial;
00135 
00136   for (i = 1; i < argc; ++i) {
00137         if (argv[i][0] == '/') {
00138                 if (commdev != NULL) {
00139                         tell(2, "term: too many communication devices\n", NIL);
00140                         exit(1);
00141                 }
00142                 commdev = argv[i];
00143         }
00144   }
00145   if (commdev == NULL) commdev = TERM_LINE;
00146 
00147   /* Save tty attributes of the terminal. */
00148   if (tcgetattr(0, &tcsavestdin) < 0) {
00149         tell(2, "term: standard input is not a terminal\n", NIL);
00150         exit(1);
00151   }
00152 
00153   lock_device(commdev);
00154 
00155   commfd = open(commdev, O_RDWR);
00156   if (commfd < 0) {
00157         tell(2, "term: can't open ", commdev, ": ", strerror(errno), "\n", NIL);
00158         quit(1);
00159   }
00160 
00161   /* Compute RAW modes of terminal and modem. */
00162   if (tcgetattr(commfd, &tccomm) < 0) {
00163         tell(2, "term: ", commdev, " is not a terminal\n", NIL);
00164         quit(1);
00165   }
00166   signal(SIGINT, quit);
00167   signal(SIGTERM, quit);
00168   tcstdin = tcsavestdin;
00169   set_raw(&tcstdin);
00170   set_raw(&tccomm);
00171   set_uart(argc, argv, &tccomm);
00172   tcsetattr(0, TCSANOW, &tcstdin);
00173   tcsetattr(commfd, TCSANOW, &tccomm);
00174 
00175   /* Start a reader process to copy modem output to the screen. */
00176   reader(1);
00177 
00178   /* Welcome message. */
00179   tell(1, "Connected to ", commdev,
00180                         ", command key is CTRL-], type ^]? for help\r\n", NIL);
00181 
00182   /* Dial. */
00183   candial = 0;
00184   for (i = 1; i < argc; ++i) {
00185         if (!isdialstr(argv[i])) continue;
00186         tell(commfd, argv[i] + 1, "\r", NIL);
00187         candial = 1;
00188   }
00189 
00190   /* Main loop of the terminal simulator. */
00191   while (read(0, &key, 1) == 1) {
00192         if (key == HOTKEY) {
00193                 /* Command key typed. */
00194                 if (read(0, &key, 1) != 1) continue;
00195 
00196                 switch (key) {
00197                 default:
00198                         /* Added command? */
00199                         for (i = 1; i < argc; ++i) {
00200                                 char *arg = argv[i];
00201 
00202                                 if (arg[0] == '-' && arg[1] == 'c'
00203                                                         && arg[2] == key) {
00204                                         reader(0);
00205                                         tcsetattr(0, TCSANOW, &tcsavestdin);
00206                                         shell(arg+3);
00207                                         tcsetattr(0, TCSANOW, &tcstdin);
00208                                         reader(1);
00209                                         break;
00210                                 }
00211                         }
00212                         if (i < argc) break;
00213 
00214                         /* Unrecognized command, print list. */
00215                         tell(1, "\r\nTerm commands:\r\n",
00216                                 " ? - this help\r\n",
00217                                 candial ? " d - redial\r\n" : "",
00218                                 " s - subshell (e.g. for file transfer)\r\n",
00219                                 " h - hangup (+++ ATH)\r\n",
00220                                 " b - send a break\r\n",
00221                                 " q - exit term\r\n",
00222                                 NIL);
00223                         for (i = 1; i < argc; ++i) {
00224                                 char *arg = argv[i];
00225                                 static char cmd[] = " x - ";
00226 
00227                                 if (arg[0] == '-' && arg[1] == 'c'
00228                                                         && arg[2] != 0) {
00229                                         cmd[1] = arg[2];
00230                                         tell(1, cmd, arg+3, "\r\n", NIL);
00231                                 }
00232                         }
00233                         tell(1, "^] - send a CTRL-]\r\n\n",
00234                                 NIL);
00235                         break;
00236                 case 'd':
00237                         /* Redial by sending the dial commands again. */
00238                         for (i = 1; i < argc; ++i) {
00239                                 if (!isdialstr(argv[i])) continue;
00240                                 tell(commfd, argv[i] + 1, "\r", NIL);
00241                         }
00242                         break;
00243                 case 's':
00244                         /* Subshell. */
00245                         reader(0);
00246                         tcsetattr(0, TCSANOW, &tcsavestdin);
00247                         shell(NULL);
00248                         tcsetattr(0, TCSANOW, &tcstdin);
00249                         reader(1);
00250                         break;
00251                 case 'h':
00252                         /* Hangup by using the +++ escape and ATH command. */
00253                         sleep(2);
00254                         tell(commfd, "+++", NIL);
00255                         sleep(2);
00256                         tell(commfd, "ATH\r", NIL);
00257                         break;
00258                 case 'b':
00259                         /* Send a break. */
00260                         tcsendbreak(commfd, 0);
00261                         break;
00262                 case 'q':
00263                         /* Exit term. */
00264                         quit(0);
00265                 case HOTKEY:
00266                         (void) write(commfd, &key, 1);
00267                         break;
00268                 }
00269         } else {
00270                 /* Send keyboard input down the serial line. */
00271                 if (write(commfd, &key, 1) != 1) break;
00272         }
00273   }
00274   tell(2, "term: nothing to copy from input to ", commdev, "?\r\n", NIL);
00275   quit(1);
00276 }
00277 
00278 
00279 int isdialstr(char *arg)
00280 {
00281 /* True iff arg is the start of a dial string, i.e. "-at...". */
00282 
00283   return (arg[0] == '-'
00284         && (arg[1] == 'a' || arg[1] == 'A')
00285         && (arg[2] == 't' || arg[2] == 'T'));
00286 }
00287 
00288 
00289 void tell(int fd, ...)
00290 {
00291 /* Write strings to file descriptor 'fd'. */
00292   va_list ap;
00293   char *s;
00294 
00295   va_start(ap, fd);
00296   while ((s = va_arg(ap, char *)) != NIL) write(fd, s, strlen(s));
00297   va_end(ap);
00298 }
00299 
00300 
00301 void reader(on)
00302 int on;
00303 {
00304 /* Start or end a process that copies from the modem to the screen. */
00305 
00306   static pid_t pid;
00307   char buf[CHUNK];
00308   ssize_t n, m, r;
00309 
00310   if (!on) {
00311         /* End the reader process (if any). */
00312         if (pid == 0) return;
00313         kill(pid, SIGKILL);
00314         (void) waitpid(pid, (int *) NULL, 0);
00315         pid = 0;
00316         return;
00317   }
00318 
00319   /* Start a reader */
00320   pid = fork();
00321   if (pid < 0) {
00322         tell(2, "term: fork() failed: ", strerror(errno), "\r\n", NIL);
00323         quit(1);
00324   }
00325   if (pid == 0) {
00326         /* Child: Copy from the modem to the screen. */
00327 
00328         while ((n = read(commfd, buf, sizeof(buf))) > 0) {
00329                 m = 0;
00330                 while (m < n && (r = write(1, buf + m, n - m)) > 0) m += r;
00331         }
00332         tell(2, "term: nothing to copy from ", commdev, " to output?\r\n", NIL);
00333         kill(getppid(), SIGTERM);
00334         _exit(1);
00335   }
00336   /* One reader on the loose. */
00337 }
00338 
00339 
00340 void shell(char *cmd)
00341 {
00342 /* Invoke a subshell to allow one to run zmodem for instance.  Run sh -c 'cmd'
00343  * instead if 'cmd' non-null.
00344  */
00345 
00346   pid_t pid;
00347   char *shell, *sh0;
00348   _PROTOTYPE(void (*isav), (int));
00349   _PROTOTYPE(void (*qsav), (int));
00350   _PROTOTYPE(void (*tsav), (int));
00351 
00352   if (cmd == NULL) {
00353         tell(1, "\nExit the shell to return to term, ",
00354                 commdev, " is open on file descriptor 9.\n", NIL);
00355   }
00356 
00357   if (cmd != NULL || (shell = getenv("SHELL")) == NULL) shell = "/bin/sh";
00358   if ((sh0 = strrchr(shell, '/')) == NULL) sh0 = shell; else sh0++;
00359 
00360   /* Start a shell */
00361   pid = fork();
00362   if (pid < 0) {
00363         tell(2, "term: fork() failed: ", strerror(errno), "\n", NIL);
00364         return;
00365   }
00366   if (pid == 0) {
00367         /* Child: Exec the shell. */
00368         setgid(getgid());
00369         setuid(getuid());
00370 
00371         if (commfd != 9) { dup2(commfd, 9); close(commfd); }
00372 
00373         if (cmd == NULL) {
00374                 execl(shell, sh0, (char *) NULL);
00375         } else {
00376                 execl(shell, sh0, "-c", cmd, (char *) NULL);
00377         }
00378         tell(2, "term: can't execute ", shell, ": ", strerror(errno), "\n",NIL);
00379         _exit(1);
00380   }
00381   /* Wait for the shell to exit. */
00382   isav = signal(SIGINT, SIG_IGN);
00383   qsav = signal(SIGQUIT, SIG_IGN);
00384   tsav = signal(SIGTERM, SIG_IGN);
00385   (void) waitpid(pid, (int *) 0, 0);
00386   (void) signal(SIGINT, isav);
00387   (void) signal(SIGQUIT, qsav);
00388   (void) signal(SIGTERM, tsav);
00389   tell(1, "\n[back to term]\n", NIL);
00390 }
00391 
00392 
00393 void lock_device(device)
00394 char *device;
00395 {
00396 /* Lock a device by creating a lock file using SYSV style locking. */
00397 
00398   struct stat stbuf;
00399   unsigned int pid;
00400   int fd;
00401   int n;
00402   int u;
00403 
00404   if (stat(device, &stbuf) < 0) fatal(device);
00405 
00406   if (!S_ISCHR(stbuf.st_mode)) {
00407         tell(2, "term: ", device, " is not a character device\n", NIL);
00408         exit(1);
00409   }
00410 
00411   /* Compute the lock file name. */
00412   setnum(lockfile + 23, (stbuf.st_dev >> 8) & 0xFF);    /* FS major (why?) */
00413   setnum(lockfile + 27, (stbuf.st_rdev >> 8) & 0xFF);   /* device major */
00414   setnum(lockfile + 31, (stbuf.st_rdev >> 0) & 0xFF);   /* device minor */
00415 
00416   /* Try to make a lock file and put my pid in it. */
00417   u = umask(0);
00418   for (;;) {
00419         if ((fd = open(lockfile, O_RDONLY)) < 0) {
00420                 /* No lock file, try to lock it myself. */
00421                 if (errno != ENOENT) fatal(device);
00422                 if ((fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, 0444)) < 0) {
00423                         if (errno == EEXIST) continue;
00424                         fatal(lockfile);
00425                 }
00426                 pid = getpid();
00427                 n = write(fd, &pid, sizeof(pid));
00428                 if (n < 0) {
00429                         n = errno;
00430                         (void) unlink(lockfile);
00431                         errno = n;
00432                         fatal(lockfile);
00433                 }
00434                 close(fd);
00435                 break;
00436         } else {
00437                 /* Already there, but who owns it? */
00438                 n = read(fd, &pid, sizeof(pid));
00439                 if (n < 0) fatal(device);
00440                 close(fd);
00441                 if (n == sizeof(pid) && !(kill(pid, 0) < 0 && errno == ESRCH)) {
00442                         /* It is locked by a running process. */
00443                         tell(2, "term: ", device,
00444                                 " is in use by another program\n", NIL);
00445                         if (getpgrp() == getpid()) sleep(3);
00446                         exit(1);
00447                 }
00448                 /* Stale lock. */
00449                 tell(1, "Removing stale lock ", lockfile, "\n", NIL);
00450                 if (unlink(lockfile) < 0 && errno != ENOENT) fatal(lockfile);
00451         }
00452   }
00453   /* Lock achieved, but what if two terms encounters a stale lock at the same
00454    * time?
00455    */
00456   umask(u);
00457 }
00458 
00459 
00460 void fatal(char *label)
00461 {
00462   tell(2, "term: ", label, ": ", strerror(errno), "\n", NIL);
00463   exit(1);
00464 }
00465 
00466 
00467 void setnum(char *s, int n)
00468 {
00469 /* Poke 'n' into string 's' backwards as three decimal digits. */
00470   int i;
00471 
00472   for (i = 0; i < 3; i++) { *--s = '0' + (n % 10); n /= 10; }
00473 }
00474 
00475 
00476 void set_uart(argc, argv, tcp)
00477 int argc;
00478 char *argv[];
00479 struct termios *tcp;
00480 {
00481 /* Set up the UART parameters. */
00482 
00483   int i;
00484   char *arg;
00485   struct param_s *param;
00486 
00487   /* Examine all the parameters and check for validity. */
00488   for (i = 1; i < argc; ++i) {
00489         arg = argv[i];
00490         if (arg[0] == '/' || arg[0] == '-') continue;
00491 
00492         /* Check parameter for legality. */
00493         for (param = &params[0];
00494              param->type != BAD && strcmp(arg, param->pattern) != 0;
00495              ++param);
00496         switch (param->type) {
00497             case BAD:
00498                 tell(2, "Invalid parameter: ", arg, "\n", NIL);
00499                 quit(1);
00500                 break;
00501             case BITS:
00502                 tcp->c_cflag &= ~CSIZE;
00503                 tcp->c_cflag |= param->value;
00504                 break;
00505             case PARITY:
00506                 tcp->c_cflag &= PARENB | PARODD;
00507                 tcp->c_cflag |= param->value;
00508                 break;
00509             case SPEED:
00510                 cfsetispeed(tcp, (speed_t) param->value);
00511                 cfsetospeed(tcp, (speed_t) param->value);
00512                 break;
00513         }
00514   }
00515 }
00516 
00517 
00518 void set_raw(tcp)
00519 struct termios *tcp;
00520 {
00521   /* Set termios attributes for RAW mode. */
00522 
00523   tcp->c_iflag &= ~(ICRNL|IGNCR|INLCR|IXON|IXOFF);
00524   tcp->c_lflag &= ~(ICANON|IEXTEN|ISIG|ECHO|ECHONL);
00525   tcp->c_oflag &= ~(OPOST);
00526   tcp->c_cc[VMIN] = 1;
00527   tcp->c_cc[VTIME] = 0;
00528 }
00529 
00530 
00531 void quit(code)
00532 int code;
00533 {
00534 /* Stop the reader process, reset the terminal, and exit. */
00535   reader(0);
00536   tcsetattr(0, TCSANOW, &tcsavestdin);
00537   (void) unlink(lockfile);
00538   exit(code);
00539 }

Generated on Fri Apr 14 22:57:12 2006 for minix by  doxygen 1.4.6