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
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
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
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.
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
54 #include <sys/types.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-] */
85 enum { BAD
, BITS
, PARITY
, SPEED
} type
;
92 { "even", PARENB
, PARITY
},
93 { "odd", PARENB
|PARODD
, PARITY
},
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
, ...);
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
);
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
);
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
);
153 lock_device(commdev
);
155 commfd
= open(commdev
, O_RDWR
);
157 tell(2, "term: can't open ", commdev
, ": ", strerror(errno
), "\n", NIL
);
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
);
166 signal(SIGINT
, quit
);
167 signal(SIGTERM
, quit
);
168 tcstdin
= tcsavestdin
;
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. */
178 /* Welcome message. */
179 tell(1, "Connected to ", commdev
,
180 ", command key is CTRL-], type ^]? for help\r\n", NIL
);
184 for (i
= 1; i
< argc
; ++i
) {
185 if (!isdialstr(argv
[i
])) continue;
186 tell(commfd
, argv
[i
] + 1, "\r", NIL
);
190 /* Main loop of the terminal simulator. */
191 while (read(0, &key
, 1) == 1) {
193 /* Command key typed. */
194 if (read(0, &key
, 1) != 1) continue;
199 for (i
= 1; i
< argc
; ++i
) {
202 if (arg
[0] == '-' && arg
[1] == 'c'
205 tcsetattr(0, TCSANOW
, &tcsavestdin
);
207 tcsetattr(0, TCSANOW
, &tcstdin
);
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",
223 for (i
= 1; i
< argc
; ++i
) {
225 static char cmd
[] = " x - ";
227 if (arg
[0] == '-' && arg
[1] == 'c'
230 tell(1, cmd
, arg
+3, "\r\n", NIL
);
233 tell(1, "^] - send a CTRL-]\r\n\n",
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
);
246 tcsetattr(0, TCSANOW
, &tcsavestdin
);
248 tcsetattr(0, TCSANOW
, &tcstdin
);
252 /* Hangup by using the +++ escape and ATH command. */
254 tell(commfd
, "+++", NIL
);
256 tell(commfd
, "ATH\r", NIL
);
260 tcsendbreak(commfd
, 0);
266 (void) write(commfd
, &key
, 1);
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
);
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'. */
296 while ((s
= va_arg(ap
, char *)) != NIL
) write(fd
, s
, strlen(s
));
304 /* Start or end a process that copies from the modem to the screen. */
311 /* End the reader process (if any). */
312 if (pid
== 0) return;
314 (void) waitpid(pid
, (int *) NULL
, 0);
322 tell(2, "term: fork() failed: ", strerror(errno
), "\r\n", NIL
);
326 /* Child: Copy from the modem to the screen. */
328 while ((n
= read(commfd
, buf
, sizeof(buf
))) > 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
);
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.
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
++;
363 tell(2, "term: fork() failed: ", strerror(errno
), "\n", NIL
);
367 /* Child: Exec the shell. */
371 if (commfd
!= 9) { dup2(commfd
, 9); close(commfd
); }
374 execl(shell
, sh0
, (char *) NULL
);
376 execl(shell
, sh0
, "-c", cmd
, (char *) NULL
);
378 tell(2, "term: can't execute ", shell
, ": ", strerror(errno
), "\n",NIL
);
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
)
396 /* Lock a device by creating a lock file using SYSV style locking. */
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
);
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. */
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;
427 n
= write(fd
, &pid
, sizeof(pid
));
430 (void) unlink(lockfile
);
437 /* Already there, but who owns it? */
438 n
= read(fd
, &pid
, sizeof(pid
));
439 if (n
< 0) fatal(device
);
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);
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
460 void fatal(char *label
)
462 tell(2, "term: ", label
, ": ", strerror(errno
), "\n", NIL
);
467 void setnum(char *s
, int n
)
469 /* Poke 'n' into string 's' backwards as three decimal digits. */
472 for (i
= 0; i
< 3; i
++) { *--s
= '0' + (n
% 10); n
/= 10; }
476 void set_uart(argc
, argv
, tcp
)
481 /* Set up the UART parameters. */
485 struct param_s
*param
;
487 /* Examine all the parameters and check for validity. */
488 for (i
= 1; i
< argc
; ++i
) {
490 if (arg
[0] == '/' || arg
[0] == '-') continue;
492 /* Check parameter for legality. */
493 for (param
= ¶ms
[0];
494 param
->type
!= BAD
&& strcmp(arg
, param
->pattern
) != 0;
496 switch (param
->type
) {
498 tell(2, "Invalid parameter: ", arg
, "\n", NIL
);
502 tcp
->c_cflag
&= ~CSIZE
;
503 tcp
->c_cflag
|= param
->value
;
506 tcp
->c_cflag
&= PARENB
| PARODD
;
507 tcp
->c_cflag
|= param
->value
;
510 cfsetispeed(tcp
, (speed_t
) param
->value
);
511 cfsetospeed(tcp
, (speed_t
) param
->value
);
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
);
527 tcp
->c_cc
[VTIME
] = 0;
534 /* Stop the reader process, reset the terminal, and exit. */
536 tcsetattr(0, TCSANOW
, &tcsavestdin
);
537 (void) unlink(lockfile
);