[net.sources] nwrite.c

flamer@omsvax.UUCP (08/12/83)

/*   Program:         nwrite.
 *   Author:          Jim Earenluindil Trethewey.
 *   Version:         2.08 (VAX edition).
 *   Index:           CSD246.
 *   Date:            27-Apr-1983.
 *   Last-edit:       08-Jul-1983.
 *   Language:        C.
 *   Ppn:             /rl2/cs414/trethewe.
 *   For:             CS 416, Operating Systems III, Toshi Minoura.
 *                    Oregon State University,  Corvallis, Oregon.
 *
 *   This is a UNIX multi-user interprocess communications (IPC) program.
 *  
 *   This module, nwrite.c, contains the program procedures.
 *
 *   This program has a few flaws:
 *
 *      1.  It is pretty slow.  It should really only be run on a VAX
 *          or on a PDP-11 with few users.
 *      2.  The method used for writer-lock semaphores is strange.
 *          It would be nicer if the operating system had global
 *          sense-switches (like in COBOL, except all processes
 *          can see them), or other elegant P/V mechanism.
 *      3.  It uses the Berkeley extended signal set, not available
 *          in standard Version 7 UNIX.  This makes it less portable.
 *          Also, work is going on at UCB to make nicer IPC features,
 *          which will require major program revisions when they are
 *          introduced.
 *      4.  The maximum number of users per conversation is limited to
 *          the maximum files allowed open on your system minus 3.  For
 *          example, our file tables only allow 20 simultaneously open
 *          files: 17 talkers + stdin + stdout + stderr.
 *          The program can be rewritten so that this is not a factor
 *          (send_message = open + write + close for each talker) but
 *          this makes it slower.
 *
 *   If you want to send flames or suggestions, here's how to find me:
 *
 *      Jim Trethewey                Jim Trethewey
 *      2458 NE Kathryn St.          Intel Corporation
 *      Hillsboro,  OR  97123        OMO Engineering
 *      phone: 503-648-0697          Mail Stop HF2-2-243
 *                                   5200 NE Elam Young Pkwy.
 *                                   Hillsboro,  OR  97123
 *                                   phone: 503-640-5444
 *                                   uucp: ...!tektronix!ogcvax!omsvax!flamer
 *                                         ...!hp-pcd!ogcvax!omsvax!flamer
 *
 */

#include "nwrite.h"


/*
 *   main:   program mainline.
 */

main (argc, argv, envp)

   int argc;
   char **argv;
   char **envp;

   {
      analyze (argc, argv);
      environ = envp;
      set_up ();
      while (!done)
         {
            eval_input ();
         }
      restore_tty_context ();
   }

/*
 *   add_mfrec:   add a new person and master file record.
 */

add_mfrec ()
   {
      open_master ("a");
      mfd.conversation = conversation;
      strcpy (mfd.person, my_name);
      strcpy (mfd.location, my_tty);
      ascii_time ();
      strcpy (mfd.time_on, current_time); 
      write_master ();
      fclose (f_master);
   }

/*
 *   analyze:   look at argc and argv, see if OK and determine the
 *              lead-in character for 'tilde escapes'.
 */

analyze (argc, argv)

   int argc;
   char *argv [];

   {
      int byte;

      if (argc > 1)
         {
            *argv++;
            byte = 0;
            while ((byte < 2) && ((*argv) [byte] == pattern [byte]))
               {
                  byte++;
               }
            if ((byte == 2) && ((*argv) [byte] != NULL))
               {
                  lead_in = (*argv) [byte];
               }
            else
               {
                  lead_in = TILDE;
               }
         }
      else
         {
            lead_in = TILDE;
         }
   }   

/*
 *   ascii_time:   get current time as ascii string CTIME(3).
 */

ascii_time ()
   {
      long clock;
      char *t_clock;
      char a_clock [26];

      time (&clock);
      t_clock = ctime (&clock);
      strncpy (a_clock, t_clock, 24);
      a_clock [24] = NULL;
      strcpy (current_time, a_clock);
   }

/*
 *   back_up:   erase a character from the screen.
 */

back_up ()
   {
      if (isprint (line [column]) || isspace (line [column]))
         {
            force (ERASE);
            if (writing)
               {
                  send_message (ERASE);
               }
         }
      column--;
   }

/*
 *   call_up:   call another person to talk to.
 */

