home *** CD-ROM | disk | FTP | other *** search
- From: bob@snuffy.uucp (Bob Smith)
- Newsgroups: alt.sources
- Subject: talk for Sun without sockets
- Message-ID: <1991Jul3.003000.557@snuffy.uucp>
- Date: 3 Jul 91 00:30:00 GMT
-
- I've just finished reworking a talk program that uses named pipes,
- to work on a Sun 2/50 (SunOS3.5). It was originally posted for
- SysV type machines, and still maintains all of the original
- source. You may be asking why do I want this? Well you probably
- don't since SunOS comes with a talk program, but the as supplied
- SunOS talk requires that sockets be alive on your machine, i.e.
- you're connected to a network with your ethernet port 'up'. If
- you're NOT connected to a network and your ethernet port is 'down',
- you could use this, trust me:-)
-
- (don't forget to take the signature off the bottom!)
- ---- cut here ---- cut here ---- cut here ---- cut here ----
- /*
- * 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
- *
- * ---
- * Modified for a Sun 2/50 (SunOS 3.5);
- * Just enter this to compile it:
- *
- * cc -O -s -DBSD talk.c -lcurses -ltermcap -o talk
- *
- * With SunOS already possessing a 'talk' program, you may be
- * asking; 'what the hell do I need this for...'. Well you
- * probably don't! This is for the special condition where
- * you want to have a talk like program, but your unfortunate
- * enough to NOT be connected to a network; which means you
- * probably have 'ifconfig {ie0|ec0|le0} ... down' in your
- * /etc/rc.boot file; which further translates into the fact
- * that socket calls on your machine will be dead, thereby
- * negating any usefulness that might be derived from the as
- * supplied SunOS 'talk'...
- *
- * As Jim says above; 'it could stand some improvement...'
- *
- * snuffy!bob (Bob Smith)
- * +1 508 670-6712
- */
-
- #include <sys/types.h>
- #include <fcntl.h>
- #include <utmp.h>
- #include <signal.h>
- #include <string.h>
- #include <curses.h>
-
- #define BELL 7
-
- #ifdef BSD
- struct utmp *getutent();
- int utmp_fp = 0, tflags;
- #else
- extern struct utmp *getutent();
- #endif
-
- extern char *ttyname();
- extern unsigned alarm();
-
- char *make_str();
- char out_fn[20];
-
- terminate ()
- {
- unlink (out_fn);
- move (LINES-1, 0);
- refresh ();
- endwin ();
- #ifdef BSD
- fcntl (0, F_GETFL, &tflags);
- fcntl (0, F_SETFL, tflags & ~FNDELAY);
- #endif
- exit (0);
- }
-
- catch_alarm () {}
-
- main (argc, argv)
- int argc;
- char *argv[];
- {
- struct utmp *utp, ctp;
- #ifndef BSD
- struct termio tflags;
- #endif
- int in_pipe, out_pipe, their_fd, count = 0;
- WINDOW *in_win, *out_win;
- char in_c, their_tty[20], in_fn[20];
- char *my_tty, my_name[L_cuserid+1], call_mesg[100];
-
- /* Check validity of arguments. */
- if (argc < 2 || argc > 3) {
- fprintf (stderr, "Use: %s username [ttyn]\n", *argv);
- exit (-1);
- }
-
- /* We will need to know the tty device name... */
- if ((my_tty = ttyname (0)) == NULL) {
- fprintf (stderr, "Sorry, I cannot figure out what tty you are on.\n");
- exit (-1);
- }
-
- /* But only the last component of it. */
- my_tty = strrchr (my_tty, '/') + 1;
-
- /*
- * Sanity check. You cannot ask to talk to yourself unless you specify
- * some tty other than the one you are on.
- */
- if (!strncmp (argv[1], cuserid(my_name), L_cuserid) &&
- (argc == 2 || !strncmp (my_tty, argv[2], 12))) {
- fprintf (stderr, "Only crazy people talk to themselves...\n");
- exit (-1);
- }
-
- /* Now find out if the requested user is logged in. */
- while (utp = getutent()) {
-
- /*
- * There are three criteria here:
- * 1. The entry must be for a user process.
- * 2. The user name must match the first argument.
- * 3. If a second argument was given, the tty name
- * must match that argument.
- * We have to allow for the possibility that the request is not
- * specific enough - i.e. no tty name was given, and the requested
- * user is logged in more than once.
- */
- #ifdef BSD
- if (!strncmp (utp->ut_name, argv[1], 8) &&
- #else
- if (utp->ut_type == USER_PROCESS &&
- !strncmp (utp->ut_user, argv[1], 8) &&
- #endif
- (argc == 2 || !strncmp (utp->ut_line, argv[2], 12))) {
- if (count++) {
- fprintf (stderr,
- "User '%s' logged in more than once. Use \"%s %s ttyn\".\n",
- argv[1], argv[0], argv[1]);
- exit (-1);
- }
- else ctp = *utp;
- }
- }
-
- if (!count) {
- fprintf (stderr, "%s not currently logged in", argv[1]);
- if (argc == 3)
- fprintf (stderr, " on %s", argv[2]);
- fprintf (stderr, ".\n");
- exit (-1);
- }
-
- /* Finally found someone. Make sure we are allowed to talk to them. */
- sprintf (their_tty, "/dev/%s", ctp.ut_line);
- if ((their_fd = open (their_tty, O_WRONLY, 0)) < 0) {
- fprintf (stderr, "Sorry, no write permission on %s.\n", their_tty);
- exit (-1);
- }
-
- /*
- * At this point, we know we are going to at least try to talk to
- * someone. Now we do the complete initialization.
- *
- * Initialize curses. Note that the signal traps set set immediately
- * upon return from iniscr() - before calling any other curses
- * functions. No sense in leaving a bigger window than necessary.
- */
- initscr ();
- signal (SIGINT, terminate);
- signal (SIGKILL, terminate);
- cbreak ();
- noecho ();
-
- /* Declare the windows to be used for the conversation. */
- in_win = subwin (stdscr, (LINES-1)/2, COLS, 0, 0);
- out_win = subwin (stdscr, (LINES-1)/2, COLS, (LINES-1)/2+1, 0);
- scrollok (out_win, TRUE);
- scrollok (in_win, TRUE);
- idlok (out_win, TRUE);
- idlok (in_win, TRUE);
- clear ();
- wclear (in_win);
- wclear (out_win);
- mvaddstr ((LINES-1)/2, 0, make_str ('-', COLS-1));
- wmove (in_win, 0, 0);
- wmove (out_win, 0, 0);
- refresh ();
-
- /*
- * Here is a decidedly shaky trick to play. Curses only gives you two
- * choices for keyboard reads - completely blocking, or completely
- * non-blocking. Obviously, we cannot use blocking reads, because we
- * need to get the input from the other person even when we have
- * nothing to say. On the other hand, completely non-blocking reads
- * would put the program in a very tight loop looking at the keyboard
- * and the pipe for input - which would put an unnecessarily heavy
- * load on the system. This loop could be slowed down with some kind
- * of a sleep() or napms(), but that would make the screen look sort
- * of jumpy. System V termio supports timed out reads, so I choose
- * to use them as the gating factor in the loop. I am assuming that
- * when I call cbreak(), curses sets ~ICANON, VMIN = 1 and VTIME = 1.
- * If I now sneak in here and set VMIN = 0 and VTIME = 10, keyboard
- * reads will time out if there is no input in 1 second, and I can
- * pick up the input from the pipe. The advantage over a simple
- * sleep() is that if there IS input from the keyboard, the read will
- * return in less than 1 second, and I can read from the pipe that
- * much sooner. This makes the screen updating less jumpy.
- *
- * BSD Note: Since SunOS doesn't do this; (at least not with the
- * stock BSD derived libraries), I resort to using usleep() to
- * the CPU burden down. (See farther below.)
- */
- #ifdef BSD
- fcntl (0, F_GETFL, &tflags);
- fcntl (0, F_SETFL, tflags | FNDELAY);
- #else
- ioctl (0, TCGETA, &tflags);
- tflags.c_cc[VMIN] = 0;
- tflags.c_cc[VTIME] =10;
- ioctl (0, TCSETA, &tflags);
- #endif
-
- /*
- * The sequence of events during the startup is important, and is
- * relatiely interesting. The situation is this:
- * 1. The user has requested a talk connection to someone.
- * 2. We don't know yet if this user is trying to originiate a
- * connection, or if they are responding to a request from
- * the other person.
- * 3. If this is an originating request, we have to notify the
- * other user of it, and then wait for that user to respond.
- * 4. If this is a response to a request from the other user, we
- * must not fool around with notification, but rather go
- * straight into the conversation.
- * We will use several characteristics of Unix pipes to resolve this:
- * 1. Attempting to open() a named pipe that does not exist will
- * fail. (I hope this is not a great surprise...)
- * 2. Opening a named pipe for reading, with O_NDELAY clear, will
- * succeed immediately if someone else already has that pipe
- * open for writing.
- * 3. Opening a named pipe for writing, with O_NDELAY clear, will
- * block the process until someone opens that pipe for reading.
- * So, what we will actually do is this:
- * 1. Make up a couple of pipe names, based on the tty names
- * involved. Note that this entire sequence could get screwed
- * up if someone other than the talk programs opened, deleted,
- * or otherwise screwed up our pipes. We therefore choose to
- * use dot files in /tmp, so they are not so visible.
- * 2. Try to open the pipe that should be created by the other
- * user's talk program. If this open succeeds, then we must
- * be replying to a call; if it fails, we are originating one.
- * 3. If the open fails, send a message to the other user, set an
- * alarm for 30 seconds, and block the process by trying to
- * open the pipe we created. If the user responds, the other
- * talk program will open our pipe, and then our open will
- * succeed. If there is no response the alarm will fire, and
- * the open will return -1.
- */
- sprintf (out_fn, "/tmp/.talk_%s", my_tty);
- sprintf (in_fn, "/tmp/.talk_%s", ctp.ut_line);
- mknod (out_fn, 0010644, 0);
-
- /* See if they are waiting on us... */
- if ((in_pipe = open (in_fn, O_RDONLY | O_NDELAY, 0644)) >= 0)
-
- /* They were, so we can open the other pipe and get to work. */
- out_pipe = open (out_fn, O_WRONLY, 0644);
- else {
-
- /* Nope, so we are the ones that have to do all the work... */
- sprintf (call_mesg,"\n\n\
- [%s is requesting a 'talk' link with you.]\n\
- [Type 'talk %s %s' to reply.]%c\n",
- my_name, my_name, my_tty, BELL);
- count = 0;
- do {
- write (their_fd, call_mesg, (unsigned) strlen(call_mesg));
- mvwprintw (in_win, 0, 0, "[%d ringy-ding", ++count);
- if (count == 1)
- wprintw (in_win, "y]\n");
- else
- wprintw (in_win, "ies]\n");
- wrefresh (in_win);
-
- /*
- * Note that the alarm catching routine is a no-op. All the
- * real work is done right here; we are simply using the alarm
- * to unblock the open() periodically.
- */
- signal (SIGALRM, catch_alarm);
- alarm ((unsigned) 30);
-
- /* We block here waiting on a reply... */
- out_pipe = open (out_fn, O_WRONLY, 0644);
- } while (count < 4 && out_pipe < 0);
- close (their_fd);
- if (out_pipe < 0) {
- waddstr (in_win, "[No answer. Giving up.]");
- wrefresh (in_win);
- terminate ();
- }
-
- /* OK, they answered, so open the other pipe. */
- in_pipe = open (in_fn, O_RDONLY | O_NDELAY, 0644);
- }
-
- /*
- * We are now ready to talk. Both processes will hold the named pipes
- * open forever from this point, so we can unlink() them, to avoid any
- * chance that someone will see them, and say "Gee, I wonder what those
- * are? Maybe I'll cat /etc/termcap to them to see what happens..."
- */
- unlink (out_fn);
- wmove (in_win, 0, 0);
- wclrtoeol (in_win);
- waddstr (in_win, "[Connected]\n");
- wrefresh (in_win);
- signal (SIGPIPE, terminate);
-
- /*
- * Believe it or not, the preceding 100+ lines of code were necessary
- * just to set things up for the following 10 line loop.
- *
- * The infinite loop is exited only upon receipt of a signal. The
- * possiblilites are:
- * 1. This user terminates the conversation by typing the
- * interrupt or quit character.
- * 2. The other user terminates the conversation, thereby
- * closing the pipes. The next attempt to write a character
- * on the output pipe generates SIGPIPE.
- * All three signals are trapped to the terminate() routine.
- *
- * Note the use of if() when reading the keyboard, and while() when
- * reading the pipe. Since we are using the keyboard timeout to
- * control the entire loop, we must drain the pipe each time the
- * keyboard read is satisfied or times out.
- *
- * BSD Note: This thing is a CPU hog without the usleep() function
- * call since it uses non-blocking reads for both the keyboard and
- * the pipe. It should be 'prettied' up somehow; possibly by
- * rewriting the whole thing into a 2 process program, 1 to read
- * the keyboard and write to the out_pipe, the other to read the
- * in_pipe. That would allow blocking reads to be used.
- *
- * Also note the addition of wclrtoeol(); a rather crude hack that
- * makes sure the last line of each window is cleared when the
- * window scrolls. There's probably a better way than that too,
- * but I'm not a curses wizard...
- */
- for (;;) {
- if (read (0, &in_c, 1) > 0) {
- waddch (out_win, in_c);
- #ifdef BSD
- wclrtoeol (out_win);
- #endif
- wrefresh (out_win);
- write (out_pipe, &in_c, 1);
- }
- while (read (in_pipe, &in_c, 1) > 0) {
- waddch (in_win, in_c);
- #ifdef BSD
- wclrtoeol (in_win);
- #endif
- wrefresh (in_win);
- }
- #ifdef BSD
- usleep((unsigned)100000L);
- #endif
- }
- }
-
- char *
- make_str (c, size)
- char c;
- int size;
- {
- char *s;
- static char t[BUFSIZ];
-
- if (size > BUFSIZ) size = BUFSIZ - 1;
- s = t;
- while (size--) *s++ = c;
- *s = '\0';
- return t;
- }
-
- #ifdef BSD
- struct utmp *
- getutent ()
- {
- static struct utmp ttmp;
-
- if (utmp_fp == 0) {
- utmp_fp = open ("/etc/utmp", O_RDONLY);
- if (utmp_fp < 0) {
- fprintf (stderr, "Cannot open /etc/utmp\n");
- exit (-1);
- }
- }
-
- if (read (utmp_fp, &ttmp, sizeof(ttmp)) <= 0) {
- close (utmp_fp);
- utmp_fp = 0;
- return (struct utmp *)0;
- }
- else
- return (&ttmp);
- }
- #endif
-
- --
- \ Bob Smith \________________________________________
- \ 835 Mammoth Rd. \ uucp: ...!wang.com!snuffy!bob
- \ Dracut, MA. 01826 \ Office && voice mail: +1 508 670-6712
-