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.