[comp.sources.misc] v20i044: mtalk - simple multi-user chat for Coherent, Part01/01

Mathew Kimmel <kimmel@umvlsi.ecs.umass.edu> (06/08/91)

Submitted-by: Mathew Kimmel <kimmel@umvlsi.ecs.umass.edu>
Posting-number: Volume 20, Issue 44
Archive-name: mtalk/part01
Environment: Coherent

This is a very small and simple multi-user chat program I wrote for
Coherent.  I wrote it as an exercise to teach myself about interprocess
communications, but thought it might be useful to the world as a whole.
It takes up very little memory, and doesn't monopolize CPU time.  It can
handle as many as 30 users (or more?); the maximum number is set at
compile time.  It may also compile on systems other than Coherent--I
haven't tried any others, but the code is fairly generic.

-Matt
---cut here---
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of shell archive."
# Contents:  Makefile README ftok.c mtalk.c mtalk.h mtalk.man mtalkd.c
#   mtalkd.man
# Wrapped by kimmel@umvlsi.ecs.umass.edu on Fri Jun  7 01:46:02 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f Makefile -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"Makefile\"
else
echo shar: Extracting \"Makefile\" \(229 characters\)
sed "s/^X//" >Makefile <<'END_OF_Makefile'
XCFLAGS = -O
X
Xall: mtalk mtalkd
X
Xmtalk: mtalk.o ftok.o
X  cc $(CFLAGS) -o mtalk mtalk.o ftok.o
X
Xmtalkd: mtalkd.o ftok.o
X  cc $(CFLAGS) -o mtalkd mtalkd.o ftok.o
X
Xmtalk.o: mtalk.c mtalk.h
X
Xmtalkd.o: mtalkd.c mtalk.h
X
Xftok.o: ftok.c
END_OF_Makefile
if test 229 -ne `wc -c <Makefile`; then
    echo shar: \"Makefile\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f README -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"README\"
else
echo shar: Extracting \"README\" \(4066 characters\)
sed "s/^X//" >README <<'END_OF_README'
XINTRODUCTION
X
Xmtalk is a small, simple multi-user chat program.  It consists of a
Xdaemon, mtalkd, which sits in memory and receives and sends data using
Xkernel message passing to user processes which have invoked the client
Xprogram and user interface, mtalk.  As the code stands at this release,
Xthe daemon takes up only 15K or so of memory, as does each user process.
XI've tested it with three simultaneous users, and it produced no significant
Xdrop in processing speed on my machine (a 286 laptop).
X
X
XMAKING mtalk
X
XBefore running make and compiling the programs, you must edit the header
Xfile mtalk.h to reflect your system's configuration.  Of special importance
Xare the #defines DAEMON_PATH and CLIENT_PATH.  These must contain the
Xfull pathnames of the daemon and client executables once they are compiled.
XIf either program can't find these two files when you run it, it will
Xcrash immediately.  Other defines you may want to change are MAX_USERS,
Xthe maximum number of users allowed on the system at any given time (I
Xwould not recommend setting this above 30, but who has 30 users at a time
Xon Coherent anyway?) and MAX_MESSAGE, which is the maximum length of a
Xpublic message.  Care should be taken in changing MAX_MESSAGE, because
Xif it is too big it may exceed Coherent's maximum message size and crash
Xthe system.
X
X
XINSTALLING AND RUNNING mtalk
X
XAssuming you have successfully compiled mtalk and mtalkd and put them in
Xthe directories specified in DAEMON_PATH and CLIENT_PATH, you have two
Xoptions for installing the mtalk daemon: installing it for one session,
Xor installing it permanently.  Before you do either, however, I recommend
Xthat you set mtalkd's owner to root and chmod it to 500 (read and execute
Xfor owner only) to prevent users from running duplicate copies (which would
Xreally mess things up).  mtalk, of course, should be chmod'd to 755, so
Xeveryone can execute it.  That done, here are the two methods of installation:
X
X1) For one session only.  mtalk uses the inter-process message passing
X   system calls, which are implemented under Coherent as a device driver.
X   Therefore you, as the superuser, need to load the device driver and
X   then execute the daemon as a background process.  To do this, log in
X   as root and type:
X
X     /etc/drvld -r /drv/msg
X     mtalkd&
X
X   This will start the daemon as a background process, which will stay
X   in the system until you kill it or shut the system down.  Although
X   it will still technically be associated with the console (or whatever
X   device you were using when you executed it); however, mtalkd takes
X   steps to dissociate itself from its terminal and will not shut down
X   if you log out or type the interrupt character.  It can only be killed
X   with the kill command, by the SIGTERM signal (i.e. just kill <pid> to
X   kill it).  NEVER kill mtalkd with a SIGQUIT signal, because it will
X   be forced to exit without cleaning up message queues and will make a
X   mess of the message system.
X
X2) Permanently.  To install mtalkd permanently, so that it is automatically
X   loaded in the background whenever you boot Coherent, you must change
X   two files in the etc directory (as root).  First, add the following line
X   to the file drvld.all:
X
X     /etc/drvld -r /drv/msg
X
X   Then, add a line to rc, in the section that invokes the cron and update
X   daemons, invoking mtalkd as a daemon (i.e. executing it in the back-
X   ground).  Then shut down and reboot the system.
X
XOnce you've done either of these, users can use mtalk by executing the
Xmtalk program.  See the mtalk man page for details about commands.
X
X
XNOTES
X
XI wrote this program as an exercise to learn to use the message passing
Xfunctions.  When I was done, I saw that I had a small and simple chat
Xprogram that others might find useful.  The code is well commented and
Xshould be easily extensible; feel free to make modifications and
Xdistribute them; just be sure and let me know (and send me a copy!).
XIf anyone has comments, questions, or bug reports, please e-mail me at
Xkimmel@umvlsi.ecs.umass.edu
X
XEnjoy!
X
X-Matt
END_OF_README
if test 4066 -ne `wc -c <README`; then
    echo shar: \"README\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f ftok.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"ftok.c\"