call_up ()
   {
      parse_request ();
      if (*callee != NULL)
         {
            lookup_callee_on_system ();
         }
      if (can_call)
         {
            mfd_search ();
            if (conversation != NO)
               {
                  if (was_found && (found_conversation == conversation))
                     {
                        printf ("? Already in this conversation.\n");
                     }
                  else
                     {
                        ring_ring ();
                     }
               }
            else
               {
                  if (was_found)
                     {
                        conversation = found_conversation;
                        enter_conference ();
                        ascii_time ();
                        strcpy (current_time, current_time);
                        open_talk_files ();
                        sprintf (message, "\n%c[Just joined: %s on %s at %s]\n",
                           BEEP, my_name, my_tty, current_time);
                        printf ("[Joining conversation %d]\n", conversation);
                        send_message (message);
                        close_talk_files ();
                     }
                  else
                     {
                        choose_conversation ();
                        enter_conference ();
                        ring_ring ();
                     }
               }
         }
   }

/*
 *   choose_conversation:   grab the first free conversation number.
 */

choose_conversation ()
   {
      int taken;

      load_mfd ();
      conversation = 0;
      taken = YES;
      while (taken)
         {
            conversation++;
            taken = NO;
            for (ptr = t_o_s; ptr != NULL; ptr = ptr->next)
               {
                  if (ptr->record.conversation == conversation)
                     {
                        taken = YES;
                     }
               }
         }
   }

/*
 *   close_talk_files:   close all conversation message files.
 */

close_talk_files ()
   {
      int i;

      for (i = 1; i <= n_open_files; i++)
         {
            fclose (out_file [i]);
         }
   }

/*
 *   delete_mfrec:   leave nwrite and get rid of master record entry.
 */

delete_mfrec ()
   {
      load_mfd ();
      ptr_1 = t_o_s;
      ptr_2 = ptr_1;
      while (ptr_1 != NULL)
         {
            if (!strcmp (ptr_1->record.location, my_tty))
               {
                  if (t_o_s != ptr_1)
                     {
                        ptr_2->next = ptr_1->next;
                        free (ptr_1);
                        ptr_1 = ptr_2->next;
                     }
                  else
                     {
                        t_o_s = ptr_1->next;
                        free (ptr_1);
                        ptr_1 = t_o_s;
                        ptr_2 = ptr_1;
                     }
               }
            else
               {
                  if (t_o_s != ptr_1)
                     {
                        ptr_1 = ptr_1->next;
                        ptr_2 = ptr_2->next;
                     }
                  else
                     {
                        ptr_1 = ptr_1->next;
                     }
               }
         }
      store_mfd ();
   }

/*
 *   enter_conference:   go into a conversation.
 */

enter_conference ()
   {
      load_mfd ();
      for (ptr = t_o_s; ptr != NULL; ptr = ptr->next)
         {
            if (!strcmp (ptr->record.location, my_tty))
               {
                  ptr->record.conversation = conversation;
                  ascii_time ();
                  strcpy (ptr->record.time_on, current_time);
               }
         }
      store_mfd ();
   }

/*
 *   eval_input:   terminal char interrupt... what command is it?
 */

eval_input ()
   {
      column = position (0);
      ch = getc (stdin);
      while (ch == EOF)
         {
            ch = getc (stdin);
         }
      if (ch == lead_in)
         {
            tilde_escape ();
         }
      else
         {
            switch (ch)
               {
                  case TILDE:    tilde_escape ();
                                 break;
                  case ESCAPE:   over ();
                                 break;
                  case CTRL(D):  printf ("^D\r\n");
                                 quit ();
                                 break;
                  case '!':      shell_escape ();
                                 break;
                  case '?':      help ();
                                 break;
                  default:       text_process ();
                                 break;
               }
         }
      fflush (stdout);
   }

/*
 *   force:   display message even if noecho set.
 */

force (message)

   char message [];

   {
      printf ("%s", message);
   }

/*
 *   get_line:   get a complete line from the terminal.
 */

get_line ()
   {
      while ((ch != CRLF) &&
             (ch != CTRL(D)) &&
             (ch != ESCAPE) &&
             (column > position (0)))
         {
            ch = getc (stdin);
            while (ch == EOF)
               {
                  ch = getc (stdin);
		  if (timed_out)
		     {
			ch = CRLF;
			timed_out = NO;
                     }
               }
            if (writing)
               {
                  alarm (dead_man);
               }
            show_char (ch);
         }
      if (writing)
         {
            if (ch == CTRL(D))
               {
                  quit ();
               }
            else if (ch == ESCAPE)
               {
                  printf ("\r");
                  over ();
               }
         }
      if (ch == CRLF)
         {
            line [column + 1] = NULL;
         }
   }

