/* term - terminal simulator Author: Andy Tanenbaum */ /* This program allows the user to turn a MINIX system into a dumb * terminal to communicate with a remote computer through one of the ttys. * It forks into two processes. The parent sits in a tight loop copying * from stdin to the tty. The child sits in a tight loop copying from * the tty to stdout. * * 2 Sept 88 BDE (Bruce D. Evans): Massive changes to make current settings the * default, allow any file as the "tty", support fancy baud rates and remove * references to and dependencies on modems and keyboards, so (e.g.) * a local login on /dev/tty1 can do an external login on /dev/tty2. * * 3 Sept 88 BDE: Split parent again to main process copies from stdin to a * pipe which is copied to the tty. This stops a blocked write to the * tty from hanging the program. * * 11 Oct 88 BDE: Cleaned up baud rates and parity stripping. * * 09 Oct 90 MAT (Michael A. Temari): Fixed bug where terminal isn't reset * if an error occurs. * * Nov 90 BDE: Don't broadcast kill(0, SIGINT) since two or more of these * in a row will kill the parent shell. * * 19 Oct 89 RW (Ralf Wenk): Adapted to MINIX ST 1.1 + RS232 driver. Split * error into error_n and error. Added resetting of the terminal settings * in error. * * 24 Nov 90 RW: Adapted to MINIX ST 1.5.10.2. Forked processes are now * doing an exec to get a better performance. This idea is stolen from * a terminal program written by Felix Croes. * * 01 May 91 RW: Merged the MINIX ST patches with Andys current version. * Most of the 19 Oct 89 patches are deleted because they are already there. * * 10 Mar 96 KJB: Termios adaption, cleanup, command key interface. * * 27 Nov 96 KJB: Add -c flag that binds commands to keys. * * Example usage: * term : baud, bits/char, parity from /dev/tty1 * term 9600 7 even : 9600 baud, 7 bits/char, even parity * term odd 300 7 : 300 baud, 7 bits/char, odd parity * term /dev/tty2 : use /dev/tty2 rather than /dev/tty1 * : Any argument starting with "/" is * : taken as the communication device. * term 8 57600 /dev/tty2 -atdt4441234 : if an argument begins with * : - , the rest of that arg is * : sent to the modem as a * : dial string */ #include #include #include #include #include #include #include #include #include #include #include #define CHUNK 1024 /* how much to read at once */ char TERM_LINE[] = "/dev/modem";/* default serial port to use */ /* device lock file */ char lockfile[] = "/usr/spool/locks/LK.iii.jjj.kkk"; char *commdev; /* communications device a.k.a. "modem". */ int commfd; /* open file no. for comm device */ struct termios tccomm; /* terminal parameters for commfd */ struct termios tcstdin; /* terminal parameters for stdin */ struct termios tcsavestdin; /* saved terminal parameters for stdin */ /* Special key to get term's attention. */ #define HOTKEY '\035' /* CTRL-] */ struct param_s { char *pattern; unsigned value; enum { BAD, BITS, PARITY, SPEED } type; } params[] = { { "5", CS5, BITS }, { "6", CS6, BITS }, { "7", CS7, BITS }, { "8", CS8, BITS }, { "even", PARENB, PARITY }, { "odd", PARENB|PARODD, PARITY }, { "50", B50, SPEED }, { "75", B75, SPEED }, { "110", B110, SPEED }, { "134", B134, SPEED }, { "200", B200, SPEED }, { "300", B300, SPEED }, { "600", B600, SPEED }, { "1200", B1200, SPEED }, { "1800", B1800, SPEED }, { "2400", B2400, SPEED }, { "4800", B4800, SPEED }, { "9600", B9600, SPEED }, { "19200", B19200, SPEED }, { "38400", B38400, SPEED }, { "57600", B57600, SPEED }, { "115200", B115200, SPEED }, { "", 0, BAD }, /* BAD type to end list */ }; #define NIL ((char *) NULL) /* tell(fd, ..., NIL) */ int main(int argc, char *argv[]); int isdialstr(char *arg); void tell(int fd, ...); void reader(int on); void shell(char *cmd); void lock_device(char *device); void fatal(char *label); void setnum(char *s, int n); void set_uart(int argc, char *argv[], struct termios *tcp); void set_raw(struct termios *tcp); void quit(int code); int main(argc, argv) int argc; char *argv[]; { int i; unsigned char key; int candial; for (i = 1; i < argc; ++i) { if (argv[i][0] == '/') { if (commdev != NULL) { tell(2, "term: too many communication devices\n", NIL); exit(1); } commdev = argv[i]; } } if (commdev == NULL) commdev = TERM_LINE; /* Save tty attributes of the terminal. */ if (tcgetattr(0, &tcsavestdin) < 0) { tell(2, "term: standard input is not a terminal\n", NIL); exit(1); } lock_device(commdev); commfd = open(commdev, O_RDWR); if (commfd < 0) { tell(2, "term: can't open ", commdev, ": ", strerror(errno), "\n", NIL); quit(1); } /* Compute RAW modes of terminal and modem. */ if (tcgetattr(commfd, &tccomm) < 0) { tell(2, "term: ", commdev, " is not a terminal\n", NIL); quit(1); } signal(SIGINT, quit); signal(SIGTERM, quit); tcstdin = tcsavestdin; set_raw(&tcstdin); set_raw(&tccomm); set_uart(argc, argv, &tccomm); tcsetattr(0, TCSANOW, &tcstdin); tcsetattr(commfd, TCSANOW, &tccomm); /* Start a reader process to copy modem output to the screen. */ reader(1); /* Welcome message. */ tell(1, "Connected to ", commdev, ", command key is CTRL-], type ^]? for help\r\n", NIL); /* Dial. */ candial = 0; for (i = 1; i < argc; ++i) { if (!isdialstr(argv[i])) continue; tell(commfd, argv[i] + 1, "\r", NIL); candial = 1; } /* Main loop of the terminal simulator. */ while (read(0, &key, 1) == 1) { if (key == HOTKEY) { /* Command key typed. */ if (read(0, &key, 1) != 1) continue; switch (key) { default: /* Added command? */ for (i = 1; i < argc; ++i) { char *arg = argv[i]; if (arg[0] == '-' && arg[1] == 'c' && arg[2] == key) { reader(0); tcsetattr(0, TCSANOW, &tcsavestdin); shell(arg+3); tcsetattr(0, TCSANOW, &tcstdin); reader(1); break; } } if (i < argc) break; /* Unrecognized command, print list. */ tell(1, "\r\nTerm commands:\r\n", " ? - this help\r\n", candial ? " d - redial\r\n" : "", " s - subshell (e.g. for file transfer)\r\n", " h - hangup (+++ ATH)\r\n", " b - send a break\r\n", " q - exit term\r\n", NIL); for (i = 1; i < argc; ++i) { char *arg = argv[i]; static char cmd[] = " x - "; if (arg[0] == '-' && arg[1] == 'c' && arg[2] != 0) { cmd[1] = arg[2]; tell(1, cmd, arg+3, "\r\n", NIL); } } tell(1, "^] - send a CTRL-]\r\n\n", NIL); break; case 'd': /* Redial by sending the dial commands again. */ for (i = 1; i < argc; ++i) { if (!isdialstr(argv[i])) continue; tell(commfd, argv[i] + 1, "\r", NIL); } break; case 's': /* Subshell. */ reader(0); tcsetattr(0, TCSANOW, &tcsavestdin); shell(NULL); tcsetattr(0, TCSANOW, &tcstdin); reader(1); break; case 'h': /* Hangup by using the +++ escape and ATH command. */ sleep(2); tell(commfd, "+++", NIL); sleep(2); tell(commfd, "ATH\r", NIL); break; case 'b': /* Send a break. */ tcsendbreak(commfd, 0); break; case 'q': /* Exit term. */ quit(0); case HOTKEY: (void) write(commfd, &key, 1); break; } } else { /* Send keyboard input down the serial line. */ if (write(commfd, &key, 1) != 1) break; } } tell(2, "term: nothing to copy from input to ", commdev, "?\r\n", NIL); quit(1); } int isdialstr(char *arg) { /* True iff arg is the start of a dial string, i.e. "-at...". */ return (arg[0] == '-' && (arg[1] == 'a' || arg[1] == 'A') && (arg[2] == 't' || arg[2] == 'T')); } void tell(int fd, ...) { /* Write strings to file descriptor 'fd'. */ va_list ap; char *s; va_start(ap, fd); while ((s = va_arg(ap, char *)) != NIL) write(fd, s, strlen(s)); va_end(ap); } void reader(on) int on; { /* Start or end a process that copies from the modem to the screen. */ static pid_t pid; char buf[CHUNK]; ssize_t n, m, r; if (!on) { /* End the reader process (if any). */ if (pid == 0) return; kill(pid, SIGKILL); (void) waitpid(pid, (int *) NULL, 0); pid = 0; return; } /* Start a reader */ pid = fork(); if (pid < 0) { tell(2, "term: fork() failed: ", strerror(errno), "\r\n", NIL); quit(1); } if (pid == 0) { /* Child: Copy from the modem to the screen. */ while ((n = read(commfd, buf, sizeof(buf))) > 0) { m = 0; while (m < n && (r = write(1, buf + m, n - m)) > 0) m += r; } tell(2, "term: nothing to copy from ", commdev, " to output?\r\n", NIL); kill(getppid(), SIGTERM); _exit(1); } /* One reader on the loose. */ } void shell(char *cmd) { /* Invoke a subshell to allow one to run zmodem for instance. Run sh -c 'cmd' * instead if 'cmd' non-null. */ pid_t pid; char *shell, *sh0; void(*isav) (int); void(*qsav) (int); void(*tsav) (int); if (cmd == NULL) { tell(1, "\nExit the shell to return to term, ", commdev, " is open on file descriptor 9.\n", NIL); } if (cmd != NULL || (shell = getenv("SHELL")) == NULL) shell = "/bin/sh"; if ((sh0 = strrchr(shell, '/')) == NULL) sh0 = shell; else sh0++; /* Start a shell */ pid = fork(); if (pid < 0) { tell(2, "term: fork() failed: ", strerror(errno), "\n", NIL); return; } if (pid == 0) { /* Child: Exec the shell. */ setgid(getgid()); setuid(getuid()); if (commfd != 9) { dup2(commfd, 9); close(commfd); } if (cmd == NULL) { execl(shell, sh0, (char *) NULL); } else { execl(shell, sh0, "-c", cmd, (char *) NULL); } tell(2, "term: can't execute ", shell, ": ", strerror(errno), "\n",NIL); _exit(1); } /* Wait for the shell to exit. */ isav = signal(SIGINT, SIG_IGN); qsav = signal(SIGQUIT, SIG_IGN); tsav = signal(SIGTERM, SIG_IGN); (void) waitpid(pid, (int *) 0, 0); (void) signal(SIGINT, isav); (void) signal(SIGQUIT, qsav); (void) signal(SIGTERM, tsav); tell(1, "\n[back to term]\n", NIL); } void lock_device(device) char *device; { /* Lock a device by creating a lock file using SYSV style locking. */ struct stat stbuf; unsigned int pid; int fd; int n; int u; if (stat(device, &stbuf) < 0) fatal(device); if (!S_ISCHR(stbuf.st_mode)) { tell(2, "term: ", device, " is not a character device\n", NIL); exit(1); } /* Compute the lock file name. */ setnum(lockfile + 23, (stbuf.st_dev >> 8) & 0xFF); /* FS major (why?) */ setnum(lockfile + 27, (stbuf.st_rdev >> 8) & 0xFF); /* device major */ setnum(lockfile + 31, (stbuf.st_rdev >> 0) & 0xFF); /* device minor */ /* Try to make a lock file and put my pid in it. */ u = umask(0); for (;;) { if ((fd = open(lockfile, O_RDONLY)) < 0) { /* No lock file, try to lock it myself. */ if (errno != ENOENT) fatal(device); if ((fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, 0444)) < 0) { if (errno == EEXIST) continue; fatal(lockfile); } pid = getpid(); n = write(fd, &pid, sizeof(pid)); if (n < 0) { n = errno; (void) unlink(lockfile); errno = n; fatal(lockfile); } close(fd); break; } else { /* Already there, but who owns it? */ n = read(fd, &pid, sizeof(pid)); if (n < 0) fatal(device); close(fd); if (n == sizeof(pid) && !(kill(pid, 0) < 0 && errno == ESRCH)) { /* It is locked by a running process. */ tell(2, "term: ", device, " is in use by another program\n", NIL); if (getpgrp() == getpid()) sleep(3); exit(1); } /* Stale lock. */ tell(1, "Removing stale lock ", lockfile, "\n", NIL); if (unlink(lockfile) < 0 && errno != ENOENT) fatal(lockfile); } } /* Lock achieved, but what if two terms encounters a stale lock at the same * time? */ umask(u); } void fatal(char *label) { tell(2, "term: ", label, ": ", strerror(errno), "\n", NIL); exit(1); } void setnum(char *s, int n) { /* Poke 'n' into string 's' backwards as three decimal digits. */ int i; for (i = 0; i < 3; i++) { *--s = '0' + (n % 10); n /= 10; } } void set_uart(argc, argv, tcp) int argc; char *argv[]; struct termios *tcp; { /* Set up the UART parameters. */ int i; char *arg; struct param_s *param; /* Examine all the parameters and check for validity. */ for (i = 1; i < argc; ++i) { arg = argv[i]; if (arg[0] == '/' || arg[0] == '-') continue; /* Check parameter for legality. */ for (param = ¶ms[0]; param->type != BAD && strcmp(arg, param->pattern) != 0; ++param); switch (param->type) { case BAD: tell(2, "Invalid parameter: ", arg, "\n", NIL); quit(1); break; case BITS: tcp->c_cflag &= ~CSIZE; tcp->c_cflag |= param->value; break; case PARITY: tcp->c_cflag &= PARENB | PARODD; tcp->c_cflag |= param->value; break; case SPEED: cfsetispeed(tcp, (speed_t) param->value); cfsetospeed(tcp, (speed_t) param->value); break; } } } void set_raw(tcp) struct termios *tcp; { /* Set termios attributes for RAW mode. */ tcp->c_iflag &= ~(ICRNL|IGNCR|INLCR|IXON|IXOFF); tcp->c_lflag &= ~(ICANON|IEXTEN|ISIG|ECHO|ECHONL); tcp->c_oflag &= ~(OPOST); tcp->c_cc[VMIN] = 1; tcp->c_cc[VTIME] = 0; } void quit(code) int code; { /* Stop the reader process, reset the terminal, and exit. */ reader(0); tcsetattr(0, TCSANOW, &tcsavestdin); (void) unlink(lockfile); exit(code); }