rsalz@uunet.uu.net (Rich Salz) (06/01/89)
Submitted-by: ka@june.cs.washington.edu (Kenneth Almquist) Posting-number: Volume 19, Issue 10 Archive-name: atty/part01 Atty is an alternative to tty(4). Atty provides a set of EMACS-like editing commands which allow you to move around the input line and make corrections where ever you want. Atty allows you to bring lines that you previously typed into the input buffer so that you can resend them, possible after modifying them. Atty also handles line wrapping and does a better job than tty of keeping input and output from getting intermingled. Not to mention its abbreviation feature... Try it, you'll like it! Atty runs entirely in user mode and requires no special privileges to run or install. It's a hack, but it works. I developed the code under Ultrix, a 4.2 BSD derivative. It has also been tested on a couple of other 4.2 BSD derivatives. The code does not run under System V, and would not be easy to port. # This is part 1 of atty. To unpack, feed it into the shell (not csh). # The atty distribution consists of four pieces. After you unpack everyting, # read the file README. mkdir atty ; cd atty echo extracting README cat > README <<\EOF Welcome to ATTY Atty is an alternative to tty(4). To correct a mistake using tty(4), you must erase all the characters between the mistake and the end of the line. In contrast, atty provides a set of EMACS-like editing commands which allow you to move around the input line and make corrections where ever you want. Atty allows you to bring lines that you previously typed into the input buffer so that you can resend them, possible after modifying them. Atty also handles line wrapping and does a better job than tty of keeping input and output from getting intermingled. Not to mention its abbreviation feature... Try it, you'll like it! Atty runs entirely in user mode and requires no special privileges to run or install. It's a hack, but it works. I developed the code under Ultrix, a 4.2 BSD derivative. It has also been tested on a couple of other 4.2 BSD derivatives. The code does not run under System V, and would not be easy to port. INSTALLATION To compile atty, just type "make". Three programs should be created: atty - the atty program kbind - program to generate key binding files fmatch - file name matching program These should be installed in a bin directory somewhere. Three additional files are generated: atty.bindc - default binding file dftbind.c - name of default binding file atty.1 - atty manual page Atty.bindc is the default key binding file, which is created from the file atty.bind. The latter two files include the absolution path name of atty.bindc. The makefile will create these files correctly if you just leave atty.bindc in the source directory. If you install atty.bindc somewhere else, you will have to edit these files. The makefile used cc. The Berkeley header files are incompatible with ANSI C, so if you compile with gcc or some other ANSI compiler, you will get incorrect code. You can use an ANSI compatable version of the header files, or you can use the -traditional flag of gcc. Having compiled atty, you are now ready to run it. However, you may want to change some the bindings first. The end of the "Getting Started" section of the atty manual page provides a tutorial introduction to doing this; if you really want to know what's going on read the kbind manual page. Some users will probably want set up their bindings to act like vi rather than emacs. The file vi.bind illustrates how to set up separate insert and command modes, like vi has. However, I have not implemented the vi commands, and you can't have a real vi emulation without them. Copyright (c) 1989 by Kenneth Almquist EOF if test `wc -c < README` -ne 2593 then echo 'README is the wrong size' fi echo extracting LICENSE cat > LICENSE <<\EOF ATTY GENERAL PUBLIC LICENSE Copyright 1989 by Kenneth Almquist. Everyone is permitted to copy and distribute verbatim copies of this license, but changing it is not allowed. You can also use this wording to make the terms for other programs. This license agreement uses wording taken from the GNU Emacs General Public License, which is copyright 1985, 1987, 1988 by Richard M. Stallman. Richard M. Stallman is not responsible for the contents of this license. Atty is copyrighted. To encourage maximum use of this software, I, Kenneth Almquist, hereby grant all recipients of atty the right to reproduce and modify atty under the terms specified in this license. COPYING POLICIES 1. You may copy and distribute verbatim copies of atty source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy a valid copyright notice "Copyright 1989 by Kenneth Almquist." (or with whatever year is appropriate); keep intact the notices on all files that refer to this License Agreement and to the absence of any warranty; and give any other recipients of the atty program a copy of this License Agreement along with the program. You may charge a distribution fee for the physical act of transferring a copy. 2. You may modify your copy or copies of atty source code or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains or is a derivative of atty or any part thereof, to be licensed at no charge to all third parties on terms identical to those contained in this License Agreement (except that you may choose to grant more extensive warranty protection to some or all third parties, at your option). c) You may charge a distribution fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another unrelated program with this program (or its derivative) on a volume of a storage or distribution medium does not bring the other program under the scope of these terms. 3. You may copy and distribute atty (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal shipping charge) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs. 4. The atty distribution contains two files, regex.h and regex.c, which are copyrighted by the Free Software Foundation, Inc. These files are not covered by this license agreement, and may only be used or copied in accordance with the license agreements that appear at the top of those files. The compilation process for the atty program links it with regex.c. Therefore binary versions of the atty program are covered by both this license and the license appearing at the top of regex.c. 5. You may not copy, sublicense, distribute or transfer atty except as expressly provided under this License Agreement. Any attempt otherwise to copy, sublicense, distribute or transfer atty is void and your rights to use atty under this License agreement shall be automatically terminated. However, parties who have received computer software programs from you with this License Agreement will not have their licenses terminated so long as such parties remain in full compliance. NO WARRANTY Because atty is licensed free of charge, I provide absolutely no warranty, to the extent permitted by applicable state law. Except when otherwise stated in writing, Kenneth Almquist and/or other parties provide atty "as is" without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the program is with you. Should the atty program prove defective, you assume the cost of all necessary servicing, repair or correction. In no event unless required by applicable law will Kenneth Almquist and/or any other party who may modify and redistribute atty as permitted above, be liable to you for damages, including any lost profits, lost monies, or other special, incidental or consequential damages arising out of the use or inability to use (including but not limited to loss of data or data being rendered inaccurate or losses sustained by third parties or a failure of the program to operate with programs provided by other parties) the program, even if you have been advised of the possibility of such damages, or for any claim by any other party. EOF if test `wc -c < LICENSE` -ne 5831 then echo 'LICENSE is the wrong size' fi echo extracting atty.1.mk cat > atty.1.mk <<\EOF echo '.TH ATTY 1' echo ".ds d `pwd`/atty.bind" cat <<\! .SH NAME atty \- alternative tty driver .SH SYNOPSYS .B atty .B -ls .B -c .I command [ .I bindings-file ] .SH COPYRIGHT .if n Copyright (C) 1989 by Kenneth Almquist. .if t Copyright \(co 1989 by Kenneth Almquist. .SH DESCRIPTION .I Atty is an alternative to .IR tty (4). It puts the terminal in .I cbreak mode to suppress the line editing normally done by .IR tty (4) .PP Most of the standard .IR tty (4) features (except for the ones relating to input line editing) are supported. Skip to the the section titled .I "Getting Started" if you just want an introduction to .IR atty . .PP When .I atty starts up, it runs the shell specified by the environment variable SHELL. It sets the environment variable ATTY to inform programs that they are running under \fIatty\fR. If the .B -l option is given, the shell is made a login shell (by prepending a minus sign to arg zero). Normally suspending the shell will cause .I atty to suspend itself. The .B -s option causes .I atty to restart a suspended shell instead. The .B -c option causes the shell to run the specified command before reading commands from the standard input. This only works with .IR ash (1). (I use it to make the prompt of my top level shell different from the default ``@\ '' \fIwithout\fR passing PS1 as an environment variable, which would change the prompt for subshells as well as the top level shell.) .PP The optional .I bindings-file controls the key bindings used by the editor (see .IR kbind (1)). If .I bindings-file is omitted, .I atty use the file "$HOME/.bindc" as the bindings file if it exists; otherwise it uses the system default binding file ``\*dc''. .PP Several escape sequences are handled specially by .IR atty . ``ESC ] P'' introduces a prompt. The remainder of the line consists of a sequence of digits giving the program ID, a semicolon, and the text of the prompt. ``ESC ] D newline'' deletes the current prompt (if any). These two escape sequences can be used by shells (such as .IR ash (1)) that know about .IR atty . ``ESC ] I'' inserts text in the input buffer. The remainder of the line contains the text to be inserted. This is used by the .B -c option of .IR fmatch (1). .PP Clearing the ODDP bit in the sgtty structure (``stty -odd'') turns off the special input editing features of .I atty and causes the normal .IR tty (4) input editing to be done. .PP When the prompt is set (via ``ESC ] P''), .I atty assumes that it can run the .IR fmatch (1) program by inputing a command line preceded by an FS character (octal 34). .sp 2 .B "Getting Started" .PP This section gives an overview of the editiing features of .I atty when the default binding file is used. For detailed descriptions of the commands, see .IR kbind (1). .PP Each command can be given a numeric prefix argument. One way to do this is to type ESC followed by the number. Another is to type ^U. ^U multiplies the preceding prefix argument by 4 if one is given, so a single ^U sets the prefix argument to 4, ^U ^U sets the prefix argument to 16, and so on. Typically, the prefix argument specifies the number of times that a command is to be repeated. Most commands will do something sensible with a negative argument. .PP Printing characters are bound to the .I self-insert command, so to enter then you just type them. Control characters are used for commands, but you can enter them by preceding them with ^V. .PP Several commands correspond to functions normally provided by .IR tty (4). DEL (^?) sends an interrupt signal, FS (^\e) sends a quit signal, and ^Z sends a stop signal. The first two of these normally flush .IR atty 's input buffer. ^D generate an end of file when typed on an empty line. (Otherwise it deletes the following character.) These keys are the ones that you are most likely to want to re-bind; see below. .PP There are several commands for moving around on a line. ^F moves forward one character and ^B moves backward one character. ESC f and ESC b move forward and backword by words rather than characters. ESC a and ESC e move to the beginning and the end of the line, respectively. .PP The commands that delete text generally save the deleted text in the .IR "kill buffer" . You can then get the text back by typing ^Y. This feature is often used to move text around. Also, if you delete text by accident, you can get it back by typing ^Y. .PP The most important deletion command is BACKSPACE, which deletes the preceding character. It only saves the deleted text to the kill buffer if you gave an explicit prefix argument. ^D is similar to BACKSPACE, but deletes forwards rather than backwards. (As a special case, ^D generates an end of file when typed on an empty line.) There are also commands for deleting words: ESC BACKSPACE deletes the preceding word and ESC d deletes the following word. Finally, ^W deletes everything between the cursor and a location in the line known as the .IR mark . You can set the location of the mark by typing ^@. .PP One final text modification command worth mentioning is ^T, which transposes the two preceding characters. .PP .I Atty mantains a history of lines typed. ^P moves to the preceding history line and ^N moves to the next history line. Both ^P and ^N skip blank lines. ^R does a reverse search through the history file for a specified regular expression (which you type in, followed by a carriage return). When you go to a line, it is loaded into the input buffer where you can then edit it. (Editing it does not affect the contents of the actual history file.) .PP When you type carriage return to send a line, .I atty moves to the end of the history file. You can also send a line using ^J (NEWLINE); in this case .I atty will advance to the next line of the history file rather than moving to the end. ^J is useful for resending a series of lines. ESC . inserts the last word of the last history line. This is useful for entering sequences like: .in +1i .nf mkdir directory cd directory .fi .in -1i Finally, ``ESC ,'' inserts the last line of output. .sp 2 .B "Changing Bindings" .PP If you want to change a few bindings, the best thing to do is to copy the system binding file \*d to the file .bind in your home directory, and then edit it. As an example, suppose you are a closet VMS fanatic and want to make CONTROL-C be your interrupt character. You will find that two of the lines in the .bind file read .sp .nf b ^C upcase-char and b \e177 tty-intr .fi .sp The first line binds ^C to upcase-char, a function that converts a character to upper case. The second line binds DELETE (written here as \e177; it could also have been written as ^?) to tty-intr. You want CONTROL C to do what DELETE does in the standard binding file, so change the ``upcase-char'' to ``tty-intr''. Now both CONTROL C and DELETE will generate an interrupt, which is perfectly legal. In practice you probably want to make DELETE delete the preceding character, which you can do by binding it to delete-backward-char. The resulting lines look like: .sp .nf b ^C tty-intr and b \e177 delete-backward-char .fi .PP After you have set up the .bind file to your satisfaction, type .sp kbind .bind .sp This will create a file named .bindc which .I atty will read instead of the default binding file. \fIIf you omit this step, atty will ignore the \fR.bind\fI file and will continue to use the default binding file.\fR Atty doesn't look for a \&.bind file, only a .bindc file. .SH AUTHORS Kenneth Almquist .SH FILES \*dc\h'0.5i'default binding file .SH "SEE ALSO" .ta 2i \fIkbind\fR(1) .SH BUGS Atty could be more intelligent about updating the screen in some cases. (The slowest terminals on the systems it was developed on were 9600 baud.) .PP The program does not create a /etc/utmp entry for the pseudo-terminal that it uses. This confuses .IR talk (1). .PP Changing the line discipline will cause problems. There are also a number of race conditions. This program really needs more operating system support. (Stream pipes would help, but an acknowledgement should really be sent back when data is read to allow prompts and input lines to be associated correctly.) ! EOF if test `wc -c < atty.1.mk` -ne 8197 then echo 'atty.1.mk is the wrong size' fi echo extracting atty.bind cat > atty.bind <<\EOF # This is the default key binding file for atty. mode 0 default self-insert b ^@ set-mark b ^A beginning-of-line b ^B backward-char b ^C upcase-char #b ^D delete-char b ^D eof-or-delete-char b ^E end-of-line b ^F forward-char b ^G undefined b ^H delete-backward-char b \t self-insert b ^J newline-and-insert b ^K kill-line b ^L undefined b \r newline b ^N next-history b ^O undefined b ^P previous-history b ^Q quoted-insert b ^R re-search-backward b ^S re-search-forward b ^T gosling-transpose-chars b ^U universal-argument b ^V quoted-insert b ^W kill-region b ^X kill-input b ^Y yank b ^Z tty-susp b \034 tty-quit b \035 undefined b \036 undefined #b \037 end-of-file b \177 tty-intr b \e^H backward-kill-word b \e\s set-mark b \e< beginning-of-history b \e> end-of-history b \e- negative-argument b \e0 digit-argument b \e1 digit-argument b \e2 digit-argument b \e3 digit-argument b \e4 digit-argument b \e5 digit-argument b \e6 digit-argument b \e7 digit-argument b \e8 digit-argument b \e9 digit-argument b \eb backward-word b \eB backward-word b \ed kill-word b \eD kill-word b \ef forward-word b \eF forward-word b \eh backward-kill-word b \eH backward-kill-word #b \em insert "/usr/spool/mail/ka" b \et transpose-words b \eT transpose-words b \eu upcase-word b \eU upcase-region b \ew copy-region-as-kill b \eW copy-region-as-kill b \ex exchange-point-and-mark b \eX exchange-point-and-mark b \e\e file-complete b \e= list-file-completions b \e, last-output-line b \e. get-history-word b \e\177 backward-kill-word # arrow keys b \e[C forward-char b \e[D backward-char b \e[A previous-history b \e[B next-history syntax word "a-zA-Z0-9$%" syntax filename "^ \t\n&();<>|" syntax abbrev "^ \t\n&();<>|/" #abbrev "~" "$HOME" EOF if test `wc -c < atty.bind` -ne 1737 then echo 'atty.bind is the wrong size' fi echo extracting atty.h cat > atty.h <<\EOF /* * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of atty, which is distributed under the terms specified * by the Atty General Public License. See the file named LICENSE. */ struct tty { /* structure containing terminal modes */ int ldisc; /* line discipline */ struct sgttyb sgtty; /* stty modes */ int lmode; /* local mode */ struct tchars tchars; /* special characters */ struct ltchars ltchars; /* more special characters */ /* window size omitted */ }; EOF if test `wc -c < atty.h` -ne 542 then echo 'atty.h is the wrong size' fi echo extracting atty.c cat > atty.c <<\EOF /* * Atty - tty(4) replacement. * * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of atty, which is distributed under the terms specified * by the Atty General Public License. See the file named LICENSE. */ #include <stdio.h> #include <sys/types.h> #include <sys/time.h> #include <signal.h> #include <sgtty.h> #include <errno.h> #include <fcntl.h> #include <setjmp.h> #include <sys/wait.h> #include <sys/resource.h> #include "atty.h" #include "attyed.h" #ifndef __STDC__ #define const #define volatile #endif #define ECHO_NL 1 /* echo input line terminated by a newline */ #define ECHO_EOF 2 /* echo input line terminated by EOF */ #define ECHO_SIG 3 /* echo interrupt character */ struct indata { struct indata *next; /* next structure on queue */ short nchars; /* number of characters */ short nwrt; /* number of characters already written */ char data[4]; /* characters; should be "char data[nchars]" */ }; struct tty origtty; /* original tty modes */ struct tty realtty; /* tty modes of real tty */ struct tty ptymodes; /* modes of pty */ int realfcntl; /* flags for real tty file descriptor */ int ptyfd; /* file descriptor of pty */ char ptydevice[12]; /* pty device */ int childpid; /* pid of child process */ jmp_buf sigjmp; /* where to jump on signal */ volatile int canjump; /* set if should do longjmp on signal */ volatile int gotsigchild; /* child is dead */ volatile int gotsigwinch; /* window size has changed */ volatile int childstatus; /* status of child */ struct indata *inputq; /* queue of data to be sent to pty */ struct indata *inqlast; /* last entry in inputq */ struct mode ttymode; /* tty characters for editor */ int remoteon; /* true if TIOCREMOTE mdoe is on */ int needclearin; /* true if we must clear the tty input */ int echoflag; /* set if output echoed */ char sigchar; /* signal character to be echoed */ int noedit; /* true if not using input editor */ int logall; /* save all output data in the trace file */ int loginshell; /* true if should start up login shell */ int nosuspend; /* don't permit atty to be suspended */ char *shellcmd; /* prompt to be used by shell */ short ospeed; /* output speed, for termcap */ int gottty; /* true if we have read the tty modes */ const struct ltchars real_ltchars = { -1, -1, -1, -1, -1, -1 }; /* C library routines */ #ifdef __STDC__ int ioctl(int, int, char *); int isatty(int); int getpid(void); char *getenv(char *); #else int ioctl(); int isatty(); int getpid(); char *getenv(); #endif #ifdef __STDC__ int openpty(void); void spawnshell(void); void runtty(void); void ttyoutc(int); void outreal(char *, int); void writereal(void); void senddata(char *, int); void writepty(void); int stripecho(int, char *, int); void flushptyin(void); void onchild(); void onwinch(); void indata(char *, int); void newttymodes(); void fillttymode(void); void sendsig(int); void flushoutput(void); void fatal(char *); void done(int); void cleanup(void); int getttymodes(int, struct tty *); int setttymodes(int, struct tty *); int copyttysize(void); #else int openpty(); void spawnshell(); void runtty(); void ttyoutc(); void outreal(); void writereal(); void senddata(); void writepty(); int stripecho(); void flushptyin(); void onchild(); void onwinch(); void indata(); void newttymodes(); void fillttymode(); void sendsig(); void flushoutput(); void fatal(); void done(); void cleanup(); int getttymodes(); int setttymodes(); int copyttysize(); #endif #define scopy(s1, s2) strcpy(s2, s1) /*TEMPORARY*/ main(argc, argv) char **argv; { int one = 1; int i; char **ap; char *p; if (! isatty(2) || ! isatty(0)) { fputs("Atty can only be run from a terminal\n", stderr); exit(2); } ap = argv; if (argc > 0) { ap++; for (;;) { if ((p = *ap) == NULL || *p++ != '-') break; ap++; if (p[0] == '-' && p[1] == '\0') /* "--" */ break; while (*p) { switch (*p++) { case 'l': loginshell++; break; case 's': nosuspend++; break; case 'c': if (*p == '\0' && (p = *ap++) == NULL) { fputs("atty: no arg for -c option\n"); exit(2); } shellcmd = p; p = ""; break; default: fprintf(stderr, "atty: Illegal option -%c\n", p[-1]); exit(2); } } } } signal(SIGCHLD, onchild); ptyfd = openpty(); if (getttymodes(2, &origtty) < 0) fatal("getttymodes"); realfcntl = fcntl(2, F_GETFL, 0); gottty = 1; ospeed = origtty.sgtty.sg_ospeed; edinit(*ap); ptymodes = origtty; ptymodes.ldisc = NTTYDISC; /* we must have literal next char */ ptymodes.sgtty.sg_flags |= EVENP|ODDP; /* no parity checking */ realtty = origtty; realtty.tchars.t_intrc = -1; realtty.tchars.t_quitc = -1; realtty.ltchars = real_ltchars; realtty.sgtty.sg_flags |= CBREAK; realtty.sgtty.sg_flags &=~ (CRMOD|ECHO|LCASE); if ((realtty.sgtty.sg_flags & TBDELAY) == XTABS) realtty.sgtty.sg_flags &=~ TBDELAY; if (setttymodes(2, &realtty) < 0) { perror("setttymodes"); exit(2); } /* copyttysize(); */ realfcntl |= FNDELAY; fcntl(2, F_SETFL, realfcntl); i = fcntl(ptyfd, F_GETFL, 0); i |= FNDELAY; fcntl(ptyfd, F_SETFL, i); #ifdef REMOTE if (ioctl(ptyfd, TIOCREMOTE, (char *)&one) < 0) fatal("TIOCREMOTE"); remoteon = 1; #endif if (ioctl(ptyfd, TIOCPKT, (char *)&one) < 0) fatal("TIOCPKT"); if (setttymodes(ptyfd, &ptymodes) < 0) fatal("set pty modes"); fillttymode(); copyttysize(); if ((childpid = fork()) == -1) { fatal("fork"); } if (childpid == 0) spawnshell(); else { signal(SIGTTOU, SIG_IGN); signal(SIGWINCH, onwinch); runtty(); } /*NOTREACHED*/ } /* * Search for an unused pty. It returns a file descriptor for the master * and leaves the name of the slave in the global variable ptydevice. * This code starts at the beginning; a more efficient version might start * at a randomly chosen point in the middle. */ int openpty() { char c; int i; int fd; scopy("/dev/ptyXX", ptydevice); for (c = 'p'; c <= 's'; c++) { for (i = 0 ; i < 16 ; i++) { ptydevice[8] = c; ptydevice[9] = "0123456789abcdef"[i]; fd = open(ptydevice, 2); if (fd >= 0) { ptydevice[5] = 't'; /* change "pty" to "tty" */ return fd; /* success */ } if (errno == ENOENT) goto failure; } } failure: fprintf(stderr, "Out of pty's\n"); exit(2); } /* * This routine exec's the shell. */ void spawnshell() { int fd; int mypid = getpid(); char *shell; char **env; char **ep; extern char **environ; char arg0[16]; char *p; char *basename; char *argv[4]; if ((shell = getenv("SHELL")) == NULL) shell = "/bin/sh"; for (ep = environ ; *ep ; ep++); if ((env = (char **)malloc((char *)(ep + 2) - (char *)environ)) == NULL) fatal("Malloc failed"); for (ep = env ; *environ ; environ++) { if (strncmp(*environ, "ATTY=", 5) != 0) *ep++ = *environ; } *ep++ = "ATTY="; *ep = NULL; close(0); close(1); close(ptyfd); fd = open("/dev/tty", O_RDWR); if (fd >= 0) { ioctl(fd, TIOCNOTTY, (char *)0); close(fd); } setpgrp(mypid, 0); if (open(ptydevice, O_RDWR) != 0) { perror(ptydevice); exit(2); } setpgrp(mypid, mypid); close(2); dup(0); /* file descriptor 1 = dup of 0 */ dup(0); /* file descriptor 2 = dup of 0 */ if (ioctl(2, TIOCSPGRP, (char *)&mypid) < 0) printf("TIOCSPGRP failed, errno=%d\n", errno); basename = shell; for (p = shell ; *p ; p++) { if (*p == '/') basename = p + 1; } if (loginshell) { arg0[0] = '-'; for (p = arg0 + 1 ; p < arg0 + 15 && *basename ; *p++ = *basename++); *p = '\0'; basename = arg0; } argv[0] = basename; argv[1] = NULL; if (shellcmd) { argv[1] = "-sc"; argv[2] = shellcmd; argv[3] = NULL; } execve(shell, argv, env); perror(shell); exit(2); } #define OUTRSIZE 512 char outrbuf[OUTRSIZE]; char *outrp = outrbuf; char *outrnext = outrbuf; int outrsize; /* * This routine contains the main loop which is run after initialization. */ void runtty() { volatile int ptybit = 1 << ptyfd; volatile int realbit = 1 << 2; volatile int nfds = ptyfd + 1; volatile int inbits, outbits, exceptbits; volatile int sel; int i, j; char buf[260]; if (setjmp(sigjmp)) goto gotsig; for (;;) { inbits = realbit; outbits = 0; if (outrsize == 0) inbits |= ptybit; else outbits = realbit; if (inputq != NULL) outbits |= ptybit; exceptbits = 0; canjump = 1; if (gotsigchild || gotsigwinch) goto gotsig; sel = select(nfds, &inbits, &outbits, &exceptbits, (struct timeval *)NULL); canjump = 0; if (0) { /* if interrupted by signal */ gotsig: if (gotsigchild) { gotsigchild = 0; i = childstatus; if ((i & 0xFF) == 0177 && (i >> 8 == SIGTSTP || i >> 8 == SIGSTOP)) { if (! nosuspend) { setttymodes(2, &origtty); fcntl(2, F_SETFL, realfcntl &~ FNDELAY); kill(getpid(), SIGTSTP); setttymodes(2, &realtty); fcntl(2, F_SETFL, realfcntl); } killpg(childpid, SIGCONT); } else { if ((i & 0xFF) == 0) i >>= 8; else i = i & 0xFF | 0x80; done(i); } } if (gotsigwinch) { gotsigwinch = 0; newscrnwidth(copyttysize()); } } else if (sel < 0) { if (errno != EINTR) fatal("select"); } else { if (inbits & realbit) { if ((i = read(2, buf, sizeof buf)) < 0) fatal("tty read"); if (i == 0) fatal("tty eof"); indata(buf, i); } if (inbits & ptybit) { if ((i = read(ptyfd, buf, sizeof buf)) < 0) { /* * Probably the problem is that the child * process has terminated, which causes the * ptc device to return EIO. */ if (gotsigchild) goto gotsig; if (errno == EIO) { sleep(1); errno = EIO; if (gotsigchild) goto gotsig; } fatal("pty read"); } if (i == 0) fatal("pty eof"); #ifndef TIOCPKT_IOCTL newttymodes(); #endif if (buf[0] == TIOCPKT_DATA) { j = 0; if (echoflag) { j = stripecho(echoflag, buf + 1, i - 1); echoflag = 0; } j++; #ifdef notdef if (logall) fwrite(buf + 1, i - 1, 1, trace); #endif outchars(buf + j, i - j); writereal(); if (noedit == 0) refresh(); } else { #ifdef TIOCPKT_IOCTL if (buf[0] & TIOCPKT_IOCTL) newttymodes(); #endif if (buf[0] & TIOCPKT_FLUSHREAD) { clearinput(); } } } if (outbits & realbit) { writereal(); } if (outbits & ptybit) { writepty(); } } } } /* * Output a character to the real tty device. */ void ttyoutc(c) char c; { outreal(&c, 1); } /* * Output a block of characters to the real tty device. */ void outreal(p, size) char *p; { int n = &outrbuf[OUTRSIZE] - outrnext; if (size + outrsize > OUTRSIZE) fatal("output overflow"); if (n > size) n = size; bcopy(p, outrnext, n); outrnext += n; if (outrnext == &outrbuf[OUTRSIZE]) outrnext = outrbuf; if (n < size) { p += n; n = size - n; bcopy(p, outrnext, n); outrnext += n; } outrsize += size; } /* * Write as much data as possible to the real tty, until we block. */ void writereal() { int i; int n; while (outrsize > 0) { n = &outrbuf[OUTRSIZE] - outrp; if (n > outrsize) n = outrsize; if ((i = write(2, outrp, n)) < 0) { if (errno == EWOULDBLOCK) break; else fatal("tty write"); } outrsize -= i; outrp += i; if (outrp == &outrbuf[OUTRSIZE]) outrp = outrbuf; } } /* * Send data to the pty. */ #define INDATAHDR ((char *)((struct indata *)0)->data - (char *)0) void senddata(p, size) char *p; { struct indata *ip; if ((ip = (struct indata *)malloc(INDATAHDR + size)) == NULL) fatal("malloc pty data"); ip->nchars = size; ip->nwrt = 0; bcopy(p, ip->data, size); ip->next = NULL; if (inputq == NULL) inputq = ip; else inqlast->next = ip; inqlast = ip; } /* * Like writereal, but for pty. */ void writepty() { #ifdef REMOTE int i; struct indata *ip; int one = 1; if ((ip = inputq) == NULL) return; if (! remoteon) { if (ioctl(ptyfd, TIOCREMOTE, (char *)&one) < 0) fatal("TIOCREMOTE"); remoteon = 1; } if ((i = write(ptyfd, ip->data, ip->nchars)) < 0) { if (errno != EWOULDBLOCK) fatal("pty write"); return; } else if (i != ip->nchars) { fatal("partial pty write"); } inputq = ip->next; free((char *)ip); #else int i; struct indata *ip; char buf[512]; int buflen; char c; char *p; int echotype; if ((ip = inputq) == NULL) return; if (noedit) { i = write(ptyfd, ip->data + ip->nwrt, ip->nchars - ip->nwrt); if (i < 0) fatal("pty write"); if ((ip->nwrt += i) >= ip->nchars) { inputq = ip->next; free((char *)ip); } } else { newttymodes(); /* just in case */ p = buf; for (i = ip->nwrt ; i < ip->nchars ; i++) { c = ip->data[i]; if (c == ptymodes.sgtty.sg_erase || c == ptymodes.sgtty.sg_kill || c == ptymodes.tchars.t_intrc || c == ptymodes.tchars.t_quitc || c == ptymodes.tchars.t_startc || c == ptymodes.tchars.t_stopc || c == ptymodes.tchars.t_eofc || c == ptymodes.tchars.t_brkc || c == ptymodes.ltchars.t_suspc || c == ptymodes.ltchars.t_dsuspc || c == ptymodes.ltchars.t_rprntc || c == ptymodes.ltchars.t_flushc || c == ptymodes.ltchars.t_werasc || c == ptymodes.ltchars.t_lnextc) *p++ = ptymodes.ltchars.t_lnextc; *p++ = c; } echotype = ECHO_NL; if (p == buf || p[-1] != '\n') { *p++ = ptymodes.tchars.t_eofc; echotype = ECHO_EOF; } buflen = p - buf; i = write(ptyfd, buf, buflen); if (i < 0) { if (errno != EWOULDBLOCK) fatal("pty write"); } else if (i < buflen) { for (p = buf ; p < buf + buflen ; p++) { if (*p == ptymodes.ltchars.t_lnextc) p++; ip->nwrt++; } } else { inputq = ip->next; free((char *)ip); } if (ptymodes.sgtty.sg_flags & ECHO) { echoflag = echotype; } } #endif } /* * Locate the end of the echoed characters. */ int stripecho(type, p, n) char *p; { register char *q; q = p; if (type == ECHO_NL) { do { if (--n < 0) return 0; } while (*q++ != '\n'); } else if (type == ECHO_EOF) { do { if (--n <= 0) return 0; } while (*q++ != '\b' || *q != '\b'); q++; } else { if (n < 2 || p[0] != '^' || p[1] != (sigchar ^ 0100)) return 0; q += 2; } return q - p; } /* * Clear pty input queue. The input queue is the one we write to. */ void flushptyin() { struct indata *ip; while (inputq != NULL) { ip = inputq; inputq = ip->next; free((char *)ip); } /* ioctl(ptyfd, TIOCFLUSH, (char *)&mode); */ } /* * Signal handler invoked when a child dies or is stopped. */ void onchild() { int pid; int status; int e = errno; if ((pid = wait3(&status, WNOHANG|WUNTRACED, (struct rusage *)0)) == childpid) { childstatus = status; gotsigchild = 1; if (canjump) longjmp(sigjmp, 1); } errno = e; } /* * Signal handler called when the terminal changes size. */ void onwinch() { gotsigwinch = 1; if (canjump) longjmp(sigjmp, 1); } /* * Called to process data received from the real tty. */ void indata(p, n) char *p; { if (noedit) { senddata(p, n); #ifdef REMOTE if (ptymodes.sgtty.sg_flags & ECHO) { outchars(p, n); writereal(); } #endif } else { while (--n >= 0) inchar(*p++); refresh(); if (needclearin) { clearinput(); needclearin = 0; } if (outrsize) writereal(); } } clearinput() { edflush(); flushptyin(); } /* * Send a signal to the user process. */ void sendsig(signo) { #ifdef REMOTE int pgrp; int zero = 0; if (ioctl(ptyfd, TIOCFLUSH, (char *)&zero) < 0) fatal("TIOCFLUSH"); needclearin++; if (ioctl(ptyfd, TIOCGPGRP, (char *)&pgrp) < 0) fatal("TIOCGPRGP"); killpg(pgrp, signo); #else char c; switch (signo) { case SIGINT: c = ttymode.intr; break; case SIGQUIT: c = ttymode.quit; break; case SIGTSTP: c = ttymode.susp; break; default: fatal("sendsig arg"); } if ((ptymodes.lmode & LNOFLSH) == 0 && signo != SIGTSTP) flushoutput(); echoflag = ECHO_SIG; sigchar = c; if (write(ptyfd, &sigchar, 1) != 1) fatal("sendsig write"); #endif } void flushoutput() { int two = 2; outrp = outrnext = outrbuf; outrsize = 0; ioctl(2, TIOCFLUSH, (char *)&two); } puttty(c) char c; { write(2, &c, 1); } puttstr(s) char *s; { write(2, s, strlen(s)); } /* * Called when the tty modes of the pty are changed by the user. */ #define SGCOPY (ALLDELAY|RAW) /* bits to copy from pty to real tty */ void newttymodes() { char startc, stopc; int changereal; int flags; if (getttymodes(ptyfd, &ptymodes) < 0) fatal("get pty modes"); noedit = 0; #ifdef notdef if (ptymodes.sgtty.sg_flags & (RAW|CBREAK)) #else if ((ptymodes.sgtty.sg_flags & (RAW|CBREAK|ODDP)) != ODDP) #endif noedit = 1; changereal = 0; startc = ptymodes.tchars.t_startc; stopc = ptymodes.tchars.t_stopc; #ifdef notdef if (ptymodes.sgtty.sg_flags & RAW) startc = stopc = -1; #endif if (realtty.tchars.t_startc != startc || realtty.tchars.t_stopc != stopc) { realtty.tchars.t_startc = startc; realtty.tchars.t_stopc = stopc; changereal = 1; } flags = ptymodes.sgtty.sg_flags & SGCOPY; if ((flags & TBDELAY) == XTABS) flags &=~ TBDELAY; if ((realtty.sgtty.sg_flags & SGCOPY) != flags) { realtty.sgtty.sg_flags &=~ SGCOPY; realtty.sgtty.sg_flags |= flags; changereal = 1; } if ((realtty.lmode & LDECCTQ) != (ptymodes.lmode & DECCTQ)) { realtty.lmode &=~ LDECCTQ; if (ptymodes.lmode & DECCTQ) realtty.lmode |= LDECCTQ; changereal = 1; } if (changereal) { if (setttymodes(2, &realtty) < 0) fatal("change real"); } if (ttymode.intr != ptymodes.tchars.t_intrc || ttymode.quit != ptymodes.tchars.t_quitc || ttymode.erase!= ptymodes.sgtty.sg_erase || ttymode.kill != ptymodes.sgtty.sg_kill || ttymode.eof != ptymodes.tchars.t_eofc || ttymode.susp != ptymodes.ltchars.t_suspc || ttymode.echo != (ptymodes.sgtty.sg_flags & ECHO)) { fillttymode(); newttychars(); } } void fillttymode() { ttymode.intr = ptymodes.tchars.t_intrc; ttymode.quit = ptymodes.tchars.t_quitc; ttymode.erase= ptymodes.sgtty.sg_erase; ttymode.kill = ptymodes.sgtty.sg_kill; ttymode.eof = ptymodes.tchars.t_eofc; ttymode.susp = ptymodes.ltchars.t_suspc; ttymode.echo = ptymodes.sgtty.sg_flags & ECHO; } /* * A unexpected error occurred. */ void fatal(msg) char *msg; { int e = errno; cleanup(); errno = e; perror(msg); fflush(stderr); abort(); } /* * Called if initialization fails. */ void badinit(msg) char *msg; { cleanup(); fputs(msg, stderr); putc('\n', stderr); exit(2); } /* * Exit the program, resetting the terminal modes first. Status is the * exit status. */ void done(status) { cleanup(); exit(status); } void cleanup() { if (gottty) { setttymodes(2, &origtty); realfcntl &=~ FNDELAY; fcntl(2, F_SETFL, realfcntl); } } int getttymodes(fd, tty) int fd; struct tty *tty; { if (ioctl(fd, TIOCGETD, (char *)&tty->ldisc) < 0 || ioctl(fd, TIOCGETP, (char *)&tty->sgtty) < 0 || ioctl(fd, TIOCGETC, (char *)&tty->tchars) < 0 || ioctl(fd, TIOCLGET, (char *)&tty->lmode) < 0 || ioctl(fd, TIOCGLTC, (char *)&tty->ltchars) < 0) return -1; return 0; } int setttymodes(fd, tty) int fd; struct tty *tty; { if (ioctl(fd, TIOCSETD, (char *)&tty->ldisc) < 0 || ioctl(fd, TIOCSETP, (char *)&tty->sgtty) < 0 || ioctl(fd, TIOCSETC, (char *)&tty->tchars) < 0 || ioctl(fd, TIOCLSET, (char *)&tty->lmode) < 0 || ioctl(fd, TIOCSLTC, (char *)&tty->ltchars) < 0) return -1; return 0; } /* * Copy the size of the tty from the real device to the pty. * Returns the number of columns. */ int copyttysize() { #ifdef TIOCGWINSZ struct winsize size; if (ioctl(2, TIOCGWINSZ, (char *)&size) >= 0) ioctl(ptyfd, TIOCSWINSZ, (char *)&size); return size.ws_col; #else return 0; #endif } EOF if test `wc -c < atty.c` -ne 22147 then echo 'atty.c is the wrong size' fi echo extracting attyed.h cat > attyed.h <<\EOF /* * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of atty, which is distributed under the terms specified * by the Atty General Public License. See the file named LICENSE. * * This file defines the interface between the editor and the rest of * atty. First come things defined in atty and exported to the editor. */ #define MAXLLEN 254 /* maximum line length (excluding newline) */ #define COLUMNS 132 /* maximum screen width we expect */ struct mode { int intr; /* interrupt character */ int quit; /* quit character */ int erase; /* erase character */ int kill; /* kill character */ int eof; /* eof character */ int susp; /* suspend character */ int echo; /* set if echo turned on */ }; extern struct mode ttymode; /* various info about the tty */ extern short ospeed; /* output speed (as returned by gtty) */ extern int promptset; /* true if prompt specified by the user */ extern char prompt[COLUMNS]; /* the prompt, if any */ extern int column; /* column for writing output to (see below) */ extern char outline[COLUMNS]; /* current output line */ extern char lastoutline[COLUMNS]; /* last complete line of output */ extern int lastoutlinelen; /* lenght of lastoutline */ #ifdef __STDC__ void ttyoutc(int); /* write a character to the tty */ void outreal(char *, int); /* write a block of characters to the tty */ void senddata(char *, int); /* send a line to the process */ void badinit(char *); /* edinit failed; print message and exit */ #else void ttyoutc(); void outreal(); void senddata(); void badinit(); #endif /* * Now routines provide by the editor. */ #ifdef __STDC__ void edinit(char *); /* initialize the editor */ void newttychars(void); /* called when ttymode changes */ void newscrnwidth(int); /* called when the screen width changes */ void inchar(int); /* called to input a character */ void edflush(void); /* discard any input */ void refresh(void); /* turn display on and update it */ void dispoff(int); /* turn display off */ void freezedisp(int); /* freeze the editor display */ void insertchars(char *, int); /* insert characters into input buffer */ #else void edinit(); void newttychars(); void newscrnwidth(); void inchar(); void edflush(); void refresh(); void dispoff(); void freezedisp(); void insertchars(); #endif EOF if test `wc -c < attyed.h` -ne 2378 then echo 'attyed.h is the wrong size' fi echo extracting bind.h cat > bind.h <<\EOF /* * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of atty, which is distributed under the terms specified * by the Atty General Public License. See the file named LICENSE. */ #define BINDMAGIC 300 /* magic number at start of .bindc file */ #define C_FUNC 0 /* start of functions */ #define C_PFXTBL 1 /* prefix table */ #define C_INSERT 2 /* insert string */ #define C_UNDEF 3 /* not defined */ #define NSYNTAX 3 /* number of syntax classes */ EOF if test `wc -c < bind.h` -ne 495 then echo 'bind.h is the wrong size' fi echo extracting bindc.5 cat > bindc.5 <<\EOF .TH BINDC 5 .SH NAME bindc \- compiled bindings file .SH COPYRIGHT .if n Copyright (C) 1989 by Kenneth Almquist. .if t Copyright \(co 1989 by Kenneth Almquist. .SH DESCRIPTION The manual page describes the format of the compiled binding files produced by .IR kbind (1) and used by .IR atty (1). The format assumes eight bit characters. Short integers are stored in two bytes, with the low order byte first. .PP The overall format of the file is: .in +1i .nf Magic number (300) (2 bytes) Version number of the bindc format (1 byte) Number of keymaps (1 bytes) Size of string area (2 bytes) Size of abbreviations (2 bytes) Keymap ... Word syntax bitmap (16 bytes) File name syntax bitmap (16 bytes) Abbrev syntax bitmap (16 bytes) String area Abbreviations area .fi .in -1i .PP A keymap consists of two 128 byte arrays, which are indexed by the input character. The first array specifies the type of operation to be performed when that key is typed. C_FUNC specifies that a function is to be performed. The number of the function is given by the second array. C_PFXTBL specifies that the next key is to be interpreted using the keymap whose number is given by the second array. (This is used to implement multicharacter commands.) \ C_INSERT causes a string in the string area to be inserted. The number of the string appears in the second array. C_UNDEF indicates that this character is undefined. .PP The string table consists of a series of strings laid end to end. Each string is preceded by a single byte giving the length of the string. The length byte is used instead of a nul terminator to allow nul characters to be included in strings. The abbreviations area contains pairs of strings laid end to end. Each string is preceded by a byte giving the length of the string. The first string in each pair is the name of an abbreviation; the second is the string that the abbreviation expands to. .SH AUTHORS Kenneth Almquist .SH "SEE ALSO" .IR kbind (1), .IR atty (1). EOF if test `wc -c < bindc.5` -ne 1987 then echo 'bindc.5 is the wrong size' fi echo extracting ed.h cat > ed.h <<\EOF /* * Stuff shared between ed.c and update.c. * * Copyright (C) 1989 by Kenneth Almquist. All rights reserved. * This file is part of atty, which is distributed under the terms specified * by the Atty General Public License. See the file named LICENSE. */ extern char curline[MAXLLEN+1]; /* line being edited */ extern char *point; /* cursor location within line */ extern char *endcurline; /* end of current line */ extern int needbeep; /* set to request beep */ extern char editprompt; /* prompt for recursive edit */ #ifdef __STDC__ void gettermcap(void); void movetoend(void); #else void gettermcap(); void movetoend(); #endif EOF if test `wc -c < ed.h` -ne 644 then echo 'ed.h is the wrong size' fi echo Archive 1 unpacked exit -- Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.