/*
 *   hang_up:   exit from a conversation.
 */

hang_up ()
   {
      if (conversation != NO)
         {
            ascii_time ();
            open_talk_files ();
            sprintf (message, "\n[over and out: %s on %s at %s]\n",
               my_name, my_tty, current_time);
            force (message);
            send_message (message);
            close_talk_files ();
            conversation = NO;
            load_mfd ();
            for (ptr = t_o_s; ptr != NULL; ptr = ptr->next)
               {
                  if (!strcmp (ptr->record.location, my_tty))
                     {
                        ptr->record.conversation = NO;
                     }
               }
            store_mfd ();
         }
      else
         {
            printf ("? You aren't in a conversation to hang up from.\n");
         }
   }

/*
 *   help:   show the list of valid commands.
 */

help ()
   {
      show_char (ch);
      get_line ();
      if (column != position (0))
         {
            help_text ();
            printf ("?\n\n");
         }
   }

/*
 *   help_text:   this is the actual list of valid nwrite commands.
 */

help_text ()
   {
      printf ("-----------------------------------------------------------------------\n");
      printf ("nwrite   v%4.2f                                        as of %s\n", version, as_of);
      printf ("\n");
      printf ("%cc [<user_name> [<tty_name>]]\n", lead_in);
      printf ("             Call <user_name> on <tty_name> for conversation.\n");
      printf ("%ce           Toggle echo-plex on/off (for half-duplex tty's).\n", lead_in);
      printf ("%ch           Hang up: leave a conversation (\"over and out\").\n", lead_in);
      printf ("%co           Send \"over\" and relinquish write control.\n", lead_in);
      printf ("%cq           Quit nwrite.\n", lead_in);
      printf ("%cw           Who is in which conversations?\n", lead_in);
      printf ("<escape>     Like %co but works in-line.\n", lead_in);
      printf ("^D           Like %cq but works in-line.\n", lead_in);
      printf ("?            Print this help list.\n");
      printf ("![<command>] Invoke subshell, optionally with <command>.\n");
      printf ("<text>       Attempt to get write control, send <text> in conversation.\n");
      printf ("-----------------------------------------------------------------------\n");
   }

/*
 *   id_me:   find out my login_name and tty.
 */

id_me ()
   {
      if (!isatty (TTY))
         {
            printf ("? You shouldn't redirect standard input for nwrite.\n");
            done = YES;
         }
      strcpy (my_name, getlogin ());
      if (*my_name == NULL)
         {
            printf ("? You don't exist.  Go away!\n");
            done = YES;
         }
      strcpy (my_devtty, ttyname (TTY));
      strcpy (my_tty, rindex (my_devtty, '/') + 1);
      if (stat (my_devtty, &stat_buf) < 0)
         {
            printf ("? Can't find logical device for your tty.\n");
            done = YES;
         }
      if (!(stat_buf.st_mode & S_OWRITE))
         {
            printf ("? You have write permission turned off.\n");
            done = YES;
         }
   }

/*
 *   init_tty_context:   set tty mode to that convenient for nwriting.
 */

init_tty_context ()
   {
      new_tty = old_tty;
      new_tty.sg_flags |= CBREAK;
      new_tty.sg_flags &= ~ECHO;
      ioctl (TTY, TIOCSETP, &new_tty);
      new_ltchars = old_ltchars;
      new_lmodes = old_lmodes | LPENDIN | LINTRUP | LTOSTOP;
      ioctl (TTY, TIOCLSET, &new_lmodes);
      sigset (SIGALRM, time_out);
      sigset (SIGTSTP, please_no);
      sigset (SIGINT, please_no);
      sigset (SIGQUIT, please_no);
      sigset (SIGSTOP, please_no);
      sigset (SIGHUP, SIG_IGN);
      sigset (SIGPIPE, SIG_IGN);
      sigset (SIGTINT, SIG_IGN);
      sigset (SIGTTOU, SIG_IGN);
      sigset (SIGTTIN, SIG_IGN);
   }

/*
 *   load_mfd:   build a linked_list of mfd records for manipulation.
 */

