sources-request@mirror.UUCP (06/20/86)
Submitted by: genrad!decvax!mcvax!mdtch!watson (James Watson) Mod.sources: Volume 6, Issue 10 Archive-name: sysVtalkA [ This is the first of two different USG talk programs. This one uses FIFO's (named pipes). I have not tried it out. -r$] I wrote the following because I got tired of having only write (ugh) on SVR2. It could stand some improvement, especially in the area of handling unlikely errors, but... James Watson Modulator SA, Koenizstrasse 194, 3097 Liebefeld-Bern, Switzerland ---------------------CUT HERE--------------------- # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # Contents: talk.c echo x - talk.c sed 's/^XX//' > "talk.c" <<'@//E*O*F talk.c//' XX#include <sys/types.h> XX#include <fcntl.h> XX#include <utmp.h> XX#include <signal.h> XX#include <string.h> XX#include <curses.h> XX#define BELL 7 XXextern struct utmp *getutent(); XXextern char *ttyname(); XXextern unsigned alarm(); XXchar out_fn[20]; XXterminate () { XX unlink (out_fn); XX endwin (); XX exit (0); XX} XXcatch_alarm () {} XXmain (argc, argv) XXint argc; XXchar *argv[]; XX{ XX struct utmp *utp, ctp; XX struct termio tflags; XX WINDOW *in_win, *out_win; XX int in_pipe, out_pipe, their_fd, count = 0; XX char in_c, their_tty[20], in_fn[20]; XX char *my_tty, my_name[L_cuserid+1], call_mesg[100]; XX /* Check validity of arguments. */ XX if (argc < 2 || argc > 3) { XX fprintf (stderr, "Use: %s username [ttyn]\n", *argv); XX exit (-1); XX } XX /* We will need to know the tty device name... */ XX if ((my_tty = ttyname (0)) == NULL) { XX fprintf (stderr, "Sorry, I cannot figure out what tty you are on.\n"); XX exit (-1); XX } XX /* But only the last component of it. */ XX my_tty = strrchr (my_tty, '/') + 1; XX /* XX * Sanity check. You cannot ask to talk to yourself unless you specify XX * some tty other than the one you are on. XX */ XX if (!strncmp (argv[1], cuserid(my_name), L_cuserid) && XX (argc == 2 || !strncmp (my_tty, argv[2], 12))) { XX fprintf (stderr, "Only crazy people talk to themselves...\n"); XX exit (-1); XX } XX /* Now find out if the requested user is logged in. */ XX while (utp = getutent()) XX /* XX * There are three criteria here: XX * 1. The entry must be for a user process. XX * 2. The user name must match the first argument. XX * 3. If a second argument was given, the tty name XX * must match that argument. XX * We have to allow for the possibility that the request is not XX * specific enough - i.e. no tty name was given, and the requested XX * user is logged in more than once. XX */ XX if (utp->ut_type == USER_PROCESS && XX !strncmp (utp->ut_user, argv[1], 8) && XX (argc == 2 || !strncmp (utp->ut_line, argv[2], 12))) { XX if (count++) { XX fprintf (stderr, XX "User '%s' logged in more than once. Use \"%s %s ttyn\".\n", XX argv[1], argv[0], argv[1]); XX exit (-1); XX } XX else ctp = *utp; XX } XX if (!count) { XX fprintf (stderr, "%s not currently logged in", argv[1]); XX if (argc == 3) XX fprintf (stderr, " on %s", argv[2]); XX fprintf (stderr, ".\n"); XX exit (-1); XX } XX /* Finally found someone. Make sure we are allowed to talk to them. */ XX sprintf (their_tty, "/dev/%s", ctp.ut_line); XX if ((their_fd = open (their_tty, O_WRONLY, 0)) < 0) { XX fprintf (stderr, "Sorry, no write permission on %s.\n", their_tty); XX exit (-1); XX } XX /* XX * At this point, we know we are going to at least try to talk to XX * someone. Now we do the complete initialization. XX * XX * Initialize curses. Note that the signal traps set set immediately XX * upon return from iniscr() - before calling any other curses XX * functions. No sense in leaving a bigger window than necessary. XX */ XX initscr (); XX signal (SIGINT, terminate); XX signal (SIGKILL, terminate); XX cbreak (); XX noecho (); XX /* Declare the windows to be used for the conversation. */ XX in_win = subwin (stdscr, LINES/2-1, COLS, 0, 0); XX out_win = subwin (stdscr, LINES/2-1, COLS, LINES/2+1, 0); XX scrollok (out_win, TRUE); XX scrollok (in_win, TRUE); XX idlok (out_win, TRUE); XX idlok (in_win, TRUE); XX clear (); XX wclear (in_win); XX wclear (out_win); XX mvaddstr (LINES/2, 0, "------------------------------------------------"); XX wmove (in_win, 0, 0); XX wmove (out_win, 0, 0); XX refresh (); XX /* XX * Here is a decidedly shaky trick to play. Curses only gives you two XX * choices for keyboard reads - completely blocking, or completely XX * non-blocking. Obviously, we cannot use blocking reads, because we XX * need to get the input from the other person even when we have XX * nothing to say. On the other hand, completely non-blocking reads XX * would put the program in a very tight loop looking at the keyboard XX * and the pipe for input - which would put an unnecessarily heavy XX * load on the system. This loop could be slowed down with some kind XX * of a sleep() or napms(), but that would make the screen look sort XX * of jumpy. System V termio supports timed out reads, so I choose XX * to use them as the gating factor in the loop. I am assuming that XX * when I call cbreak(), curses sets ~ICANON, VMIN = 1 and VTIME = 1. XX * If I now sneak in here and set VMIN = 0 and VTIME = 10, keyboard XX * reads will time out if there is no input in 1 second, and I can XX * pick up the input from the pipe. The advantage over a simple XX * sleep() is that if there IS input from the keyboard, the read will XX * return in less than 1 second, and I can read from the pipe that XX * much sooner. This makes the screen updating less jumpy. XX */ XX ioctl (0, TCGETA, &tflags); XX tflags.c_cc[VMIN] = 0; XX tflags.c_cc[VTIME] =10; XX ioctl (0, TCSETA, &tflags); XX /* XX * The sequence of events during the startup is important, and is XX * relatiely interesting. The situation is this: XX * 1. The user has requested a talk connection to someone. XX * 2. We don't know yet if this user is trying to originiate a XX * connection, or if they are responding to a request from XX * the other person. XX * 3. If this is an originating request, we have to notify the XX * other user of it, and then wait for that user to respond. XX * 4. If this is a response to a request from the other user, we XX * must not fool around with notification, but rather go XX * straight into the conversation. XX * We will use several characteristics of Unix pipes to resolve this: XX * 1. Attempting to open() a named pipe that does not exist will XX * fail. (I hope this is not a great surprise...) XX * 2. Opening a named pipe for reading, with O_NDELAY clear, will XX * succeed immediately if someone else already has that pipe XX * open for writing. XX * 3. Opening a named pipe for writing, with O_NDELAY clear, will XX * block the process until someone opens that pipe for reading. XX * So, what we will actually do is this: XX * 1. Make up a couple of pipe names, based on the tty names XX * involved. Note that this entire sequence could get screwed XX * up if someone other than the talk programs opened, deleted, XX * or otherwise screwed up our pipes. We therefore choose to XX * use dot files in /tmp, so they are not so visible. XX * 2. Try to open the pipe that should be created by the other XX * user's talk program. If this open succeeds, then we must XX * be replying to a call; if it fails, we are originating one. XX * 3. If the open fails, send a message to the other user, set an XX * alarm for 30 seconds, and block the process by trying to XX * open the pipe we created. If the user responds, the other XX * talk program will open our pipe, and then our open will XX * succeed. If there is no response the alarm will fire, and XX * the open will return -1. XX */ XX sprintf (out_fn, "/tmp/.talk_%s", my_tty); XX sprintf (in_fn, "/tmp/.talk_%s", ctp.ut_line); XX mknod (out_fn, 0010644, 0); XX /* See if they are waiting on us... */ XX if ((in_pipe = open (in_fn, O_RDONLY | O_NDELAY, 0644)) >= 0) XX /* They were, so we can open the other pipe and get to work. */ XX out_pipe = open (out_fn, O_WRONLY, 0644); XX else { XX /* Nope, so we are the ones that have to do all the work... */ XX sprintf (call_mesg,"\ XX[%s is requesting a 'talk' link with you.]\n\ XX[Type 'talk %s %s' to reply.]%c\n", XX my_name, my_name, my_tty, BELL); XX count = 0; XX do { XX write (their_fd, call_mesg, (unsigned) strlen(call_mesg)); XX mvwprintw (in_win, 0, 0, "[%d ringy-ding", ++count); XX if (count == 1) XX wprintw (in_win, "y]\n"); XX else wprintw (in_win, "ies]\n"); XX wrefresh (in_win); XX /* XX * Note that the alarm catching routine is a no-op. All the XX * real work is done right here; we are simply using the alarm XX * to unblock the open() periodically. XX */ XX signal (SIGALRM, catch_alarm); XX alarm ((unsigned) 30); XX /* We block here waiting on a reply... */ XX out_pipe = open (out_fn, O_WRONLY, 0644); XX } while (count < 4 && out_pipe < 0); XX close (their_fd); XX if (out_pipe < 0) { XX waddstr (in_win, "[No answer. Giving up.]"); XX wrefresh (in_win); XX terminate (); XX } XX /* OK, they answered, so open the other pipe. */ XX in_pipe = open (in_fn, O_RDONLY | O_NDELAY, 0644); XX } XX /* XX * We are now ready to talk. Both processes will hold the named pipes XX * open forever from this point, so we can unlink() them, to avoid any XX * chance that someone will see them, and say "Gee, I wonder what those XX * are? Maybe I'll cat /etc/termcap to them to see what happens..." XX */ XX unlink (out_fn); XX wmove (in_win, 0, 0); XX wclrtoeol (in_win); XX waddstr (in_win, "[Connected]\n"); XX wrefresh (in_win); XX signal (SIGPIPE, terminate); XX /* XX * Believe it or not, the preceding 100+ lines of code were necessary XX * just to set things up for the following 10 line loop. XX * XX * The infinite loop is exited only upon receipt of a signal. The XX * possiblilites are: XX * 1. This user terminates the conversation by typing the XX * interrupt or quit character. XX * 2. The other user terminates the conversation, thereby XX * closing the pipes. The next attempt to write a character XX * on the output pipe generates SIGPIPE. XX * All three signals are trapped to the terminate() routine. XX * XX * Note the use of if() when reading the keyboard, and while() when XX * reading the pipe. Since we are using the keyboard timeout to XX * control the entire loop, we must drain the pipe each time the XX * keyboard read is satisfied or times out. XX */ XX for (;;) { XX if (read (0, &in_c, 1) > 0) { XX waddch (out_win, in_c); XX wrefresh (out_win); XX write (out_pipe, &in_c, 1); XX } XX while (read (in_pipe, &in_c, 1) > 0) { XX waddch (in_win, in_c); XX wrefresh (in_win); XX } XX } XX} @//E*O*F talk.c// chmod u=rw,g=rw,o=rw talk.c exit 0