home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 October
/
usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso
/
unix
/
volume22
/
pty
/
part02
/
master.c
< prev
next >
Wrap
C/C++ Source or Header
|
1990-10-09
|
18KB
|
712 lines
/* 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; }
#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
VCF; /* yahoo! */
(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);
}