load_mfd ()
   {
      open_master ("r");
      if (f_master != NULL)
         {
            t_o_s = NULL;
            read_master ();
            while (n != f_EOF)
               {
                  ptr_2 = (struct mct_node *) malloc (sizeof (struct mct_node));
                  ptr_2->record.conversation = mfd.conversation;
                  strcpy (ptr_2->record.person, mfd.person);
                  strcpy (ptr_2->record.location, mfd.location);
                  strcpy (ptr_2->record.time_on, mfd.time_on);
                  ptr_2->next = NULL;
                  if (t_o_s == NULL)
                     {
                        t_o_s = ptr_2;
                        ptr_1 = t_o_s;
                     }
                  else
                     {
                        ptr_1->next = ptr_2;
                        ptr_1 = ptr_1->next;
                     }
                  read_master ();
               }
            fclose (f_master);
         }
   }

/*
 *   lookup_callee_on_system:   look in /etc/utmp for callee and get his_tty.
 */

lookup_callee_on_system ()
   {
      int tty_specified;

      f_utmp = fopen (h_utmp, "r");
      match_tty = NO;
      logged_on = NO;
      if (*his_tty == NULL)
         {
            tty_specified = NO;
         }
      else
         {
            tty_specified = YES;
         }
      n = fread (&ubuf, sizeof ubuf, 1, f_utmp);
      while (n != f_EOF)
         {
            name_match = !strncmp (ubuf.ut_name, callee, strlen (callee));
            if (name_match)
               {
                  if (*his_tty != NULL)
                     {
                        if (!match_tty)
                           {
                              match_tty = !strncmp (ubuf.ut_line, his_tty, strlen (his_tty));
                           }
                     }
                  else
                     {
                        strcpy (his_tty, ubuf.ut_line);
                        match_tty = YES;
                     }
                  logged_on++;
               }
            n = fread (&ubuf, sizeof ubuf, 1, f_utmp);
         }
      fclose (f_utmp);
      can_call = NO;
      if (!logged_on)
         {
            printf ("? %s is not logged in.\n", callee);
         }
      else
         {
            if (!match_tty)
               {
                  printf ("? %s is not on %s.\n", callee, his_tty);
               }
            else if ((logged_on == 1) || tty_specified)
               {
                  can_call = YES;
               }
            else
               {
                  printf ("? %s is logged on more than once... please specify tty.\n", callee);
               }
         }
   }

/*
 *   mfd_search:   is the callee currently running nwrite?
 */

mfd_search ()
   {
      was_found = NO;
      load_mfd ();
      for (ptr = t_o_s; (ptr != NULL) && !was_found; ptr = ptr->next)
         {
            if (!strcmp (callee, ptr->record.person) &&
                !strcmp (his_tty, ptr->record.location) &&
                (ptr->record.conversation != NO))
               {
                  was_found = YES;
                  found_conversation = ptr->record.conversation;
               }
         }
   }

/*
 *   open_master:   open the master file.
 */
 
open_master (mode)
   
   char mode [];

   {
      f_master = fopen (h_mfd, mode);
      if (f_master == NULL)
         {
            printf ("? Can't open conversation master file %s.\n", h_mfd);
         }
   }

/* 
 *   open_talk_files:   open conversation message files.
 */

open_talk_files ()
   {
      load_mfd ();
      n_open_files = 0;
      for (ptr = t_o_s; ptr != NULL; ptr = ptr->next)
         {
            if ((ptr->record.conversation == conversation) &&
                strcmp (ptr->record.location, my_tty))
               {
                  strcat (strcpy (f_devtty, h_tty), ptr->record.location);
                  f_otty = fopen (f_devtty, "w");
                  if (f_otty != NULL)
                     {
                        n_open_files++;
                        out_file [n_open_files] = f_otty;
                     }
               }
         }
   }

/*
 *   over:   I'm done with this message, relinquish write control.
 */

over ()
   {
      if (writing)
         {
            ascii_time ();
            sprintf (message, "\n[over: %s on %s at %s]\n\n",
               my_name, my_tty, current_time);
            force (message);
            send_message (message);
            close_talk_files ();
            alarm (0);
            V (conversation);
         }
      else
         {
            printf ("? You don't have write control, you can't \"over\".\n");
         }
   }

/*
 *   P:   lock myself as conversation writer (a pseudo-semaphore).
 *        Actually, the file <c_file> is linked to a user temp file.
 */

