/* Copyright (c) 1987,1988 Oliver Laumann, Technical University of Berlin.
 * Not derived from licensed software.
 *
 * Permission is granted to freely use, copy, modify, and redistribute
 * this software, provided that no attempt is made to gain profit from it,
 * the author is not construed to be liable for any results of using the
 * software, alterations are clearly marked as such, and this notice is
 * not modified.
 */

/* This program Copyright 1990 by Howard Chu.
 *
 * This is rmgr, a remote window manager for Bellcore's MGR window
 * manager, by Howard Chu @ University of Michigan (Ann Arbor, MI).
 * Much of this program consists of Oliver Laumann's code; I have not
 * (usually) highlighted my changes in any way. Oliver didn't use any
 * comments at all; for the most part I have followed his lead...  }-)
 * Suffice to say, some of the routines are original, some are heavily
 * hacked upon versions of his code, and some are untouched from his
 * program. Of course, the volume of code here represents only half
 * of the code used in the screen program; the rest, support for ANSI
 * terminal emulation, was discarded.
 */

static char Version[] = "$Header: /usr/src/rmgr/RCS/rmgr.c,v 1.5 90/10/15 03:39:02 hyc Stable $";

#include <stdio.h>
#include <sgtty.h>
#include <signal.h>
#include <errno.h>
#include <ctype.h>
#include <utmp.h>
#include <pwd.h>
#include <nlist.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include "rmgr.h"

#ifdef GETTTYENT
#   include <ttyent.h>
#else
    static struct ttyent {
	char *ty_name;
    } *getttyent();
    static char *tt, *ttnext;
    static char ttys[] = "/etc/ttys";
#endif

#define MAXWIN     10
#define MSGWAIT     5

#define Ctrl(c) ((c)&037)

extern char **environ;
extern errno;
extern char *index(), *rindex(), *malloc(), *getenv(), *MakeTermcap();
extern char *getlogin(), *ttyname();
static void AttacherFinit(), Finit(), SigHup(), SigChld();
static char *Filename(), **SaveArgs(), *GetTtyName();

static char PtyName[32], TtyName[32];
static char *ShellProg;
static char *ShellArgs[2];
static char inbuf[IOSIZE];
static inlen;
static ESCseen;
static GotSignal;
static char DefaultShell[] = "/bin/sh";
static char DefaultPath[] = ":/usr/ucb:/bin:/usr/bin";
static char PtyProto[] = "/dev/ptyXY";
static char TtyProto[] = "/dev/ttyXY";
static int TtyMode = 0622;
static char SockPath[512];
static char SockDir[] = ".rmgr";
static char *SockNamePtr, *SockName;
static ServerSocket;
static char *NewEnv[MAXARGS];
static char Esc = Ctrl('a');
static char *home;
static HasWindow;
static utmp, utmpf;
static char UtmpName[] = "/etc/utmp";
static char *LoginName;
static char HostName[MAXSTR];
static Detached;
static AttacherPid;	/* Non-Zero in child if we have an attacher */
static DevTty;
static char EventStr[]="%c%cA";
static char CommNames[MAXLINE];
static int VoluntaryDetach = 0;
static int MyFont;

struct mode {
    struct sgttyb m_ttyb;
    struct tchars m_tchars;
    struct ltchars m_ltchars;
    int m_ldisc;
    int m_lmode;
} OldMode, NewMode;

static struct win *curr, *other;
static CurrNum, OtherNum;
static struct win *wtab[MAXWIN];

