home *** CD-ROM | disk | FTP | other *** search
- Subject: v06i010: A "talk" for system V.2 (sysVtalkA)
- Newsgroups: mod.sources
- Approved: rs@mirror.UUCP
-
- 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
-