[net.sources] Maryland phone program. Uses 4.1bsd, IPC, and windows.

james@umcp-cs.UUCP (10/26/83)

: Run this shell script with "sh" not "csh"
PATH=:/bin:/usr/bin:/usr/ucb
export PATH
all=FALSE
if [ $1x = -ax ]; then
	all=TRUE
fi
/bin/echo 'Extracting phone.1l'
sed 's/^X//' <<'//go.sysin dd *' >phone.1l
X.TH PHONE 1 local
X.SH NAME
phone \- talk with other users
X.SH SYNOPSIS
X.B phone
[ -a ] [ -p | -c ] [ -s
X.I number
] [ user [user ...] ]
X.SH DESCRIPTION
X.I Phone
is used to set up a conversation between two users on CRT or printing
terminals.  The connection is started by one user typing ``phone
X.IR username ''.
If the other user is logged in and has messages enabled, he will
be notified that someone is phoning him.  He can then answer by
typing ``phone''.  Both screens will be cleared and
divided in half horizontally.  Everything each user types will
appear in the top half of his screen, and everything the other user
types will appear in the bottom half.  The conversation continues
until either user types control-D.  A control-L (formfeed) will
refresh the screen.
X.B Phone
will inform you of when the connection is really opened -- the
screen is cleared immediately, but the window label reflects the
actual state of the connection.
X.PP
X.B Phone
is capable of talking with up to eight users, as in a conference
call.  By default, when you answer someone who's talking to more
than one person,
X.B phone
also calls everyone that someone is calling.  They can choose to
not answer, if they like; or you can disconnect them if you don't
want to talk to them.  You can use the ``colon format'' (see below)
to answer a particular caller if you so wish.
X.PP
The options to phone are as follows:
X.TP
X.B -c
Do not use windows; instead, assume that the terminal is a CRT.
This is useful at low baud rates.
X.TP
X.B -p
Do not use windows; instead, assume that the terminal is a printing
terminal.  The difference between this and
X.B -c
is that under
X.BR -p ,
X.B phone
prints backspaces as number signs (#).
X.TP
X.BR -s " number"
This option only affects window mode.
X.I Number
is used as the number of lines by which windows are to scroll
(called the
X.IR "scroll step" ).
If
X.I number
is zero, the windows won't scroll at all, but rather the cursor
will wrap from the bottom line of a window to the top line, and
clear each line as it gets to it.  This is faster on some terminals,
but is somewhat confusing.
X.PP
While you are talking to others, you can ask to call more users,
or hang up on some of the users you are talking to.  To get to
command mode, type ESCAPE.  This will pop up a window and prompt
you for a command.  The commands are as follows:
X.TP
X.B call
Call other users.  This command takes a list of users, and calls
each named user.
X.TP
X.B answer
Answers calls.  With no arguments, answers everyone.  With a list
of users, answers each specified call.  (You can also answer by
calling the user who called.)
X.TP
X.B quit
Exits
X.BR phone
(equivalent to typing control-D).
X.TP
X.B hangup
Hangs up on some of the users you're connected to.  This command
takes a list of users, and hangs up each one.
X.TP
X.B scrollstep
Changes the scroll step (as if you had given the
X.B -s
option on the command line).  This command takes a numeric argument
which is the new scroll step.
X.TP
X.B ?
Gives a short help listing.
X.SH "THE ``COLON FORMAT''"
If one user is logged onto several terminals, phone will normally
attempt to call each terminal's user.  This can be prevented by
giving the user's name as "user:tty" (where tty is the terminal
you really want to call).  All user names are treated this way;
you can answer "bob" on "tty6" by typing ``ESC answer bob:tty6
RETURN''.
X.PP
This program uses the CMU IPC facility and the Maryland Window
Library, and is not likely to work on machines without both.
X.SH FILES
X/tmp/ph*	user lists
X.SH SEE ALSO
mesg(1), write(1), who(1), mail(1)
X.SH BUGS
X.I \-home
option not implemented.
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 phone.1l
	/bin/echo -n '	'; /bin/ls -ld phone.1l
fi
/bin/echo 'Extracting Makefile'
sed 's/^X//' <<'//go.sysin dd *' >Makefile
PH=	answer.o chat.o cmd.o display.o ipcio.o keys.o phone.o\
	ngets.o phio.o u.o
PHSRC=	answer.c chat.c cmd.c display.c ipcio.c keys.c phone.c\
	ngets.c phio.c u.c
HDRS=	phone.h ipc.h
CFLAGS=	-O -R
DESTDIR=/usr/local

phone: $(PH)
	cc $(CFLAGS) -o phone $(PH) -lipc -lwinlib -ljobs -ltermlib

install:
	rm -f $(DESTDIR)/phone
	cp phone $(DESTDIR)/phone
	strip $(DESTDIR)/phone
	uucp -c /usr/local/phone cvl!~uucp

clean:
	rm -f *.o phone

script:
	cd /usr/src/local/cmd/phone; makescript /tmp/phonescript Makefile $(HDRS) $(PHSRC)

answer.o: phone.h ipc.h
chat.o: phone.h ipc.h
cmd.o: phone.h ipc.h
display.o: phone.h ipc.h
ipcio.o: ipc.h
keys.o: phone.h ipc.h
phone.o: phone.h ipc.h
ngets.o: phone.h ipc.h
phio.o: phone.h ipc.h
u.o: phone.h ipc.h
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 Makefile
	/bin/echo -n '	'; /bin/ls -ld Makefile
fi
/bin/echo 'Extracting ipc.h'
sed 's/^X//' <<'//go.sysin dd *' >ipc.h
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>

#define	IPCD	2048		/* Max data in an IPC buffer */
#define	NPORTS	20		/* Max number of ports */

struct ipcio {
	int	i_cnt;		/* Data count, strategically placed */
	int	i_mode:1,	/* 0 => incoming, 1 => outgoing */
		i_blocked:1,	/* True => port is blocked */
		i_deleted:1,	/* True => port was deleted */
		i_closed:1;	/* True => port is closed */
	char	i_name[50];	/* Port name */
	struct Msg i_msg;	/* Msg structure for I/O */
	char	*i_data;	/* Data */
};

extern struct ipcio _ports[NPORTS];

#define	IPCIO	struct ipcio

IPCIO	*iopen();

#define	FixSysPort(p) ((p)->i_msg.LocalPort = SysLocalPort)
#define	SetSysPort(x) (SysLocalPort = (x))
#define	ISOPEN(p) ((p)->i_closed == 0 && (p)->i_msg.RemotePort!=NULLPORT)

extern localport SysLocalPort;
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 ipc.h
	/bin/echo -n '	'; /bin/ls -ld ipc.h
fi
/bin/echo 'Extracting phone.h'
sed 's/^X//' <<'//go.sysin dd *' >phone.h
#include "ipc.h"
#include <local/window.h>

#define	CTRL(c)	((c)&0x1f)

#define	MAXCONN	8		/* Max number of users to connect to */

X/*
 * con_c means "I have called him, and am waiting for him to call back,
 * at which point I will answer"; con_t means "talking".
 */

enum constates { con_c, con_t };

X/*
 * Disp_Unix means have not initialized display.  Disp_Windows means
 * windows are being used.  Disp_CRT & Disp_Printer indicate that the
 * terminal is not using windows, and is or is not able to overwrite
 * characters (CRT==use \b \b, Printer==use #).  This information is not
 * local to display.c since GetChar() needs to know if it should call
 * Wrefresh().
 */