main (ac, av) char **av; {
    register n, len;
    register struct win **pp, *p;
    char *ap;
    static InitMenu();
    int s, r, w, x = 0;
    int rflag = 0;
    struct timeval tv;
    time_t now;
    char buf[IOSIZE], *myname = (ac == 0) ? "rmgr" : av[0];
    struct stat st;
    char *winname="";

    while (ac > 0) {
	ap = *++av;
	if (--ac > 0 && *ap == '-') {
	    switch (ap[1]) {
	    case 'r':
		rflag = 1;
		if (ap[2]) {
		    SockName = ap+2;
		    if (ac != 1) goto help;
		} else if (--ac == 1) {
		    SockName = *++av;
		} else if (ac != 0) goto help;
		break;
	    case 'n':
		if (ap[2]) {
		    ap += 2;
		} else {
		    if (--ac == 0) goto help;
		    ap = *++av;
		}
		winname=ap;
		break;
	    default:
	    help:
		Msg (0, "Use: %s [-n windowname] [cmd args]\n\
 or: %s -r [host.tty]", myname, myname);
	    }
	} else break;
    }
    CommNames[0]='}';
    if ((ShellProg = getenv ("SHELL")) == 0)
	ShellProg = DefaultShell;
    ShellArgs[0] = ShellProg;
    if (ac == 0) {
	ac = 1;
	av = ShellArgs;
    }
    if ((home = getenv ("HOME")) == 0)
	Msg (0, "$HOME is undefined.");
    sprintf (SockPath, "%s/%s", home, SockDir);
    if (stat (SockPath, &st) == -1) {
	if (errno == ENOENT) {
	    if (mkdir (SockPath, 0700) == -1)
		Msg (errno, "Cannot make directory %s", SockPath);
	    (void) chown (SockPath, getuid (), getgid ());
	} else Msg (errno, "Cannot get status of %s", SockPath);
    } else {
	if ((st.st_mode & S_IFMT) != S_IFDIR)
	    Msg (0, "%s is not a directory.", SockPath);
	if ((st.st_mode & 0777) != 0700)
	    Msg (0, "Directory %s must have mode 700.", SockPath);
	if (st.st_uid != getuid ())
	    Msg (0, "You are not the owner of %s.", SockPath);
    }
    (void) gethostname (HostName, MAXSTR);
    HostName[MAXSTR-1] = '\0';
    if (ap = index (HostName, '.'))
	*ap = '\0';
    strcat (SockPath, "/");
    SockNamePtr = SockPath + strlen (SockPath);
    if ((DevTty = open ("/dev/tty", O_RDWR|O_NDELAY)) == -1)
	Msg (errno, "/dev/tty");
    if (rflag) {
	Attach (MSG_ATTACH);
	Attacher ();
	/*NOTREACHED*/
    }
    if (GetSockName ()) {
	s = MakeClientSocket (1);
	SendCreateMsg (s, ac, av, winname);
	close (s);
	exit (0);
    }
    switch (fork ()) {
    case -1:
	Msg (errno, "fork");
	/*NOTREACHED*/
    case 0:
	break;
    default:
	Attacher ();
	/*NOTREACHED*/
    }
    AttacherPid = getppid ();
    ServerSocket = s = MakeServerSocket ();
    InitTerm ();
    InitMenu ();
    MakeNewEnv ();
    GetTTY (0, &OldMode);
    InitUtmp ();
    signal (SIGHUP, SigHup);
    signal (SIGINT, Finit);
    signal (SIGQUIT, Finit);
    signal (SIGTERM, Finit);
    signal (SIGTTIN, SIG_IGN);
    signal (SIGTTOU, SIG_IGN);
    if ((n = MakeWindow (*av, av, winname, (char *)0)) == -1) {
	SetTTY (0, &OldMode);
	FinitTerm ();
	Kill (AttacherPid, SIGHUP);
	exit (1);
    }
    SetCurrWindow (n);
    HasWindow = 1;
    SetMode (&OldMode, &NewMode);
    SetTTY (0, &NewMode);
    signal (SIGCHLD, SigChld);
    tv.tv_usec = 0;
    while (1) {
	r = 0;
	w = 0;
	if (inlen && curr->wpid>=0)
	    w |= 1 << curr->ptyfd;
	else
	    r |= 1 << 0;
	for (pp = wtab; pp < wtab+MAXWIN; ++pp) {
	    if (!(p = *pp))
		continue;
	    if (p->wpid >=0)
	    	r |= 1 << p->ptyfd;
	}
	r |= 1 << s;
	(void) fflush (stdout);
	if (GotSignal) {
	    SigHandler ();
	    continue;
	}
	if (select (32, &r, &w, &x, (struct timeval *)0) == -1) {
	    if (errno == EINTR)
		continue;
	    HasWindow = 0;
	    Msg (errno, "select");
	    /*NOTREACHED*/
	}
	if (GotSignal) {
	    SigHandler ();
	    continue;
	}
	if (r & 1 << s) {
	    ReceiveMsg (s);
	}
	if (r & 1 << 0) {
	    if (ESCseen) {
		inbuf[0] = Esc;
		inlen = read (0, inbuf+1, IOSIZE-1) + 1;
		ESCseen = 0;
	    } else {
		inlen = read (0, inbuf, IOSIZE);
	    }
	    if (inlen > 0)
		inlen = ProcessInput (inbuf, inlen);
	    if (inlen > 0)
		continue;
	}
	if (GotSignal) {
	    SigHandler ();
	    continue;
	}
	if (curr && w & 1 << curr->ptyfd && inlen > 0) {
	    if ((len = write (curr->ptyfd, inbuf, inlen)) > 0) {
		inlen -= len;
		bcopy (inbuf+len, inbuf, inlen);
	    }
	}
	if (GotSignal) {
	    SigHandler ();
	    continue;
	}
	for (n=0; n<MAXWIN; n++) {
	    if (!(p = wtab[n]))
		continue;
	    if (r & 1 << p->ptyfd) {
		if ((len = read (p->ptyfd, buf, IOSIZE)) == -1) {
		    if (errno == EWOULDBLOCK)
			len = 0;
		}
		if (len > 0)
		    WriteString (n, buf, len);
	    }
	}
	if (GotSignal)
	    SigHandler ();
    }
    /*NOTREACHED*/
}

static InitTerm() {
    register char *s;

    if ((s = getenv("TERM")) == 0)
	Msg(0, "No TERM in environment.");
    if (strcmp(s,"mgr"))
	Msg(0, "Only runs on mgr terminals.");

    m_setup(M_FLUSH);
    m_push(P_DEFAULT|P_POSITION);
    MyFont=get_font(NULL,NULL);
    m_dupkey(Esc);
}

static FinitTerm() {
    m_nomenu2(); m_nomenu();
    m_clearmenu(1); m_clearmenu(2);
    m_clearevent(ACTIVATE); m_clearevent(RESHAPE); m_clearevent(DESTROY);
    m_popall();
}
    
static WriteString(n, buf, len) int n, len; char *buf; {
    register int i, j;
    register struct win *p;

    p=wtab[n];

    if (!Detached) {
	m_selectwin(n);
	write(fileno(m_termout), buf, len);
    } else
	p->win_ok=0;		/* Screen no longer matches reality... */

    i=SCRBUFSIZE - (p->outptr - p->outbuf);
    if (i>=len) {
	bcopy(buf,p->outptr,len);
	if (i==len) {
		p->outptr=p->outbuf;
		p->outful=1;
	} else
		p->outptr+=len;
    } else {
	j=len-i;
	bcopy(buf,p->outptr,i);
	bcopy(&buf[i],p->outbuf,j);
	p->outptr=p->outbuf+j;
	p->outful=1;
    }
}

