Phone program re-visited
Mark Keating
markk at censor.UUCP
Wed Mar 29 00:25:48 AEST 1989
Some time ago the program 'Phone' was posted on the Net, following
that some requests were make because of some problems with it.
I decided to play with this to better understand IPC's and ended
up fixing the major problems with it, and so am posting it back.
Things fixed:
Phone now negotiates with each end to determine whether
the callee is already in a conversation, if so a message
is returned to the caller saying that the line is busy,
also a message is put up of the callee's status line who
last tried calling (sort of a call waiting). This eliminated
the problems with phone going out to lunch when a third
caller tried to get in on the conversation.
Added in an option to specify the tty port for when a user
is logged on more than once.
Added in one of those silly little clocks that keeps reminding
you what time it is while your busy yakkity-yakkin on the phone.
Fixed up some minor annoyances where phone would leave the tty
in a somewhat insane state and also didn't cleanly disconnect
when the connection couldn't be made.
I still find this program nice in the fact that it doesn't require a
server, and only requires that you run it.
Hope you find it useful, seems like an excellent lil Unix comm program.
------------------------------cut here------------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
# phone.1
# phone.c
# Makefile
# This archive created: Tue Mar 28 08:45:55 1989
export PATH; PATH=/bin:$PATH
if test -f 'phone.1'
then
echo shar: will not over-write existing file "'phone.1'"
else
cat << \SHAR_EOF > 'phone.1'
.TH PHONE 1 "July 17, 1988"
.UC 4
.SH NAME
phone \- phone another user, typing screen to screen.
.SH SYNOPSIS
phone [ user ] / [ -t ttyXXX ]
.SH DESCRIPTION
.I Phone
causes a message to appear on the terminal being used by
.I user
that indicates that someone is trying to phone him.
If the other user also executes phone, then both screens are cleared
and the users are set up in a two way conversation.
.PP
Each user's input is displayed at the bottom of their own
screen and at the top of the other user's screen. The
following control characters are handled:
.TP 20
.BI "ERASE\ (Control\ H)"
Erase the character before the cursor.
.TP
.BI "KILL\ (Control\ U)"
Erase the line the cursor is on and move the cursor to the beginning
of the (now blank) line.
.TP
.BI "Control\ L"
Refresh your own screen.
.TP
.BI "Control\ G"
Either flash (preferably) or ring the bell on other user's terminal.
.TP
.BI "EOF\ (Control\ D)"
Discontinue execution. The other user is informed.
.PP
(The characters in parenthesis above are typical assignments
for these control characters. Those assigned by the user
will be used. See STTY(1)).
.PP
.I Phone
handles only two users. If a third user attempts entry into
a conversation, they are notified that the line is busy, also
the person on the line is notified by a status message that
indicates who was trying to call.
.PP
Works only when each user is logged in once. An error
message is indicated if phone cannot determine the proper
port. In this case using the '-t tty' option will
allow proper access.
.PP
Requires System V, since it uses Sys V IPC.
.SH AUTHOR
Jack Bonn
.br
Software Labs, Ltd.
.br
Box 451
.br
Easton, CT 06612
.PP
jack at swlabs.UUCP
.br
uunet!swlabs!jack
SHAR_EOF
fi # end of overwriting check
if test -f 'phone.c'
then
echo shar: will not over-write existing file "'phone.c'"
else
cat << \SHAR_EOF > 'phone.c'
/* opt: -O -o phone -lcurses
Nice program but missing some things:
- added in call negotiation to stop from flaking when a third
user requests access
- added in notification of who was trying to call
(sort of like call waiting)
- added in a silly little time clock in corner to let you
know how much time you have been waisting here
One of the nice thing about phone is the fact that a server is not
required and just the single point to point connections do all the work
*/
#include <sys/param.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <utmp.h>
#include <signal.h>
#include <curses.h>
#include <stdio.h>
#include <string.h>
#define SAME 0 /* For calls to strcmp */
#define CTRL_G ('G'-'A'+1)
#define CTRL_K ('K'-'A'+1)
#define CTRL_L ('L'-'A'+1)
#define DELETE 0x7F
/*
packet.type is one of these --
*/
#define HOWDY 1 /* Initiate/Start connection */
#define ADIOS 2 /* Hang up/disolve connection */
#define DISPLAY 3 /* Display character on screen */
#define FLASH 4 /* Ring bell/flash screen */
#define REFRESH 5 /* Redraw screen on CTL-L */
#define ERASE 6 /* Erase previous character */
#define KILL 7 /* Erase line cursor is on */
/*
packet.origin is one of these --
*/
#define LOCAL 0
#define REMOTE 1
#define IPC_ID 231 /* Upper part of key (for uniqueness) */
#define RING_TIME 10 /* Ring cadence in seconds */
int pid, busy, exit_flag; /* pid for offspring, if non-zero */
int loc_q, rem_q; /* message queue id's */
key_t my_key, other_key; /* keys for the two queues */
key_t ftok(); /* convert a file to a key */
char *my_name; /* My User Name */
char *my_tty; /* Path of my Device */
char other_name[24]; /* Other User Name */
char other_tty[24]; /* Path of other Device */
int w_size, w_next; /* window size and pointer */
int erase_ch, eof_ch; /* Special control chars for user */
int screen_set; /* flag to indicate curses init'ed */
WINDOW *topwin, *botwin; /* curses windows */
struct utmp *u_elem, *getutent();
struct termio new_ioargs, old_ioargs;
struct packet { /* This is the packet that we ipc */
long type;
int origin;
char keypress;
char buff[31];
} s_pkt, r_pkt;
void wrapup();
char *getenv();
char *ttyname();
main(argc,argv)
int argc;
char *argv[];
{
if (argc == 2)
strcpy(other_name, argv[1]); /* get by other user's name */
else if (argc == 3 && (strcmp(argv[1], "-t") == SAME)) {
strcpy(other_tty, "/dev/");
strcat(other_tty, argv[2]); /* or by other tty's name */
}
else {
puts("usage: phone [ user ] / [ -t ttyXXX ]");
exit(1);
}
my_name = getenv("LOGNAME"); /* Get our name */
my_tty = ttyname(0); /* and Device */
ioctl(0, TCGETA, &old_ioargs);
erase_ch = old_ioargs.c_cc[VERASE];
eof_ch = old_ioargs.c_cc[VEOF];
signal(SIGINT, wrapup);
if (get_tty(argc == 2) == TRUE) { /* Get other User's Name / Device */
/* Get unique keys for naming message queues */
my_key = ftok(my_tty, IPC_ID);
other_key = ftok(other_tty, IPC_ID);
if (open_queues()) {
if (pid=fork()) scrn_proc(); /* Returns when ADIOS packet received */
else key_proc(); /* Never returns, must be killed */
}
else exit_flag = 2;
}
else exit_flag = 3;
wrapup();
}
void wrapup()
{
int ret_code; /* Wait insists on an argument */
struct msqid_ds buf;
if (pid) { /* Kill sibbling if present */
kill(pid, SIGQUIT);
wait(&ret_code);
}
if (screen_set == TRUE) endwin(); /* Clean up curses windows */
msgctl(loc_q, IPC_RMID, &buf); /* Remove our message queue */
if (!busy) send_remote(ADIOS, 0); /* In case other side is still up */
ioctl(0, TCSETA, &old_ioargs);
exit(exit_flag);
}
void wrapup_child()
{
exit(0);
}
key_proc()
{
register int ch;
/* Keyboard process */
signal(SIGQUIT, wrapup_child);
new_ioargs = old_ioargs;
new_ioargs.c_lflag &= ~(ISIG | ICANON | ECHO);
new_ioargs.c_cc[VMIN] = (char)1;
ioctl(0, TCSETA, &new_ioargs);
for (;;) {
ch = getchar();
if (ch >= DELETE) send_both (ERASE, 0);
else if (ch >= ' ') send_both (DISPLAY, ch);
else if (ch == erase_ch) send_both (ERASE, 0);
else if (ch == CTRL_K) send_both (KILL, 0);
else if (ch == CTRL_L) send_local(REFRESH, 0);
else if (ch == CTRL_G) send_both (FLASH, 0);
else if (ch == eof_ch) send_local(ADIOS, 0);
else if (ch == '\r' || ch == '\n')
send_both (DISPLAY, '\n');
else send_local(FLASH, 0);
}
}
scrn_proc()
{
register int y, x;
register int ch;
register WINDOW *window; /* temp curses windows */
/* Screen process */
init_screen();
do {
if (msgrcv(loc_q, &r_pkt, sizeof(r_pkt)-sizeof(r_pkt.type), 0L, 0)
== -1) continue;
window = (r_pkt.origin == LOCAL) ? botwin : topwin;
ch = r_pkt.keypress;
switch ((int)r_pkt.type) {
case HOWDY: disable_third();
break;
case ERASE: waddch(window, '\b'); wdelch(window);
break;
case KILL: getyx(window, y, x); wmove(window, y, 0);
wdeleteln(window); break;
case DISPLAY: waddch(window, (char)(ch));
break;
case ADIOS: if (r_pkt.origin == REMOTE)
wprintw(botwin, "\n*** %s has hung up. ***\n",
other_name);
break;
case REFRESH: clearok(curscr, TRUE);
break;
case FLASH: flash();
break;
}
if (r_pkt.origin == REMOTE) wnoutrefresh(topwin);
wnoutrefresh(botwin);
doupdate();
} while (r_pkt.type != ADIOS);
}
/* third entry not allowed so disconnect it */
disable_third()
{
int bad_q; /* message queue id's */
key_t third_key; /* keys for the two queues */
char *dev_ptr;
char buffer[40];
if ((dev_ptr=strchr(r_pkt.buff, ' ')) == NULL) return 0;
*dev_ptr++ = '\0';
if (strncmp(dev_ptr, "/dev/tty", 8) != SAME) return 0;
third_key = ftok(dev_ptr, IPC_ID);
if ((bad_q = msgget(third_key, 0)) == -1) return 0;
s_pkt.origin = REMOTE; s_pkt.type = ADIOS;
s_pkt.keypress = 'd'; strcpy(s_pkt.buff, other_name);
strcat(s_pkt.buff, " "); strcat(s_pkt.buff, other_tty);
msgsnd(bad_q, &s_pkt, sizeof(s_pkt.keypress) + sizeof(s_pkt.origin)
+ strlen(s_pkt.buff) + 1, 0);
sprintf(buffer, "`%s [%s]' Tried Calling", r_pkt.buff, dev_ptr+5);
mvaddstr(w_next, COLS - strlen(buffer), buffer);
flash(); refresh(); return 1;
}
init_screen()
{
char buffer[40];
initscr(); screen_set = TRUE;
w_size = (w_next = LINES/2) - 2;
topwin = newwin(w_size,0,1,0); botwin = newwin(w_size,0,w_next+1,0);
idlok(topwin,TRUE); idlok(botwin,TRUE);
scrollok(topwin,TRUE); scrollok(botwin,TRUE);
attron(A_REVERSE | A_BOLD);
sprintf(buffer, "%s [%s]:", other_name, other_tty+5);
mvprintw(0, 0, "%-*s", COLS-16, buffer);
sprintf(buffer, "%s [%s]:", my_name, my_tty+5);
mvprintw(w_next, 0, "%-*s", COLS, buffer);
put_time();
}
put_time()
{
int clock, i;
char *ct_ptr, *ctime();
clock = time((long *) 0); ct_ptr = ctime(&clock);
move(0, COLS-16); for (i=0; i<16; i++) addch(*ct_ptr++);
refresh(); wrefresh(botwin);
signal(SIGALRM, put_time); alarm(60 - (clock % 60));
}
int open_queues()
{
int i_am_phoning=0;
register int seconds;
if ((loc_q = msgget(my_key, 0666 | IPC_CREAT)) == -1) {
printf("Unable to create my queue\n");
return(FALSE);
}
/* Stand on our head to test creation of rem_q more often
than when we ring the other user. */
for (seconds=RING_TIME; ; seconds++) {
if ((rem_q = msgget(other_key, 0)) != -1) break;
else i_am_phoning = 1;
if (seconds == RING_TIME) {
seconds = 0;
if (!ring(other_tty)) {
printf("%s's phone is off the hook (mesg -n).\n\n", other_name);
return(FALSE);
}
printf("Ringing %s on %.14s\n\n", other_name, other_tty);
}
if (sleep(1)) /* != 0 if interrupted by other signal */
return(FALSE);
}
if (i_am_phoning) {
msgrcv(loc_q, &r_pkt, sizeof(r_pkt) - sizeof(r_pkt.type), 0L, 0);
if (r_pkt.type == HOWDY) {
s_pkt.origin = REMOTE; s_pkt.type = HOWDY;
s_pkt.keypress = 'y'; strcpy(s_pkt.buff, my_name);
strcat(s_pkt.buff, " "); strcat(s_pkt.buff, my_tty);
msgsnd(rem_q, &s_pkt, sizeof(s_pkt.keypress) + sizeof(s_pkt.origin)
+ strlen(s_pkt.buff) + 1, 0);
}
else return(FALSE);
}
else {
while (msgrcv(loc_q, &r_pkt, 32, 0L, IPC_NOWAIT) != -1) ;
s_pkt.origin = REMOTE; s_pkt.type = HOWDY;
s_pkt.keypress = 'n'; strcpy(s_pkt.buff, my_name);
strcat(s_pkt.buff, " "); strcat(s_pkt.buff, my_tty);
msgsnd(rem_q, &s_pkt, sizeof(s_pkt.keypress) + sizeof(s_pkt.origin)
+ strlen(s_pkt.buff) + 1, 0);
msgrcv(loc_q, &r_pkt, sizeof(r_pkt) - sizeof(r_pkt.type), 0L, 0);
if (r_pkt.type != HOWDY) {
printf("%s's line is busy, try again later.\n\n", other_name);
busy = 1; return(FALSE);
}
}
return(TRUE);
}
int get_tty(by_name)
int by_name;
{
int found=0;
while(u_elem=getutent()) {
if (by_name) {
if (strcmp(u_elem->ut_user, other_name) == SAME) {
++found; strcpy(other_tty, "/dev/");
strncpy(&other_tty[5], u_elem->ut_line, sizeof(u_elem->ut_line));
}
}
else {
if ((u_elem->ut_type == USER_PROCESS)
&& (strcmp(&other_tty[5], u_elem->ut_line) == SAME)) {
++found;
strncpy(other_name, u_elem->ut_name, sizeof(u_elem->ut_name));
}
}
}
if (found == 1) return(TRUE);
if (!by_name) printf("%s User is not logged in.\n", other_tty);
else {
if (found == 0)
printf("User %s is not logged in.\n", other_name);
else printf("User %s is logged in more than once.\n", other_name);
}
return(FALSE);
}
int ring(device)
char *device;
{
FILE *out_s;
if (!(out_s = fopen(device, "w"))) return(FALSE);
fprintf(out_s, "\n%s is phoning you from %s. Type `phone %s' to answer.\007\n\n",
my_name, my_tty, my_name);
fclose(out_s);
return(TRUE);
}
send_both(type, ch)
register int type;
register int ch;
{
send_local (type, ch);
send_remote(type, ch);
}
send_local(type, ch)
register int type;
register int ch;
{
s_pkt.origin = LOCAL; s_pkt.type = type; s_pkt.keypress = ch;
msgsnd(loc_q, &s_pkt, sizeof(s_pkt.keypress) + sizeof(s_pkt.origin), 0);
}
send_remote(type, ch)
register int type;
register int ch;
{
s_pkt.origin = REMOTE; s_pkt.type = type; s_pkt.keypress = ch;
msgsnd(rem_q, &s_pkt, sizeof(s_pkt.keypress) + sizeof(s_pkt.origin), 0);
}
SHAR_EOF
fi # end of overwriting check
if test -f 'Makefile'
then
echo shar: will not over-write existing file "'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
phone: phone.c
cc -O phone.c -o phone -lcurses
lint:
lint phone.c -lcurses
install:
cp phone /usr/lbin
shar:
shar phone.1 phone.c Makefile >phone.sh
SHAR_EOF
fi # end of overwriting check
# End of shell archive
exit 0
More information about the Alt.sources
mailing list