markk@censor.UUCP (Mark Keating) (03/28/89)
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@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