static SigHandler () {
    while (GotSignal) {
	GotSignal = 0;
	DoWait ();
    }
}

static void SigChld () {
    GotSignal = 1;
}

static void SigHup () {
    Detach (0);
}

static DoWait () {
    register pid;
    register int n;
    register struct win **pp;
    union wait wstat;

    while ((pid = wait3 (&wstat, WNOHANG|WUNTRACED, NULL)) > 0) {
	for (n = 0; n<MAXWIN; n++) {
	    if (wtab[n] && pid == wtab[n]->wpid) {
		if (WIFSTOPPED (wstat)) {
		    (void) killpg (getpgrp (wtab[n]->wpid), SIGCONT);
		} else
			KillWindow(n);
	    }
	}
    }
    for (n=0; n<MAXWIN; n++)
	if (wtab[n] && wtab[n]->wpid >=0)
		break;
    if (n == MAXWIN)
	Finit();
}

static KillWindow (n) int n; {
    if (Detached || n!=CurrNum || !n) {
	WriteString(n,"[window shut down]",18);
	wtab[n]->wpid = -1;	/* Shut it down, but don't close */
	if (!n)
    	    FreeWindow (n);	/* Don't ever close window 0, but free it */
    } else {
	CurrNum = 0;
	curr = 0;
    	FreeWindow (n);
    }
}

static void Finit () {
    register int n;
    register struct win *p, **pp;

    for (n=0; n<MAXWIN; n++) {
	if (wtab[n])
	    FreeWindow(n);
    }
    SetTTY (0, &OldMode);
    FinitTerm ();
    printf ("\r[rmgr is terminating]\033c\n");
    Kill (AttacherPid, SIGHUP);
    exit (0);
}

static linemode(onoff) int onoff; {
	struct sgttyb buff;
	gtty(0,&buff);
	if (onoff)
		buff.sg_flags &= ~CBREAK;
	else
		buff.sg_flags |= CBREAK;
	stty(0,&buff);
}

static ProcessInput (buf, len) char *buf; {
    register n, k;
    register char *s, *p, *q;
    register struct win **pp;

    for (s = p = buf; len > 0; len--, s++) {
	if (*s == Esc) {
	    if (len > 1) {
		len--; s++;
		if (*s == Esc) {
		    *p++ = Esc;
		} else {		/* Process event or menu string */
		if (*s != ' ') {
		    n=(*s++)-'!';
		    len--;
		    if (!len) {		/* Make sure to get 3rd char */
			while(!read(0,s,1));
			len++;
		    }
		    p=buf;
		    switch (*s) {	/* Event Strings */
			case 'A':
				SetCurrWindow(n);
				break;
			case 'D':
				FreeWindow(n);
				break;
			case 'R':
				SizeWindow(n);
				break;
			case 'c':	/* Menu Strings */
				if ((n=MakeWindow((char *)0, (char *)0,
					"", (char *)0)) != -1)
					SetCurrWindow(n);
				break;
			case 'd':
				VoluntaryDetach=1;
				Detach(0);
				break;
			case 'f':
				SwitchWindow(n);
				break;
			case 'k':
				KillWindow(n);
				break;
			case 'n':
				if ((n=MakeWindow(ShellProg, ShellArgs,
					"", (char *)0)) != -1)
					SetCurrWindow(n);
				break;
			case 'q':
				Finit();
				break;
			case 's':
				VoluntaryDetach=1;
				Detach(1);
				break;
			}
		    }
		} 
	    } else ESCseen = 1;
	} else *p++ = *s;
    }
    return p - buf;
}

static SwitchWindow(n) {
    if (wtab[n]) {
    	SetCurrWindow(n);
    	m_setmode(M_ACTIVATE);
    }
}

static SetCurrWindow (n) {
    CurrNum = n;
    curr = wtab[n];
    m_selectwin(n);
    if (curr->wpid < 0)
	m_setmode(M_NOINPUT);	/* Dead window, disallow input */
}

static SizeWindow (n) int n; {
	struct winsize wbuf;
	char buf[32]; 

	m_selectwin(n);
	m_getinfo(G_WINSIZE);
	linemode(1);
	read(0,buf,31);
	linemode(0);
	buf[31]='\0';
	sscanf(buf,"%*c %d %d",&(wtab[n]->cols),&(wtab[n]->rows));
#ifdef	TIOCSWINSZ
	wbuf.ws_row = wtab[n]->rows;
	wbuf.ws_col = wtab[n]->cols;
	ioctl (wtab[n]->ptyfd, TIOCSWINSZ, &wbuf);
#endif
}

static FreeWindow (n) register int n; {
    register i;
    register struct win *wp;

    wp=wtab[n];
    if (wp) {
    	RemoveUtmp (wp->slot);
    	(void) chmod (wp->tty, 0666);
    	(void) chown (wp->tty, 0, 0);
    	close (wp->ptyfd);
    	free (wp);
    	wtab[n]=0;
    }
    if (n) {
	m_selectwin(n);
	m_clearevent(DESTROY);	/* Avoid redundant calling of FreeWindow */
	m_destroywin(n);
    }
    CollectNames();
    sleep(1);
    UpdateMenu(n);
}

