Create SFFS library out of HGFS
[minix.git] / commands / term / term.c
blobb1c2c7b2ae658330305e5ae378315aebfc17c02d
1 /* term - terminal simulator Author: Andy Tanenbaum */
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.
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.
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.
18 * 11 Oct 88 BDE: Cleaned up baud rates and parity stripping.
20 * 09 Oct 90 MAT (Michael A. Temari): Fixed bug where terminal isn't reset
21 * if an error occurs.
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.
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.
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.
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.
37 * 10 Mar 96 KJB: Termios adaption, cleanup, command key interface.
39 * 27 Nov 96 KJB: Add -c flag that binds commands to keys.
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
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>
66 #define CHUNK 1024 /* how much to read at once */
68 char TERM_LINE[] = "/dev/modem";/* default serial port to use */
70 /* device lock file */
71 char lockfile[] = "/usr/spool/locks/LK.iii.jjj.kkk";
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 */
79 /* Special key to get term's attention. */
80 #define HOTKEY '\035' /* CTRL-] */
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 },
92 { "even", PARENB, PARITY },
93 { "odd", PARENB|PARODD, PARITY },
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 */
114 #define NIL ((char *) NULL) /* tell(fd, ..., NIL) */
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);
128 int main(argc, argv)
129 int argc;
130 char *argv[];
132 int i;
133 unsigned char key;
134 int candial;
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);
142 commdev = argv[i];
145 if (commdev == NULL) commdev = TERM_LINE;
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);
153 lock_device(commdev);
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);
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);
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);
175 /* Start a reader process to copy modem output to the screen. */
176 reader(1);
178 /* Welcome message. */
179 tell(1, "Connected to ", commdev,
180 ", command key is CTRL-], type ^]? for help\r\n", NIL);
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;
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;
196 switch (key) {
197 default:
198 /* Added command? */
199 for (i = 1; i < argc; ++i) {
200 char *arg = argv[i];
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;
212 if (i < argc) break;
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 - ";
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);
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);
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;
269 } else {
270 /* Send keyboard input down the serial line. */
271 if (write(commfd, &key, 1) != 1) break;
274 tell(2, "term: nothing to copy from input to ", commdev, "?\r\n", NIL);
275 quit(1);
279 int isdialstr(char *arg)
281 /* True iff arg is the start of a dial string, i.e. "-at...". */
283 return (arg[0] == '-'
284 && (arg[1] == 'a' || arg[1] == 'A')
285 && (arg[2] == 't' || arg[2] == 'T'));
289 void tell(int fd, ...)
291 /* Write strings to file descriptor 'fd'. */
292 va_list ap;
293 char *s;
295 va_start(ap, fd);
296 while ((s = va_arg(ap, char *)) != NIL) write(fd, s, strlen(s));
297 va_end(ap);
301 void reader(on)
302 int on;
304 /* Start or end a process that copies from the modem to the screen. */
306 static pid_t pid;
307 char buf[CHUNK];
308 ssize_t n, m, r;
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;
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);
325 if (pid == 0) {
326 /* Child: Copy from the modem to the screen. */
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;
332 tell(2, "term: nothing to copy from ", commdev, " to output?\r\n", NIL);
333 kill(getppid(), SIGTERM);
334 _exit(1);
336 /* One reader on the loose. */
340 void shell(char *cmd)
342 /* Invoke a subshell to allow one to run zmodem for instance. Run sh -c 'cmd'
343 * instead if 'cmd' non-null.
346 pid_t pid;
347 char *shell, *sh0;
348 void(*isav) (int);
349 void(*qsav) (int);
350 void(*tsav) (int);
352 if (cmd == NULL) {
353 tell(1, "\nExit the shell to return to term, ",
354 commdev, " is open on file descriptor 9.\n", NIL);
357 if (cmd != NULL || (shell = getenv("SHELL")) == NULL) shell = "/bin/sh";
358 if ((sh0 = strrchr(shell, '/')) == NULL) sh0 = shell; else sh0++;
360 /* Start a shell */
361 pid = fork();
362 if (pid < 0) {
363 tell(2, "term: fork() failed: ", strerror(errno), "\n", NIL);
364 return;
366 if (pid == 0) {
367 /* Child: Exec the shell. */
368 setgid(getgid());
369 setuid(getuid());
371 if (commfd != 9) { dup2(commfd, 9); close(commfd); }
373 if (cmd == NULL) {
374 execl(shell, sh0, (char *) NULL);
375 } else {
376 execl(shell, sh0, "-c", cmd, (char *) NULL);
378 tell(2, "term: can't execute ", shell, ": ", strerror(errno), "\n",NIL);
379 _exit(1);
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);
393 void lock_device(device)
394 char *device;
396 /* Lock a device by creating a lock file using SYSV style locking. */
398 struct stat stbuf;
399 unsigned int pid;
400 int fd;
401 int n;
402 int u;
404 if (stat(device, &stbuf) < 0) fatal(device);
406 if (!S_ISCHR(stbuf.st_mode)) {
407 tell(2, "term: ", device, " is not a character device\n", NIL);
408 exit(1);
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 */
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);
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);
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);
448 /* Stale lock. */
449 tell(1, "Removing stale lock ", lockfile, "\n", NIL);
450 if (unlink(lockfile) < 0 && errno != ENOENT) fatal(lockfile);
453 /* Lock achieved, but what if two terms encounters a stale lock at the same
454 * time?
456 umask(u);
460 void fatal(char *label)
462 tell(2, "term: ", label, ": ", strerror(errno), "\n", NIL);
463 exit(1);
467 void setnum(char *s, int n)
469 /* Poke 'n' into string 's' backwards as three decimal digits. */
470 int i;
472 for (i = 0; i < 3; i++) { *--s = '0' + (n % 10); n /= 10; }
476 void set_uart(argc, argv, tcp)
477 int argc;
478 char *argv[];
479 struct termios *tcp;
481 /* Set up the UART parameters. */
483 int i;
484 char *arg;
485 struct param_s *param;
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;
492 /* Check parameter for legality. */
493 for (param = &params[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;
518 void set_raw(tcp)
519 struct termios *tcp;
521 /* Set termios attributes for RAW mode. */
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;
531 void quit(code)
532 int code;
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);