This is tty.c in view mode; [Download] [Up]
/* UNIX Tty and Process interface Copyright (C) 1992 Joseph H. Allen This file is part of JOE (Joe's Own Editor) JOE is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. JOE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with JOE; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ /** System include files **/ /* These should exist on every UNIX system */ #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <signal.h> #include <fcntl.h> #include <errno.h> extern int errno; #include <sys/param.h> #include "config.h" /* We use the defines in sys/ioctl to determine what type * tty interface the system uses and what type of system * we actually have. */ #ifdef TTYPOSIX #ifdef SYSPOSIX #include <sys/termios.h> #else #include <termios.h> #endif #else #ifdef TTYSV #ifdef SYSSV #include <sys/termio.h> #else #include <termio.h> #endif #else #include <sgtty.h> #endif #endif /* If the signal SIGVTALRM exists, assume we have the setitimer system call * and the include file necessary for it. I'm not so sure that this method * of detecting 'setitimer' is foolproof, so this is the only place where * SIGVTALRM will be checked... after here the itimer code will look for * ITIMER_REAL (which is defined in sys/time.h). */ #ifndef _SEQUENT_ #ifdef SIGVTALRM #include <sys/time.h> #endif #endif /* I'm not sure if SCO_UNIX and ISC have __svr4__ defined, but I think they might */ #ifdef SCO_UNIX #ifndef __svr4__ #define __svr4__ 1 #endif #endif #ifdef ISC #ifndef __svr4__ #define __svr4__ 1 #endif #endif #ifdef __svr4__ /* I don't think these two are needed if you have 'stropts' (sgi doesn't * even have them). */ /* #include <sys/stream.h> */ /* #include <sys/ptem.h> */ #include <stropts.h> #endif /* JOE include files */ #include "config.h" #include "path.h" #include "tty.h" /* The pwd function */ #ifdef TTYPOSIX char *getcwd(); char *pwd() { static char buf[1024]; return getcwd(buf,1024); } #else #ifdef TTYSV char *getcwd(); char *pwd() { static char buf[1024]; return getcwd(buf,1024); } #else char *getwd(); char *pwd() { static char buf[1024]; return getwd(buf); } #endif #endif /** Aliased defines **/ /* O_NDELAY, O_NONBLOCK, and FNDELAY are all synonyms for placing a descriptor * in non-blocking mode; we make whichever one we have look like O_NDELAY */ #ifndef O_NDELAY #ifdef O_NONBLOCK #define O_NDELAY O_NONBLOCK #endif #ifdef FNDELAY #define O_NDELAY FNDELAY #endif #endif /* Some systems define this, some don't */ #ifndef sigmask #define sigmask(x) (1<<((x)-1)) #endif /* Some BSDs don't have TILDE */ #ifndef TILDE #define TILDE 0 #endif /* Global configuration variables */ int noxon=0; /* Set if ^S/^Q processing should be disabled */ int Baud=0; /* Baud rate from joerc, cmd line or environment */ /* The terminal */ FILE *termin=0; FILE *termout=0; /* Original state of tty */ #ifdef TTYPOSIX struct termios oldterm; #else #ifdef TTYSV static struct termio oldterm; #else static struct sgttyb oarg; static struct tchars otarg; static struct ltchars oltarg; #endif #endif /* Output buffer, index and size */ char *obuf=0; int obufp=0; int obufsiz; /* The baud rate */ unsigned baud; /* Bits per second */ unsigned long upc; /* Microseconds per character */ /* TTY Speed code to baud-rate conversion table (this is dumb- is it really * too much to ask for them to just use an integer for the baud-rate?) */ static int speeds[]= { B50,50,B75,75,B110,110,B134,134,B150,150,B200,200,B300,300,B600,600, B1200,1200,B1800,1800,B2400,2400,B4800,4800,B9600,9600 #ifdef EXTA ,EXTA,19200 #endif #ifdef EXTB ,EXTB,38400 #endif #ifdef B19200 ,B19200,19200 #endif #ifdef B38400 ,B38400,38400 #endif }; /* Input buffer */ int have=0; /* Set if we have pending input */ static unsigned char havec; /* Character read in during pending input check */ int leave=0; /* When set, typeahead checking is disabled */ /* TTY mode flag. 1 for open, 0 for closed */ static int ttymode=0; /* Signal state flag. 1 for joe, 0 for normal */ static int ttysig=0; /* Stuff for shell windows */ static int kbdpid; /* PID of kbd client */ static int ackkbd= -1; /* Editor acks keyboard client to this */ static int mpxfd; /* Editor reads packets from this fd */ static int mpxsfd; /* Clients send packets to this fd */ static int nmpx=0; static int accept=MAXINT; /* =MAXINT if we have last packet */ struct packet { MPX *who; int size; int ch; char data[1024]; } pack; MPX asyncs[NPROC]; /* Versions of 'read' and 'write' which automatically retry during signals * (yuck, yuck, yuck... we the #$%#$@ did they have to do this?) */ int jread(fd,buf,siz) char *buf; { int rt; do rt=read(fd,buf,siz); while(rt<0 && errno==EINTR); return rt; } int jwrite(fd,buf,siz) char *buf; { int rt; do rt=write(fd,buf,siz); while(rt<0 && errno==EINTR); return rt; } /* Set signals for JOE */ void sigjoe() { if(ttysig) return; ttysig=1; signal(SIGHUP,ttsig); signal(SIGTERM,ttsig); signal(SIGINT,SIG_IGN); signal(SIGPIPE,SIG_IGN); } /* Restore signals for exiting */ void signrm() { if(!ttysig) return; ttysig=0; signal(SIGHUP,SIG_DFL); signal(SIGTERM,SIG_DFL); signal(SIGINT,SIG_DFL); signal(SIGPIPE,SIG_DFL); } /* Open terminal and set signals */ void ttopen() { sigjoe(); ttopnn(); } /* Close terminal and restore signals */ void ttclose() { ttclsn(); signrm(); } /* Window size interrupt handler */ static int winched=0; static void winchd() { ++winched; #ifdef SIGWINCH signal(SIGWINCH,winchd); #endif } /* Second ticker */ int ticked=0; extern int dostaupd; static void dotick() { ticked=1; dostaupd=1; } void tickoff() { alarm(0); } #ifdef SA_INTERRUPT struct sigaction vnew={dotick,0,SA_INTERRUPT}; #else #ifdef SV_INTERRUPT struct sigvec vnew={dotick,0,SV_INTERRUPT}; #endif #endif void tickon() { ticked=0; #ifdef SA_INTERRUPT sigaction(SIGALRM,&vnew,(struct sigaction *)0); #else #ifdef SV_INTERRUPT sigvec(SIGALRM,&vnew,(struct sigvec *)0); #else signal(SIGALRM,dotick); #endif #endif alarm(1); } /* Open terminal */ void ttopnn() { int x, bbaud; #ifdef TTYPOSIX struct termios newterm; #else #ifdef TTYSV struct termio newterm; #else struct sgttyb arg; struct tchars targ; struct ltchars ltarg; #endif #endif if(!termin) #ifdef IDLEOUT if(!(termin=stdin) || !(termout=stdout)) #else if(!(termin=fopen("/dev/tty","r")) || !(termout=fopen("/dev/tty","w"))) #endif { fprintf(stderr,"Couldn\'t open /dev/tty\n"); exit(1); } else { #ifdef SIGWINCH signal(SIGWINCH,winchd); #endif tickon(); } if(ttymode) return; ttymode=1; fflush(termout); #ifdef TTYPOSIX tcgetattr(fileno(termin),&oldterm); newterm=oldterm; newterm.c_lflag=0; if(noxon) newterm.c_iflag&=~(ICRNL|IGNCR|INLCR|IXON); else newterm.c_iflag&=~(ICRNL|IGNCR|INLCR); newterm.c_oflag=0; newterm.c_cc[VMIN]=1; newterm.c_cc[VTIME]=0; tcsetattr(fileno(termin),TCSADRAIN,&newterm); bbaud=cfgetospeed(&newterm); #else #ifdef TTYSV ioctl(fileno(termin),TCGETA,&oldterm); newterm=oldterm; newterm.c_lflag=0; if(noxon) newterm.c_iflag&=~(ICRNL|IGNCR|INLCR|IXON); else newterm.c_iflag&=~(ICRNL|IGNCR|INLCR); newterm.c_oflag=0; newterm.c_cc[VMIN]=1; newterm.c_cc[VTIME]=0; ioctl(fileno(termin),TCSETAW,&newterm); bbaud=(newterm.c_cflag&CBAUD); #else ioctl(fileno(termin),TIOCGETP,&arg); ioctl(fileno(termin),TIOCGETC,&targ); ioctl(fileno(termin),TIOCGLTC,<arg); oarg=arg; otarg=targ; oltarg=ltarg; arg.sg_flags=( (arg.sg_flags&~(ECHO|CRMOD|XTABS|ALLDELAY|TILDE) ) | CBREAK) ; if(noxon) targ.t_startc= -1, targ.t_stopc= -1; targ.t_intrc= -1; targ.t_quitc= -1; targ.t_eofc= -1; targ.t_brkc= -1; ltarg.t_suspc= -1; ltarg.t_dsuspc= -1; ltarg.t_rprntc= -1; ltarg.t_flushc= -1; ltarg.t_werasc= -1; ltarg.t_lnextc= -1; ioctl(fileno(termin),TIOCSETN,&arg); ioctl(fileno(termin),TIOCSETC,&targ); ioctl(fileno(termin),TIOCSLTC,<arg); bbaud=arg.sg_ospeed; #endif #endif baud=9600; upc=0; for(x=0;x!=30;x+=2) if(bbaud==speeds[x]) { baud=speeds[x+1]; break; } if(Baud) baud=Baud; upc=DIVIDEND/baud; if(obuf) free(obuf); if(!(TIMES*upc)) obufsiz=4096; else { obufsiz=1000000/(TIMES*upc); if(obufsiz>4096) obufsiz=4096; } if(!obufsiz) obufsiz=1; obuf=(char *)malloc(obufsiz); } /* Close terminal */ void ttclsn() { int oleave; if(ttymode) ttymode=0; else return; oleave=leave; leave=1; ttflsh(); #ifdef TTYPOSIX tcsetattr(fileno(termin),TCSADRAIN,&oldterm); #else #ifdef TTYSV ioctl(fileno(termin),TCSETAW,&oldterm); #else ioctl(fileno(termin),TIOCSETN,&oarg); ioctl(fileno(termin),TIOCSETC,&otarg); ioctl(fileno(termin),TIOCSLTC,&oltarg); #endif #endif leave=oleave; } /* Timer interrupt handler */ static int yep; static void dosig() { yep=1; } /* FLush output and check for typeahead */ #ifdef ITIMER_REAL #ifdef SIG_SETMASK maskit() { sigset_t set; sigemptyset(&set); sigaddset(&set,SIGALRM); sigprocmask(SIG_SETMASK,&set,NULL); } unmaskit() { sigset_t set; sigemptyset(&set); sigprocmask(SIG_SETMASK,&set,NULL); } pauseit() { sigset_t set; sigemptyset(&set); sigsuspend(&set); } #else maskit() { sigsetmask(sigmask(SIGALRM)); } unmaskit() { sigsetmask(0); } pauseit() { sigpause(0); } #endif #endif int ttflsh() { /* Flush output */ if(obufp) { unsigned long usec=obufp*upc; /* No. usecs this write should take */ #ifdef ITIMER_REAL if(usec>=500000/HZ && baud<9600) { struct itimerval a,b; a.it_value.tv_sec=usec/1000000; a.it_value.tv_usec=usec%1000000; a.it_interval.tv_usec=0; a.it_interval.tv_sec=0; alarm(0); signal(SIGALRM,dosig); yep=0; maskit(); setitimer(ITIMER_REAL,&a,&b); jwrite(fileno(termout),obuf,obufp); while(!yep) pauseit(0); unmaskit(); tickon(); } else jwrite(fileno(termout),obuf,obufp); #else jwrite(fileno(termout),obuf,obufp); #ifdef FIORDCHK if(baud<9600 && usec/1000) nap(usec/1000); #endif #endif obufp=0; } /* Ack previous packet */ if(ackkbd!= -1 && accept!=MAXINT && !have) { char c=0; if(pack.who && pack.who->func) jwrite(pack.who->ackfd,&c,1); else jwrite(ackkbd,&c,1); accept=MAXINT; } /* Check for typeahead or next packet */ if(!have && !leave) if(ackkbd!= -1) { fcntl(mpxfd,F_SETFL,O_NDELAY); if(read(mpxfd,&pack,sizeof(struct packet)-1024)>0) { fcntl(mpxfd,F_SETFL,0); jread(mpxfd,pack.data,pack.size); have=1, accept=pack.ch; } else fcntl(mpxfd,F_SETFL,0); } else { /* Set terminal input to non-blocking */ fcntl(fileno(termin),F_SETFL,O_NDELAY); /* Try to read */ if(read(fileno(termin),&havec,1)==1) have=1; /* Set terminal back to blocking */ fcntl(fileno(termin),F_SETFL,0); } return 0; } /* Read next character from input */ void mpxdied(); int ttgetc() { int stat; loop: ttflsh(); while(winched) winched=0, edupd(1), ttflsh(); if(ticked) edupd(0), ttflsh(), tickon(); if(ackkbd!= -1) { if(!have) /* Wait for input */ { stat=read(mpxfd,&pack,sizeof(struct packet)-1024); if(pack.size && stat>0) jread(mpxfd,pack.data,pack.size); else if(stat<1) if(winched || ticked) goto loop; else ttsig(0); accept=pack.ch; } have=0; if(pack.who) /* Got bknd input */ { if(accept!=MAXINT) { if(pack.who->func) pack.who->func(pack.who->object,pack.data,pack.size), edupd(1); } else mpxdied(pack.who); goto loop; } else { if(accept!=MAXINT) return accept; else { ttsig(0); return 0; } } } if(have) have=0; else { if(read(fileno(termin),&havec,1)<1) if(winched || ticked) goto loop; else ttsig(0); } return havec; } /* Write string to output */ void ttputs(s) char *s; { while(*s) { obuf[obufp++]= *s++; if(obufp==obufsiz) ttflsh(); } } /* Get window size */ void ttgtsz(x,y) int *x, *y; { #ifdef TIOCGSIZE struct ttysize getit; #else #ifdef TIOCGWINSZ struct winsize getit; #endif #endif *x=0; *y=0; #ifdef TIOCGSIZE if(ioctl(fileno(termout),TIOCGSIZE,&getit)!= -1) { *x=getit.ts_cols; *y=getit.ts_lines; } #else #ifdef TIOCGWINSZ if(ioctl(fileno(termout),TIOCGWINSZ,&getit)!= -1) { *x=getit.ws_col; *y=getit.ws_row; } #endif #endif } void ttshell(cmd) char *cmd; { int x,omode=ttymode; char *s=getenv("SHELL"); if(!s) return; ttclsn(); if(x=fork()) { if(x!= -1) wait(NULL); if(omode) ttopnn(); } else { signrm(); if(cmd) execl(s,s,"-c",cmd,NULL); else { fprintf(stderr,"You are at the command shell. Type 'exit' to return\n"); execl(s,s,NULL); } _exit(0); } } void ttsusp() { int omode; tickoff(); #ifdef SIGTSTP omode=ttymode; ttclsn(); fprintf(stderr,"You have suspended the program. Type 'fg' to return\n"); kill(0,SIGTSTP); if(ackkbd!= -1) kill(kbdpid,SIGCONT); if(omode) ttopnn(); #else ttshell(NULL); #endif tickon(); } void mpxstart() { int fds[2]; pipe(fds); mpxfd=fds[0]; mpxsfd=fds[1]; pipe(fds); accept=MAXINT; have=0; if(!(kbdpid=fork())) { close(fds[1]); do { unsigned char c; int sta; pack.who=0; sta=jread(fileno(termin),&c,1); if(sta==0) pack.ch=MAXINT; else pack.ch=c; pack.size=0; jwrite(mpxsfd,&pack,sizeof(struct packet)-1024); } while(jread(fds[0],&pack,1)==1); _exit(0); } close(fds[0]); ackkbd=fds[1]; } void mpxend() { kill(kbdpid,9); while(wait(NULL)<0 && errno==EINTR); close(ackkbd); ackkbd= -1; close(mpxfd); close(mpxsfd); if(have) havec=pack.ch; } /* Get a pty/tty pair. Returns open pty in 'ptyfd' and returns tty name * string in static buffer or NULL if couldn't get a pair. */ #ifdef sgi /* Newer sgi machines can do it the __svr4__ way, but old ones can't */ extern char *_getpty(); char *getpty(ptyfd) int *ptyfd; { return _getpty(ptyfd,O_RDWR,0600,0); } #else #ifdef __svr4__ /* Strange streams way */ extern char *ptsname(); char *getpty(ptyfd) int *ptyfd; { int fdm; char *name; *ptyfd=fdm=open("/dev/ptmx",O_RDWR); grantpt(fdm); unlockpt(fdm); return ptsname(fdm); } #else /* The normal way: for each possible pty/tty pair, try to open the pty and * then the corresponding tty. If both could be opened, close them both and * then re-open the pty. If that succeeded, return with the opened pty and the * name of the tty. * * Logically you should only have to succeed in opening the pty- but the * permissions may be set wrong on the tty, so we have to try that too. * We close them both and re-open the pty because we want the forked process * to open the tty- that way it gets to be the controlling tty for that * process and the process gets to be the session leader. */ char *getpty(ptyfd) int *ptyfd; { int x, fd; char *orgpwd=pwd(); static char **ptys=0; static char *ttydir; static char *ptydir; static char ttyname[32]; if(!ptys) { ttydir="/dev/pty/"; ptydir="/dev/ptym/"; /* HPUX systems */ if(chpwd(ptydir) || !(ptys=rexpnd("pty*"))) if(!ptys) { ttydir=ptydir="/dev/"; /* Everyone else */ if(!chpwd(ptydir)) ptys=rexpnd("pty*"); } } chpwd(orgpwd); if(ptys) for(fd=0;ptys[fd];++fd) { zcpy(ttyname,ptydir); zcat(ttyname,ptys[fd]); if((*ptyfd=open(ttyname,O_RDWR))>=0) { ptys[fd][0]='t'; zcpy(ttyname,ttydir); zcat(ttyname,ptys[fd]); ptys[fd][0]='p'; x=open(ttyname,O_RDWR); if(x>=0) { close(x); close(*ptyfd); zcpy(ttyname,ptydir); zcat(ttyname,ptys[fd]); *ptyfd=open(ttyname,O_RDWR); ptys[fd][0]='t'; zcpy(ttyname,ttydir); zcat(ttyname,ptys[fd]); ptys[fd][0]='p'; return ttyname; } else close(*ptyfd); } } return 0; } #endif #endif int dead=0; void death() { wait(NULL); dead=1; } #ifndef SIGCHLD #define SIGCHLD SIGCLD #endif #ifdef SA_INTERRUPT struct sigaction inew={death,0,SA_INTERRUPT}; #else #ifdef SV_INTERRUPT struct sigvec inew={death,0,SV_INTERRUPT}; #endif #endif MPX *mpxmk(ptyfd,cmd,args,func,object,die,dieobj) int *ptyfd; char *cmd; char *args[]; void (*func)(); void *object; void (*die)(); void *dieobj; { int fds[2]; int comm[2]; int pid; int x; MPX *m; char *name; if(!(name=getpty(ptyfd))) return 0; for(x=0;x!=NPROC;++x) if(!asyncs[x].func) { m=asyncs+x; goto ok; } return 0; ok: ttflsh(); ++nmpx; if(ackkbd== -1) mpxstart(); m->func=func; m->object=object; m->die=die; m->dieobj=dieobj; pipe(fds); pipe(comm); m->ackfd=fds[1]; if(!(m->kpid=fork())) { close(fds[1]); close(comm[0]); dead=0; #ifdef SA_INTERRUPT sigaction(SIGCHLD,&inew,(struct sigaction *)0); #else #ifdef SV_INTERRUPT sigvec(SIGCHLD,&inew,(struct sigvec *)0); #else signal(SIGCHLD,death); #endif #endif if(!(pid=fork())) { signrm(); close(*ptyfd); #ifdef TIOCNOTTY x=open("/dev/tty",O_RDWR); ioctl(x,TIOCNOTTY,0); #endif setpgrp(0,0); for(x=0;x!=32;++x) close(x); /* Yes, this is quite a kludge... all in the name of portability */ if((x=open(name,O_RDWR))!= -1) /* Standard input */ { #ifdef __svr4__ ioctl(x,I_PUSH,"ptem"); ioctl(x,I_PUSH,"ldterm"); #endif dup(x); dup(x); /* Standard output, standard error */ /* (yes, stdin, stdout, and stderr must all be open for reading and * writing. On some systems the shell assumes this */ /* We could probably have a special TTY set-up for JOE, but for now * we'll just use the TTY setup for the TTY was was run on */ #ifdef TTYPOSIX tcsetattr(0,TCSADRAIN,&oldterm); #else #ifdef TTYSV ioctl(0,TCSETAW,&oldterm); #else ioctl(0,TIOCSETN,&oarg); ioctl(0,TIOCSETC,&otarg); ioctl(0,TIOCSLTC,&oltarg); #endif #endif /* Execute the shell */ execv(cmd,args); } _exit(0); } jwrite(comm[1],&pid,sizeof(int)); loop: pack.who=m; pack.ch=0; if(dead) pack.size=0; else pack.size=read(*ptyfd,pack.data,1024); if(pack.size>0) { jwrite(mpxsfd,&pack,sizeof(struct packet)-1024+pack.size); jread(fds[0],&pack,1); goto loop; } else { pack.ch=MAXINT; pack.size=0; jwrite(mpxsfd,&pack,sizeof(struct packet)-1024); _exit(0); } } jread(comm[0],&m->pid,sizeof(int)); close(comm[0]); close(comm[1]); close(fds[0]); return m; } void mpxdied(m) MPX *m; { if(!--nmpx) mpxend(); while(wait(NULL)<0 && errno==EINTR); if(m->die) m->die(m->dieobj); m->func=0; edupd(1); }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.