static CollectNames() {		/* Collect command names for second menu */
    register struct win **wp;
    register int n;
    char buf[8];

    CommNames[1]='\0';
    for (wp=wtab; wp<wtab+MAXWIN; wp++)
	if (*wp) {
	    strcat(CommNames,(*wp)->cmd);
	    strcat(CommNames,"}");
	}

    buf[0]=Esc;
    buf[1]='!';
    buf[2]='f';
    buf[3]='}';
    buf[4]='\0';
    for (n=0;n<MAXWIN;n++)
	if (wtab[n]) {
	    buf[1]=n+'!';
	    strcat(CommNames,buf);
	}
}

static char *MenuNames[]={"MGR Windows =>", "", "-=-=-=-=-=-=-",
	"Shell Window","Other Window","Kill Window","Suspend","Detach","Quit"};
static char MenuNull='\0';
static char MenuActs[24];
static char MenuLabs[8]="ncksdq";
static struct menu_entry MainMenu[9];

static InitMenu() {
    register int i;
    register char *ptr;

    ptr=MenuActs;
    for (i=3;i<9;i++) {
	MainMenu[i].action=ptr;
	*ptr++=Esc;
	*ptr++='!';
	*ptr++=MenuLabs[i-3];
	*ptr++='\0';
    }
    for (i=0;i<3;i++)
	MainMenu[i].action=(&MenuNull);

    for (i=0;i<9;i++)
	MainMenu[i].value=MenuNames[i];
}

static MakeMenu(n) int n; {
    MenuActs[9]=n+'!';
    MainMenu[1].value=wtab[n]->cmd;
    menu_load(1,9,MainMenu);
    m_loadmenu(2,CommNames);
    m_linkmenu(1,0,2,MF_SNIP);
    m_selectmenu2(1);
}

static UpdateMenu(n) int n; {
    register int i;

    for (i=0; i<MAXWIN; i++)
	if (wtab[i] && i!=n) {
	    m_selectwin(i);
	    m_loadmenu(2,CommNames);
	}
}

static Parse (buf, args) char *buf, **args; {
    register char *p = buf, **ap = args;
    register delim, argc = 0;

    argc = 0;
    for (;;) {
	while (*p && (*p == ' ' || *p == '\t')) ++p;
	if (*p == '\0' || *p == '#')
	    return argc;
	if (argc > MAXARGS-1)
	    Msg (0, "Too many tokens.");
	delim = 0;
	if (*p == '"' || *p == '\'') {
	    delim = *p; *p = '\0'; ++p;
	}
	++argc;
	*ap = p; ++ap;
	while (*p && !(delim ? *p == delim : (*p == ' ' || *p == '\t')))
	    ++p;
	if (*p == '\0') {
	    if (delim)
		Msg (0, "Missing quote.");
	    else
		return argc;
	}
	*p++ = '\0';
    }
}

static char TermBuf[MAXLINE];

static MakeWindow (prog, args, name, dir)
	char *prog, *name, **args, *dir; {
    register struct win *p;
    register char **cp;
    register n, f;
    int tf;
    int mypid;
    char ebuf[16];
    char ibuf[MY_MAXLINE];
    char *av[MAXARGS];
    char *ptr,*index();

    if ((f = OpenPTY ()) == -1) {
	Msg (0, "No more PTYs.");
	return -1;
    }
    (void) fcntl (f, F_SETFL, FNDELAY);
    if ((p = (struct win *)malloc (sizeof (struct win))) == 0) {
	Msg (0, "Out of memory.");
	return -1;
    }
    p->outful=0;
    p->outptr=p->outbuf;

    m_ttyset();
    if (!wtab[0]) {
	n=0;
    } else {
	m_resetflags(CBREAK);
	m_newwin(0,0,50,50);
	read(0, TermBuf, MAXLINE);
	TermBuf[MAXLINE-1]='\0';
    	n = atoi(&TermBuf[2]);
    }
    	m_selectwin(n);
        m_dupkey(Esc);
	m_font(MyFont);
    	m_sizeall(n*5,n*5,80,24);
	strcpy(TermBuf,"TERMCAP=");
	m_getinfo(G_TERMCAP);
	read(0, &TermBuf[6], MAXLINE);
	TermBuf[MAXLINE-1]='\0';
	m_setflags(CBREAK);
	TermBuf[6]='P';
	TermBuf[7]='=';
	ptr=index(TermBuf,'\n');
	*ptr='\0';
    m_clear();
    sprintf(ebuf,EventStr,Esc,n+'!');
    m_setevent(ACTIVATE,ebuf);
    ebuf[2]='D';
    m_setevent(DESTROY,ebuf);
    ebuf[2]='R';
    m_setevent(RESHAPE,ebuf);
    m_ttyreset();
    wtab[n]=p;

    p->ptyfd = f;

    if (!prog) {
	int argc;

	SetTTY(0, &OldMode);
	printf("Enter command and arguments: ");
	fflush(stdout);
	ptr=gets(ibuf);
	SetTTY(0, &NewMode);
	if (!ptr || ((argc = Parse(ibuf, av)) == 0)) {
	    KillWindow(n);
	    return;
	}
	av[argc] = NULL;
	if (strcmp(av[0],"-n") == 0) {
	    name=av[1];
	    args=(&av[2]);
	} else 
	    args=av;
	prog=args[0];
    }

    strncpy (p->cmd, *name ? name : Filename (args[0]), MAXSTR-1);
    p->cmd[MAXSTR-1] = '\0';

    CollectNames();
    MakeMenu(n);
    UpdateMenu(n);
    strncpy (p->tty, TtyName, MAXSTR-1);
    (void) chown (TtyName, getuid (), getgid ());
    (void) chmod (TtyName, TtyMode);
    p->slot = SetUtmp (TtyName);
    p->rows=24;
    p->cols=80;
    switch (p->wpid = fork ()) {
    case -1:
	Msg (errno, "fork");
	free ((char *)p);
	return -1;
    case 0:
	signal (SIGHUP, SIG_DFL);
	signal (SIGINT, SIG_DFL);
	signal (SIGQUIT, SIG_DFL);
	signal (SIGTERM, SIG_DFL);
	signal (SIGTTIN, SIG_DFL);
	signal (SIGTTOU, SIG_DFL);
	setuid (getuid ());
	setgid (getgid ());
	if (dir && chdir (dir) == -1) {
	    SendErrorMsg ("Cannot chdir to %s: %s", dir, strerror(errno));
	    exit (1);
	}
	mypid = getpid ();
	ioctl (DevTty, TIOCNOTTY, (char *)0);
	if ((tf = open (TtyName, O_RDWR)) == -1) {
	    SendErrorMsg ("Cannot open %s: %s", TtyName, strerror(errno));
	    exit (1);
	}
	(void) dup2 (tf, 0);
	(void) dup2 (tf, 1);
	(void) dup2 (tf, 2);
	for (f = getdtablesize () - 1; f > 2; f--)
	    close (f);
	ioctl (0, TIOCSPGRP, &mypid);
	(void) setpgrp (0, mypid);
	SetTTY (0, &OldMode);
#ifdef	TIOCSWINSZ
	{
		struct winsize wbuf;
		wbuf.ws_row = p->rows;
		wbuf.ws_col=p->cols;
		ioctl (0, TIOCSWINSZ, &wbuf);
	}
#endif
	NewEnv[2] = TermBuf;
	sprintf (ebuf, "WINDOW=%d", n);
	NewEnv[3] = ebuf;
	execvpe (prog, args, NewEnv);
	SendErrorMsg ("Cannot exec %s: %s", prog, strerror(errno));
	exit (1);
    }
    return n;
}