enum Disp_Modes { Disp_Unix = 0, Disp_Windows, Disp_CRT, Disp_Printer };

struct connection {
	IPCIO	*con_ipc;
	char	con_user[9];
	char	con_tty[9];
	char	con_id[18];
	enum constates con_state;
	union {			/* For the display routines */
		Win *d_win;
		struct {
			char *l_base;
			int l_cnt;
		} d_line;
	} con_d;
#define	d_base	d_line.l_base
#define	d_cnt	d_line.l_cnt
} conlist[MAXCONN];

int	nconn;			/* Number of active connections */
int	ScrollStep;		/* Scroll step for windows */
int	lineno;			/* Line number for puts_more */
char	LogFile[30];		/* Name of phone log file */
char	*Caller;		/* Name of caller if From == 0 */
struct connection
	*From;			/* For GetChar(0), user char is from */

X/* Shorthand */
#define	Myself	(&conlist[0])
#define	Me	Myself->con_user
#define	MyTty	Myself->con_tty
#define	MyID	Myself->con_id

X/* Non int functions */
char	*tyme (), *concat (), *NextU (), *malloc (), *strsave (), *alloc ();
struct connection
	*ph_iscon ();
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 phone.h
	/bin/echo -n '	'; /bin/ls -ld phone.h
fi
/bin/echo 'Extracting answer.c'
sed 's/^X//' <<'//go.sysin dd *' >answer.c
#ifndef lint
static char rcsid[] = "@(#)answer.c	U of Maryland ACT 31-Mar-1983";
#endif

#include "phone.h"
#include <sys/dir.h>
#include <sys/stat.h>

AnswerAllCalls ()
{
	struct direct tmp;
	struct stat sbuf;
	register FILE *f, *dirf;
	char nbuf[DIRSIZ+10];
	char lbuf[100];

	dirf = fopen ("/tmp", "r");
	if (dirf == NULL) {
		error (0, "Can't open /tmp !?\r\n");
		return;
	}
	while (fread ((char *)&tmp, sizeof tmp, 1, dirf) == 1) {
		if (tmp.d_ino == 0 || strncmp (tmp.d_name, "ph", 2))
			continue;
		sprintf (nbuf, "/tmp/%.*s", DIRSIZ, tmp.d_name);
		if ((f = fopen (nbuf, "r")) == NULL)
			continue;
		if (fgets (lbuf, sizeof lbuf, f))/* First line is caller */
			while (fgets (lbuf, sizeof lbuf, f)) {
				lbuf[strlen(lbuf) - 1] = 0;
				if (strcmp (MyID, lbuf) == 0) {
					callthem (f);
					break;
				}
			}
		fclose (f);
	}
	fclose (dirf);
}