else
echo shar: Extracting \"ftok.c\" \(1216 characters\)
sed "s/^X//" >ftok.c <<'END_OF_ftok.c'
X/* key_t ftok(filename,c) - Create a unique IPC key based on a filename
X *                          and an 8-bit number.
X *
X * This function takes as parameters a pointer to an ascii string
X * containing the pathname of a file, and an integer.  It then returns
X * a (hopefully) unique IPC key.  The key is a 32-bit integer, and is
X * constructed as follows: the lower 8 bits are the low 8 bits of c.
X * The next 8 bits are the low 8 bits of the device descriptor of the
X * device the file is located on.  The upper 16 bits are the inode
X * of the file.
X *
X * This code copyright (c) Matt Kimmel 1991.  Permission granted for
X * unrestricted use in non-commercial products.
X */
X#include <sys/ipc.h>
X#include <sys/stat.h>
X
Xkey_t ftok(filename,c)
Xchar *filename;
Xint c;
X{
X  struct stat fs;
X  union {
X    key_t key;
X    struct {
X      char c;
X      char dev;
X      int inode;
X      } info;
X    } keyval;
X
X  /* First attempt to stat the file */
X  if(stat(filename,&fs) == -1) {
X    perror("ftok");
X    exit(1); /* Best to exit if this happens, or we may have a major IPC collision... */
X    }
X
X  keyval.info.c = (char)c;
X  keyval.info.dev = (char)fs.st_dev;
X  keyval.info.inode = (int)fs.st_ino;
X  return(keyval.key);
X}
X
END_OF_ftok.c
if test 1216 -ne `wc -c <ftok.c`; then
    echo shar: \"ftok.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f mtalk.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"mtalk.c\"