static execvpe (prog, args, env) char *prog, **args, **env; {
    register char *path, *p;
    char buf[1024];
    char *shargs[MAXARGS+1];
    register i, eaccess = 0;

    if (prog[0] == '/')
	path = "";
    else if ((path = getenv ("PATH")) == 0)
	path = DefaultPath;
    do {
	p = buf;
	while (*path && *path != ':')
	    *p++ = *path++;
	if (p > buf)
	    *p++ = '/';
	strcpy (p, prog);
	if (*path)
	    ++path;
	execve (buf, args, env);
	switch (errno) {
	case ENOEXEC:
	    shargs[0] = DefaultShell;
	    shargs[1] = buf;
	    for (i = 1; shargs[i+1] = args[i]; ++i)
		;
	    execve (DefaultShell, shargs, env);
	    return;
	case EACCES:
	    eaccess = 1;
	    break;
	case ENOMEM: case E2BIG: case ETXTBSY:
	    return;
	}
    } while (*path);
    if (eaccess)
	errno = EACCES;
}

static OpenPTY () {
    register char *p, *l, *d;
    register i, f, tf;

    strcpy (PtyName, PtyProto);
    strcpy (TtyName, TtyProto);
    for (p = PtyName, i = 0; *p != 'X'; ++p, ++i) ;
    for (l = "qpr"; *p = *l; ++l) {
	for (d = "0123456789abcdef"; p[1] = *d; ++d) {
	    if ((f = open (PtyName, O_RDWR)) != -1) {
		TtyName[i] = p[0];
		TtyName[i+1] = p[1];
		if ((tf = open (TtyName, O_RDWR)) != -1) {
		    close (tf);
		    return f;
		}
		close (f);
	    }
	}
    }
    return -1;
}

static SetTTY (fd, mp) struct mode *mp; {
    ioctl (fd, TIOCSETP, &mp->m_ttyb);
    ioctl (fd, TIOCSETC, &mp->m_tchars);
    ioctl (fd, TIOCSLTC, &mp->m_ltchars);
    ioctl (fd, TIOCLSET, &mp->m_lmode);
    ioctl (fd, TIOCSETD, &mp->m_ldisc);
}

static GetTTY (fd, mp) struct mode *mp; {
    ioctl (fd, TIOCGETP, &mp->m_ttyb);
    ioctl (fd, TIOCGETC, &mp->m_tchars);
    ioctl (fd, TIOCGLTC, &mp->m_ltchars);
    ioctl (fd, TIOCLGET, &mp->m_lmode);
    ioctl (fd, TIOCGETD, &mp->m_ldisc);
}

static SetMode (op, np) struct mode *op, *np; {
    *np = *op;
    np->m_ttyb.sg_flags &= ~(CRMOD|ECHO);
    np->m_ttyb.sg_flags |= CBREAK;
    np->m_tchars.t_intrc = -1;
    np->m_tchars.t_quitc = -1;
    np->m_ltchars.t_suspc = -1;
    np->m_ltchars.t_dsuspc = -1;
    np->m_ltchars.t_flushc = -1;
    np->m_ltchars.t_lnextc = -1;
}

static char *GetTtyName () {
    register char *p;
    register n;

    for (p = 0, n = 0; n <= 2 && !(p = ttyname (n)); n++)
	;
    if (!p || *p == '\0')
	Msg (0, "rmgr must run on a tty.");
    return p;
}

