home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume6 / talk.sysv.vera < prev    next >
Text File  |  1986-11-30  |  12KB  |  293 lines

  1. Subject: v06i010:  A "talk" for system V.2 (sysVtalkA)
  2. Newsgroups: mod.sources
  3. Approved: rs@mirror.UUCP
  4.  
  5. Submitted by: genrad!decvax!mcvax!mdtch!watson (James Watson)
  6. Mod.sources: Volume 6, Issue 10
  7. Archive-name: sysVtalkA
  8.  
  9. [ This is the first of two different USG talk programs.  This one
  10.   uses FIFO's (named pipes).  I have not tried it out.  -r$]
  11. I wrote the following because I got tired of having only write (ugh) on
  12. SVR2.  It could stand some improvement, especially in the area of
  13. handling unlikely errors, but...
  14.  
  15.     James Watson
  16.     Modulator SA, Koenizstrasse 194,
  17.     3097 Liebefeld-Bern, Switzerland
  18.  
  19. ---------------------CUT HERE---------------------
  20. # This is a shell archive.  Remove anything before this line,
  21. # then unpack it by saving it in a file and typing "sh file".
  22. # Contents:  talk.c
  23.  
  24. echo x - talk.c
  25. sed 's/^XX//' > "talk.c" <<'@//E*O*F talk.c//'
  26. XX#include <sys/types.h>
  27. XX#include <fcntl.h>
  28. XX#include <utmp.h>
  29. XX#include <signal.h>
  30. XX#include <string.h>
  31. XX#include <curses.h>
  32.  
  33. XX#define BELL 7
  34.  
  35. XXextern struct utmp *getutent();
  36. XXextern char *ttyname();
  37. XXextern unsigned alarm();
  38.  
  39. XXchar out_fn[20];
  40.  
  41. XXterminate () {
  42. XX    unlink (out_fn);
  43. XX    endwin ();
  44. XX    exit (0);
  45. XX}
  46.  
  47. XXcatch_alarm () {}
  48.  
  49. XXmain (argc, argv)
  50. XXint argc;
  51. XXchar *argv[];
  52. XX{
  53. XX    struct utmp *utp, ctp;
  54. XX    struct termio tflags;
  55. XX    WINDOW *in_win, *out_win;
  56. XX    int in_pipe, out_pipe, their_fd, count = 0;
  57. XX    char in_c, their_tty[20], in_fn[20];
  58. XX    char *my_tty, my_name[L_cuserid+1], call_mesg[100];
  59.  
  60. XX    /* Check validity of arguments. */
  61. XX    if (argc < 2 || argc > 3) {
  62. XX    fprintf (stderr, "Use: %s username [ttyn]\n", *argv);
  63. XX    exit (-1);
  64. XX    }
  65. XX    /* We will need to know the tty device name... */
  66. XX    if ((my_tty = ttyname (0)) == NULL) {
  67. XX    fprintf (stderr, "Sorry, I cannot figure out what tty you are on.\n");
  68. XX    exit (-1);
  69. XX    }
  70. XX    /* But only the last component of it. */
  71. XX    my_tty = strrchr (my_tty, '/') + 1;
  72. XX    /*
  73. XX     * Sanity check.  You cannot ask to talk to yourself unless you specify
  74. XX     * some tty other than the one you are on.
  75. XX     */
  76. XX    if (!strncmp (argv[1], cuserid(my_name), L_cuserid) &&
  77. XX        (argc == 2 || !strncmp (my_tty, argv[2], 12))) {
  78. XX    fprintf (stderr, "Only crazy people talk to themselves...\n");
  79. XX    exit (-1);
  80. XX    }
  81. XX    /* Now find out if the requested user is logged in. */
  82. XX    while (utp = getutent())
  83. XX    /*
  84. XX     * There are three criteria here:
  85. XX     *    1. The entry must be for a user process.
  86. XX     *    2. The user name must match the first argument.
  87. XX     *    3. If a second argument was given, the tty name
  88. XX     *       must match that argument.
  89. XX     * We have to allow for the possibility that the request is not
  90. XX     * specific enough - i.e. no tty name was given, and the requested
  91. XX     * user is logged in more than once.
  92. XX     */
  93. XX    if (utp->ut_type == USER_PROCESS &&
  94. XX        !strncmp (utp->ut_user, argv[1], 8) &&
  95. XX        (argc == 2 || !strncmp (utp->ut_line, argv[2], 12))) {
  96. XX        if (count++) {
  97. XX        fprintf (stderr,
  98. XX         "User '%s' logged in more than once.  Use \"%s %s ttyn\".\n",
  99. XX         argv[1], argv[0], argv[1]);
  100. XX        exit (-1);
  101. XX        }
  102. XX        else ctp = *utp;
  103. XX    }
  104.  
  105. XX    if (!count) {
  106. XX    fprintf (stderr, "%s not currently logged in", argv[1]);
  107. XX    if (argc == 3)
  108. XX        fprintf (stderr, " on %s", argv[2]);
  109. XX    fprintf (stderr, ".\n");
  110. XX    exit (-1);
  111. XX    }
  112. XX    /* Finally found someone.  Make sure we are allowed to talk to them. */
  113. XX    sprintf (their_tty, "/dev/%s", ctp.ut_line);
  114. XX    if ((their_fd = open (their_tty, O_WRONLY, 0)) < 0) {
  115. XX    fprintf (stderr, "Sorry, no write permission on %s.\n", their_tty);
  116. XX    exit (-1);
  117. XX    }
  118. XX    /*
  119. XX     * At this point, we know we are going to at least try to talk to
  120. XX     * someone.  Now we do the complete initialization.
  121. XX     *
  122. XX     * Initialize curses.  Note that the signal traps set set immediately
  123. XX     * upon return from iniscr() - before calling any other curses
  124. XX     * functions.  No sense in leaving a bigger window than necessary.
  125. XX     */
  126. XX    initscr ();
  127. XX    signal (SIGINT, terminate);
  128. XX    signal (SIGKILL, terminate);
  129. XX    cbreak ();
  130. XX    noecho ();
  131. XX    /* Declare the windows to be used for the conversation. */
  132. XX    in_win = subwin (stdscr, LINES/2-1, COLS, 0, 0);
  133. XX    out_win = subwin (stdscr, LINES/2-1, COLS, LINES/2+1, 0);
  134. XX    scrollok (out_win, TRUE);
  135. XX    scrollok (in_win, TRUE);
  136. XX    idlok (out_win, TRUE);
  137. XX    idlok (in_win, TRUE);
  138. XX    clear ();
  139. XX    wclear (in_win);
  140. XX    wclear (out_win);
  141. XX    mvaddstr (LINES/2, 0, "------------------------------------------------");
  142. XX    wmove (in_win, 0, 0);
  143. XX    wmove (out_win, 0, 0);
  144. XX    refresh ();
  145. XX    /*
  146. XX     * Here is a decidedly shaky trick to play.  Curses only gives you two
  147. XX     * choices for keyboard reads - completely blocking, or completely
  148. XX     * non-blocking.  Obviously, we cannot use blocking reads, because we
  149. XX     * need to get the input from the other person even when we have
  150. XX     * nothing to say.  On the other hand, completely non-blocking reads
  151. XX     * would put the program in a very tight loop looking at the keyboard
  152. XX     * and the pipe for input - which would put an unnecessarily heavy
  153. XX     * load on the system.  This loop could be slowed down with some kind
  154. XX     * of a sleep() or napms(), but that would make the screen look sort
  155. XX     * of jumpy.  System V termio supports timed out reads, so I choose
  156. XX     * to use them as the gating factor in the loop.  I am assuming that
  157. XX     * when I call cbreak(), curses sets ~ICANON, VMIN = 1 and VTIME = 1.
  158. XX     * If I now sneak in here and set VMIN = 0 and VTIME = 10, keyboard
  159. XX     * reads will time out if there is no input in 1 second, and I can
  160. XX     * pick up the input from the pipe.  The advantage over a simple
  161. XX     * sleep() is that if there IS input from the keyboard, the read will
  162. XX     * return in less than 1 second, and I can read from the pipe that
  163. XX     * much sooner.  This makes the screen updating less jumpy.
  164. XX     */
  165. XX    ioctl (0, TCGETA, &tflags);
  166. XX    tflags.c_cc[VMIN] = 0;
  167. XX    tflags.c_cc[VTIME] =10;
  168. XX    ioctl (0, TCSETA, &tflags);
  169. XX    /*
  170. XX     * The sequence of events during the startup is important, and is
  171. XX     * relatiely interesting.  The situation is this:
  172. XX     *        1. The user has requested a talk connection to someone.
  173. XX     *        2. We don't know yet if this user is trying to originiate a
  174. XX     *          connection, or if they are responding to a request from
  175. XX     *          the other person.
  176. XX     *        3. If this is an originating request, we have to notify the
  177. XX     *           other user of it, and then wait for that user to respond.
  178. XX     *        4. If this is a response to a request from the other user, we
  179. XX     *           must not fool around with notification, but rather go
  180. XX     *           straight into the conversation.
  181. XX     * We will use several characteristics of Unix pipes to resolve this:
  182. XX     *        1. Attempting to open() a named pipe that does not exist will
  183. XX     *          fail.  (I hope this is not a great surprise...)
  184. XX     *        2. Opening a named pipe for reading, with O_NDELAY clear, will
  185. XX     *          succeed immediately if someone else already has that pipe
  186. XX     *           open for writing.
  187. XX     *        3. Opening a named pipe for writing, with O_NDELAY clear, will
  188. XX     *           block the process until someone opens that pipe for reading.
  189. XX     * So, what we will actually do is this:
  190. XX     *        1. Make up a couple of pipe names, based on the tty names
  191. XX     *           involved.  Note that this entire sequence could get screwed
  192. XX     *           up if someone other than the talk programs opened, deleted,
  193. XX     *          or otherwise screwed up our pipes.  We therefore choose to
  194. XX     *           use dot files in /tmp, so they are not so visible.
  195. XX     *        2. Try to open the pipe that should be created by the other
  196. XX     *           user's talk program.  If this open succeeds, then we must
  197. XX     *           be replying to a call; if it fails, we are originating one.
  198. XX     *        3. If the open fails, send a message to the other user, set an
  199. XX     *           alarm for 30 seconds, and block the process by trying to
  200. XX     *          open the pipe we created.  If the user responds, the other
  201. XX     *          talk program will open our pipe, and then our open will
  202. XX     *           succeed.  If there is no response the alarm will fire, and
  203. XX     *          the open will return -1.
  204. XX     */
  205. XX    sprintf (out_fn, "/tmp/.talk_%s", my_tty);
  206. XX    sprintf (in_fn, "/tmp/.talk_%s", ctp.ut_line);
  207. XX    mknod (out_fn, 0010644, 0);
  208. XX    /* See if they are waiting on us... */
  209. XX    if ((in_pipe = open (in_fn, O_RDONLY | O_NDELAY, 0644)) >= 0)
  210. XX    /* They were, so we can open the other pipe and get to work. */
  211. XX    out_pipe = open (out_fn, O_WRONLY, 0644);
  212. XX    else {
  213. XX    /* Nope, so we are the ones that have to do all the work... */
  214. XX    sprintf (call_mesg,"\
  215. XX[%s is requesting a 'talk' link with you.]\n\
  216. XX[Type 'talk %s %s' to reply.]%c\n",
  217. XX            my_name, my_name, my_tty, BELL);
  218. XX    count = 0;
  219. XX    do {
  220. XX        write (their_fd, call_mesg, (unsigned) strlen(call_mesg));
  221. XX        mvwprintw (in_win, 0, 0, "[%d ringy-ding", ++count);
  222. XX        if (count == 1)
  223. XX        wprintw (in_win, "y]\n");
  224. XX        else wprintw (in_win, "ies]\n");
  225. XX        wrefresh (in_win);
  226. XX        /*
  227. XX         * Note that the alarm catching routine is a no-op.  All the
  228. XX         * real work is done right here; we are simply using the alarm
  229. XX         * to unblock the open() periodically.
  230. XX         */
  231. XX        signal (SIGALRM, catch_alarm);
  232. XX        alarm ((unsigned) 30);
  233. XX        /* We block here waiting on a reply... */
  234. XX        out_pipe = open (out_fn, O_WRONLY, 0644);
  235. XX    } while (count < 4 && out_pipe < 0);
  236. XX    close (their_fd);
  237. XX    if (out_pipe < 0) {
  238. XX        waddstr (in_win, "[No answer.  Giving up.]");
  239. XX        wrefresh (in_win);
  240. XX        terminate ();
  241. XX    }
  242. XX    /* OK, they answered, so open the other pipe. */
  243. XX    in_pipe = open (in_fn, O_RDONLY | O_NDELAY, 0644);
  244. XX    }
  245. XX    /*
  246. XX     * We are now ready to talk.  Both processes will hold the named pipes
  247. XX     * open forever from this point, so we can unlink() them, to avoid any
  248. XX     * chance that someone will see them, and say "Gee, I wonder what those
  249. XX     * are?  Maybe I'll cat /etc/termcap to them to see what happens..."
  250. XX     */
  251. XX    unlink (out_fn);
  252. XX    wmove (in_win, 0, 0);
  253. XX    wclrtoeol (in_win);
  254. XX    waddstr (in_win, "[Connected]\n");
  255. XX    wrefresh (in_win);
  256. XX    signal (SIGPIPE, terminate);
  257. XX    /*
  258. XX     * Believe it or not, the preceding 100+ lines of code were necessary
  259. XX     * just to set things up for the following 10 line loop.
  260. XX     *
  261. XX     * The infinite loop is exited only upon receipt of a signal.  The
  262. XX     * possiblilites are:
  263. XX     *        1. This user terminates the conversation by typing the
  264. XX     *           interrupt or quit character.
  265. XX     *        2. The other user terminates the conversation, thereby
  266. XX     *           closing the pipes.  The next attempt to write a character
  267. XX     *           on the output pipe generates SIGPIPE.
  268. XX     * All three signals are trapped to the terminate() routine.
  269. XX     *
  270. XX     * Note the use of if() when reading the keyboard, and while() when
  271. XX     * reading the pipe.  Since we are using the keyboard timeout to
  272. XX     * control the entire loop, we must drain the pipe each time the
  273. XX     * keyboard read is satisfied or times out.
  274. XX     */
  275. XX    for (;;) {
  276. XX    if (read (0, &in_c, 1) > 0) {
  277. XX        waddch (out_win, in_c);
  278. XX        wrefresh (out_win);
  279. XX        write (out_pipe, &in_c, 1);
  280. XX    }
  281. XX    while (read (in_pipe, &in_c, 1) > 0) {
  282. XX        waddch (in_win, in_c);
  283. XX        wrefresh (in_win);
  284. XX    }
  285. XX    }
  286. XX}
  287.  
  288.  
  289. @//E*O*F talk.c//
  290. chmod u=rw,g=rw,o=rw talk.c
  291.  
  292. exit 0
  293.