static callthem (f)
register FILE *f;
{
	char lbuf[100];

	rewind (f);
	while (fgets (lbuf, sizeof lbuf, f)) {
		lbuf[strlen (lbuf) - 1] = 0;
		if (strcmp (lbuf, MyID))
			PlaceCall (lbuf, 0, 1);
	}
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 answer.c
	/bin/echo -n '	'; /bin/ls -ld answer.c
fi
/bin/echo 'Extracting chat.c'
sed 's/^X//' <<'//go.sysin dd *' >chat.c
#ifndef lint
static char rcsid[] = "@(#)chat.c	U of Maryland ACT 31-Mar-1983";
#endif

#include "phone.h"

static EndChat;

X/* quit command */
c_quit () {
	EndChat++;
}

X/* Chat() -- take Myself's characters and do the right stuff */
Chat () {
	register c;

	ioption (REPLY);	/* Set REPLY mode */
	while (!EndChat && nconn > 1 && (c = GetChar ()) >= 0) {
		switch (c) {
		case '\r':
			DChar ('\n', Myself);
			chatter ('\n', 1);
			break;
		default:
			if (c < 32 || c == 127)
				break;
		case '\t':
		case '\b':
		case CTRL('X'):
		case '\n':
			DChar (c, Myself);
			chatter (c, 1);
			break;
		case 033:
			DoCommand ();
			break;
		case CTRL('L'):
			ScreenGarbaged++;
			break;
		case CTRL('Z'):
			Suspend ();
			break;
		case CTRL('D'):
			EndChat++;
			break;
		}
	}
}

ChatChar (c)
register c;
{
	static char stop[] = " [^Z] ";

	if (From == 0) {
		if (c == CTRL('D'))
			return;/* Extra eof - ignore */
		if (c != CTRL('C'))
			error (0, "message from '%s'? (%c)\r\n",
				Caller, c);
		else
			error (0, "%s is calling (type ESC ? for help)\r\n",
				Caller);
		return;
	}
	switch (c) {
	case '\r':
		DChar ('\n', From);
		return;
	default:
		if (c < 32 || c == 127)
			return;
	case '\t':
	case '\b':
	case CTRL('X'):
	case '\n':
		DChar (c, From);
		return;
	case CTRL('Z'):
		DStr (stop, From);
		return;
	case CTRL('Y'):
		c = strlen (stop);
		while (--c >= 0)
			DChar ('\b', From);
		return;
	case CTRL('D'):
		ph_disconn (From);
		return;
	case CTRL('C'):
		/* he is calling me; I had called him; I answer */
		From -> con_state = con_t;
		ph_write (From, "\1");
		DConn (From);
		return;
	case CTRL('A'):
		/* I had called him; he is answering */
		From -> con_state = con_t;
		DConn (From);
		return;
	}
}

X/* Send character c to everyone (but not waiting-fors if not all) */
chatter (c, all)
char c;
int all;
{
	register struct connection *ch;
	static char cbuf[2];

	cbuf[0] = c;
	for (ch = conlist + 1; ch < &conlist[MAXCONN]; ch++)
		if (ch -> con_user[0] && (all || (ch -> con_state == con_t)))
			ph_write (ch, cbuf);
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 chat.c
	/bin/echo -n '	'; /bin/ls -ld chat.c
fi
/bin/echo 'Extracting cmd.c'
sed 's/^X//' <<'//go.sysin dd *' >cmd.c
#ifndef lint
static char rcsid[] = "@(#)cmd.c	U of Maryland ACT 31-Mar-1983";
#endif

#include "phone.h"

X/* c_quit is in chat.c */
extern c_help(), c_call(), c_ss(), c_who(), c_quit();

struct cmd {
	char	*cmd_name;	/* Cmd name */
	int	(*cmd_func)();	/* Function */
	int	cmd_arg2;	/* Arg 2 to function */
};

static struct cmd cmdlist[] = {
	"?",		c_help, 0,
	"help",		c_help, 0,
	"call",		c_call, 0,
	"hangup",	c_call, 1,
	"answer",	c_call, 2,
	"scrollstep",	c_ss,	0,
	"who",		c_who,	0,
	"quit",		c_quit,	0,
	0,		0,	0
};

DoCommand ()
{
	register char *cp;
	register struct cmd *p, *c;
	char *s;
	int len;

	s = (char *) DGetCmd ();
	for (cp = s; *cp && *cp != ' '; cp++)
		;
	if (*cp) {
		*cp++ = 0;	/* terminate command */
		while (*cp == ' ')
			cp++;
	}
	len = strlen (s);
	if (len == 0)		/* no command */
		return;
	c = 0;
	for (p = cmdlist; p -> cmd_name; p++) {
		if (strncmp (s, p -> cmd_name, len) == 0)
			if (c) {
				error (0, "%s: ambiguous\r\n", s);
				return;
			}
			else
				c = p;
	}
	if (c == 0) {
		error (0, "%s: command not recognized\r\n", s);
		return;
	}
	(*c -> cmd_func) (cp, c -> cmd_arg2);
}

c_help () {
	error (0, "\
Use ^D (Control-D) to quit.\r\n\
Commands are:\r\n\
    call user [user ...]\r\n\
    answer [user [user ...]]\r\n\
    hangup user [user ...]\r\n\
    scrollstep number\r\n\
    who\r\n\
    quit\r\n\
where user is either ``name'', or ``name:tty''.\r\n\
Only the first few characters of a command need\r\n\
be typed.  If you're using windows:  to make\r\n\
this window go away, type ESC RETURN (i.e. an\r\n\
empty command).\r\n");
}

c_who () {
	error (0, "Not implemented yet\r\n");
}

c_ss (s)
char *s;
{
	if (!*s)
		error (0, "Usage: scrollstep number\r\n");
	else {
		ScrollStep = atoi (s);
		DFixScroll ();
	}
}

X/* call, answer, and hangup */
c_call (users, what)
char *users;
{
	register char *cp = users;

	if (*cp == 0 && what == 2) {
		AnswerAllCalls ();
		return;
	}
	if (!*cp) {
		error (0, "Usage: %s user(s)\r\n", what ? "hangup" : "call");
		return;
	}
	while (*cp) {
		users = cp;
		while (*cp && *cp != ' ')
			cp++;
		if (*cp)
			*cp++ = 0;
		PlaceCall (users, what, 0);
		while (*cp == ' ')
			cp++;
	}
}

X/* Place a call to (or hang up on) a particular user.  (2 == answer) */
PlaceCall (u, what, okerr)
register char *u;
int what, okerr;
{
	register char *t;

	t = u;
	while (*t && *t != ':')
		t++;
	if (*t) {
		*t++ = 0;
		if (what != 1 && !IsOn (u, t)) {
			if (!okerr) {
				if (what == 2)
					error (0, "%s isn't calling\r\n");
				else
					error (0, "%s isn't on %s\r\n", u, t);
			}
			return;
		}
		if (what == 1)
			Hangup (u, t, okerr);
		else
			Call (u, t, okerr);
	}
	else {
		RewindU ();
		t = NextU (u);
		if (t == 0)
			if (!okerr)
				error (0, "%s isn't %s\r\n", u,
					what == 2 ? "calling" : "logged on");
		while (t) {
			if (what == 1)
				Hangup (u, t, okerr);
			else
				Call (u, t, okerr);
			t = NextU (u);
		}
	}
}

X/* Call user u on tty t */
Call (u, t, okerr)
register char *u, *t;
int okerr;
{
	if (strcmp(u, Myself->con_user)==0 && strcmp(t, Myself->con_tty)==0) {
		if (!okerr)
			error (0, "You can't call yourself.\r\n");
		return;
	}
	if (ph_iscon (u, t)) {
		if (!okerr)
			error (0, "%s:%s already connected!\r\n", u, t);
		return;
	}
	if (nconn >= MAXCONN) {
		error (0, "I can't handle any more outgoing calls\r\n");
		return;
	}
	if (ph_conn (u, t))
		error (0, "%s:%s is not receiving calls\r\n", u, t);
	/* Else connection established.  In any case, nothing more
	   to do. */
}

X/* Hang up line to user u, tty t */
X/* "okerr" arg is unused as yet */
Hangup (u, t, okerr)
register char *u, *t;
int okerr;
{
	struct connection *c;

	if (!(c = ph_iscon (u, t))) {
		error (0, "Not calling %s:%s!\r\n", u, t);
		return;
	}
	ph_disconn (c);
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 cmd.c
	/bin/echo -n '	'; /bin/ls -ld cmd.c
fi
/bin/echo 'Extracting display.c'
sed 's/^X//' <<'//go.sysin dd *' >display.c
#ifndef lint
static char rcsid[] = "@(#)display.c	U of Maryland ACT 31-Mar-1983";
#endif

#include "phone.h"
#include <sgtty.h>
#include <signal.h>

extern struct sgttyb WOld;

X/* Anything having to do with displaying things */

enum Disp_Modes DisplayMode;
static NoLocalEcho;		/* Set for n mode; prevents Myself echo */
static CCol;			/* The current column on the crt/printer */
static Rows, Cols;		/* The actual screen size [for windows] */
static NextRow, NextCol;	/* Used during DAdjust - next Win posn */
static struct connection *CConn;/* The connection using CCol */
static char StringBuf[1024];	/* The string buffer */
static Win *CommandWindow;	/* The error/command window */

X/* Given fmt & args & such, stuff into StringBuf the printf-ed output */
static
sprintrmt (arg)
register char **arg;
{
	struct _iobuf _strbuf;

	_strbuf._flag = _IOSTRG;
	_strbuf._ptr = StringBuf;
	_strbuf._cnt = sizeof StringBuf - 1;
	_doprnt (*arg, arg+1, &_strbuf);
	_strbuf._cnt++;
	putc (0, &_strbuf);
}

X/* This is almost more an "announce" than an "error" function.  Oh well. */
error (fatal, m) {
	sprintrmt (&m);
	switch (DisplayMode) {
	case Disp_Unix:
		fputs (StringBuf, stdout);
		fflush (stdout);
		break;
	case Disp_Windows:
		Wfront (CommandWindow);
		Wunhide (CommandWindow);
		if (fatal) {
			Wputs (StringBuf, CommandWindow);
			Wrefresh (0);
		}
		else
			puts_more (StringBuf, CommandWindow);
		break;
	case Disp_CRT: case Disp_Printer:
		if (CCol) {
			putchar ('\r');
			putchar ('\n');
		}
		fputs (StringBuf, stdout);
		fflush (stdout);
		CCol = 0;
		break;
	}
	if (!fatal)
		return;
	if (DisplayMode != Disp_Unix) {
		ph_disall ();
		CleanUpDisplay (1);
	}
	Exit (1);
}

X/* for ngets */
static WEcho (c)
register c;
{
	if (c == '\n') {
		Wputs ("\r\n", CommandWindow);
		return;
	}
	Wputc (c, CommandWindow);
	if (c == '\b')
		Wputs (" \b", CommandWindow);
}

X/* for ngets */
static DEcho (c) {
	DChar (c, Myself);
}

X/* Get a command string */
char *
DGetCmd ()
{
	static char cmd[] = "Command: ", buf[200];

	if (DisplayMode == Disp_Windows) {
		Win *oldcur = CurWin;

		Woncursor (CommandWindow, 0);
		Wfront (CommandWindow);
		Wunhide (CommandWindow);
		Wputs (cmd, CommandWindow);
		CurWin = CommandWindow;
		ngets (buf, sizeof buf, WEcho, 0);
		Whide (CommandWindow);
		Woncursor (CommandWindow, 1);
		CurWin = oldcur;
	}
	else {
		DStr (cmd, Myself);
		ngets (buf, sizeof buf, DEcho, DisplayMode == Disp_Printer);
	}
	return buf;
}

X/* Display string s on connection c */
DStr (s, c)
register char *s;
register struct connection *c;
{
	while (*s)
		DChar (*s++, c);
}

X/* Display char ch on connection c */
DChar (ch, c)
register ch;
register struct connection *c;
{
	switch (DisplayMode) {
	case Disp_Unix:		/* Flake-o */
		error (1, "No display mode at DChar\n");
	case Disp_Windows:
		if (ch == CTRL('x')) {
			Wputc ('\r', c -> con_d.d_win);
			Wclearline (c -> con_d.d_win, 0);
		}
		else if (ch == '\b')
			Wputs ("\b \b", c -> con_d.d_win);
		else if (ch == '\n') {
			Wputs ("\r\n", c -> con_d.d_win);
			if (c == Myself)
				Whide (CommandWindow);
		}
		else
			Wputc (ch, c -> con_d.d_win);
		return;
	case Disp_CRT: case Disp_Printer:
		if (c == Myself && NoLocalEcho)
			return;
		/* Myself overrides everyone else */
		if (CCol && CConn != c && c == Myself) {
			putchar ('\r');
			putchar ('\n');
			printf ("%s: ", MyID);
			CConn = c;
			CCol = 1;/* ok, so its not the column number */
		}
		if (ch == '\r')
			ch = '\n';

		/* If we are in the midst of something, then compare the
		   connection with the one last printed.  If same, then
		   continue, otherwise store characters up to a newline.
		   Newline forces pending characters to be pushed out. */

		if (CCol) {
			/* Same as last connection? */
			if (c == CConn) {
				if (ch == CTRL('x')) {
					fputs (" XXX", stdout);
					fflush (stdout);
					ch = '\n';
				}
put:
				if (ch == '\n')
					putchar ('\r');
				if (ch == '\b') {
					/* Backspace and decrement column
					   (but dont get to zero) */
					if (DisplayMode == Disp_CRT) {
						putchar (ch);
						putchar (' ');
						putchar (ch);
					}
					else
						putchar ('#');
					fflush (stdout);
					/* if (--CCol <= 0)
						CCol = 1; */
					break;
				}
				putchar (ch);
				fflush (stdout);
				/* Compute new column for tab/newline/other */
				/* else if (ch == '\t')
					CCol = (CCol + 8) & ~7;
				else */ if (ch == '\n')
					CCol = 0;
				/* else
					CCol++; */
			}
			/* Different connection */
			else {
				if (ch == CTRL('x'))
					c -> con_d.d_cnt = 0;
				else if (ch == '\b') {
					if (c -> con_d.d_cnt)
						c -> con_d.d_cnt--;
				}
				else {
					c->con_d.d_base[c->con_d.d_cnt++] = ch;
					if (ch == '\n')
						goto pushout;
				}
			}
			break;	/* go to if (CCol == 0) below */
		}
		/* Wasnt in the midst of something, just start this
		   connection going */
		if (ch == CTRL('x') || ch == '\b')
			break;
		printf ("%s: ", c -> con_id);
		CConn = c;
		CCol = 1;
		goto put;
	}
	/* If now not in the midst of things, flush pending line(s).
	   Try to wind up in midst of last pushed out line. */
	if (CCol == 0) {
pushout:
		for (c = conlist; c < &conlist[MAXCONN]; c++) {
			if (c -> con_user[0] == 0)
				continue;
			if (c -> con_d.d_cnt) {
				c -> con_d.d_base[c -> con_d.d_cnt] = 0;
				if (CCol) {
					putchar ('\r');
					putchar ('\n');
				}
				printf ("%s: %s", c->con_id, c->con_d.d_base);
				if (c->con_d.d_base[c->con_d.d_cnt-1] != '\n')
					CCol = 1;/* a bit off... */
				else {
					putchar ('\r');
					CCol = 0;
				}
				c -> con_d.d_cnt = 0;
				fflush (stdout);
				CConn = c;
			}
		}
	}
}

X/* Go out of funny mode so can exit */
CleanUpDisplay (ref) {
	if (DisplayMode == Disp_Windows) {
		if (ref)
			Wrefresh (0);	/* Force display of errors */
		Wcleanup ();
	}
	else {
		if (DisplayMode != Disp_Unix)
			stty (0, &WOld);
		putchar ('\n');
	}
}

X/* Suspend for a while */
Suspend () {
	extern DisallowSuspends;	/* set for OTTYDISC users! */

	if (DisallowSuspends)
		return;
	chatter (CTRL('Z'), 0); /* sigh */
	if (DisplayMode == Disp_Windows)
		Wsuspend ();
	else {
		stty (0, &WOld);
		kill (0, SIGTSTP);
		Raw ();
	}
	chatter (CTRL('Y'), 0);
}

X/* Allocate a display thingy */
DAlloc (c)
register struct connection *c;
{
	if (DisplayMode == Disp_Windows) {
		register Win *w, *oldcur;

		oldcur = CurWin;
		w = Wopen (0, 0, 0, Cols, Rows, 0, 0);
		if (w == 0)
			error (1, "Can't open window !?\r\n");
		c -> con_d.d_win = w;
		Wsetpopup (w, ScrollStep);
		if (c == Myself)
			Woncursor (w, 0);
		else
			CurWin = oldcur;
		DAdjust ();	/* Adjust all windows */
		return;
	}
	c -> con_d.d_base = alloc (IPCD);
	c -> con_d.d_cnt = 0;
}

X/* A connection has been established, notify */
DConn (c)
register struct connection *c;
{
	if (DisplayMode == Disp_Windows) {
		sprintf (StringBuf, "Talking to %s", c -> con_id);
		Wlabel (c -> con_d.d_win, StringBuf, 0, 1);
		Ding ();	/* awaken the user */
	}
	else
		error (0, "[open %s]\7\r\n", c -> con_id);
}

X/* Free the display thingy */
DFree (c)
register struct connection *c;
{
	if (DisplayMode == Disp_Windows) {
		Wclose (c -> con_d.d_win);
		DAdjust ();
		return;
	}
	error (0, "[EOF %s]\r\n", c -> con_id);
	free (c -> con_d.d_base);
}

X/* Initialize display */
StartDisplay (mode)
int mode;	/* 0 = whatever 1 = Disp_CRT 2 = Disp_Printer 3 = n */
{
	struct sgttyb s;
	if (mode) {
		DisplayMode = mode == 1 ? Disp_CRT : Disp_Printer;
		if (mode == 3)
			NoLocalEcho++;
		Raw ();
		return;
	}
	gtty (0, &s);
	s.sg_flags |= RAW;
	if (Winit (&s, 0)) {
		ioctl (0, TIOCLGET, &mode);
		DisplayMode = (mode & LCRTBS) ? Disp_CRT : Disp_Printer;
		Raw ();
		return;
	}
	Wscreensize (&Rows, &Cols);
	CommandWindow = Wopen (0, 0, 0, Min (Cols, 50), Min (Rows, 10), 0, 0);
	if (CommandWindow == 0) {
		printf ("error opening cmd win\r\n");
		Wexit (1);
	}
	Whide (CommandWindow);
	Wframe (CommandWindow);
	Wlabel (CommandWindow, "Commands & Such", 0, 1);
	DisplayMode = Disp_Windows;
}

X/* Start raw mode */
static Raw () {
	struct sgttyb s;

	gtty (0, &WOld);
	s = WOld;
	s.sg_flags |= RAW;
	s.sg_flags &= ~ECHO;
	stty (0, &s);
}

X/* Fix the scroll step for all existing windows */
DFixScroll ()
{
	register struct connection *c;

	if (DisplayMode != Disp_Windows)
		return;
	for (c = conlist; c < &conlist[MAXCONN]; c++)
		if (c -> con_user[0])
			Wsetpopup (c -> con_d.d_win, ScrollStep);
}

X/* Adjust the sizes and positions of all the windows on the display */
static DAdjust ()
{
	register width, height, n;
	register struct connection *c;

	if (nconn < 2)
		return;
	width = Cols / ((nconn + 3) / 4);
	height = Rows / ((nconn - 1) % 4 + 1);
	NextRow = 0;
	NextCol = 0;
	for (c = conlist; c < &conlist[MAXCONN]; c++) {
		if (c -> con_user[0]) {
			sprintf (StringBuf,
				c == Myself ? "Outgoing line" :
				c -> con_state == con_t ? "Talking to %s" :
				"Waiting for %s",
				c -> con_id);
			SetSize (c -> con_d.d_win, width, height);
		}
	}
}

X/* Set the size and position of the specified window, relscrolling as
   necessary to retain the last visible context. */
static SetSize (w, cols, rows)
register Win *w;
int cols, rows;
{
	int orows;		/* Old number of rows */

	orows = w -> w_cursor.row + 3;
	Wmove (w, 0, 0);	/* Move it home so Wsize will succeed */
	Wsize (w, cols, rows);
	Wmove (w, NextCol, NextRow);
	if (rows < orows)
		Wrelscroll (w, orows - rows, 0, 1);
	Wframe (w);
	Wlabel (w, StringBuf, 0, 1);
	NextCol += cols;
	if (NextCol >= Cols) {
		NextCol = 0;
		NextRow += rows;
		if (NextRow >= Rows)
			NextRow = 0;
	}
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 display.c
	/bin/echo -n '	'; /bin/ls -ld display.c
fi
/bin/echo 'Extracting ipcio.c'
sed 's/^X//' <<'//go.sysin dd *' >ipcio.c
#ifndef lint
static char rcsid[] = "@(#)ipcio.c	U of Maryland ACT 31-Mar-1983";
#endif

#include "ipc.h"

char *malloc ();

localport SysLocalPort;
IPCIO _ports[NPORTS];

static Option;

X/* Called to set REPLY or WAIT mode, as appropriate */
ioption (opt)
int opt;
{
	Option = opt;
}

X/* Called to invalidate all ports.  The keyboard server (started with a
   fork() in keys.c) uses this to tell these routines that all those
   IPCIO things he inherited are no good. */
iinvalid ()
{
	register IPCIO *i;

	for (i = _ports; i < &_ports[NPORTS]; i++)
		i -> i_name[0] = 0;
}

X/* Open a port and return a pointer to it */
IPCIO *
iopen (name, mode)
register char *name, *mode;
{
	register IPCIO *p;

	for (p = _ports; p < &_ports[NPORTS]; p++)
		if (p -> i_name[0] == 0)
			break;
	if (p >= &_ports[NPORTS])/* None free */
		return 0;
	strcpy (p -> i_name, name);
	p -> i_mode = *mode == 'w' ? 1 : 0;
	p -> i_blocked = 0;
	p -> i_closed = 0;
	p -> i_cnt = 0;
	if (p -> i_data == 0) {
		p -> i_data = malloc (IPCD);
		if (p -> i_data == 0)
			return 0;
	}
	p -> i_msg.MsgType = NORMALMSG;
	p -> i_msg.LPType = TYPEPT;
	p -> i_msg.LocalPort = SysLocalPort;
	p -> i_msg.RPType = TYPEPT;
	p -> i_msg.ID = 1;
	p -> i_msg.Type = TYPEBYTE;
	p -> i_msg.DataPtr = (caddr_t) p -> i_data;
	/* If reading, create a port for others to write to */
	if (*mode == 'r') {
		p -> i_msg.RemotePort = ipcallocateport (0);
		if (p -> i_msg.RemotePort == NULLPORT) {
			p -> i_name[0] = 0;
			return 0;
		}
		if (!ipcassertname (name, p -> i_msg.RemotePort)) {
			ipcdeallocateport (p -> i_msg.RemotePort);
			p -> i_name[0] = 0;
			return 0;
		}
		return p;
	}
	p -> i_msg.RemotePort = ipclocate (p -> i_name);
	return p;
}

X/* Close a port.  force says if data is pending, close anyway. */
iclose (p, force)
register IPCIO *p;
int force;
{
	if (p -> i_msg.RemotePort == NULLPORT) {
		ifree (p);
		return;
	}
	isend (p, 0);		/* Flush output if possible */
	p -> i_closed = 1;	/* Mark as closed */
	if (p -> i_cnt == 0 || force)
		ifree (p);	/* MACRO (see ipc.h) */
}

X/* This routine really closes the IPC port.  This won't happen unless someone
   forces it to, or if iclose is called and there's no pending data, or if
   the pending data gets flushed. */
ifree (p)
register IPCIO *p;
{
	ipcdeallocateport (p -> i_msg.RemotePort);
	p -> i_msg.RemotePort = NULLPORT;
	p -> i_name[0] = 0;
}

X/* Write data from buf (n bytes) to ipc port p.  n<0 means write -n bytes
   but do not flush. */
iwrite (p, buf, n)
register IPCIO *p;
char *buf;
register n;
{
	int noflush = n < 0;

	if (n < 0)
		n = -n;
	if (n == 0)
		return 0;
	if (p -> i_mode == 0)	/* Its a read port, not a write port */
		return -1;
	if (p -> i_closed)	/* Its closed, dont write */
		return -1;
	if (p -> i_cnt + n > IPCD)/* See if its near-full */
		n = IPCD - p -> i_cnt;
	if (n <= 0)
		return -1;
	bcopy (buf, &p -> i_data[p -> i_cnt], n);
	p -> i_cnt += n;
	if (p -> i_msg.RemotePort == NULLPORT)
		itryopen (p);
	if (!noflush && !p -> i_blocked && p -> i_msg.RemotePort != NULLPORT)
		return isend (p, 0, Option) ? 0 : n;
	return 0;
}

X/* Actually send the pending data on port p.  Won't try if p->i_blocked is
   true and t == 0. */
isend (p, t)
register IPCIO *p;
int t;
{
	if (p -> i_mode == 0)	/* Its an incoming port (!) */
		return -1;
	if (p -> i_cnt == 0 || p -> i_msg.RemotePort == NULLPORT)
		return -1;	/* Cant send, no data */
	if (p -> i_blocked && t == 0)
		return -1;	/* Cant send, blocked */
	p -> i_msg.NumElts = p -> i_cnt;
	t = ipcsend (&p -> i_msg, t, Option);
	if (t == 0)
		p -> i_blocked = 1;
	else if (t == -1)	/* Didnt get sent */
		return -1;
	p -> i_cnt = 0;
	if (p -> i_closed)	/* Done with this port */
		ifree (p);
	return 0;
}

X/* Try to open port p if it isn't already. */
itryopen (p)
register IPCIO *p;
{
	if (p -> i_msg.RemotePort == NULLPORT) {/* Try to open it */
		p -> i_msg.RemotePort = ipclocate (p -> i_name);
		if (p -> i_msg.RemotePort == NULLPORT)
			return -1;/* Didnt get it */
	}
	return 0;
}

X/* Read a message from port(s) */
iread (ports, buf, n)
struct SetOfPorts *ports;
char *buf;
register n;
{
	struct Msg msgin;
	register localport p;
	register IPCIO *i;

rec:
	msgin.Type = TYPEBYTE;
	msgin.NumElts = n;
	msgin.DataPtr = (caddr_t) buf;
	if (ipcreceive (ports, &msgin, 0) != true)
		return -1;
	if (msgin.LocalPort == DATAPORT) {
		p = * (localport *) buf;
		if (p == DATAPORT)
			goto rec;	/* ignore */
		for (i = _ports; i < &_ports[NPORTS]; i++)
			if (i -> i_msg.RemotePort == p)
				break;
		if (i >= &_ports[NPORTS]) {
			error (0, "?? p %d (msgid = %d)\r\n", p, msgin.ID);
			goto rec;
		}
		if (msgin.ID == PORTDELETED)
			i -> i_deleted = 1;
		else if (msgin.ID == MSGACCEPTED) {
			i -> i_blocked = 0;
			isend (i, 0);
		}
		goto rec;
	}
	return msgin.NumElts;
}

X/* This is called after "the" system local port is obtained */
X/* SetSysPort (p): CHANGED TO MACRO */

X/* Called to fix the localport value for the system port */
X/* FixSysPort (p): CHANGED TO MACRO */
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 ipcio.c
	/bin/echo -n '	'; /bin/ls -ld ipcio.c
fi
/bin/echo 'Extracting keys.c'
sed 's/^X//' <<'//go.sysin dd *' >keys.c
#ifndef lint
static char rcsid[] = "@(#)keys.c	U of Maryland ACT 31-Mar-1983";
#endif

#include "phone.h"
#include <signal.h>

static ChildPID;

StartKeyboardServer ()
{
	int pid = fork ();

	if (pid == -1)
		error (1, "Can't fork!?\r\n");
	if (pid) {
		ChildPID = pid;
		return;
	}
	if (KInit ()) {
		kill (getppid (), SIGINT);
		exit (1);
	}
	Keys ();
	printf("done with Keys() (shouldn't happen)\r\n");
	kill (getppid (), SIGINT);
	exit (1);
}

Exit (e)
{
	if (ChildPID)
		kill (ChildPID, SIGKILL);
	exit (e);
}

static
KInit ()
{
	sigset (SIGTSTP, SIG_DFL);
	sigset (SIGINT, SIG_DFL);
	ioption (WAIT);
	iinvalid ();
	return ph_findmyself ();
}

static
Keys ()
{
	static char cbuf[100];
	register n;

	for (;;) {
		n = read (0, cbuf, sizeof cbuf - 1);
		if (n <= 0) {
			ph_write (Myself, "\4");
			sleep (20);	/* Give parent time to kill me */
			return;
		}
		cbuf[n] = 0;
		ph_write (Myself, cbuf);
	}
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 keys.c
	/bin/echo -n '	'; /bin/ls -ld keys.c
fi
/bin/echo 'Extracting ngets.c'
sed 's/^X//' <<'//go.sysin dd *' >ngets.c
#ifndef lint
static char rcsid[] = "@(#)ngets.c	U of Maryland ACT 31-Mar-1983";
#endif

#include <sgtty.h>
#include "phone.h"

#define	BK	'\\'
#define	BKSL	01
#define	LNXT	02

extern struct sgttyb WOld;

X/*
 * ngets (s, n, f)
 *
 * 27 March 1983 ACT
 *
 * Get at most n characters into buffer s from stdin, echoing via (*f)()
 */

char *ngets (s, n, f, sendctlx)
char *s;		/* Space get into */
register n;		/* Max bytes */
register (*f)();	/* Echo function */
int sendctlx;		/* True iff ^X should just "echo" ^X */
{
	register c;
	register char *cp = s;
	int md = 0;

	for (;;) {
		c = GetChar ();
		if (c < 0) {
			*cp = 0;
			return s;
		}
		/* Check for literal-next */
		if (md & LNXT) {
			md &= ~LNXT;
			goto literal;
		}
		/* Check for specials.  ^L: redraw screen */
		if (c == CTRL('L')) {
			ScreenGarbaged++;/* does nothing in printer mode */
			continue;
		}
		/* ^M, ^J: enter the line */
		if (c == '\r' || c == '\n') {
			*cp = 0;
			(*f) ('\n');
			return s;
		}
		/* ^V: Literal next */
		if (c == CTRL('V')) {
			if (n < 2)
				continue;
			md |= LNXT;
			md &= ~BKSL;
			continue;
		}
		/* ^W: Word erase */
		if (c == CTRL('W')) {
			if (cp == s)
				continue;
			if (*--cp == ' ' || *cp == '\t') {
				while (cp >= s && *cp == ' ') {
					back (*cp, f);
					n++;
					cp--;
				}
			}
			while (cp >= s && *cp != ' ') {
				back (*cp, f);
				n++;
				cp--;
			}
			cp++;
			continue;
		}
		/* ^R: Retype */
		if (c == CTRL('R')) {
			(*f) ('^');
			(*f) ('R');
			(*f) ('\n');
			c = cp - s;
			cp = s;
			while (--c >= 0)
				echo (*cp++, f);
			continue;
		}
		/* ^Z: Suspend */
		if (c == CTRL('Z')) {
			Suspend ();
			continue;
		}
		/* Check for backspace or line kill, but only if didn't
		   just type a backslash (backslash lnexts them) */
		if ((md & BKSL) == 0) {
			if (c == WOld.sg_erase) {
				if (cp > s) {
					back (*--cp, f);
					n++;
				}
				continue;
			}
			else if (c == WOld.sg_kill) {
				n += cp - s;
				if (sendctlx)
					(*f) (CTRL ('X'));
				else
					while (cp > s)
						back (*--cp, f);
				cp = s;
				continue;
			}
		}
		else if (c == WOld.sg_erase || c == WOld.sg_kill) {
			md &= ~BKSL;
			(*f) ('\b');
			--cp;
			goto put;	/* lnext it */
		}
		if (n < 2)
			continue;
		if (c == BK)
			md |= BKSL;
		else
			md &= ~BKSL;
literal:
		n--;
put:
		echo (c, f);
		*cp++ = c;
	}
}

X/* Echo a character, converting controls to ^ escapes */
static echo (c, f)
register c;
register (*f)();
{
	if (c >= ' ' && c < 0177)
		(*f) (c);
	else {
		(*f) ('^');
		(*f) (c == 0177 ? '?' : c + '@');
	}
}

X/* Called to un-type one character */
static back (c, f)
register c;
register (*f)();
{
	if (c < ' ' || c == 0177)
		f ('\b');
	f ('\b');
}

X/*
 * puts_more (s, w)
 *
 * 31 March 1983 ACT
 *
 * Put string 's' into window 'w' with 'more' mode.
 */

static struct q {
	char	*q_s;	/* queued string */
	struct q *q_n;	/* next in queue */
} *putsh, *putst;

puts_more (s, w)
register char *s;
register Win *w;
{
	register winlines = w -> IYE;
	register char *cp;
	int dofree = 0;
	char c;
	Win *oldcur;
	static nest;

	if (nest) {
		if (putsh)
			putst = putst -> q_n =
				(struct q *) alloc (sizeof (struct q));
		else
			putsh = putst = (struct q *) alloc (sizeof (struct q));
		putst -> q_s = strsave (s);
		putst -> q_n = 0;
		return;
	}
	nest++;
	oldcur = CurWin;
	CurWin = w;
	Woncursor (w, 0);
top:
	cp = s;
	while (*cp) {
		Wputc (*cp, w);
		if (*cp++ == '\n')
			lineno++;
		if (lineno == winlines - 1 && (*cp || putsh)) {
			Wsetmode (w, WINVERSE);
			Wputs ("--More--[space to continue, DEL to abort]\r",
				w);
			Wsetmode (w, 0);
			c = GetChar ();
			Wclearline (w, 0);
			if (c == 0177 || c == 'q')
				break;
			lineno = 0;
		}
	}
	if (dofree)
		free (s);
	if (putsh) {
		s = putsh -> q_s;
		dofree++;
		free (putsh);
		putsh = putsh -> q_n;
		goto top;
	}
	CurWin = oldcur;
	Woncursor (w, 1);
	nest = 0;
}

X/* malloc, but complains if fails */
char *alloc (n) unsigned n; {
	char *s = malloc (n);

	if (s)
		return s;
	else
		error (1, "malloc failed (shouldn't happen)\r\n");
}

X/* save a string */
char *strsave (s)
register char *s;
{
	strcpy (alloc (strlen (s) + 1), s);
	return s;
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 ngets.c
	/bin/echo -n '	'; /bin/ls -ld ngets.c
fi
/bin/echo 'Extracting phio.c'
sed 's/^X//' <<'//go.sysin dd *' >phio.c
#ifndef lint
static char rcsid[] = "@(#)phio.c	U of Maryland ACT 31-Mar-1983";
#endif

#include "phone.h"

X/*
 * NEWLOG is not in use.  I don't think I need it.
 */

static DisableLog;		/* If true ph_log() will do nothing */

ph_makemyself ()
{
	register struct connection *c = conlist;
	char *user, *tty;

	if ((user = (char *) getlogin ()) == 0)
		error (1, "Who are you?\r\n");
	if ((tty = (char *) ttyname (0)) == 0)
		error (1, "Where are you?\r\n");
	if (strncmp (tty, "/dev/", 5) == 0)
		tty += 5;
	else if (strncmp (tty, "/tmp/dev_", 9) == 0)
		tty += 9;
	else {
		register char *c = tty;

		while (*c)
			if (*c++ == '/')
				c[-1] = '.';
	}
	strcpy (c -> con_user, user);
	strcpy (c -> con_tty, tty);
	strcpy (c -> con_id, concat (user, ":", tty, 0));
	sprintf (LogFile, "/tmp/ph%s", c -> con_id);
	c -> con_ipc = iopen (concat (user, ":", tty, 0), "r");
	if (c -> con_ipc == 0)
		error (1, "Failed to open IPC port\r\n");

	SetSysPort (c -> con_ipc -> i_msg.RemotePort);/* ycch */
	FixSysPort (c -> con_ipc);/* ycch */

	c -> con_state = con_t;
	nconn++;
	DAlloc (c);
}

X/* Find Myself port */
ph_findmyself () {
	register struct connection *c;
	for (c = &conlist[1]; c < &conlist[MAXCONN]; c++)
		c -> con_user[0] = 0;
	Myself -> con_ipc = iopen (MyID, "w");
	return ISOPEN (Myself -> con_ipc) ? 0 : -1;
}

X/* Connect to user "user" on tty "tty" */
ph_conn (user, tty)
char *user, *tty;
{
	register struct connection *c;

	for (c = conlist; c < &conlist[MAXCONN]; c++)
		if (c -> con_user[0] == 0)
			break;
	if (c >= &conlist[MAXCONN])
		error (1, "No slot for connection!\r\n");
	strcpy (c -> con_user, user);
	strcpy (c -> con_tty, tty);
	strcpy (c -> con_id, concat (user, ":", tty, 0));
	c -> con_ipc = iopen (c -> con_id, "w");
	c -> con_state = con_c;
	if (ISOPEN (c -> con_ipc)) {
		c -> con_state = con_t;/* Force ph_write to send */
		ph_write (c, "\3");/* We know this wont block */
		c -> con_state = con_c;
	}
	else if (tell (user, tty,
"\r\n%s:%s is calling you.  Type \"phone\" to answer.\r\n\7", Me, MyTty)) {
		c -> con_user[0] = 0;
		return -1;
	}
	nconn++;
	ph_log ();		/* Update the log file */
	DAlloc (c);		/* Allocate a display */
	return 0;
}

X/* Write string s to connection c */
ph_write (c, s)
register struct connection *c;
register char *s;
{
	register IPCIO *i = c -> con_ipc;
	static beenhere, mlen;
	static char m[100];

	if (!beenhere) {
		beenhere++;
		/* Use ~ to separate user:tty from text */
		sprintf (m, "%s~", MyID);
		mlen = -strlen (m);
	}
	if (i -> i_cnt == 0)
		iwrite (i, m, mlen);

	/* Do NOT attempt to flush when writing to a waiting-for conn.! */

	if (c -> con_state == con_t)
		iwrite (i, s, strlen (s));
	else
		iwrite (i, s, -strlen (s));
}

X/* Disconnect connection c */
ph_disconn (c)
register struct connection *c;
{
	if (c -> con_state == con_c /* && !ISOPEN (c -> con_ipc) */) {
		/* Have to send him a message */
		tell (c -> con_user, c -> con_tty,
			"%s:%s hung up at %s\r\n", Me, MyTty, tyme ());
		iclose (c -> con_ipc, 1);
	}
	else {
		ph_write (c, "\4");/* Write a ^D to him */
		isend (c -> con_ipc, 40);
		iclose (c -> con_ipc, 0);
	}
	c -> con_user[0] = 0;
	nconn--;
	if (!DisableLog)
		ph_log ();	/* Update the log file */
	DFree (c);		/* Free the display for conn c */
}

X/* NOTE: SECRET +1s ARE TO SKIP Myself AS A CONNECTION */
X/* Disconnect all connections */
ph_disall ()
{
	register struct connection *c;

	unlink (LogFile);
	DisableLog++;
	for (c = &conlist[1]; c < &conlist[MAXCONN]; c++)
		if (c -> con_user[0])
			ph_disconn (c);
}

X/* Returns conn. if user u on tty t is connected (or being waited for) */
struct connection *
ph_iscon (u, t)
register char *u, *t;
{
	register struct connection *c;

	for (c = &conlist[1]; c < &conlist[MAXCONN]; c++)
		if (strcmp (c -> con_user, u) == 0 &&
				strcmp (c -> con_tty, t) == 0)
			return c;
	return 0;
}

X/* (Re)write the log file of who's connected */
ph_log ()
{
	register struct connection *c;
	register FILE *fp;

	unlink (LogFile);
	if (DisableLog)
		return;
	fp = fopen (LogFile, "w");
	if (fp == 0)
		error (1, "Can't open phone log file '%s'\r\n", LogFile);
	chmod (LogFile, 0644);
	for (c = conlist; c < &conlist[MAXCONN]; c++) {
		if (c -> con_user[0])
#ifdef NEWLOG
			fprintf (fp, "%s %c\n", c -> con_id,
				c -> con_state == con_t ? 't' : 'c');
#else not NEWLOG
			fprintf (fp, "%s\n", c -> con_id);
#endif NEWLOG
	}
	fclose (fp);
}

X/*
 * Get a character from keyboard port.  Call ChatChar for characters
 * from other ports.  In any case, connection is left in From (0 ==
 * new caller) and name in Caller.  As a special hack, when ChatChar
 * is called, before reading more, a 0 will be returned if there are
 * < 2 connections.  This is so that Chat() can exit when there's no
 * one left to talk to.
 */

GetChar ()
{
	register char *p;
	register struct connection *c;
	int others = 0;
	static char buf[IPCD + 1], *cp;
	static n;
	extern enum Disp_Modes DisplayMode;

top:
	while (n) {
		n--;
		*cp &= 0177;
		if (From != Myself) {
			ChatChar (*cp++);
			others++;
		}
		else {
			lineno = 0;
			return *cp++;
		}
	}
get:
	do {
		if (others && nconn < 2)
			return 0;
		if (DisplayMode == Disp_Windows) {
			WRCurRow = CurWin -> OYO + CurWin -> IYO +
				   CurWin -> w_cursor.row;
			WRCurCol = CurWin -> OXO + CurWin -> IXO +
				   CurWin -> w_cursor.col;
			Wrefresh (0);
		}
		n = iread ((struct SetOfPorts *) -1, buf, sizeof buf - 1);
	} while (n == 0);
	if (n < 0) {
		error (0, "ipcreceive returned false (shouldn't happen)\r\n");
		goto get;
	}
	buf[n] = 0;

	/* Find ~ that marks text, and figure out who it came from */
	for (p = buf; *p && *p != '~'; p++)
		;
	n -= p - buf + 1;
	if (*p) {
		*p++ = 0;
		cp = p;
		From = 0;
		Caller = buf;
		for (c = conlist; c < &conlist[MAXCONN]; c++) {
			if (c->con_user[0] && strcmp (c->con_id, buf) == 0) {
				From = c;
				break;
			}
		}
		goto top;
	}
	/* This shouldnt happen */
	error (0, "bad format data '%s' (shouldn't happen)\r\n", buf);
	goto get;
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 phio.c
	/bin/echo -n '	'; /bin/ls -ld phio.c
fi
/bin/echo 'Extracting phone.c'
sed 's/^X//' <<'//go.sysin dd *' >phone.c
#ifndef lint
static char rcsid[] = "@(#)phone.c	U of Maryland ACT 31-Mar-1983";
#endif

#include "phone.h"
#include <sys/ioctl.h>

X/*
 * Phone
 * version 2.2
 *
 * Chris Torek @ umaryland
 * From phone by Jim Rees @ uw-beaver
 *
 * Usage: phone [-a] [-c|-p|-n] [-s#] [user ...]
 *	where
 * -a   => answer all incoming calls (default if no user specified)
 * -c   => CRT mode: don't use windows, but do assume that backspace works
 * -p   => printer mode: don't use windows, # for \b, " XXX" for line kill
 * -n	=> no local echo, crt mode
 * -s#  => set scroll step to <number> -- like $scroll # command
 * user => call user (user is of form "loginname[:ttyname]")
 */

int DisallowSuspends;

main (argc, argv)
register argc;
register char **argv;
{
	register i;
	int nusers = 0, answerall = 0, crtmode = 0, printmode = 0, nmode = 0;

	ScrollStep = 1;
	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') switch (argv[i][1]) {
		case 'a':
			answerall++;
			break;
		case 'c':
			if (printmode || nmode)
				error (0, "Use only one of (-p, -c, -n)\r\n");
			else
				crtmode++;
			break;
		case 'p':
			if (crtmode || nmode)
				error (0, "Use only one of (-p, -c, -n)\r\n");
			else
				printmode++;
			break;
		case 'n':
			if (crtmode || printmode)
				error (0, "Use only one of (-p, -c, -n)\r\n");
			else
				nmode++;
			break;
		case 's':
			ScrollStep = atoi (argv[i] + 2);
			if (ScrollStep < 0)
				ScrollStep = 0;
			break;
		default:
			error (0, "Unrecognized option -%c\r\n", argv[i][1]);
			break;
		}
		else
			nusers++;
	}
	if (nusers == 0)
		answerall++;

	if (ioctl (0, TIOCGETD, &DisallowSuspends) == 0
					&& DisallowSuspends == OTTYDISC)
		DisallowSuspends = 1;
	else
		DisallowSuspends = 0;

	StartDisplay (crtmode ? 1 : printmode ? 2 : nmode ? 3 : 0);
	ph_makemyself ();
	if (answerall)
		AnswerAllCalls ();
	if (nusers) {
		for (i = 1; i < argc; i++)
			if (argv[i][0] != '-')
				PlaceCall (argv[i], 0, 0);
	}
	if (nconn == 1) {
		if (nusers == 0)
			error (0, "No one is calling you.\r\n");
		CleanUpDisplay (1);
		exit (1);
	}
	StartKeyboardServer ();
	Chat ();
	ph_disall ();		/* Disconnect everyone */
	CleanUpDisplay (0);
	Exit (0);
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 phone.c
	/bin/echo -n '	'; /bin/ls -ld phone.c
fi
/bin/echo 'Extracting u.c'
sed 's/^X//' <<'//go.sysin dd *' >u.c
#ifndef lint
static char rcsid[] = "@(#)u.c	U of Maryland ACT 31-Mar-1983";
#endif

#include "phone.h"
#include <utmp.h>
#include <time.h>

struct utmp u;

#define	NMAX	(sizeof u.ut_name)
#define	LMAX	(sizeof u.ut_line)

X/* tell gives the specified person a message. */
tell (user, tty, fmt, args)
register char *user, *tty;
char *fmt;
int args;
{
	register FILE *f;

	if (!IsOn (user, tty))
		return -1;	/* Can't tell, not on spec'd tty */

	f = fopen (concat ("/dev/", tty, 0), "w");
	if (f == NULL)
		return 1;	/* Cant tell, user is not accepting */
	_doprnt (fmt, &args, f);/* Ick */
	fclose (f);
	return 0;
}

X/* Return the time as a string: nn:nn {am,pm} */
char *
tyme ()
{
	static char tymbuf[12];
	long now;
	register struct tm *lt;
	struct tm *localtime();

	(void) time (&now);
	lt = localtime (&now);
	sprintf (tymbuf, "%d:%02d %s",
		lt -> tm_hour == 0 ? 12 :
		lt -> tm_hour > 12 ? lt -> tm_hour - 12 : lt -> tm_hour,
		lt -> tm_min,
		lt -> tm_hour >= 12 ? "pm" : "am");
	return tymbuf;
}

X/* Returns the concatenation of its arglist */
char *
concat (s1)
char *s1;
{
	static char buf[1024];
	register char **sp = &s1, *s, *d = buf;

	while (s = *sp++) {
		while (*d++ = *s++)
			;
		d--;
	}
	return buf;
}

static FILE *ufp;

RewindU ()
{
	if (!ufp) {
		ufp = fopen ("/etc/utmp", "r");
		if (!ufp)
			error (1, "Can't open /etc/utmp\r\n");
		return;
	}
	rewind (ufp);
}

char *NextU (who)
char *who;
{
	static char tt[LMAX+1];

	while (fread (&u, sizeof u, 1, ufp) == 1) {
		if (u.ut_line[0] && u.ut_name[0] &&
		    (who == 0 || strncmp (who, u.ut_name, NMAX) == 0)) {
			strncpy (tt, u.ut_line, LMAX);
			return tt;
		}
	}
	return 0;
}

IsOn (who, t)
char *who, *t;
{
	long pos;

	pos = ufp ? ftell (ufp) : 0L;
	RewindU ();
	while (fread (&u, sizeof u, 1, ufp) == 1) {
		if (u.ut_line[0] && u.ut_name[0] &&
		    strncmp (who, u.ut_name, NMAX) == 0 &&
		    strncmp (t, u.ut_line, LMAX) == 0) {
			fseek (ufp, pos, 0);
			return 1;
		}
	}
	fseek (ufp, pos, 0);
	return 0;
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 u.c
	/bin/echo -n '	'; /bin/ls -ld u.c
fi

jdi@psuvax.UUCP (10/27/83)

Ok, now that we have the program, just what is 'libipc.a'???  I mean I realize
it must be inter-process control, and it sounds interesting, but I sure don't
have it.

Can it be sent via sources?
-- 
			John Irwin
			Pennsylvania State University
			{akgua, allegra, ihnp4, burdvax}!psuvax!jdi
			(814) 238-7556