static Attach (how) {
    register s, lasts, found = 0;
    register DIR *dirp;
    register struct direct *dp;
    struct msg m;
    char last[MAXNAMLEN+1];

    if (SockName) {
	if ((lasts = MakeClientSocket (0)) == -1)
	    if (how == MSG_CONT)
		Msg (0,
		    "This session has already been continued from elsewhere.");
	    else
		Msg (0, "There is no session to be resumed from %s.", SockName);
    } else {
	if ((dirp = opendir (SockPath)) == NULL)
	    Msg (0, "Cannot open %s", SockPath);
	while ((dp = readdir (dirp)) != NULL) {
	    SockName = dp->d_name;
	    if (SockName[0] == '.')
		continue;
	    if ((s = MakeClientSocket (0)) != -1) {
		if (found == 0) {
		    strcpy (last, SockName);
		    lasts = s;
		} else {
		    if (found == 1) {
			printf ("There are detached sessions on:\n");
			printf ("   %s\n", last);
			close (lasts);
		    }
		    printf ("   %s\n", SockName);
		    close (s);
		}
		found++;
	    }
	}
	if (found == 0)
	    Msg (0, "There is no session to be resumed.");
	if (found > 1)
	    Msg (0, "Type \"rmgr -r host.tty\" to resume one of them.");
	closedir (dirp);
	strcpy (SockNamePtr, last);
	SockName = SockNamePtr;
    }
    m.type = how;
    strcpy (m.m.attach.tty, GetTtyName ());
    m.m.attach.apid = getpid ();
    if (write (lasts, (char *)&m, sizeof (m)) != sizeof (m))
	Msg (errno, "write");
}

static void AttacherFinit () {
    exit (0);
}

static void ReAttach () {
    Attach (MSG_CONT);
}

static Attacher () {
    signal (SIGHUP, AttacherFinit);
    signal (SIGCONT, ReAttach);
    while (1)
	pause ();
}

static Detach (suspend) {
    register struct win **pp;
    register int i;

    if (Detached)
	return;
    signal (SIGHUP, SIG_IGN);
    if (VoluntaryDetach) {	/* Can't save state if line dropped */
    for (i=MAXWIN-1;i>0;i--) {
	if (wtab[i]) {
	m_selectwin(i);
	m_nomenu2();
	m_push(P_EVENT);
    	}
    }
    m_selectwin(0);
    m_nomenu2(); m_nomenu();
    m_push(P_ALL);
    m_setmode(M_ACTIVATE);
    }
    SetTTY (0, &OldMode);
    if (suspend) {
	Kill (AttacherPid, SIGTSTP);
	for (pp=wtab; pp < wtab+MAXWIN; ++pp)
	    if (*pp) (*pp)->win_ok=1;
    } else {
	for (pp=wtab; pp < wtab+MAXWIN; ++pp)
	    if (*pp) RemoveUtmp ((*pp)->slot);
	printf ("\n[detached]\n");
	Kill (AttacherPid, SIGHUP);
	AttacherPid = 0;
    }
    close (0);
    close (1);
    close (2);
    ioctl (DevTty, TIOCNOTTY, (char *)0);
    Detached = 1;
    do {
	ReceiveMsg (ServerSocket); 
    } while (Detached);
    if (!suspend) {
	for (pp = wtab; pp < wtab+MAXWIN; ++pp)
	    if (*pp) (*pp)->slot = SetUtmp ((*pp)->tty);
    }
    signal (SIGHUP, SigHup);
}

static Kill (pid, sig) {
    if (pid != 0)
	(void) kill (pid, sig);
}

static GetSockName () {
    register client;
    static char buf[2*MAXSTR];

    if ((SockName = getenv ("STY")) != 0 && *SockName != '\0') {
	client = 1;
	setuid (getuid ());
	setgid (getgid ());
    } else {
	sprintf (buf, "%s.%s", HostName, Filename (GetTtyName ()));
	SockName = buf;
	client = 0;
    }
    return client;
}

static MakeServerSocket () {
    register s;
    struct sockaddr_un a;
    char *p;

    if ((s = socket (AF_UNIX, SOCK_STREAM, 0)) == -1)
	Msg (errno, "socket");
    a.sun_family = AF_UNIX;
    strcpy (SockNamePtr, SockName);
    strcpy (a.sun_path, SockPath);
    if (connect (s, (struct sockaddr *)&a, strlen (SockPath)+2) != -1) {
	p = Filename (SockPath);
	Msg (0, "You already have a session running on %s.\n\
If it has been detached, try \"rmgr -r\".", p);
	/*NOTREACHED*/
    }
    (void) unlink (SockPath);
    if (bind (s, (struct sockaddr *)&a, strlen (SockPath)+2) == -1)
	Msg (errno, "bind");
    (void) chown (SockPath, getuid (), getgid ());
    if (listen (s, 5) == -1)
	Msg (errno, "listen");
    return s;
}

static MakeClientSocket (err) {
    register s;
    struct sockaddr_un a;

    if ((s = socket (AF_UNIX, SOCK_STREAM, 0)) == -1)
	Msg (errno, "socket");
    a.sun_family = AF_UNIX;
    strcpy (SockNamePtr, SockName);
    strcpy (a.sun_path, SockPath);
    if (connect (s, (struct sockaddr *)&a, strlen (SockPath)+2) == -1) {
	if (err) {
	    Msg (errno, "connect: %s", SockPath);
	} else {
	    close (s);
	    return -1;
	}
    }
    return s;
}

