home *** CD-ROM | disk | FTP | other *** search
- /* Copyright 1990, Daniel J. Bernstein. All rights reserved. */
-
- #include <sys/types.h>
- #include <sys/time.h>
- #include <sys/resource.h>
- #include <sys/wait.h>
- #include <stdio.h>
- #include "err.h"
- #include "config.h"
- #include "pty.h"
- #include "master.h"
- #include "sig.h"
- #include "tty.h"
- #include "file.h"
- #include "sock.h"
- #include "logs.h"
- #include "misc.h"
-
- static char fnre[20];
-
- static char fnsess[20];
- static int fdsess;
-
- static char *glfnsty;
-
- static char soutbuf[OUTBUFSIZE];
- static char sptybuf[OUTBUFSIZE];
-
- static struct ttymodes tmowinpty;
- static struct ttymodes tmowintty;
-
- static char *outbuf = soutbuf;
- static int outbufsiz = OUTBUFSIZE;
- static int outbuflen = 0;
- static char *ptybuf = sptybuf;
- static int ptybufsiz = OUTBUFSIZE;
- static int ptybuflen = 0;
-
- static int flagconnected = 1; /* 0: disconnected. 2: idling for stop. */
- /* 3: idling for stop but child is dead. */
- static int flagchild = 1; /* 0: dead. 2: stopped. */
- static int childsig; /* signal that stopped/killed child */
- static int flagsigler = 1; /* 0: dead. */
- static int siglerpid; /* only defined if flagconnected */
- static int slavepid;
-
- static int flagqwinch = 0;
-
- static void quickdeath(i)
- int i;
- {
- /* All exits from master() go through here. */
- if (flagsession) (void) unlink(fnsess);
- if (flagxchown)
- (void) fchown(fdsty,PTYOWNER,PTYGROUP);
- (void) fchmod(fdsty,UNUSEDPTYMODE);
- date = now();
- if (flagxutmp)
- if (utmp(glfnsty + PTYUTMP_OFFSET,"","",date) == -1)
- ; /* too bad. */
- if (flagxwtmp)
- if (wtmp(glfnsty + PTYWTMP_OFFSET,"","",date) == -1)
- ; /* too bad. */
- fatal(i);
- }
-
- static void death(i)
- int i;
- {
- (void) kill(siglerpid,SIGTERM);
- /* XXX: should wait while flagsigler */
- quickdeath(i);
- }
-
- /*ARGSUSED*/
- static void sig_force(i)
- sig_num i;
- {
- /* Forced death, presumably from the sesskill program. */
- sig_ignore(SIGCHLD);
- /* XXX: Should we test for !flagchild here? sesskill does. */
- flagchild = 0;
- quickdeath(SIGCHLD);
- }
-
- /*ARGSUSED*/
- static void sig_usr2(i)
- sig_num i;
- {
- if (flagsession)
- {
- int newuid = uid;
- char newsuid[10];
- char foo[100];
-
- /* XXX: We should have some error recovery here! */
-
- (void) lseek(fdsess,(long) 0,0);
- (void) read(fdsess,(char *) &newuid,sizeof(int));
- (void) sprintf(newsuid,"%d",newuid);
-
- (void) chdir("..");
- if (chdir(newsuid) == -1)
- {
- (void) mkdir(newsuid,0700);
- (void) chdir(newsuid);
- }
-
- (void) sprintf(foo,"../%d/%s",uid,fnsess);
- (void) rename(foo,fnsess);
-
- (void) sprintf(foo,"../%d/%s",uid,fnre);
- (void) rename(foo,fnre); /* in case we're already disconnected */
-
- uid = newuid;
- (void) setreuid(uid,euid);
- setusername();
-
- if (flagxutmp)
- if (utmp(glfnsty + PTYUTMP_OFFSET,username,PTYUTMP_SWHOST,date) == -1)
- ; /* too bad. */
- if (flagxwtmp)
- if (wtmp(glfnsty + PTYWTMP_OFFSET,username,PTYWTMP_SWHOST,date) == -1)
- ; /* too bad. */
- if (flagsigler)
- (void) kill(siglerpid,SIGUSR2);
- }
- }
-
- /*ARGSUSED*/
- static void sig_pipe(i)
- sig_num i;
- {
- flagsigler = 0; /* XXX: is this appropriate? race? */
- /* Will end up giving child HUP. */
- }
-
- /*ARGSUSED*/
- static void sig_chld(i)
- sig_num i;
- {
- union wait w;
-
- if (wait3(&w,WNOHANG | WUNTRACED,(struct rusage *) 0) <= 0)
- return; /* why'd we get the CHLD? it must have stopped & restarted? */
-
- if (w.w_stopval == WSTOPPED)
- {
- childsig = w.w_stopsig;
- flagchild = 2;
- }
- else
- {
- childsig = w.w_termsig; /* can't do much with this */
- flagchild = 0;
- }
- }
-
- /*ARGSUSED*/
- static void sig_term(i)
- sig_num i;
- {
- flagsigler = 0;
- }
-
- /* If we have made it to being master, we should never get TTIN or TTOU, */
- /* except possibly while restarting after a stop (e.g., if the user puts */
- /* us back into the background). But we let the signaller handle putting */
- /* the tty modes back before restarting us, so we should never, ever, */
- /* ever get a TTIN or TTOU. If the user is messing around and we do get */
- /* a TTIN or TTOU, we'll just pretend the child died and hope we get */
- /* around to telling the signaller about it. */
-
- /*ARGSUSED*/
- static void sig_ttin(i)
- sig_num i;
- {
- if (flagchild)
- {
- childsig = SIGTTIN;
- flagchild = 2;
- }
- }
-
- /*ARGSUSED*/
- static void sig_ttou(i)
- sig_num i;
- {
- if (flagchild)
- {
- childsig = SIGTTOU;
- flagchild = 2;
- }
- }
-
- /*ARGSUSED*/
- static void sig_tstp(i)
- sig_num i;
- {
- if (flagchild)
- {
- childsig = SIGCONT;
- flagchild = 2;
- }
- }
-
- /* Most job-control shells (including csh) behave absolutely miserably. */
- /* (Well, that goes without saying.) In particular, rather than sending */
- /* a CONT to every one of their children in the process group, they feel */
- /* a need to kill the entire process group. Grrrr. Because of this, we */
- /* are forced to use the nonintuitive USR1 to communicate CONT, and ignore */
- /* CONT entirely. Anyway, read cont as usr1 where necessary. */
-
- /* We can only get USR1 from the signaller (or from us after reconnect). */
- /* By convention, the signaller handles setting the tty modes back to */
- /* chartty, even though we handled restoring the modes before stop. */
-
- /*ARGSUSED*/
- static void sig_cont(i)
- sig_num i;
- {
- if (flagchild)
- {
- flagchild = 1;
- (void) kill(slavepid,SIGCONT);
- (void) kill(pid,SIGWINCH);
- }
- if (flagconnected == 3)
- flagconnected = 1; /* XXX: should be internal to master() */
- (void) setpgrp(0,pgrp);
- }
-
- /* If it weren't for WINCH, which must be in the master if NO_FDPASSING, */
- /* and for the stupid conventions surrounding a process's control tty, */
- /* then all mention of fdtty could disappear from master. This would */
- /* slightly complicate the signaller's T{STP,TIN,TOU} handling but make */
- /* reconnect a lot simpler. Sigh. */
-
- /*ARGSUSED*/
- static void sig_winch(i)
- sig_num i;
- {
- int pg;
-
- flagqwinch = 0;
- #ifdef TTY_WINDOWS
- /* An unfortunate but slight race: Another handler could change the pgrp */
- /* if the child suddenly stops and we're queued for delivery. So we have */
- /* to change it back. */
- pg = getpgrp(0);
- (void) setpgrp(0,pgrp);
- if (!flagsigler)
- flagqwinch = 1;
- else
- if (tty_getmodes(fdsty,&tmopty) == 0)
- if (tty_getmodes(fdtty,&tmowintty) == 0)
- {
- tty_copymodes(&tmowinpty,&tmopty);
- tty_copywin(&tmowinpty,&tmowintty);
- (void) tty_modifymodes(fdsty,&tmowinpty,&tmopty);
- }
- (void) setpgrp(0,pg);
- #endif
- }
-
- static int disconnect(fnsty)
- char *fnsty;
- {
- if (fdtty != -1)
- {
- (void) tty_dissoc(fdtty); /* must succeed */
- (void) close(fdtty);
- fdtty = -1;
- }
- if (fdpass != -1)
- {
- /* We used to write the dot to fdpass here. It's in sigler now, to */
- /* prevent a race condition. */
- (void) close(fdpass);
- fdpass = -1;
- }
- if (fdin != -1)
- {
- (void) close(fdin);
- fdin = -1;
- }
- if (fdout != -1)
- {
- (void) close(fdout);
- fdout = -1;
- }
- if (fdre != -1)
- {
- (void) close(fdre);
- fdre = -1;
- }
-
- fdre = pty_readsock(fnsty,fnre);
- if (fdre == -1)
- return -1; /* damn. */
- return 0;
- }
-
- static int reconnect()
- {
- int t;
- char buf[1];
- char fntty[TTYNAMELEN]; /* sigh */
- int flags = 0;
-
- t = pty_acceptsock(fdre);
- (void) close(fdre);
- fdre = t;
- if (fdre == -1)
- return -1;
-
- #define VCF (void) close(fdre)
- #define BONK(xxx,yyy) if ((xxx) == -1) { VCF; return -1; } else (yyy);
-
- /* What about fd 2 for warnings & errors? No, master doesn't use them. */
-
- /* Must have: in, out, siglerpid, pgrp, flagjobctrl. 1, 2, 16, 32, 256. */
- /* Except if NO_FDPASSING: just flagjobctrl in that case. */
- /* If fdtty, must have also tmochartty, tmotty, fntty. 8: 64, 128, 1024. */
- /* Finally, fdpass is independent of all the rest. */
-
- /* CHANGE: With fdpass, fdin and fdout are irrelevant. */
-
- if (pty_sendint(fdre,'p',&pid) == -1)
- {
- VCF;
- return -1;
- }
-
- while (pty_getch(fdre,buf) == 0)
- switch(buf[0])
- {
- #ifdef NO_FDPASSING
- case 's': BONK(pty_putgetstr(fdre,'s',fntty),flags |= 8) break;
- #else
- case '0': BONK(pty_putgetfd(fdre,'0',&fdin),flags |= 1) break;
- case '1': BONK(pty_putgetfd(fdre,'1',&fdout),flags |= 2) break;
- case 'f': BONK(pty_putgetfd(fdre,'f',&fdpass),flags |= 4) break;
- case 't': BONK(pty_putgetfd(fdre,'t',&fdtty),flags |= 8) break;
- case 's': BONK(pty_putgetstr(fdre,'s',fntty),flags |= 1024) break;
- #endif
- case 'p': BONK(pty_putgetint(fdre,'p',&siglerpid),flags |= 16) break;
- case 'g': BONK(pty_putgetint(fdre,'g',&pgrp),flags |= 32) break;
- case 'c': BONK(pty_putgettty(fdre,'c',&tmochartty),flags |= 64) break;
- case 'n': BONK(pty_putgettty(fdre,'n',&tmotty),flags |= 128) break;
- case 'j': BONK(pty_putgetint(fdre,'j',&flagjobctrl),flags |= 256) break;
- #ifdef NO_FDPASSING
- case ' ': if ((flags & 256) != 256) { VCF; return -1; }
- #else
- case ' ': if (flags & 4) flags |= 3;
- if ((flags & 307) != 307) { VCF; return -1; }
- if (flags & 8) if ((flags & 1024) != 1024) { VCF; return -1; }
- #endif
- if (flags & 8) if ((flags & 192) != 192) { VCF; return -1; }
- VCF; /* yahoo! */
-
- #ifdef NO_FDPASSING
- if ((fdtty = open(fntty,O_RDWR)) == -1)
- return -1;
- if ((fdin = dup(fdre)) == -1)
- {
- (void) close(fdtty);
- fdtty = -1;
- return -1;
- }
- if ((fdout = dup(fdre)) == -1)
- {
- (void) close(fdtty);
- fdtty = -1;
- (void) close(fdout);
- fdout = -1;
- return -1;
- }
- #endif
- (void) close(open(fntty,O_RDWR));
- /* XXX: do we really have to reattach? */
- /* I wish there were no concept of controlling tty. */
- /* Instead, an ioctl on /dev/tty (i.e., fd 3) would */
- /* return a session identifier. */
-
- if (fdpass != -1)
- {
- if (pty_sendint(fdpass,'G',&siglerpid) == -1)
- return -1;
- /* XXX: death(1) might be more intuitive. Then */
- /* again, it may also be much more destructive. */
- if (pty_sendfd(fdpass,'m',&fdmty) == -1)
- return -1;
- if (pty_sendfd(fdpass,'s',&fdsty) == -1)
- return -1;
- }
-
- /* So that we can disconnect again, we have to reset the */
- /* siglerpid in fdsess. That done, we've totally severed */
- /* our previous link to a connection. */
- (void) lseek(fdsess,(long) sizeof(int),0);
- (void) write(fdsess,(char *) &siglerpid,sizeof(int));
-
- flagsigler = 1;
- (void) setpgrp(0,pgrp);
- (void) kill(pid,SIGUSR1); /* grrrr */
- return 0;
- default: (void) pty_putch(fdre," "); break;
- }
- VCF;
- return -1;
- }
-
- struct timeval instant = { 0, 0 };
-
- void master(fnsty,child)
- char *fnsty;
- int child;
- {
- fd_set rfds;
- fd_set wfds;
- int fdnum;
- int r;
-
- /* XXX: is it a race for child to set pty modes? */
-
- /* Note that we don't close fdsty. */
-
- siglerpid = getppid();
- slavepid = child;
- pid = getpid();
- glfnsty = fnsty;
-
- if (flagsession)
- {
- /* Security note: This is the only file we actually create, */
- /* not counting the reconnect socket. */
- (void) sprintf(fnsess,"sess.%s",fnsty + sizeof(DEVSTY) - 3);
- fdsess = open(fnsess,O_RDWR | O_CREAT | O_TRUNC,0600);
- (void) write(fdsess,(char *) &uid,sizeof(int));
- (void) write(fdsess,(char *) &siglerpid,sizeof(int));
- (void) write(fdsess,(char *) &pid,sizeof(int));
- (void) write(fdsess,(char *) &slavepid,sizeof(int));
- /* We'll never actually bother closing fdsess. Who cares? */
- }
-
- sig_ignore(SIGURG);
- sig_ignore(SIGIO);
- sig_ignore(SIGHUP);
- sig_ignore(SIGQUIT);
- sig_ignore(SIGINT);
- sig_sethandler(SIGXCPU,sig_force); sig_handle(SIGXCPU);
- sig_ignore(SIGXFSZ);
- sig_ignore(SIGPROF);
- sig_ignore(SIGVTALRM);
-
- sig_default(SIGEMT); /* XXX: really dump? */
- sig_default(SIGIOT);
- sig_default(SIGTRAP);
- sig_default(SIGSYS);
- sig_default(SIGFPE);
- sig_default(SIGILL);
- sig_default(SIGSEGV);
-
- sig_default(SIGSTOP);
-
- sig_sethandler(SIGTTIN,sig_ttin); sig_handle(SIGTTIN);
- sig_sethandler(SIGTTOU,sig_ttou); sig_handle(SIGTTOU);
- sig_sethandler(SIGTSTP,sig_tstp); sig_handle(SIGTSTP);
- sig_sethandler(SIGUSR1,sig_cont); sig_handle(SIGUSR1);
- sig_ignore(SIGCONT); /* grrrr. see explanation above sig_cont. */
- sig_sethandler(SIGPIPE,sig_pipe); sig_handle(SIGPIPE);
-
- sig_sethandler(SIGCHLD,sig_chld); sig_handle(SIGCHLD);
-
- sig_sethandler(SIGTERM,sig_term); sig_handle(SIGTERM);
- sig_sethandler(SIGWINCH,sig_winch); sig_handle(SIGWINCH);
-
- sig_sethandler(SIGUSR2,sig_usr2); sig_handle(SIGUSR2);
-
- if (fdpass != -1)
- {
- if (pty_sendint(fdpass,'G',&siglerpid) == -1)
- death(1);
- if (pty_sendfd(fdpass,'m',&fdmty) == -1)
- death(1);
- if (pty_sendfd(fdpass,'s',&fdsty) == -1)
- death(1);
- }
-
- #define SET_FDNUM fdnum = fdin; if (fdout > fdnum) fdnum = fdout; \
- if (fdmty > fdnum) fdnum = fdmty; fdnum++;
-
- SET_FDNUM
-
- if (fdpass == -1)
- (void) fcntl(fdmty,F_SETFL,FNDELAY);
- /* If it doesn't work, too bad. */
-
- #ifdef SIGINTERRUPT
- sig_interrupt();
- #endif
-
- for (;;)
- {
- /* Stage 1: Mangle internal states. This could be made into a */
- /* critical section, but there's no point. */
-
- if ((flagconnected == 2) && (flagchild != 2))
- flagconnected = 1 + 2 * (flagchild == 0);
- if ((flagconnected != 0) && (flagsigler == 0))
- {
- flagconnected = 0;
- if (flagsession)
- {
- (void) kill(siglerpid,SIGTERM);
- #ifdef NO_SESSION
- ; /* impossible */
- #else
- if (disconnect(fnsty) == -1)
- quickdeath(1); /* XXX: sigh */
- if (fdnum <= fdre)
- fdnum = fdre + 1;
- #endif
- }
- }
-
- /* Stage 2: Prepare fds, and select(). */
-
- FD_ZERO(&rfds);
- FD_ZERO(&wfds);
-
- if ((fdpass == -1) && (outbuflen < outbufsiz))
- FD_SET(fdmty,&rfds);
- if ((fdpass == -1) && ptybuflen)
- FD_SET(fdmty,&wfds);
- if ((fdpass == -1)
- &&(ptybuflen < ptybufsiz) && (flagsigler == 1)
- &&(flagconnected == 1) && (flagchild == 1))
- FD_SET(fdin,&rfds);
- if ((fdpass == -1)
- &&(outbuflen) && (flagsigler == 1) && (flagconnected == 1))
- FD_SET(fdout,&wfds);
-
- if (flagsession && (flagconnected == 0))
- FD_SET(fdre,&rfds);
-
- /* The times to flush buffers: when the child has stopped and we're */
- /* connected; when the child has died and we're connected; when the */
- /* signaller has died and we don't support sessions. */
- if (((flagconnected == 1) && (flagchild != 1))
- ||((flagconnected == 0) && (flagsession == 0)))
- r = select(fdnum,&rfds,&wfds,(fd_set *) 0,&instant);
- else
- r = select(fdnum,&rfds,&wfds,(fd_set *) 0,(struct timeval *) 0);
-
-
- /* Stage 3: Interpret the results and handle special cases. */
-
- if (r <= 0)
- if (r == -1)
- switch(errno)
- {
- case EBADF: death(1);
- break;
- case EINTR: break; /* fine. */
- case EINVAL: break; /* impossible. */
- default: break; /* say what? */
- }
- else /* r is 0 */
- {
- if (flagconnected == 1) /* flagchild is 0 or 2 */
- if (flagchild == 0)
- break; /* That's it! Child died, and we're outta here! */
- else
- { /* done with flush, time to stop sigler & idle */
- if (flagjobctrl)
- {
- /* As usual, if we don't have a tty, tmotty == tmochartty
- and it won't matter that fdtty is undefined. */
- (void) setpgrp(0,pgrp);
- if (tty_modifymodes(fdtty,&tmotty,&tmochartty) == -1)
- ; /* XXX: what to do? */
- (void) setpgrp(0,pid);
- switch(childsig)
- {
- case SIGSTOP: (void) kill(siglerpid,SIGSTOP); break;
- case SIGTTOU: (void) kill(siglerpid,SIGTTOU); break;
- case SIGTTIN: (void) kill(siglerpid,SIGTTIN); break;
- case SIGTSTP: (void) kill(siglerpid,SIGTSTP); break;
- case SIGCONT: break; /* special case---see sig_tstp */
- default: (void) kill(siglerpid,SIGSTOP); break;
- }
- flagconnected = 2;
- }
- }
- else if (flagconnected == 0) /* non-session, sigler dead */
- break; /* Giving pty pgrp a HUP, ho hum */
- /* Most pgrp-based killing would be more logically done */
- /* one process at a time, i.e., we should give our child */
- /* a signal specially. But nobody else does, so we won't. */
- }
- else
- {
- #ifndef NO_SESSION
- if (flagconnected == 0)
- if (FD_ISSET(fdre,&rfds))
- if (reconnect() == -1)
- {
- if (disconnect(fnsty) == -1)
- quickdeath(1); /* sigh */
- if (fdnum <= fdre)
- fdnum = fdre + 1;
- }
- else
- {
- flagconnected = 1; /* yay! */
- SET_FDNUM
- continue; /* XXX */
- }
- #endif
-
-
- /* Stage 4: Do normal I/O. */
-
- #ifdef SIGINTERRUPT
- sig_startring(); /* blocking? never heard of it */
- #endif
-
- if (FD_ISSET(fdin,&rfds))
- {
- /* ptybuflen must be smaller than ptybufsiz. */
- r = read(fdin,ptybuf + ptybuflen,ptybufsiz - ptybuflen);
- if (r == -1)
- switch(errno)
- {
- case EINTR: case EWOULDBLOCK: break; /* fine */
- default: death(1);
- }
- else if (r == 0) /* EOF */
- {
- ; /* XXX: there's no way to pass an EOF */
- }
- else
- ptybuflen += r;
- }
- if (FD_ISSET(fdmty,&rfds))
- {
- /* outbuflen must be smaller than outbufsiz. */
- r = read(fdmty,outbuf + outbuflen,outbufsiz - outbuflen);
- if (r == -1)
- switch(errno)
- {
- case EINTR: case EWOULDBLOCK: break; /* fine */
- default: death(1);
- }
- else if (r == 0) /* EOF */
- {
- ; /* This can't happen. The slave can't pass an EOF. */
- /* XXX: Should we close fdout anyway? */
- }
- else
- outbuflen += r;
- }
- if (FD_ISSET(fdout,&wfds))
- {
- r = write(fdout,outbuf,outbuflen);
- if (r == -1)
- switch(errno)
- {
- case EINTR: case EWOULDBLOCK: break; /* fine */
- default: death(1);
- }
- else if (r == 0) /* ? */
- ; /* impossible */
- else if (r == outbuflen)
- outbuflen = 0;
- else
- {
- outbuflen -= r;
- copy(outbuf,outbuf + r,outbuflen);
- }
- }
- if (FD_ISSET(fdmty,&wfds))
- {
- r = write(fdmty,ptybuf,ptybuflen);
- if (r == -1)
- switch(errno)
- {
- case EINTR: case EWOULDBLOCK: break; /* fine */
- default: death(1);
- }
- else if (r == 0) /* ? */
- ; /* impossible */
- else if (r == ptybuflen)
- ptybuflen = 0;
- else
- {
- ptybuflen -= r;
- copy(ptybuf,ptybuf + r,ptybuflen);
- }
- }
-
- #ifdef SIGINTERRUPT
- sig_stopring();
- #endif
- }
- }
-
- death(0);
- }
-