else
echo shar: Extracting \"mtalk.c\" \(7324 characters\)
sed "s/^X//" >mtalk.c <<'END_OF_mtalk.c'
X/* Client for mtalk - multiuser talk program.  This program is invoked
X * by a user and sends and receives messages from the mtalk daemon.
X *
X * Copyright (c) 1991, Matthew Kimmel.  Permission granted for unlimted
X * non-commercial use and distribution.
X */
X#include <stdio.h>
X#include <string.h>
X#include <ctype.h>
X#include <signal.h>
X#include <errno.h>
X#include <sgtty.h>
X#include <sys/fcntl.h>
X#include <sys/msg.h>
X#include "mtalk.h"
X
Xstruct genmsg msgin;  /* Buffer for unprocessed messages */
Xint ouruid; /* uid of this user */
Xint ourmqid; /* our message queue id */
Xint dmqid; /* mtalk daemon's message queue id */
Xstruct sgttyb t0, t1; /* tty structs for stdin and stdout */
Xint oldfcntl; /* preserved old fcntl value for stdin */
X
Xmain()
X{
X  char buf[MAX_MESSAGE];
X  int pos = 0; /* position in message buffer */
X  char ch;
X  int i;
X
X  puts("Welcome to mtalk!  Attempting connection...");
X  init();
X  login();
X  buf[0] = '\0';
X
X  /* The program's main loop.  This could be accomplished more easily
X     by forking another process, one to process user input and one
X     to process messages from the server, but that would double the
X     memory that this program uses. */
X  for(;;) {
X    /* First check for input from the terminal */
X    if(read(0,&ch,1) > 0)
X      switch(ch) {
X        case 127 :
X        case 8 : /* Erase */
X                 if(pos > 0) {
X                   pos--;
X                   buf[pos] = '\0';
X                   write(1,"\b \b",3);
X                   }
X                 break;
X        case 3 : /* CTRL-C */
X                 doexit();
X        case 21 : /* Kill - CTRL-U */
X                  pos = 0;
X                  buf[0] = '\0';
X                  write(1,"^U\r\n",4);
X                  break;
X        case 18 : /* CTRL-R - refresh line */
X                  if(pos > 0) {
X                    write(1,"^R\r\n",4);
X                    write(1,buf,strlen(buf));
X                    }
X                  break;
X        case 13 : write(1,"\r\n",2);
X                  if(pos > 0) {
X                    sendline(buf);
X                    pos = 0;
X                    buf[0] = '\0';
X                    }
X                  break;
X        default : if(pos <= (MAX_MESSAGE - 2)) {
X                    write(1,&ch,1);
X                    buf[pos] = ch;
X                    pos++;
X                    buf[pos] = '\0';
X                    }
X                  break;
X        }
X
X    /* Now check for incoming messages */
X    if((i = msgrcv(ourmqid,&msgin,1024,0L,IPC_NOWAIT)) == -1)
X      if(errno == EDOM) {
X        perror("mtalk: msgrcv");
X        cleanup();
X        exit(1);
X        }
X    if(i >= 0) {
X      if(msgin.msgtype == M_DMNTEXT) {
X        write(1,msgin.contents,strlen(msgin.contents));
X        write(1,"\r\n",2);
X        continue;
X        }
X      if(msgin.msgtype == M_DMNFULL) {
X        puts("Sorry, mtalk is full now.  Try again later.\r");
X        cleanup();
X        exit(0);
X        }
X      }
X    }
X}
X
X/* Initialize various things--message queues, signals, cbreak, etc. */
Xinit()
X{
X  key_t ftok();
X  int doexit();
X
X  /* First try to connect with the daemon's message queue...using the secret formula! */
X  if((dmqid = msgget(ftok(DAEMON_PATH,1),0)) == -1) {
X    perror("mtalk: msgget");
X    exit(1);
X    }
X
X  /* Now get our uid, and attempt to create our own message queue. */
X  ouruid = getuid();
X  if((ourmqid = msgget(ftok(CLIENT_PATH,ouruid),(0622 | IPC_CREAT))) == -1) {
X    perror("mtalk: msgget");
X    exit(1);
X    }
X
X  /* Set up to handle signals - we DON'T want to exit without notifying
X     the server, if at all possible. */
X  signal(SIGHUP,doexit);
X  signal(SIGQUIT,doexit);
X  signal(SIGTERM,doexit);
X  signal(SIGREST,doexit);
X
X  /* Now set up stdin */
X  oldfcntl = fcntl(0,F_GETFL,0);
X  fcntl(0,F_SETFL,(oldfcntl | O_NDELAY));
X  gtty(0,&t0);
X  t0.sg_flags |= RAW;
X  t0.sg_flags &= ~ECHO;
X  stty(0,&t0);
X
X  /* Set up stdout */
X  gtty(1,&t1);
X  t1.sg_flags |= RAW;
X  stty(1,&t1);
X}
X
X/* Send a login attempt to the daemon */
Xlogin()
X{
X  struct {
X    long mtype;
X    struct usrin info;
X    } mbuf;
X
X  mbuf.mtype = M_USRIN;
X  mbuf.info.uid = ouruid;
X  msgsnd(dmqid,&mbuf,sizeof(struct usrin),0);
X}
X
X/* This function takes a line of text that the user has entered, and
X   determines whether it is a public message or a command.  If it is
X   a public message, it sends it; otherwise, it tries to process the
X   command. */
Xsendline(txt)
Xchar *txt;
X{
X  struct {
X    long mtype;
X    struct usrmsg info;
X    } mbuf;
X
X  if(txt[0] != '/') { /* Not a command */
X    mbuf.mtype = M_USRMSG;
X    mbuf.info.uid = ouruid;
X    strcpy(mbuf.info.text,txt);
X    msgsnd(dmqid,&mbuf,sizeof(struct usrmsg),0);
X    return;
X    }
X
X  /* Determine which command it is by letter following slash */
X  switch(tolower(txt[1])) {
X    case 'w' : /* Whisper */
X               dowhisper(txt);
X               return;
X    case 'u' : /* Who (Users) */
X               dowho();
X               return;
X    case 'e' : /* Exit */
X               doexit();
X    case '?' :
X    case 'h' : /* Help */
X               puts("Commands available\r");
X               puts("  /users - show current mtalk users\r");
X               puts("  /whisper name message - send message privately to user name\r");
X               puts("  /exit - exit the program\r");
X               puts("  /help - see this help screen\r");
X               return;
X    default : puts("Unknown command.\r");
X              return;
X    }
X}
X
X/* Process the whisper command */
Xdowhisper(txt)
Xchar *txt;
X{
X  struct {
X    long mtype;
X    struct usrcmd info;
X    } mbuf;
X  char dummy[20];
X  char *p;
X
X  mbuf.mtype = M_USRCMD;
X  mbuf.info.uid = ouruid;
X  mbuf.info.cmd = U_WHISPER;
X
X  /* Make sure there is a first space and hence a name in the line */
X  if((p = (char *)index(txt,' ')) == NULL) {
X    puts("usage: /whisper name text\r");
X    return;
X    }
X
X  /* Attempt to extract the name.  The line should be of the form
X     /whisper name lots of text */
X  sscanf(txt,"%s %s",dummy,mbuf.info.user);
X
X  /* Now obtain a pointer to the text of the message */
X  /* We want to find the second space in the string. */
X  p++;
X  if((p = (char *)index(p,' ')) == NULL) {
X    puts("usage: /whisper name text\r");
X    return;
X    }
X  p++;
X
X  /* p now points to the message text.  Copy it into our structure and send it. */
X  strcpy(mbuf.info.text,p);
X  msgsnd(dmqid,&mbuf,sizeof(struct usrcmd),0);
X}
X
X/* Process a who command--send a request for a list of users. */
Xdowho()
X{
X  struct {
X    long mtype;
X    struct usrcmd info;
X    } mbuf;
X
X  mbuf.mtype = M_USRCMD;
X  mbuf.info.uid = ouruid;
X  mbuf.info.cmd = U_WHO;
X  msgsnd(dmqid,&mbuf,sizeof(struct usrcmd),0);
X}
X
X/* Process an exit command--send an exit message, and exit the program.
X   Also called by some signals. */
Xdoexit()
X{
X  struct {
X    long mtype;
X    struct usrcmd info;
X    } mbuf;
X
X  mbuf.mtype = M_USRCMD;
X  mbuf.info.uid = ouruid;
X  mbuf.info.cmd = U_EXIT;
X  msgsnd(dmqid,&mbuf,sizeof(struct usrcmd),0);
X
X  write(1,"\r\n",2);
X  cleanup();
X
X  exit(0);
X}
X
X/* un-initialize various things--called before exiting. */
Xcleanup()
X{
X  /* Kill our message queue */
X  msgctl(ourmqid,IPC_RMID,(struct msqid_ds *)0);
X
X  /* Set terminal back to normal */
X  fcntl(0,F_SETFL,oldfcntl);
X  gtty(0,&t0);
X  t0.sg_flags &= ~RAW;
X  t0.sg_flags |= ECHO;
X  stty(0,&t0);
X  gtty(1,&t1);
X  t1.sg_flags &= ~RAW;
X  stty(1,&t1);
X}
X 
END_OF_mtalk.c
if test 7324 -ne `wc -c <mtalk.c`; then
    echo shar: \"mtalk.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f mtalk.h -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"mtalk.h\"