static SendCreateMsg (s, ac, av, name) char **av, *name; {
    struct msg m;
    register char *p;
    register len, n;

    m.type = MSG_CREATE;
    p = m.m.create.line;
    for (n = 0; ac > 0 && n < MAXARGS-1; ++av, --ac, ++n) {
	len = strlen (*av) + 1;
	if (p + len >= m.m.create.line+MY_MAXLINE)
	    break;
	strcpy (p, *av);
	p += len;
    }
    m.m.create.nargs = n;
    if (name)
	strcpy(m.m.create.name,name);
    else
	m.m.create.name[0]='\0';
    if (getwd (m.m.create.dir) == 0)
	Msg (0, "%s", m.m.create.dir);
    if (write (s, (char *)&m, sizeof (m)) != sizeof (m))
	Msg (errno, "write");
}

/*VARARGS1*/
static SendErrorMsg (fmt, p1, p2, p3, p4, p5, p6) char *fmt; {
    register s;
    struct msg m;

    s = MakeClientSocket (1);
    m.type = MSG_ERROR;
    sprintf (m.m.message, fmt, p1, p2, p3, p4, p5, p6);
    (void) write (s, (char *)&m, sizeof (m));
    close (s);
    sleep (2);
}

static char m_rbuf[MAXLINE];

static char *m_read() {
    register char *ptr; char *index();
    register int i;

    i=read(0,m_rbuf,MAXLINE-1);
    m_rbuf[i]='\0';
    ptr=index(m_rbuf,Esc);
    if (!ptr)
	ptr=m_rbuf;
    else
	ptr++;
    return(ptr);
}

static ReplayWindow(p) struct win *p; {
    register int n;

	n=p->outptr-p->outbuf;
	if (p->outful)		/* Replay last screen of data */
		write(1,p->outptr,SCRBUFSIZE-n);
	write(1,p->outbuf,n);
}

static RestoreWindows(mt) int mt; {
    register int i, j=0;
    struct window_data wd;
    register char *ptr;
    register struct win *p;
    char buf[8];
    int n, nw;
    int oldset=1;

    if (mt != MSG_CONT) {	/* Restore after a detach, not suspend */
    linemode(1);
    for (i=MAXWIN-1; i>=0; i--) {
	m_selectwin(i);
	m_getinfo(G_ID);
	ptr=m_read();
	sscanf(ptr,"%d %d",&n, &nw);
	if (wtab[i]) {
	    p=wtab[i];
	    if (n!=i) {		/* That one didn't exist, remake it... */
		oldset=0;	/* Looks like the old context is gone. */
		m_newwin(j,j,50,50);
		ptr=m_read();
		n=atoi(ptr);
		if (!n) 
			Msg(0, "Error restoring window.");
		m_selectwin(n);
	    }
	    if (!oldset) {
		m_size(p->cols,p->rows);
	    	m_dupkey(Esc);
	    	sprintf(buf,EventStr,Esc,n+'!');
	    	m_setevent(ACTIVATE,buf);
	    	buf[2]='D';
	    	m_setevent(DESTROY,buf);
	    	buf[2]='R';
	    	m_setevent(RESHAPE,buf);
		MakeMenu(n);
		ReplayWindow(p);
	    } else if (VoluntaryDetach) {
		m_pop();
	    } else ReplayWindow(p);	/* Redraw for hangup */
	} else if (i) {		/* Gee, a window we didn't create. Byebye. */
	    oldset=0;		/* Again, we must have lost the old stuff */
	    m_destroywin(i);
	}
    }
    linemode(0);
    } else {
	for (i=0;i<MAXWIN;i++) {
	    p=wtab[i];
	    if (p) {
		m_selectwin(i);
		m_pop();
		m_selectmenu2(1);
		if (!p->win_ok)
		    ReplayWindow(p);
	    }
	}
    }
    VoluntaryDetach=0;
}
	    
static ReceiveMsg (s) {
    register ns;
    struct sockaddr_un a;
    int left, len = sizeof (a);
    struct msg m;
    char *p;

    if ((ns = accept (s, (struct sockaddr *)&a, &len)) == -1) {
	Msg (errno, "accept");
	return;
    }
    p = (char *)&m;
    left = sizeof (m);
    while (left > 0 && (len = read (ns, p, left)) > 0) {
	p += len;
	left -= len;
    }
    close (ns);
    if (len == -1)
	Msg (errno, "read");
    if (left > 0)
	return;
    switch (m.type) {
    case MSG_CREATE:
	if (!Detached)
	    ExecCreate (&m);
	break;
    case MSG_CONT:
	if (m.m.attach.apid != AttacherPid || !Detached)
	    break;	/* Intruder Alert */
	/*FALLTHROUGH*/
    case MSG_ATTACH:
	if (Detached) {
	    if (kill (m.m.attach.apid, 0) == 0 &&
		    open (m.m.attach.tty, O_RDWR) == 0) {
		(void) dup (0);
		(void) dup (0);
		AttacherPid = m.m.attach.apid;
		Detached = 0;
		GetTTY (0, &OldMode);
		SetMode (&OldMode, &NewMode);
		SetTTY (0, &NewMode);
		RestoreWindows(m.type);
		SwitchWindow(CurrNum);
	    }
	} else {
	    Kill (m.m.attach.apid, SIGHUP);
	    Msg (0, "Not detached.");
	}
	break;
    case MSG_ERROR:
	Msg (0, "%s", m.m.message);
	break;
    default:
	Msg (0, "Invalid message (type %d).", m.type);
    }
}