P (conversation)

   int conversation;

   {
      char c_file [40];
      int link_error;

      sprintf (c_file, h_conv, conversation);
      link_error = link (tmp_file, c_file);
      if (!link_error)
         {
            writing = YES;
         }
   }

/*
 *   page_confirm:   handle SIGTTOU signal.
 */

page_confirm ()
   {
      if (debug)
         {
            printf ("<<< page_confirm >>>\n");
         }
   }

/*
 *   parse_request:   strip out arguments of the ~c command.
 */

parse_request ()
   {
      char word_1 [40], word_2 [40];
      int i, j;

      i = 0;
      while (!isspace (line [i]) && (line [i] != NULL))
         {
            i++;
         }
      while (isspace (line [i]) && (line [i] != NULL))
         {
            i++;
         }
      j = 0;
      while (!isspace (line [i]) && (line [i] != NULL))
         {
            word_1 [j++] = line [i++];
         }
      word_1 [j] = NULL;
      while (isspace (line [i]) && (line [i] != NULL))
         {
            i++;
         }
      j = 0;
      while (!isspace (line [i]) && (line [i] != NULL))
         {
            word_2 [j++] = line [i++];
         }
      word_2 [j] = NULL;
      if (*word_1 != NULL)
         {
            if (*word_2 != NULL)
               {
                  strcpy (his_tty, word_2);
               }
            else
               {
                  if (strcmp (callee, word_1))
                     {
                        *his_tty = NULL;
                     }
               }
            strcpy (callee, word_1);
         }
      if (*callee == NULL)
         {
            printf ("? Call who?\n");
         }
   }

/*
 *   please_no:   ask user not to use interrupt signals, etc.
 */

please_no ()
   {
      printf ("\n%cPlease %cuse %c^D %cto %cexit %cnwrite!\n",
         BEEP, BEEP, BEEP, BEEP, BEEP, BEEP);
   }

/*
 *   position:   point to the i'th character in a string.
 */

position (i)
   
   int i;

   {
      return (i - 1);
   }

/*
 *   print_header:   look official.
 */

print_header ()
   {
      printf ("\nnwrite   v%4.2f\n", version);
      printf ("%s\n", myname);
      printf ("Type ? for help.\n");
   }

/*
 *   quit:   leave nwrite, go back to the shell.
 */

quit ()
   {
      if (writing)
         {
            over ();
         }
      if (conversation != NO)
         {
            hang_up ();
         }
      printf ("[Leaving nwrite]\n");
      done = YES;
      delete_mfrec ();
      unlink (tmp_file);
      sleep (1);
   }

/*
 *   read_master:   get a master_control_table record.
 */

read_master ()
   {
      n = fread (&mfd, sizeof mfd, 1, f_master);
   }

/*
 *   read_pipe:   get reader control flag from child process.
 */

read_pipe ()
   {
      int ctrl_flag;

      read (ctrl_pipe [0], &ctrl_flag, 2);
      return (ctrl_flag);
   }

/*
 *   receive_message:   if someone sends us a message, display it.
 */

receive_message ()
   {
      ch = getc (f_tty);
      if (ch != EOF)
         {
            putc (ch, stdout);
            fflush (stdout);
         }
   }

/*
 *   restore_tty_context:   put tty modes back as they were.
 */

restore_tty_context ()
   {
      ioctl (TTY, TIOCSETP, &old_tty);
      ioctl (TTY, TIOCLSET, &old_lmodes);
      sigset (SIGALRM, SIG_DFL);
   }

/*
 *   ring_ring:   hello?  are you there?  I want to talk to you.
 */

ring_ring ()
   {
      char p_file [40];
      char m_file [40];

      strcat (strcpy (p_file, h_tty), his_tty);
      f_pager = fopen (p_file, "w");
      if (f_pager != NULL)
         {
            ascii_time ();
            fprintf (f_pager, "\n%c%cMessage from: %s on %s at %s...\n",
               BEEP, BEEP, my_name, my_tty, current_time);
            fprintf (f_pager, "Use nwrite to respond.\n");
            printf ("[Message sent to: %s on %s at %s]\n",
               callee, his_tty, current_time);
            fclose (f_pager);
         }
      else
         {
            printf ("[User %s on %s has write protection turned on...\n",
               callee, his_tty);
            sprintf (m_file, "mail %s", callee);
            f_mail = popen (m_file, "w");
            if (f_mail != NULL)
               {
                  ascii_time ();
                  fprintf (f_mail, "\n%c%cMessage from: %s on %s at %s...\n",
                     BEEP, BEEP, my_name, my_tty, current_time);
                  fprintf (f_mail, "Use nwrite to respond.\n");
                  fclose (f_mail);
                  printf ("   ...sent mail at %s]\n", current_time);
               }
            else
               {
                  printf ("   ...couldn't send mail either]\n");
               }
         }
   }