else
echo shar: Extracting \"mtalk.h\" \(1981 characters\)
sed "s/^X//" >mtalk.h <<'END_OF_mtalk.h'
X/* mtalk - general info for both daemon and client.
X *
X * Copyright (c) 1991, Matthew Kimmel.  Permission granted for unlimited
X * non-commercial use and distribution.
X */
X
X/* Number of users that can use mtalk at one time. */
X#define MAX_USERS	3
X
X/* Maximum length of a user's message--must be smaller then the maximum
X   size of a msg queue message.  In the tradition of MUDs, I have it set
X   to 256 bytes.  Newline never included. */
X#define MAX_MESSAGE	256
X
X/* Where the executables are located--used with ftok(). */
X#define DAEMON_PATH	"/usr/kimmel/work/mtalk/mtalkd"
X#define CLIENT_PATH	"/usr/kimmel/work/mtalk/mtalk"
X
X/* Message types that can be sent to the daemon */
X#define M_USRIN		1L	/* User logging in. */
X#define M_USRMSG	2L	/* A public message from a user */
X#define M_USRCMD	3L	/* A command from a user */
X
X/* Message types that can be received by the client */
X#define M_DMNTEXT	4L	/* Text to be displayed to user */
X#define M_DMNFULL	5L	/* Too many users; user cannot join mtalk */
X
X/* User commands that can be sent to the server as type M_USRCMD */
X#define U_WHISPER	0	/* Whisper to a specific user */
X#define U_WHO		1	/* Who's on? */
X#define U_EXIT		2	/* Exit mtalk */
X
X/* Structure passed with M_USRIN type */
Xstruct usrin {
X  int uid;	/* This user's uid */
X  };
X
X/* Structure passed with M_USRMSG type */
Xstruct usrmsg {
X  int uid;	/* User's uid */
X  char text[MAX_MESSAGE];	 /* Text of message */
X  };
X
X/* Structure passed with M_USRCMD type */
Xstruct usrcmd {
X  int uid;	/* User's uid */
X  int cmd;	/* Which command is being sent */
X  char user[31];	/* Which user it concerns */
X  char text[MAX_MESSAGE];	/* Text that accompanies the command */
X  };
X
X/* Note: type M_DMNTEXT is just straight text, and type M_DMNFULL has
X   no parameters. */
X
X/* This is a "generic message" structure.  It is used to receive messages
X   before their types (and thus formats) are known. */
Xstruct genmsg {
X  long msgtype;
X  char contents[1024]; /* Just to be safe */
X  };
X
END_OF_mtalk.h
if test 1981 -ne `wc -c <mtalk.h`; then
    echo shar: \"mtalk.h\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f mtalk.man -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"mtalk.man\"