static ExecCreate (mp) struct msg *mp; {
    char *args[MAXARGS];
    register n;
    register char **pp = args, *p = mp->m.create.line;

    for (n = mp->m.create.nargs; n > 0; --n) {
	*pp++ = p;
	p += strlen (p) + 1;
    }
    *pp = 0;
    if ((n = MakeWindow (mp->m.create.line, args, mp->m.create.name,
	    mp->m.create.dir)) != -1)
	SwitchWindow (n);
}

static char **SaveArgs (argc, argv) register argc; register char **argv; {
    register char **ap, **pp;

    if ((pp = ap = (char **)malloc ((argc+1) * sizeof (char **))) == 0)
	Msg (0, "Out of memory.");
    while (argc--) {
	if ((*pp = malloc (strlen (*argv)+1)) == 0)
	    Msg (0, "Out of memory.");
	strcpy (*pp, *argv);
	++pp; ++argv;
    }
    *pp = 0;
    return ap;
}

static MakeNewEnv () {
    register char **op, **np = NewEnv;
    static char buf[MAXSTR];

    if (strlen (SockName) > MAXSTR-5)
	SockName = "?";
    sprintf (buf, "STY=%s", SockName);
    *np++ = buf;
    *np++ = "TERM=mgr";
    np += 2;
    for (op = environ; *op; ++op) {
	if (np == NewEnv + MAXARGS - 1)
	    break;
	if (!IsSymbol (*op, "TERM") && !IsSymbol (*op, "TERMCAP")
		&& !IsSymbol (*op, "STY"))
	    *np++ = *op;
    }
    *np = 0;
}

static IsSymbol (e, s) register char *e, *s; {
    register char *p;
    register n;

    for (p = e; *p && *p != '='; ++p) ;
    if (*p) {
	*p = '\0';
	n = strcmp (e, s);
	*p = '=';
	return n == 0;
    }
    return 0;
}

/*VARARGS2*/
Msg (err, fmt, p1, p2, p3, p4, p5, p6) char *fmt; {
    char buf[1024];
    register char *p = buf;

    if (Detached)
	return;
    sprintf (p, fmt, p1, p2, p3, p4, p5, p6);
    if (err) {
	p += strlen (p);
	if (err > 0 && err < sys_nerr)
	    sprintf (p, ": %s", strerror(err));
	else
	    sprintf (p, ": Error %d", err);
    }
    printf ("%s\r\n", buf);
    if (!HasWindow) {
	Kill (AttacherPid, SIGHUP);
	exit (1);
    }
}

static char *Filename (s) char *s; {
    register char *p;

    p = s + strlen (s) - 1;
    while (p >= s && *p != '/') --p;
    return ++p;
}

static IsNum (s, base) register char *s; register base; {
    for (base += '0'; *s; ++s)
	if (*s < '0' || *s > base)
	    return 0;
    return 1;
}

static InitUtmp () {
    struct passwd *p;

    if ((utmpf = open (UtmpName, O_WRONLY)) == -1) {
	if (errno != EACCES)
	    Msg (errno, UtmpName);
	return;
    }
    if ((LoginName = getlogin ()) == 0 || LoginName[0] == '\0') {
	if ((p = getpwuid (getuid ())) == 0)
	    return;
	LoginName = p->pw_name;
    }
    utmp = 1;
}

static SetUtmp (name) char *name; {
    register char *p;
    register struct ttyent *tp;
    register slot = 1;
    struct utmp u;

    if (!utmp)
	return 0;
    if (p = rindex (name, '/'))
	++p;
    else p = name;
    setttyent ();
    while ((tp = getttyent ()) != NULL && strcmp (p, tp->ty_name) != 0)
	++slot;
    if (tp == NULL)
	return 0;
    strncpy (u.ut_line, p, 8);
    strncpy (u.ut_name, LoginName, 8);
    u.ut_host[0] = '\0';
    time (&u.ut_time);
    (void) lseek (utmpf, (long)(slot * sizeof (u)), 0);
    (void) write (utmpf, (char *)&u, sizeof (u));
    return slot;
}

static RemoveUtmp (slot) {
    struct utmp u;

    if (slot) {
	bzero ((char *)&u, sizeof (u));
	(void) lseek (utmpf, (long)(slot * sizeof (u)), 0);
	(void) write (utmpf, (char *)&u, sizeof (u));
    }
}

#ifndef GETTTYENT

static setttyent () {
    struct stat s;
    register f;
    register char *p, *ep;

    if (ttnext) {
	ttnext = tt;
	return;
    }
    if ((f = open (ttys, O_RDONLY)) == -1 || fstat (f, &s) == -1)
	Msg (errno, ttys);
    if ((tt = malloc (s.st_size + 1)) == 0)
	Msg (0, "Out of memory.");
    if (read (f, tt, s.st_size) != s.st_size)
	Msg (errno, ttys);
    close (f);
    for (p = tt, ep = p + s.st_size; p < ep; ++p)
	if (*p == '\n') *p = '\0';
    *p = '\0';
    ttnext = tt;
}

static struct ttyent *getttyent () {
    static struct ttyent t;

    if (*ttnext == '\0')
	return NULL;
    t.ty_name = ttnext + 2;
    ttnext += strlen (ttnext) + 1;
    return &t;
}

#endif

#ifndef USEBCOPY
bcopy (s1, s2, len) register char *s1, *s2; register len; {
    if (s1 < s2 && s2 < s1 + len) {
	s1 += len; s2 += len;
	while (len-- > 0) {
	    *--s2 = *--s1;
	}
    } else {
	while (len-- > 0) {
	    *s2++ = *s1++;
	}
    }
}
#endif
