1 /* term - terminal simulator Author: Andy Tanenbaum */ 2 3 /* This program allows the user to turn a MINIX system into a dumb 4 * terminal to communicate with a remote computer through one of the ttys. 5 * It forks into two processes. The parent sits in a tight loop copying 6 * from stdin to the tty. The child sits in a tight loop copying from 7 * the tty to stdout. 8 * 9 * 2 Sept 88 BDE (Bruce D. Evans): Massive changes to make current settings the 10 * default, allow any file as the "tty", support fancy baud rates and remove 11 * references to and dependencies on modems and keyboards, so (e.g.) 12 * a local login on /dev/tty1 can do an external login on /dev/tty2. 13 * 14 * 3 Sept 88 BDE: Split parent again to main process copies from stdin to a 15 * pipe which is copied to the tty. This stops a blocked write to the 16 * tty from hanging the program. 17 * 18 * 11 Oct 88 BDE: Cleaned up baud rates and parity stripping. 19 * 20 * 09 Oct 90 MAT (Michael A. Temari): Fixed bug where terminal isn't reset 21 * if an error occurs. 22 * 23 * Nov 90 BDE: Don't broadcast kill(0, SIGINT) since two or more of these 24 * in a row will kill the parent shell. 25 * 26 * 19 Oct 89 RW (Ralf Wenk): Adapted to MINIX ST 1.1 + RS232 driver. Split 27 * error into error_n and error. Added resetting of the terminal settings 28 * in error. 29 * 30 * 24 Nov 90 RW: Adapted to MINIX ST 1.5.10.2. Forked processes are now 31 * doing an exec to get a better performance. This idea is stolen from 32 * a terminal program written by Felix Croes. 33 * 34 * 01 May 91 RW: Merged the MINIX ST patches with Andys current version. 35 * Most of the 19 Oct 89 patches are deleted because they are already there. 36 * 37 * 10 Mar 96 KJB: Termios adaption, cleanup, command key interface. 38 * 39 * 27 Nov 96 KJB: Add -c flag that binds commands to keys. 40 * 41 * Example usage: 42 * term : baud, bits/char, parity from /dev/tty1 43 * term 9600 7 even : 9600 baud, 7 bits/char, even parity 44 * term odd 300 7 : 300 baud, 7 bits/char, odd parity 45 * term /dev/tty2 : use /dev/tty2 rather than /dev/tty1 46 * : Any argument starting with "/" is 47 * : taken as the communication device. 48 * term 8 57600 /dev/tty2 -atdt4441234 : if an argument begins with 49 * : - , the rest of that arg is 50 * : sent to the modem as a 51 * : dial string 52 */ 53 54 #include <sys/types.h> 55 #include <fcntl.h> 56 #include <termios.h> 57 #include <signal.h> 58 #include <stdlib.h> 59 #include <string.h> 60 #include <unistd.h> 61 #include <stdarg.h> 62 #include <errno.h> 63 #include <sys/wait.h> 64 #include <sys/stat.h> 65 66 #define CHUNK 1024 /* how much to read at once */ 67 68 char TERM_LINE[] = "/dev/modem";/* default serial port to use */ 69 70 /* device lock file */ 71 char lockfile[] = "/usr/spool/locks/LK.iii.jjj.kkk"; 72 73 char *commdev; /* communications device a.k.a. "modem". */ 74 int commfd; /* open file no. for comm device */ 75 struct termios tccomm; /* terminal parameters for commfd */ 76 struct termios tcstdin; /* terminal parameters for stdin */ 77 struct termios tcsavestdin; /* saved terminal parameters for stdin */ 78 79 /* Special key to get term's attention. */ 80 #define HOTKEY '\035' /* CTRL-] */ 81 82 struct param_s { 83 char *pattern; 84 unsigned value; 85 enum { BAD, BITS, PARITY, SPEED } type; 86 } params[] = { 87 { "5", CS5, BITS }, 88 { "6", CS6, BITS }, 89 { "7", CS7, BITS }, 90 { "8", CS8, BITS }, 91 92 { "even", PARENB, PARITY }, 93 { "odd", PARENB|PARODD, PARITY }, 94 95 { "50", B50, SPEED }, 96 { "75", B75, SPEED }, 97 { "110", B110, SPEED }, 98 { "134", B134, SPEED }, 99 { "200", B200, SPEED }, 100 { "300", B300, SPEED }, 101 { "600", B600, SPEED }, 102 { "1200", B1200, SPEED }, 103 { "1800", B1800, SPEED }, 104 { "2400", B2400, SPEED }, 105 { "4800", B4800, SPEED }, 106 { "9600", B9600, SPEED }, 107 { "19200", B19200, SPEED }, 108 { "38400", B38400, SPEED }, 109 { "57600", B57600, SPEED }, 110 { "115200", B115200, SPEED }, 111 { "", 0, BAD }, /* BAD type to end list */ 112 }; 113 114 #define NIL ((char *) NULL) /* tell(fd, ..., NIL) */ 115 116 int main(int argc, char *argv[]); 117 int isdialstr(char *arg); 118 void tell(int fd, ...); 119 void reader(int on); 120 void shell(char *cmd); 121 void lock_device(char *device); 122 void fatal(char *label); 123 void setnum(char *s, int n); 124 void set_uart(int argc, char *argv[], struct termios *tcp); 125 void set_raw(struct termios *tcp); 126 void quit(int code); 127 128 int main(argc, argv) 129 int argc; 130 char *argv[]; 131 { 132 int i; 133 unsigned char key; 134 int candial; 135 136 for (i = 1; i < argc; ++i) { 137 if (argv[i][0] == '/') { 138 if (commdev != NULL) { 139 tell(2, "term: too many communication devices\n", NIL); 140 exit(1); 141 } 142 commdev = argv[i]; 143 } 144 } 145 if (commdev == NULL) commdev = TERM_LINE; 146 147 /* Save tty attributes of the terminal. */ 148 if (tcgetattr(0, &tcsavestdin) < 0) { 149 tell(2, "term: standard input is not a terminal\n", NIL); 150 exit(1); 151 } 152 153 lock_device(commdev); 154 155 commfd = open(commdev, O_RDWR); 156 if (commfd < 0) { 157 tell(2, "term: can't open ", commdev, ": ", strerror(errno), "\n", NIL); 158 quit(1); 159 } 160 161 /* Compute RAW modes of terminal and modem. */ 162 if (tcgetattr(commfd, &tccomm) < 0) { 163 tell(2, "term: ", commdev, " is not a terminal\n", NIL); 164 quit(1); 165 } 166 signal(SIGINT, quit); 167 signal(SIGTERM, quit); 168 tcstdin = tcsavestdin; 169 set_raw(&tcstdin); 170 set_raw(&tccomm); 171 set_uart(argc, argv, &tccomm); 172 tcsetattr(0, TCSANOW, &tcstdin); 173 tcsetattr(commfd, TCSANOW, &tccomm); 174 175 /* Start a reader process to copy modem output to the screen. */ 176 reader(1); 177 178 /* Welcome message. */ 179 tell(1, "Connected to ", commdev, 180 ", command key is CTRL-], type ^]? for help\r\n", NIL); 181 182 /* Dial. */ 183 candial = 0; 184 for (i = 1; i < argc; ++i) { 185 if (!isdialstr(argv[i])) continue; 186 tell(commfd, argv[i] + 1, "\r", NIL); 187 candial = 1; 188 } 189 190 /* Main loop of the terminal simulator. */ 191 while (read(0, &key, 1) == 1) { 192 if (key == HOTKEY) { 193 /* Command key typed. */ 194 if (read(0, &key, 1) != 1) continue; 195 196 switch (key) { 197 default: 198 /* Added command? */ 199 for (i = 1; i < argc; ++i) { 200 char *arg = argv[i]; 201 202 if (arg[0] == '-' && arg[1] == 'c' 203 && arg[2] == key) { 204 reader(0); 205 tcsetattr(0, TCSANOW, &tcsavestdin); 206 shell(arg+3); 207 tcsetattr(0, TCSANOW, &tcstdin); 208 reader(1); 209 break; 210 } 211 } 212 if (i < argc) break; 213 214 /* Unrecognized command, print list. */ 215 tell(1, "\r\nTerm commands:\r\n", 216 " ? - this help\r\n", 217 candial ? " d - redial\r\n" : "", 218 " s - subshell (e.g. for file transfer)\r\n", 219 " h - hangup (+++ ATH)\r\n", 220 " b - send a break\r\n", 221 " q - exit term\r\n", 222 NIL); 223 for (i = 1; i < argc; ++i) { 224 char *arg = argv[i]; 225 static char cmd[] = " x - "; 226 227 if (arg[0] == '-' && arg[1] == 'c' 228 && arg[2] != 0) { 229 cmd[1] = arg[2]; 230 tell(1, cmd, arg+3, "\r\n", NIL); 231 } 232 } 233 tell(1, "^] - send a CTRL-]\r\n\n", 234 NIL); 235 break; 236 case 'd': 237 /* Redial by sending the dial commands again. */ 238 for (i = 1; i < argc; ++i) { 239 if (!isdialstr(argv[i])) continue; 240 tell(commfd, argv[i] + 1, "\r", NIL); 241 } 242 break; 243 case 's': 244 /* Subshell. */ 245 reader(0); 246 tcsetattr(0, TCSANOW, &tcsavestdin); 247 shell(NULL); 248 tcsetattr(0, TCSANOW, &tcstdin); 249 reader(1); 250 break; 251 case 'h': 252 /* Hangup by using the +++ escape and ATH command. */ 253 sleep(2); 254 tell(commfd, "+++", NIL); 255 sleep(2); 256 tell(commfd, "ATH\r", NIL); 257 break; 258 case 'b': 259 /* Send a break. */ 260 tcsendbreak(commfd, 0); 261 break; 262 case 'q': 263 /* Exit term. */ 264 quit(0); 265 case HOTKEY: 266 (void) write(commfd, &key, 1); 267 break; 268 } 269 } else { 270 /* Send keyboard input down the serial line. */ 271 if (write(commfd, &key, 1) != 1) break; 272 } 273 } 274 tell(2, "term: nothing to copy from input to ", commdev, "?\r\n", NIL); 275 quit(1); 276 } 277 278 279 int isdialstr(char *arg) 280 { 281 /* True iff arg is the start of a dial string, i.e. "-at...". */ 282 283 return (arg[0] == '-' 284 && (arg[1] == 'a' || arg[1] == 'A') 285 && (arg[2] == 't' || arg[2] == 'T')); 286 } 287 288 289 void tell(int fd, ...) 290 { 291 /* Write strings to file descriptor 'fd'. */ 292 va_list ap; 293 char *s; 294 295 va_start(ap, fd); 296 while ((s = va_arg(ap, char *)) != NIL) write(fd, s, strlen(s)); 297 va_end(ap); 298 } 299 300 301 void reader(on) 302 int on; 303 { 304 /* Start or end a process that copies from the modem to the screen. */ 305 306 static pid_t pid; 307 char buf[CHUNK]; 308 ssize_t n, m, r; 309 310 if (!on) { 311 /* End the reader process (if any). */ 312 if (pid == 0) return; 313 kill(pid, SIGKILL); 314 (void) waitpid(pid, (int *) NULL, 0); 315 pid = 0; 316 return; 317 } 318 319 /* Start a reader */ 320 pid = fork(); 321 if (pid < 0) { 322 tell(2, "term: fork() failed: ", strerror(errno), "\r\n", NIL); 323 quit(1); 324 } 325 if (pid == 0) { 326 /* Child: Copy from the modem to the screen. */ 327 328 while ((n = read(commfd, buf, sizeof(buf))) > 0) { 329 m = 0; 330 while (m < n && (r = write(1, buf + m, n - m)) > 0) m += r; 331 } 332 tell(2, "term: nothing to copy from ", commdev, " to output?\r\n", NIL); 333 kill(getppid(), SIGTERM); 334 _exit(1); 335 } 336 /* One reader on the loose. */ 337 } 338 339 340 void shell(char *cmd) 341 { 342 /* Invoke a subshell to allow one to run zmodem for instance. Run sh -c 'cmd' 343 * instead if 'cmd' non-null. 344 */ 345 346 pid_t pid; 347 char *shell, *sh0; 348 void(*isav) (int); 349 void(*qsav) (int); 350 void(*tsav) (int); 351 352 if (cmd == NULL) { 353 tell(1, "\nExit the shell to return to term, ", 354 commdev, " is open on file descriptor 9.\n", NIL); 355 } 356 357 if (cmd != NULL || (shell = getenv("SHELL")) == NULL) shell = "/bin/sh"; 358 if ((sh0 = strrchr(shell, '/')) == NULL) sh0 = shell; else sh0++; 359 360 /* Start a shell */ 361 pid = fork(); 362 if (pid < 0) { 363 tell(2, "term: fork() failed: ", strerror(errno), "\n", NIL); 364 return; 365 } 366 if (pid == 0) { 367 /* Child: Exec the shell. */ 368 setgid(getgid()); 369 setuid(getuid()); 370 371 if (commfd != 9) { dup2(commfd, 9); close(commfd); } 372 373 if (cmd == NULL) { 374 execl(shell, sh0, (char *) NULL); 375 } else { 376 execl(shell, sh0, "-c", cmd, (char *) NULL); 377 } 378 tell(2, "term: can't execute ", shell, ": ", strerror(errno), "\n",NIL); 379 _exit(1); 380 } 381 /* Wait for the shell to exit. */ 382 isav = signal(SIGINT, SIG_IGN); 383 qsav = signal(SIGQUIT, SIG_IGN); 384 tsav = signal(SIGTERM, SIG_IGN); 385 (void) waitpid(pid, (int *) 0, 0); 386 (void) signal(SIGINT, isav); 387 (void) signal(SIGQUIT, qsav); 388 (void) signal(SIGTERM, tsav); 389 tell(1, "\n[back to term]\n", NIL); 390 } 391 392 393 void lock_device(device) 394 char *device; 395 { 396 /* Lock a device by creating a lock file using SYSV style locking. */ 397 398 struct stat stbuf; 399 unsigned int pid; 400 int fd; 401 int n; 402 int u; 403 404 if (stat(device, &stbuf) < 0) fatal(device); 405 406 if (!S_ISCHR(stbuf.st_mode)) { 407 tell(2, "term: ", device, " is not a character device\n", NIL); 408 exit(1); 409 } 410 411 /* Compute the lock file name. */ 412 setnum(lockfile + 23, (stbuf.st_dev >> 8) & 0xFF); /* FS major (why?) */ 413 setnum(lockfile + 27, (stbuf.st_rdev >> 8) & 0xFF); /* device major */ 414 setnum(lockfile + 31, (stbuf.st_rdev >> 0) & 0xFF); /* device minor */ 415 416 /* Try to make a lock file and put my pid in it. */ 417 u = umask(0); 418 for (;;) { 419 if ((fd = open(lockfile, O_RDONLY)) < 0) { 420 /* No lock file, try to lock it myself. */ 421 if (errno != ENOENT) fatal(device); 422 if ((fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, 0444)) < 0) { 423 if (errno == EEXIST) continue; 424 fatal(lockfile); 425 } 426 pid = getpid(); 427 n = write(fd, &pid, sizeof(pid)); 428 if (n < 0) { 429 n = errno; 430 (void) unlink(lockfile); 431 errno = n; 432 fatal(lockfile); 433 } 434 close(fd); 435 break; 436 } else { 437 /* Already there, but who owns it? */ 438 n = read(fd, &pid, sizeof(pid)); 439 if (n < 0) fatal(device); 440 close(fd); 441 if (n == sizeof(pid) && !(kill(pid, 0) < 0 && errno == ESRCH)) { 442 /* It is locked by a running process. */ 443 tell(2, "term: ", device, 444 " is in use by another program\n", NIL); 445 if (getpgrp() == getpid()) sleep(3); 446 exit(1); 447 } 448 /* Stale lock. */ 449 tell(1, "Removing stale lock ", lockfile, "\n", NIL); 450 if (unlink(lockfile) < 0 && errno != ENOENT) fatal(lockfile); 451 } 452 } 453 /* Lock achieved, but what if two terms encounters a stale lock at the same 454 * time? 455 */ 456 umask(u); 457 } 458 459 460 void fatal(char *label) 461 { 462 tell(2, "term: ", label, ": ", strerror(errno), "\n", NIL); 463 exit(1); 464 } 465 466 467 void setnum(char *s, int n) 468 { 469 /* Poke 'n' into string 's' backwards as three decimal digits. */ 470 int i; 471 472 for (i = 0; i < 3; i++) { *--s = '0' + (n % 10); n /= 10; } 473 } 474 475 476 void set_uart(argc, argv, tcp) 477 int argc; 478 char *argv[]; 479 struct termios *tcp; 480 { 481 /* Set up the UART parameters. */ 482 483 int i; 484 char *arg; 485 struct param_s *param; 486 487 /* Examine all the parameters and check for validity. */ 488 for (i = 1; i < argc; ++i) { 489 arg = argv[i]; 490 if (arg[0] == '/' || arg[0] == '-') continue; 491 492 /* Check parameter for legality. */ 493 for (param = ¶ms[0]; 494 param->type != BAD && strcmp(arg, param->pattern) != 0; 495 ++param); 496 switch (param->type) { 497 case BAD: 498 tell(2, "Invalid parameter: ", arg, "\n", NIL); 499 quit(1); 500 break; 501 case BITS: 502 tcp->c_cflag &= ~CSIZE; 503 tcp->c_cflag |= param->value; 504 break; 505 case PARITY: 506 tcp->c_cflag &= PARENB | PARODD; 507 tcp->c_cflag |= param->value; 508 break; 509 case SPEED: 510 cfsetispeed(tcp, (speed_t) param->value); 511 cfsetospeed(tcp, (speed_t) param->value); 512 break; 513 } 514 } 515 } 516 517 518 void set_raw(tcp) 519 struct termios *tcp; 520 { 521 /* Set termios attributes for RAW mode. */ 522 523 tcp->c_iflag &= ~(ICRNL|IGNCR|INLCR|IXON|IXOFF); 524 tcp->c_lflag &= ~(ICANON|IEXTEN|ISIG|ECHO|ECHONL); 525 tcp->c_oflag &= ~(OPOST); 526 tcp->c_cc[VMIN] = 1; 527 tcp->c_cc[VTIME] = 0; 528 } 529 530 531 void quit(code) 532 int code; 533 { 534 /* Stop the reader process, reset the terminal, and exit. */ 535 reader(0); 536 tcsetattr(0, TCSANOW, &tcsavestdin); 537 (void) unlink(lockfile); 538 exit(code); 539 } 540