/*
 *   save_tty_context:   remember current tty modes so we can restore later.
 */

save_tty_context ()
   {
      ioctl (TTY, TIOCGETP, &old_tty);
      ioctl (TTY, TIOCGLTC, &old_ltchars);
      ioctl (TTY, TIOCLGET, &old_lmodes);
   }

/*
 *   send_message:   send the message to all receivers in conversation.
 */

send_message (message)

   char message [];

   {
      int i;

      for (i = 1; i <= n_open_files; i++)
         {
            fprintf (out_file [i], "%s", message);
            fflush (out_file [i]);
         }
   }

/*
 *   set_up:   lots of initialization.
 */

set_up ()
   {
      echo = YES;
      done = NO;
      print_header ();
      printf ("\n");
      save_tty_context ();
      init_tty_context ();
      id_me ();
      conversation = NO;
      timed_out = NO;
      writing = NO;
      *callee = NULL;
      *his_tty = NULL;
      if (!done)
         {
            add_mfrec ();
            strcpy (tmp_file, mktemp (h_temp));
            creat (tmp_file, MODE);
         }
   }

/*
 *   shell_escape:   perform a shell command or invoke a subshell.
 */

shell_escape ()
   {
      int pid;
      char *csh;

      if (writing)
         {
            over ();
         }
      show_char (ch);
      get_line ();
      if (column != position (0))
         {
            restore_tty_context ();
            pid = fork ();
            if (pid == -1)
               {
                  printf ("? Can't fork.\n");
               }
            else if (pid != 0)
               {
                  while (wait (0) != pid) 
                     {
                        /* spin in a tight loop */;
                     }
               }
            else
               {
                  setuid (getuid ());
                  csh = getenv ("SHELL");
                  if (*csh == NULL)
                     {
                        csh = sh;
                     }
                  if (column == position (1))
                     {
                        execle (csh, "-", 0, environ);
                     }
                  else
                     {
                        execle (csh, &csh[4], "-c", &line[position (2)], 0, environ);
                     }
                  exit (0);
               }
            init_tty_context ();
            printf ("!\n\n");
         }
   }

/*
 *   show_char:   we typed something; show it.
 */

show_char (ch)
   
   char ch;
   
   {
      if (isprint (ch) || isspace (ch))
         {
            if (echo)
               {
                  putc (ch, stdout);
                  fflush (stdout);
               }
            else if ((ch == CRLF) && !echo)
               {
                  printf ("\n");
               }
            if (writing)
               {
                  sprintf (message, "%c", ch);
                  send_message (message);
               }
            column++;
            line [column] = ch;
         }
      else
         {
            if (ch == old_tty.sg_erase)
               {
                  if (!echo)
                     {
                        force (" ");
                     }
                  back_up ();
               }
            else if (ch == old_ltchars.t_werasc)
               {
                  while (isspace (line [column]) && (column != position (0)))
                     {
                        back_up ();
                     }
                  while (!isspace (line [column]) && 
                         (isprint (line [column])) &&
                         (column != position (0)))
                     {
                        back_up ();
                     }
               }
            else if (ch == old_tty.sg_kill)
               {
                  while (column != position (0))
                     {
                        back_up ();
                     }
               }
             else if (ch == CTRL(D))
               {
                  printf ("^D\n");
               }
         }
   }

/*
 *   store_mfd:   we tweaked master records, now put them back.
 */

store_mfd ()
   {
      open_master ("w");
      if (f_master != NULL)
         {
            ptr_1 = t_o_s;
            while (ptr_1 != NULL)
               {
                  mfd.conversation = ptr_1->record.conversation;
                  strcpy (mfd.person, ptr_1->record.person);
                  strcpy (mfd.location, ptr_1->record.location);
                  strcpy (mfd.time_on, ptr_1->record.time_on);
                  write_master ();
                  ptr_2 = ptr_1;
                  ptr_1 = ptr_1->next;
                  free (ptr_2);
               }
            fflush (f_master);
            fclose (f_master);
         }
   }