else
echo shar: Extracting \"mtalk.man\" \(1005 characters\)
sed "s/^X//" >mtalk.man <<'END_OF_mtalk.man'
X.TH mtalk
X.SH USAGE
X.PP
Xmtalk
X.SH SYNOPSIS
X.PP
Xmtalk is the user interface and client for
Xthe mtalk multiuser talk program.  It allows the
Xuser to log into mtalk and chat with other people
Xusing mtalk.  It takes no parameters.
X.SH COMMANDS
X.PP
XOnce in mtalk, several commands are available to the
Xuser.  All commands have the slash character '/' as
Xthe first character in the line; any line input that
Xdoes not start with a slash is taken to be a public message
Xto be sent to everyone logged onto mtalk.  The commands are:
X.PP
X/users - Shows all users currently logged on to mtalk.
X.PP
X/whisper <name> <message> - Send <message> privately to user <name>.
X.PP
X/exit - Exit the program.
X.PP
X/help - Get a help message describing commands.
X.SH LINE EDITING
X.PP
XWhile entering a line of text, limited line editing is available.
XBackspace or delete deletes the last character you typed.  CTRL-U
Xcancels the line.  CTRL-R redisplays your line of text up to the last
Xcharacter you typed.
X.SH SEE ALSO
X.PP
Xmtalkd
END_OF_mtalk.man
if test 1005 -ne `wc -c <mtalk.man`; then
    echo shar: \"mtalk.man\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f mtalkd.c -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"mtalkd.c\"
