john (12/21/82)
echo x - Makefile cat >Makefile <<'!Funky!Stuff!' # # Makefile generated by jpn 7/22/82 # # Specify -g in the cflags line to provide output for sdb. CFLAGS= # HEADERS= talk.h # all: talk central page # talk : talk.c talk.h cc -o talk $(CFLAGS) talk.c -lcurses -ltermcap # central : central.c talk.h cc -o central $(CFLAGS) central.c # page : page.c cc -o page $(CFLAGS) page.c # !Funky!Stuff! echo x - central.c cat >central.c <<'!Funky!Stuff!' /* Please read the documentation in talk.c */ #include "talk.h" #include <signal.h> char *chname; /* name of the multiplexed file (channel) being used */ int channel; /* fd of channel file */ int online = 0; /* count of connected users */ struct input_record { short index; short count; short ccount; char dta[80]; }input; struct output_record { short i; short c; short cc; char *d; }output; struct player { int indx; int xpos; int ypos; char luid; char huid; } guys[16]; PACKET info; /* addnew * find a guy index for the channel index just connected */ addnew(index) int index; { int i; for(i = 0; i < 16; ++i) { if (guys[i].indx == 0) { guys[i].indx = index; guys[i].xpos = 0; guys[i].ypos = 0; break; } } ++online; return(i); } /* whodat * find the guy index associated with the channel index */ whodat(index) int index; { int i; for(i = 0; i < 16; ++i) { if (guys[i].indx == index) return(i); } } /* makechan * attempt to create channel */ makechan() { int i; int save; save = umask(0); channel = mpx(chname,0666); umask(save); if (channel < 0) { write(2,"cannot create channel - ABORTING central.\n",42); exit(1); } output.d = (char *) &info; /* set all guys to not connected state */ for (i = 0; i < 16; ++i) guys[i].indx = 0; } /* main * one parameter MUST be given - the multiplexed file to use */ main(argc,argv) int argc; char **argv; { register int i; int temp; int guy; if (argc != 2) exit(1); chname = argv[1]; /* Don't exit due to any signals! */ signal(SIGHUP,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN); signal(SIGALRM,SIG_IGN); signal(SIGTERM,SIG_IGN); signal(SIGTSTP,SIG_IGN); signal(SIGCONT,SIG_IGN); signal(SIGTTIN,SIG_IGN); signal(SIGTTOU,SIG_IGN); /* create channel for communication */ makechan(); while(1) { /* wait for something to happen */ read(channel,&input,sizeof(struct input_record)); if (input.count == 0) { /* signal - not data */ switch (input.dta[0]) { case M_WATCH: /* somebody new wishes to connect */ attach(input.index,channel); guy = addnew(input.index); guys[guy].luid = input.dta[2]; guys[guy].huid = input.dta[3]; send(input.index,YOUARE,guy); for (i = 0; i < 16; ++i) { if (guys[i].indx != 0) { /* let others know */ send(guys[i].indx,NEWGUY,guy); send(guys[i].indx,input.dta[2],input.dta[3]); if (guy != i) { /* let new guy know about any active players */ send(guys[guy].indx,NEWGUY,i); send(guys[guy].indx,guys[i].luid,guys[i].huid); } } } break; case M_EOT: /* this is ignored, just someone clearing his pipe */ break; case M_CLOSE: case M_SIG: /* somebody just quit */ detach(input.index,channel); guy = whodat(input.index); guys[guy].indx = 0; --online; if(!online) { close(channel); unlink(chname); exit(0); } /* must tell everyone that this guy just quit on us */ for (i = 0; i < 16; ++i) { if (guys[i].indx != 0) { send(guys[i].indx,KILLGUY,guy); } } break; } } else { /* data sent across channel */ guy = whodat(input.index); sendall(guy,*(input.dta)); } } } /* send * send the control,data pair to the channel index specified */ send(index,control,data) int index; int control,data; { output.i = index; output.c = 2; info.control = control; info.data = data; write(channel,&output,sizeof(struct output_record)); } /* sendall * send the control,data pair to all active channel indexes */ sendall(control,data) int control,data; { int i; for (i = 0; i < 16; ++i) { if (guys[i].indx != 0) { send(guys[i].indx,control,data); } } } !Funky!Stuff! echo x - page.c cat >page.c <<'!Funky!Stuff!' #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <pwd.h> #include <utmp.h> #define WRITE 2 #define MAIL 0100 #define TRUE 1 #define FALSE 0 FILE *fopen(); char devicename[20] = ""; char username[20] = ""; char *userptr = 0; char *getlogin(); char buffer[128]; int multi = 0; /* cmpare * like strcmp except only returns TRUE or FALSE */ cmpare(string1,string2) register char * string1; register char * string2; { while(*string1) if(*string1++ != *string2++) return(0); if(*string2 == '\0') return(1); return(0); } /* whereis * search for person in /etc/utmp */ char * whereis(person,tty) char * person; char * tty; { struct utmp string; int i = 0; int fd; fd = open("/etc/utmp",0); while(read(fd,&string,20) > 0) { if(string.ut_line[0] == '\0' || string.ut_name[0] == '\0') continue; if(cmpare(person,string.ut_name) && (tty == 0 || cmpare(tty,string.ut_line))) { strcpy(devicename,"/dev/"); strcpy(&devicename[5],string.ut_line); if (tty == 0) { /* check for multiple logins */ while(read(fd,&string,20) > 0) { if(cmpare(person,string.ut_name) && string.ut_line[0]!='\0') { multi++; } } } close(fd); return(devicename); } } close(fd); return(0); } /* access * check for terminal write permission */ access(filename,mode) char *filename; unsigned short mode; { struct stat buf; struct stat *bufp = &buf; stat(filename,bufp); if (buf.st_mode & mode) return(TRUE); return(FALSE); } /* main * one parameter is required - the login name to be paged */ main(argc,argv) int argc; char ** argv; { char * sendto; FILE *fd; if (argc != 2) { exit(1); } sendto = whereis(argv[1],0); userptr = getlogin(); if (userptr) strcpy(username,userptr); if (sendto) { if (access(sendto,WRITE)) { fd = fopen (sendto,"w"); fprintf(fd,"%s on %s wants to talk to you via the talk program\n",username,ttyname(0)); close(fd); if (multi) mailto(argv[1]); } else { mailto(argv[1]); } } else { exit(2); } exit(0); } FILE *popen(); char mailline[] = "mail x "; /* mailto * use mail notification to alert name */ mailto(name) char *name; { register char *ptr; int i; FILE *fd; for(ptr = name, i = 5; *ptr; ++ptr, ++i) { mailline[i] = *ptr; } if ((fd = popen(mailline,"w")) == 0) { exit(0); } fprintf(fd,"%s wants to talk to you via the talk program\n",username); fprintf(fd,"Just run \"talk\" if it is not much later than this message time\n"); close(fd); } !Funky!Stuff! echo x - talk.c cat >talk.c <<'!Funky!Stuff!' /* talk.c * talk is a program which allows communication between users that works * better than write using curses. On startup, it will test if there is * a central program running, if not, it will start one up (note that the * lock routines will prevent starting up more than one central.) * it will then fork into two processes, number one polling the keyboard * and sending data to the multiplexed file, and number two polling the * multiplexed file, and updating the display as necessary. The display * consists of a number of user's areas (LINESPER lines long each) which * will be updated whenever a user types a key. * * note that there are a number of installation dependent parameters - * lockfile,linkto,defchan,central, and altcent. * * lockfile and defchan must be (normally nonexistant) files in a directory * writable by all. Talk will take two different types of command line * arguments. The first is just the login name of someone to notify that * a talk session is wanted. the second type must be prefaced by -c and * should be the (full path) name of a (normally nonexistant) file in a * directory which is writable by all who will use this channel. * * The only requirement on linkto is that it is on the same filesystem * as lockfile, and that it always exists! * * central and altcent are the pathname and alternate pathname for the * central.c program. * */ #include "talk.h" #include <signal.h> #include <curses.h> #define STDIN 0 #define STDOUT 1 #define STDERR 2 #define XWRAP 78 /* position for x to wrap around */ #define XDISPOS 8 /* window's beginning of line */ #define LINESPER 4 /* # lines per talker */ int channel; /* multiplexed file descriptor */ int locked = 0; char errmsg[] = "Some error occurred starting up Talk.\n"; char wait[] = " Please wait ...."; /*************** installation dependant parameters *****************/ /* file whose's existance provides locking mechanism */ /* MUST be in a directory writable by all! */ char lockfile[] = "/usr/spool/talk/cblock"; /* file which lockfile is linked to */ /* MUST be on same filesystem as lockfile */ char linkto[] = "/usr/local/linkto"; /* the default channel to use. See note above!!! */ /* MUST be in a directory writable by all! */ char defchan[] = "/usr/spool/talk/channel"; /* pathname of the central program */ char central[] = "/usr/local/central"; char altcent[] = "/usr/local/central"; /* pathname of the page progam */ char pager[] = "/usr/local/page"; /************* end of installation dependant parameters ***************/ /* structure for keeping track of each window for each person (guy) */ #define MAXGUYS 16 WINDOW *win[MAXGUYS]; int yname[MAXGUYS]; WINDOW *msgwin; WINDOW *pagewin; int ppid; /* parent process id */ char *chname; /* channel name being used */ char pagename[50]; /* login name of person being paged */ PACKET info; /* communication packet from central program */ int iam = 0; /* the guy index that I have been assigned */ /* bye * the normal cleanup exit routine */ bye() { close(channel); if (locked) unlock(); mvcur(0,COLS-1,LINES-1,0); /* refresh(); */ endwin(); exit(0); } /* main * main takes as arguments the names of the people who should * be paged. Also, an alternate channel may be requested by * a command line of the form: talk -c altchannel * where altchannel is the full pathname of the file to use * as the alternate mutiplexed file. */ main(argc,argv) int argc; char **argv; { int gotdata(); char c; int i; chname = defchan; /* parse command line strings before fucking around with the screen */ for (i = 1; i < argc; ++i) { if (strcmp("-c",argv[i]) == 0) { /* alternate channel request */ ++i; if (i >= argc) break; chname = argv[i]; } else { if (vfork() == 0) { execl(pager,"page",argv[i],0); _exit(0); } } } /* initialize windows */ for(i = 0; i < MAXGUYS; ++i) win[i] = 0; /* set up curses */ initscr(); leaveok(stdscr,1); refresh(); /* allocate top line for error messages */ msgwin = newwin(1,0,0,0); leaveok(msgwin,1); /* allocate bottom line for pageing */ pagewin = newwin(1,0,LINES-1,0); leaveok(pagewin,0); lock(); /* do not allow user to kill program until after channel is created */ signal(SIGHUP,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGQUIT,SIG_IGN); signal(SIGALRM,SIG_IGN); signal(SIGTERM,bye); signal(SIGTSTP,SIG_IGN); signal(SIGCONT,SIG_IGN); signal(SIGTTIN,SIG_IGN); signal(SIGTTOU,SIG_IGN); ppid = getpid(); /* one character at a time from the terminal */ crmode(); noecho(); channel = open(chname,2); if (channel < 0) { if (vfork() == 0) { execl(central,"central",chname,0); execl(altcent,"central",chname,0); clear(); waddstr(msgwin,errmsg); wrefresh(msgwin); signal(ppid,SIGTERM); _exit(1); } wclear(msgwin); waddstr(msgwin,wait); wrefresh(msgwin); sleep(5); /* allow time for central to run and create channel */ channel = open(chname,2); if (channel < 0) { wclear(msgwin); waddstr(msgwin,errmsg); wrefresh(msgwin); bye(); } } /* force ^C ^Y etc to exit cleanly */ signal(SIGHUP,bye); signal(SIGINT,bye); signal(SIGQUIT,bye); signal(SIGALRM,bye); signal(SIGTERM,bye); signal(SIGTSTP,bye); signal(SIGCONT,bye); signal(SIGTTIN,bye); signal(SIGTTOU,bye); unlock(); clear(); if (fork() == 0) { /* get input only */ while(1) { if (read(STDIN,&c,1) < 0) { kill(ppid,SIGINT); exit(0); } if (c == '\032') { kill(ppid,SIGINT); exit(0); } if (c == '\n' || c == '\b' || c == '\177' || c == '\014' || c == '\002' || c == '\005') write(channel,&c,1); else if (c == '\020') /* ^P pager */ { int savx,savy; wclear(pagewin); waddstr(pagewin,"Who do you wish to page? "); wrefresh(pagewin); for(i = 0; ; ++i) { read(STDIN,&(pagename[i]),1); if(pagename[i] == '\177' || pagename[i] == '\b') { if (i > 0) { getyx(pagewin,savy,savx); wmove(pagewin,savy,savx - 1); i -= 2; wrefresh(pagewin); continue; } } if(pagename[i] == '\n') { pagename[i] = '\0'; break; } waddch(pagewin,pagename[i]); wrefresh(pagewin); } wclear(pagewin); wrefresh(pagewin); write(channel,"\014",1); if (vfork() == 0) { execl(pager,"page",pagename,0); _exit(0); } } else if (c < ' ') ; /* throw away this control character */ else write(channel,&c,1); } } else { /* display results only */ while(1) { read(channel,&info,sizeof (PACKET)); switch(info.control) { case YOUARE: /* signal from cental specifying my index */ iam = info.data; break; case KILLGUY: /* someone exited */ killguy(info.data); break; case NEWGUY: /* new person running the program */ newguy(info.data); break; default: /* must be data */ putdat(info.data,info.control); break; } } } } /* myrefresh * restore cursor position to the iam window */ myrefresh() { int savx,savy; refresh(); if (win[iam] != 0) { wrefresh(win[iam]); } } /* killguy * someone running the program has exited. His window will be erased */ killguy(guy) int guy; { int i; werase(win[guy]); wrefresh(win[guy]); delwin(win[guy]); win[guy] = 0; move(yname[guy],0); addstr("*EOF*"); clrtoeol(); myrefresh(); } /* newguy * someone new has started up talk. a new window must be allocated * and initialized. */ newguy(guy) int guy; { int y, uid, i; int savx,savy; char pwbuf[120]; pwbuf[0] = '\0'; /* read the process id of the newguy from the channel */ read(channel,&info,sizeof (PACKET)); uid = (255 & info.control) + ((255 & info.data) << 8); /* calculate new window */ y = (LINES-1) - ((guy + 1) * LINESPER); win[guy] = subwin(stdscr, LINESPER - 1, COLS - XDISPOS - 2, y + 1, XDISPOS); yname[guy] = y; scrollok(win[guy],1); leaveok(win[guy],0); /* leaveok(win[guy],guy != iam); */ /* get the login name */ getpw(uid,pwbuf); for (i = 0; pwbuf[i] != ':' ; ++i) ; pwbuf[i] = 0; /* update the screen */ move(y,0); addstr(pwbuf); clrtoeol(); myrefresh(); } /* putdat * just character data. Update the display as required */ putdat(dat,guy) int guy,dat; { int savx,savy; register WINDOW *ptr; ptr = win[guy]; switch (dat) { case '\177': case '\b': getyx(ptr,savy,savx); wmove(ptr,savy, savx > 0 ? savx - 1 : 0); break; case '\014': /* refresh request */ if (guy == iam) { wrefresh(curscr); myrefresh(); } break; case '\n': case '\015': waddch(ptr,'\n'); wstandend(ptr); break; case '\002': wstandout(ptr); break; case '\005': wstandend(ptr); break; default: /* this is a kludge to avoid the double scroll generated */ /* on end of line wraparound ! */ if (ptr->_curx >= ptr->_maxx - 1) { waddch(ptr,'\n'); wstandend(ptr); } waddch(ptr,dat); break; } wrefresh(ptr); myrefresh(); } /* lock * lock will not return until no one else is locked */ lock() { locked = 1; while(link(linkto,lockfile) < 0) { wclear(msgwin); waddstr(msgwin,wait); wrefresh(msgwin); sleep(2); } } /* unlock * unlock allows others to lock */ unlock() { unlink(lockfile); locked = 0; } !Funky!Stuff! echo x - talk.h cat >talk.h <<'!Funky!Stuff!' #include <sys/mx.h> #define YOUARE 0x42 #define NEWGUY 0x43 #define KILLGUY 0x44 #define DATA 0x45 #define PACKET struct packet PACKET { char control; char data; }; !Funky!Stuff! echo x - READ_ME cat >READ_ME <<'!Funky!Stuff!' Talk is a program designed to let multiple people communicate with each other using a window mechanism where each person is assigned a window which will be updated on all sceens running the program whenever a key is struck. Note that talk is still somewhat experimental. The current version uses multiplexed files as the communication medium, and since only Berkley 4.x systems have multiplexed files, talk will not run on all sites on this net. I intend to update talk to run under BSD4.2 as soon as we get that system running here. Brief Discription: When running talk, the cursor should always remain at your own window's current cursor position. Talk is written to use the curses package, and will work on any terminal that has a direct cursor addressing. As currently set up in talk.c, each user is assigned a three line window which may be modified, as well as one line above his window which will display the login name of the user id running the program. As each character is typed, the cursor position is incremented by one, wrapping around to the next line if necessary. If the end of the window is reached, the window will scroll to provide a new line to type in. Most control characters are intercepted and thrown away. The only control characters recognized are backspace, delete, return, and ^P, ^B and ^E. Backspace and delete work identically, and simply decrement the cursor position without erasing the character at that position. Return will put the cursor at the beginning of the next line, and erase the rest of the line. Control P will invoke the paging sub-program after prompting for the login name of the person who is to be paged. The paging program first looks for the terminal that the person is using (via /etc/utmp,) and then checks the write permission on that terminal. If write permission is turned on, page will directly output a message to the terminal. If write permission is turned off, or if the person is logged in more than once, mail will be sent to that person. I forgot to mention that the signals TSTP and KILL are intercepted and will cause talk to exit cleanly. We use ^Z as end-of-file, so ^Z is also recognized as being an exit signal. Talk occasionally has problems. These problems seem to be limitations in the ability of the muliplexed file facility to deal with a large flow of information. When too many people are typing simultaneously (or the system is particularly slow) characters typed at the keyboard will be lost completely. This seems to be caused by the talk program writing to a channel which is already full, causing the system to throw away the data. This seems like a stupid thing for the system to do, but there you are. Once I was able to hang up the whole talk system by having three people hold down auto-repeating keys simultaneously. Apparently, the multiplexed file became so clogged that it was not able to recover properly. If talk should hang up completely, the solution is to kill all processes associated with talk (including the "central" program) and then remove the lock file. Subsequent instances of talk will run properly. Currently there is no way to do this automatically, it must be done by hand. The easiest way to avoid the problem is to have only one user typing at a time, and for no one to hold down auto-repeating keys Talk can also take a filename "alternate channel" parameter. This allows an alternate conversation to be taking place, or it will allow a "secret" conversation if the persons running are the only ones who can access the multiplexed file. Any other parameters given are assumed to be persons who are to be "paged". Note: Before compiling this program, be sure that you modify the installation dependant parameters in the first part of talk.c!!! Be sure to read the header comments at the beginning of talk.c. !Funky!Stuff! echo x - talk.1 cat >talk.1 <<'!Funky!Stuff!' .TH WIDE 1 talk .SH NAME talk \- communications program .SH SYNOPSIS .B talk [-c channel] [login-name ...] .SH DESCRIPTION .I Talk will configure your terminal into a series of windows each prefaced by the login name of the person who currently "owns" that window. Your window will have the cursor displayed at the current typing position. Whenever anyone running talk types a character, it will be simultaneously displayed on all terminals running talk. Most control characters are intercepted and thrown away, but a few are recognized by talk as being control functions. .PP .I channel is the (full pathname) file which is to be used to create an alternate "channel". If this parameter is given, it must be a file (normally nonexistant) on a directory which is writable by all who will use this channel. This parameter should not normally be used unless you want to have a private conversation. .PP .I person is the login name of someone who you want to be notified that a talk session is requested. Notification is done by writing to that person's terminal if possible, or by sending mail if not. .PP Control characters recognized by talk: .PP ^C, ^Z and ^Y will all terminate your copy of talk, and will erase your name from any other terminal that is still running it. .PP <RETURN> will start a new line,scrolling your window if necessary .PP ^P will page someone in the same manner as the command line argument (the name will be prompted for.) .PP ^B will begin displaying the characters you type as BOLD. Bold mode ends when you type <RETURN> or ^E, or if the line should wrap around. .PP ^E will end Bold mode. .PP ^L will redraw the screen if it should be altered by other programs. .PP <BACKSPACE> and <DELETE> will both allow you to back up the cursor so that you can correct typing errors. .SH AUTHOR John P. Nelson .SH FILES /usr/spool/talk/cblock .br /usr/local/linkto .br /usr/spool/talk/channel .br /usr/local/central .br /usr/local/page .SH BUGS Occasionally, when too many people are typing simultaneously, talk will become "clogged" and will lose characters. .PP Even worse, if two people hold down auto-repeating keys, talk will "hang" and nothing more can be typed. The solution is to exit talk and start it up again. !Funky!Stuff!