/*
 *   text_process:   try to get write control and/or send text message.
 */

text_process ()
   {
      if (writing)
         {
            show_char (ch);
            get_line ();
         }
      else
         {
            if (conversation == NO)
               {
                  printf ("? You haven't called anyone yet.\n");
               }
            else
               {
                  P (conversation);
                  if (writing)
                     {
                        open_talk_files ();
                        ascii_time ();
                        sprintf (message, "\n[From: %s on %s at %s]\n",
                           my_name, my_tty, current_time);
                        send_message (message);
                        printf ("[you have write control of conversation %d]\n",
                           conversation);
                        show_char (ch);
                        alarm (dead_man);
                        get_line ();
                     }
               }
         }
   }

/*
 *   tilde_escape:   a tilde was typed, which command does he want?
 */

tilde_escape ()
   {
      show_char (ch);
      get_line ();
      if (column != position (0))
         {
            if (column == position (1))
               {
                  ch = line [position (1)];
               }
            else
               {
                  ch = line [position (2)];
               }
            switch (ch)
               {
                  case 'c':   call_up ();
                              break;
                  case 'e':   toggle_echo ();
                              break;
                  case 'h':   hang_up ();
                              break;
                  case 'o':   over ();
                              break;
                  case 'q':   quit ();
                              break;
                  case 'w':   who ();
                              break;
                  default:    printf ("? Invalid tilde escape.  Type ? for help.\n");
                              break;
               }
            printf ("%c\n\n", lead_in);
         }
   }

/*
 *   time_out:   take away write control if you're a terminal hog.
 */

time_out ()
   {
      if (writing)
         {
            ascii_time ();
            sprintf (message, "\n%c[%d second time out: %s on %s at %s]\n",
               BEEP, dead_man, my_name, my_tty, current_time);
            force (message);
            send_message (message);
            V (conversation);
	    timed_out = YES;
         }
   }

/*
 *   toggle_echo:   change echo/noecho (for half duplex tty's).
 */

toggle_echo ()
   {
      if (echo)
         {
            printf ("[Echo now OFF]\n");
         }
      else
         {
            printf ("[Echo now ON]\n");
         }
      echo = !echo;
   }

/*
 *   trapse_list:   print linked list for debugging purposes.
 */

trapse_list ()
   {
      struct mct_node *ptr;

      printf ("=============================================================\n");
      printf ("Conv Person        Location      Time On\n");
      printf ("-------------------------------------------------------------\n");
      for (ptr = t_o_s; ptr != NULL; ptr = ptr->next)
          {
             printf ("%3d  %-12s  %-12s  %24s\n", ptr->record.conversation,
                ptr->record.person, ptr->record.location, ptr->record.time_on);
          }
      printf ("=============================================================\n");
   }

/*
 *   V:   unlock write control (a pseudo-semaphore).
 *        Actually, the file <c_file> is unlinked from a user temp file.
 */

V (conversation)

   int conversation;

   {
      char c_file [40];

      writing = NO;
      sprintf (c_file, h_conv, conversation);
      unlink (c_file);
   }

/*
 *   who:   who is running nwrite, and what conversations are they in?
 */

who ()
   {
      load_mfd ();
      printf ("Conversation    User        Where        Talking since\n");
      printf ("-----------------------------------------------------------------\n");
      for (ptr = t_o_s; ptr != NULL; ptr = ptr->next)
         {
            if (ptr->record.conversation == 0)
               {
                  printf ("      -");
               }
            else
               {   
                  printf ("    %3d", ptr->record.conversation);
               }
            printf ("         %-8s    %-8s     %-25s\n",
               ptr->record.person, ptr->record.location, ptr->record.time_on);
         }
   }

/*
 *   write_master:   write a master_control_table record.
 */

write_master ()
   {
      int tries;

      tries = 1;
      n = fwrite (&mfd, sizeof mfd, 1, f_master);
      while ((n < 1) && (tries < 10))
         {
            if (debug)
               {
                  printf ("<<< failure to write master record. >>>\n");
               }
            tries++;
            n = fwrite (&mfd, sizeof mfd, 1, f_master);
         }
   }

/*
 *   write_pipe:   send the reader control flag to parent process.
 */

write_pipe (ctrl_flag)

   int ctrl_flag;

   {
      write (ctrl_pipe [1], &ctrl_flag, 2);
   }