else
echo shar: Extracting \"mtalkd.c\" \(6803 characters\)
sed "s/^X//" >mtalkd.c <<'END_OF_mtalkd.c'
X/* mtalkd - daemon for mtalk.  Handles and redistributes user messages
X * and requests.
X *
X * Copyright (c) 1991, Matthew Kimmel.  Permission granted for unlimited
X * non-commercial use and distribution.
X */
X#include <stdio.h>
X#include <string.h>
X#include <signal.h>
X#include <pwd.h>
X#include <sys/msg.h>
X#include "mtalk.h"
X
Xstruct user {  /* Record of logged-on user */
X  int uid;	/* if this is -1, this user slot is empty */
X  int mqid;	/* user's message queue id */
X  char name[31]; /* Their name */
X  };
X
Xstruct genmsg msgin; /* Soon-to-be-malloc'd structure we use to catch incoming messages before processing */
Xint ourmqid; /* Our message queue id; */
Xstruct user users[MAX_USERS]; /* List of active users */
X
Xmain()
X{
X  init(); /* Initialize stuff */
X  server(); /* Go be the mtalk server */
X  cleanup(); /* If we get here, we'd better clean things up */
X}
X
X/* Initialize various stuff--memory, message queue, signals, etc. */
Xinit()
X{
X  key_t ftok();
X  int handle_sigs();
X  char *malloc();
X  int i;
X
X  /* Get our incoming message queue using the....magic formula! */
X  if((ourmqid = msgget(ftok(DAEMON_PATH,1),(0622 | IPC_CREAT))) == -1) {
X    perror("mtalkd: msgget");
X    exit(1);
X    }
X
X  /* Set up to handle signals */
X  signal(SIGHUP,SIG_IGN);
X  signal(SIGINT,SIG_IGN);
X  signal(SIGQUIT,SIG_IGN);
X  signal(SIGTERM,handle_sigs);
X  signal(SIGREST,handle_sigs);
X
X  /* Initialize user list to no users */
X  for(i = 0;i < MAX_USERS;i++)
X    users[i].uid = -1;
X}
X
X/* This is the main loop of mtalkd.  It waits for messages, and
X   processes them.  It should spend most of its time blocked when
X   nothing is happening, and save system time. */
Xserver()
X{
X  for(;;) {
X    if(msgrcv(ourmqid,&msgin,1024,0L,0) == -1) { /* This should NEVER happen. */
X      perror("mtalkd: msgrcv");
X      exit(1);
X      }
X    if(msgin.msgtype == M_USRMSG) { /* A public message from a user */
X      usrpubmsg(msgin.contents);
X      continue;
X      }
X    if(msgin.msgtype == M_USRCMD) { /* A command from a user */
X      usrcommand(msgin.contents);
X      continue;
X      }
X    if(msgin.msgtype == M_USRIN) { /* A user logging in */
X      usrlogin(msgin.contents);
X      continue;
X      }
X    }
X}
X
X/* Assemble and distribute a public message from a user */
Xusrpubmsg(mesg)
Xstruct usrmsg *mesg;
X{
X  char txt[315]; /* Better safe than sorry */
X  int i;
X
X  for(i=0;i<MAX_USERS;i++)
X    if(users[i].uid == mesg->uid)
X      break;
X
X  if(i == MAX_USERS) { /* Unknow uid--this should NEVER happen */
X    sprintf(txt,"Message from unlogged uid %d -- tell programmer!",mesg->uid);
X    sendtoall(txt);
X    }
X
X  sprintf(txt,"%s: %s",users[i].name,mesg->text);
X  sendtoall(txt);
X}
X
X/* Process an incoming command from a user */
Xusrcommand(command)
Xstruct usrcmd *command;
X{
X  switch(command->cmd) {
X    case U_WHISPER : dowhisper(command->uid,command->user,command->text);
X                     break;
X    case U_WHO : dowho(command->uid);
X                 break;
X    case U_EXIT : doexit(command->uid);
X                  break;
X    } /* Unrecognized commands are ignored. */
X}
X
X/* Process the whisper command */
Xdowhisper(uid,usr,text)
Xint uid;  /* uid of sending user */
Xchar *usr; /* name of receiving user */
Xchar *text; /* Text to be whispered */
X{
X  int duid = -1; /* Receipient's uid */
X  char *sname; /* Pointer to sender's name */
X  int i;
X  char buf[1024];
X
X  for(i=0;i<MAX_USERS;i++) {
X    if(users[i].uid == uid)
X      sname = users[i].name;
X    if(!strcmp(users[i].name,usr))
X      duid = users[i].uid;
X      }
X
X  if(duid == -1) { /* User not found */
X    sprintf(buf,"%s: no such user",usr);
X    sendtouid(uid,buf);
X    }
X
X  /* Assemble whisper and send it */
X  sprintf(buf,"%s whispers: %s",sname,text);
X  sendtouid(duid,buf);
X}
X
X/* Process the who command */
Xdowho(uid)
Xint uid; /* uid of requestor */
X{
X  char buf[1024];
X  int i;
X
X  /* Assemble a string to be sent to the user.  This may break if */
X  /* MAX_USERS is more than 30. */
X  strcpy(buf,"Users currently logged on:\r\n");
X  for(i=0;i<MAX_USERS;i++)
X    if(users[i].uid != -1) {
X      strcat(buf,"  ");
X      strcat(buf,users[i].name);
X      strcat(buf,"\r\n");
X      }
X
X  /* Send the string */
X  sendtouid(uid,buf);
X}
X
X/* Process the exit command */
Xdoexit(uid)
Xint uid; /* uid of exiter */
X{
X  int i;
X  char buf[1024];
X
X  /* Find user and delete him from user list, and assemble a string
X     notifying other users that he has exited. */
X  for(i=0;i<MAX_USERS;i++)
X    if(users[i].uid == uid) {
X      sprintf(buf,"%s has exited.",users[i].name);
X      users[i].uid = -1;
X      }
X
X  /* Send the notification to everyone else. */
X  sendtoall(buf);
X}
X 
X/* Log a user into the system (if there's a free slot) */
Xusrlogin(usr)
Xstruct usrin *usr;
X{
X  key_t ftok();
X  int slot, tmpid;
X  struct passwd *pw;
X  struct {
X    long mtype;
X    } fullmsg;
X  char announce[50];
X
X  /* First, try to get their message queue */
X  if((tmpid = msgget(ftok(CLIENT_PATH,usr->uid),0)) == -1) {
X    perror("mtalkd: msgget");
X    return;
X    }
X
X  /* Look for a slot for the user. */
X  for(slot=0;slot<MAX_USERS;slot++)
X    if(users[slot].uid == -1)
X      break;
X
X  /* Send a full message if no slots open */
X  if(slot == MAX_USERS) {
X    fullmsg.mtype = M_DMNFULL;
X    msgsnd(tmpid,&fullmsg,0,0);
X    return;
X    }
X
X  /* Otherwise, put the user in the user records. */
X  users[slot].uid = usr->uid;
X  users[slot].mqid = tmpid;
X  setpwent();
X  pw = getpwuid(usr->uid);
X  strncpy(users[slot].name,pw->pw_name,30);
X  endpwent();
X
X  /* Announce that the user has logged in */
X  sprintf(announce,"%s has logged in.",users[slot].name);
X  sendtoall(announce);
X}
X
X/* Send a line of text to all users logged in */
Xsendtoall(txt)
Xchar *txt;
X{
X  int i;
X  struct {
X    long mtype;
X    char text[1024];
X    } mbuf;
X
X  /* Now do a msgsnd to all users */
X  mbuf.mtype = M_DMNTEXT;
X  strncpy(mbuf.text,txt,1023);
X  for(i=0;i<MAX_USERS;i++)
X    if(users[i].uid != -1)
X      msgsnd(users[i].mqid,&mbuf,(strlen(mbuf.text)+1),0);
X}
X
X/* Send a line of text to a specific uid.  If the uid is not found in the
X   list of users, the message is discarded, to save hassle. */
Xsendtouid(uid,txt)
Xint uid;
Xchar *txt;
X{
X  int i;
X  struct {
X    long mtype;
X    char text[1024];
X    } mbuf;
X
X  /* Find the uid, and send the message to that user */
X  for(i=0;i<MAX_USERS;i++)
X    if(users[i].uid == uid) {
X      mbuf.mtype = M_DMNTEXT;
X      strncpy(mbuf.text,txt,1023);
X      msgsnd(users[i].mqid,&mbuf,(strlen(mbuf.text)+1),0);
X      }
X}
X
X/* Handle any caught signals--at present, all caught signals make the
X   program clean up and terminate. */
Xhandle_sigs()
X{
X  cleanup();
X  exit(0);
X}
X
X/* This function kills our message queue and frees memory when we exit.
X   It leaves any users stranded, since this should never happen when
X   users are logged on! */
Xcleanup()
X{
X  msgctl(ourmqid,IPC_RMID,NULL);
X}
X
END_OF_mtalkd.c
if test 6803 -ne `wc -c <mtalkd.c`; then
    echo shar: \"mtalkd.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f mtalkd.man -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"mtalkd.man\"
else
echo shar: Extracting \"mtalkd.man\" \(310 characters\)
sed "s/^X//" >mtalkd.man <<'END_OF_mtalkd.man'
X.TH mtalkd
X.SH USAGE
X.PP 
Xmtalkd&
X.SH SYNOPSIS
X.PP 
Xmtalkd is the daemon for mtalk.
XIt must be running in memory before
Xmtalk is invoked by any user.  It
Xrequires that the msg device driver be
Xinstalled before it is invoked.  It
Xtakes no parameters.
X.SH FILES
X.PP
Xmtalk, /drv/msg
X.SH SEE ALSO
X.PP
Xmtalk, drvld
END_OF_mtalkd.man
if test 310 -ne `wc -c <mtalkd.man`; then
    echo shar: \"mtalkd.man\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of shell archive.
exit 0

exit 0 # Just in case...
-- 
Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
Sterling Software, IMD           UUCP:     uunet!sparky!kent
Phone:    (402) 291-8300         FAX:      (402) 291-4362
Please send comp.sources.misc-related mail to kent@uunet.uu.net.